Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
d4986af
container: agent-runner stuck-session recovery + IPC race fix + OneCL…
Apr 27, 2026
e460f2d
host: voice in/out, observer module, HTML allowlist, OneCLI proxy + m…
Apr 27, 2026
46e6dea
docs/config: identity rename to LoMBot, calendar/gmail/smartthings/vo…
Apr 27, 2026
ffca4dc
observer: lift 400-char thinking cap, chunk over Telegram limit
Apr 27, 2026
b40b0bb
container: send_file rejects non-deliverable paths upfront
Apr 27, 2026
88ae3be
observer: prettier formatting
Apr 27, 2026
68ef691
agent-runner: pin thinking display=summarized for Opus 4.7 + add obse…
Apr 27, 2026
6893c54
container-runner: include reactions table in filtered DB for untruste…
Apr 27, 2026
d9007bb
sanitizer: include underscores in tag-name regex so <tool_use_error> …
Apr 27, 2026
f10ad13
tessl: bump nanoclaw-core 0.1.80→0.1.83, nanoclaw-trusted 0.1.44→0.1.46
Apr 27, 2026
8c117f4
untrusted: expose gcal_freebusy via OneCLI proxy with calendar clamp
Apr 27, 2026
38fa73d
untrusted: tell the bot who its owner is by Telegram handle
Apr 27, 2026
25a061d
untrusted: put owner identity in SOUL-untrusted.md (the only file act…
Apr 27, 2026
3639401
telegram: document why host auto-react excludes untrusted
Apr 27, 2026
c3d4183
agent-runner: stop draining JSON files mid-query — race lost messages
Apr 27, 2026
a81784e
container-runner: wipe stale _close sentinels before spawning a conta…
Apr 27, 2026
76b2f5f
observer: skip watchdog for scheduled tasks — fix false 'Still workin…
Apr 27, 2026
c0bd868
observer: prettier formatting
Apr 27, 2026
a7fe522
agent-runner: suppress final result.text echo when send_message tool …
Apr 27, 2026
8135334
docs/global: teach the bot where its tools live + git push/PR boundaries
Apr 27, 2026
730c506
tessl: bump nanoclaw-core 0.1.83→0.1.85, nanoclaw-trusted 0.1.46→0.1.47
Apr 27, 2026
5dc059a
send_message: accept optional chat_jid for cross-chat sends from main
Apr 27, 2026
3f38e43
scheduler: prune completed once-tasks after 24h TTL (#2)
ligolnik Apr 27, 2026
a76ff3f
tessl: bump nanoclaw-trusted 0.1.47→0.1.48
Apr 27, 2026
2761c86
docs/global: forbid bot from merging PRs or pushing to main
Apr 27, 2026
cecfdec
smartthings: PAT watchdog + tessl bump
Apr 27, 2026
8da3df7
merge: pull jbaruch/nanoclaw-public main (19 commits, mostly today)
Apr 27, 2026
f640140
tests: align selectTiles assertions with TileRef[] + add fork-local m…
Apr 27, 2026
14d30cf
Merge pull request #12 from ligolnik/sync/upstream-2026-04-27
ligolnik Apr 27, 2026
bc71f23
port PR #46/#47 review fixes from upstream-prs
Apr 28, 2026
09fd810
observer: port PR #49 review fixes — watchdog re-entry, final reactio…
Apr 28, 2026
0c4d3da
agent-runner: keep Composio MCP block alongside OneCLI
Apr 28, 2026
8cc6582
sync: merge upstream main (PRs #40, #41, #43, #45, #46, #47)
Apr 28, 2026
9e5bb0c
tessl: bump nanoclaw-host 0.1.23→0.1.25
Apr 28, 2026
25c39b5
group-queue: port PR #51 review fixes from upstream-prs
Apr 28, 2026
fdd3ec0
agent-runner: port PR #52 review fixes from upstream-prs
Apr 28, 2026
0755537
agent-runner: port PR #53/#55 review fixes from upstream-prs
Apr 28, 2026
f511420
container-runner: port PR #56 review — trim, warn on AGENT_MODEL typo
Apr 28, 2026
6cb2525
ci: install agent-runner deps so vitest can resolve its imports
Apr 28, 2026
7b28994
container-runner: port PR #56 v3 — resolveAgentModel unit tests
Apr 28, 2026
1b85dec
container: exclude *.test.ts from agent-runner tsc build
Apr 28, 2026
c0d708d
observer: fix init order + relax privacy gate to warn-not-block
Apr 28, 2026
7798c49
telegram: record latest user message for observer even in untrusted
Apr 28, 2026
61f643a
scheduler+observer: fix zombie-task resurrection storm + scope watchd…
Apr 28, 2026
3138a34
observer: gate reactions on agent engagement (text/tool_use), suppres…
Apr 28, 2026
bc393b0
observer: pin reactions to the message the query is processing, not t…
Apr 28, 2026
45728ef
observer: don't treat <internal>...</internal> text as engagement com…
Apr 28, 2026
e8b370b
scheduler: clear next_run on once-task pre-advance to completed (clos…
Apr 28, 2026
3d615d9
Revert "scheduler: clear next_run on once-task pre-advance to complet…
Apr 28, 2026
0ae3e22
db: enable WAL + busy_timeout to prevent transient malformed-image reads
Apr 28, 2026
3d74d9a
Merge fix/db-wal: enable WAL + busy_timeout (closes #9)
Apr 28, 2026
728d5a7
observer: suppress watchdog pings after send_message delivers reply (…
ligolnik Apr 28, 2026
c26b789
db: apply busy_timeout pragma at every opener (closes #13) (#19)
ligolnik Apr 28, 2026
8d7432a
telegram: emit 👀 reaction on incoming voice messages before transcrip…
ligolnik Apr 28, 2026
b22c68f
fix(scheduler): stop passing sessionId to scheduled-task containers t…
ligolnik Apr 28, 2026
d99e9d4
fix(scheduler): atomically log task run and update scheduled_tasks to…
ligolnik Apr 28, 2026
9cf6eb9
observer: format <internal> strip+trim as multi-line chain
Apr 28, 2026
479399c
feat(scripts): host-side cron to pull each group's workspace (#28)
ligolnik Apr 29, 2026
4ca868a
feat(container): inject scoped GITHUB_TOKEN via env-file (closes #16)…
ligolnik Apr 29, 2026
8896195
fix(container): forward .env API keys to scheduled-task containers vi…
ligolnik Apr 29, 2026
6bf3d66
fix(scheduler): propagate gate-script failure to task_run_logs.status…
ligolnik Apr 29, 2026
2efea83
feat(ipc): add chat_jid to send_file and send_voice for cross-chat se…
ligolnik Apr 29, 2026
e5d4c6a
fix(scheduler): close past-schedule + dispatch-loss gaps that silentl…
ligolnik Apr 29, 2026
bf56b37
fix(agent-runner): remove 165k auto-compact hardcode, default to 800k…
ligolnik Apr 29, 2026
f5e0e18
fix(container-runner): fall back to readEnvFile when process.env miss…
ligolnik Apr 29, 2026
10b39aa
feat(container): install gh CLI in agent image (#38)
ligolnik Apr 29, 2026
82ef876
fix(container-runner): use DELETE journal_mode for filtered-db so :ro…
ligolnik Apr 29, 2026
de13377
fix(config): fall back HOST_UID/HOST_GID to process.getuid/getgid bef…
ligolnik Apr 29, 2026
8e11bb7
fix(ipc): persist consumed input list and add host GC to bound input/…
ligolnik Apr 29, 2026
e17aea7
fix(container-runner): bump main/trusted memory cap to 2GB/3GB-swap (…
ligolnik Apr 29, 2026
8ea38d0
fix(scheduler): self-clean orphaned tasks for deregistered groups (cl…
ligolnik Apr 29, 2026
d9e643b
fix(telegram): translate bot-emitted IDs for reactions and log reject…
ligolnik Apr 29, 2026
86edd32
feat(wedge-diagnostics): capture container state before dispatch-loss…
ligolnik Apr 29, 2026
3eea5c9
fix(agent-runner): include model in query-done log for cost attributi…
ligolnik Apr 30, 2026
4eb367c
feat(cost): import maintenance blocklist + per-task session reuse (#64)
ligolnik May 1, 2026
65f76c1
fix(task-scheduler.test): drop unimported RegisteredGroup type annota…
May 1, 2026
0014f86
feat(cost): /workspace/state mount + wire heartbeat precheck-gate (#65)
ligolnik May 1, 2026
cdf3b5b
fix(container-runner): skip non-files when copying skill scripts (imp…
ligolnik May 1, 2026
ed5eb42
fix(db): enable WAL + busy_timeout to prevent transient malformed-ima…
ligolnik May 1, 2026
aa3ff70
fix(config): warn loudly when HOST_UID/HOST_GID are malformed (import…
ligolnik May 1, 2026
8aaf4f9
fix(scheduler): synthesize terminal success on silent-stop, lower mai…
ligolnik May 1, 2026
e410890
fix(#106): reject embedded credentials in git remote URL on deploy (#…
ligolnik May 1, 2026
9eeffee
fix(#81-adj): redact bot-token URLs in log output (#91) (#71)
ligolnik May 1, 2026
dffe231
fix(#81-adj): shadow SECRET_FILES across additionalMounts (#90) (#72)
ligolnik May 1, 2026
0992c41
fix(#69): rebuild agent image before orchestrator to close stale-imag…
ligolnik May 1, 2026
71f13d0
fix(deploy): graceful agent close instead of unconditional docker kil…
ligolnik May 1, 2026
f9ba42b
fix(#100): nuke_session deletes the on-disk JSONL transcript (#111) (…
ligolnik May 1, 2026
f292b9e
fix(telegram): downgrade unactionable Telegram errors from ERROR to W…
ligolnik May 1, 2026
621837d
feat(container-runner): per-group AGENT_MODEL override (#77)
ligolnik May 1, 2026
c5b7f87
feat(observability): surface effective agent model in /status skill
May 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ jobs:
cache: npm
- run: npm ci

# Install agent-runner deps so its src/**/*.test.ts files (which
# vitest now picks up — see vitest.config.ts) can import their
# transitive deps (@anthropic-ai SDK types, nodemailer for MIME).
# The agent-runner has its own package.json because it's also
# installed standalone inside the container image at build time;
# root doesn't carry those deps.
- name: Install agent-runner deps
run: npm ci
working-directory: container/agent-runner

- name: Format check
run: npm run format:check

Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to NanoClaw will be documented in this file.

For detailed release notes, see the [full changelog on the documentation site](https://docs.nanoclaw.dev/changelog).

## [Unreleased]

- New canonical writable per-group state mount at `/workspace/state/` — sourced from `data/state/<folder>/` on the host and mounted into every container regardless of trust tier (ported from jbaruch/nanoclaw#220, addressing #99 Cat 4). Gives every tier a single canonical writable location for skills that persist state across runs, replacing per-skill workarounds. Per-group scoping (not per-session) so a scheduled task and a user-facing turn in the same group can read each other's state.
- Heartbeat now uses the precheck-gate `script` field — the non-main heartbeat task runs `unanswered-precheck.py` first, and the agent only wakes when the precheck reports new candidates (or errors). Closes part of #62 (heartbeat container spawns running 0 queries) by skipping spawn entirely when there is nothing to do. A one-shot startup migration backfills the `script` column on existing `heartbeat-*` rows so deployed installs pick up the gate without manual DB edits.

## [1.2.36] - 2026-03-26

- [BREAKING] Replaced pino logger with built-in logger. WhatsApp users must re-merge the WhatsApp fork to pick up the Baileys logger compatibility fix: `git fetch whatsapp main && git merge whatsapp/main`. If the `whatsapp` remote is not configured: `git remote add whatsapp https://github.com/qwibitai/nanoclaw-whatsapp.git`.
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Single Node.js process with skill-based channel system. Channels (WhatsApp, Tele

## Secrets / Credentials / Proxy (OneCLI)

API keys, secret keys, OAuth tokens, and auth credentials are managed by the OneCLI gateway — which handles secret injection into containers at request time, so no keys or tokens are ever passed to containers directly. Run `onecli --help`.
Most API keys, OAuth tokens, and auth credentials are managed by the OneCLI gateway — secrets are injected at request time so no value is passed to containers directly. **Exception:** main/trusted-tier containers receive `GITHUB_TOKEN` directly via the env-file mechanism (`SECRET_CONTAINER_VARS` in `src/container-runner.ts`). The token is a scoped fine-grained PAT — no admin, no branch-protection bypass — so the bot can `git push`/`pull` and call the GitHub REST API without OneCLI's HTTPS rewrite. Untrusted-tier containers receive nothing. Run `onecli --help`.

## Skills

Expand Down
21 changes: 21 additions & 0 deletions container/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ RUN apt-get update && apt-get install -y \
poppler-utils \
&& rm -rf /var/lib/apt/lists/*

# Install gh CLI from GitHub's official apt repo. Picks up GITHUB_TOKEN
# from env automatically — wired via SECRET_CONTAINER_VARS env-file
# (PR #32) for main/trusted tier; untrusted gets nothing and gh fails
# closed with an auth error rather than silently using a stale token.
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
&& chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list \
&& apt-get update \
&& apt-get install -y gh \
&& rm -rf /var/lib/apt/lists/*

# Set Chromium path for agent-browser
ENV AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium
ENV PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium
Expand Down Expand Up @@ -73,6 +86,14 @@ RUN git config --global pack.threads 1 && \
git config --global pack.deltaCacheSize 1m && \
git config --global pack.windowMemory 100m

# Git credential helper — uses $GITHUB_TOKEN env var at request time so
# `git fetch/pull/push` over HTTPS works directly. The token is injected
# via --env-file at container spawn (see SECRET_CONTAINER_VARS in the
# orchestrator). Only main/trusted tier containers receive the token;
# untrusted-tier containers will see this helper return empty creds and
# get the same authentication failure they get today.
RUN git config --system credential.helper '!f() { echo username=x-access-token; echo "password=$GITHUB_TOKEN"; }; f'


# Run as non-root node user (uid 1000) by default.
# The orchestrator may override with --user for non-main groups.
Expand Down
21 changes: 21 additions & 0 deletions container/agent-runner/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions container/agent-runner/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
"@anthropic-ai/claude-agent-sdk": "^0.2.112",
"@modelcontextprotocol/sdk": "^1.12.1",
"cron-parser": "^5.0.0",
"nodemailer": "^6.10.1",
"zod": "^4.0.0"
},
"devDependencies": {
"@types/node": "^22.10.7",
"@types/nodemailer": "^6.4.23",
"typescript": "^5.7.3"
}
}
54 changes: 54 additions & 0 deletions container/agent-runner/src/index.thinking-only.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, it, expect } from 'vitest';
import { isThinkingOnlyEndTurn } from './index.js';

describe('isThinkingOnlyEndTurn — pseudo-turn detection', () => {
it('detects a turn that is only thinking + end_turn', () => {
expect(isThinkingOnlyEndTurn('end_turn', ['thinking'])).toBe(true);
});

it('detects multiple thinking blocks + end_turn', () => {
expect(isThinkingOnlyEndTurn('end_turn', ['thinking', 'thinking'])).toBe(
true,
);
});

it('detects redacted_thinking blocks alongside thinking', () => {
expect(
isThinkingOnlyEndTurn('end_turn', ['thinking', 'redacted_thinking']),
).toBe(true);
});

it('detects only-redacted_thinking + end_turn', () => {
expect(isThinkingOnlyEndTurn('end_turn', ['redacted_thinking'])).toBe(true);
});

it('does NOT trigger when stop_reason is not end_turn', () => {
expect(isThinkingOnlyEndTurn('tool_use', ['thinking'])).toBe(false);
expect(isThinkingOnlyEndTurn('max_tokens', ['thinking'])).toBe(false);
expect(isThinkingOnlyEndTurn(undefined, ['thinking'])).toBe(false);
});

it('does NOT trigger when text blocks are present', () => {
expect(isThinkingOnlyEndTurn('end_turn', ['thinking', 'text'])).toBe(false);
expect(isThinkingOnlyEndTurn('end_turn', ['text'])).toBe(false);
});

it('does NOT trigger when tool_use blocks are present', () => {
expect(isThinkingOnlyEndTurn('end_turn', ['thinking', 'tool_use'])).toBe(
false,
);
});

it('does NOT trigger on an empty block list (defensive)', () => {
// A turn with zero content blocks is an SDK-shape edge case during
// certain error paths — explicitly NOT the "model decided to say
// nothing" pseudo-turn we're targeting. Falling back to a previous
// turn there would be an over-reach.
expect(isThinkingOnlyEndTurn('end_turn', [])).toBe(false);
});

it('readonly array argument is accepted', () => {
const blocks: readonly string[] = ['thinking'];
expect(isThinkingOnlyEndTurn('end_turn', blocks)).toBe(true);
});
});
Loading