Reviewers on #2933 asked for tests on the button-style plumbing. Two layers:
- src/channels/ask-question.test.ts (new): normalizeOption/normalizeOptions
style whitelist — primary/danger/default pass through; unknown strings,
case variants, and non-string values drop to undefined; string-shorthand
options carry no style; style coexists with the label/selectedLabel/value
defaulting. This is the load-bearing gate: an invalid style reaching
Slack Block Kit fails the whole card with invalid_blocks, which in the
approval flow is an effective auto-deny.
- src/channels/chat-sdk-bridge.test.ts: ask_question delivery passes each
normalized option style into Button() and omits it when unset; an invalid
style in the raw payload is stripped before the card is built.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The active batch's inReplyTo lived in module-level state in current-batch.ts,
but the nanoclaw MCP server runs as a separate stdio subprocess from the poll
loop, so getCurrentInReplyTo() always read null there. The a2a reply stamp was
therefore dead — only the host peer-affinity fallback kept replies routing.
Publish the stamp through session_state in outbound.db (both processes already
open it): the poll loop writes it at batch start and clears it in the existing
finally; the MCP tools read it with an updated_at staleness guard so a stamp
left behind by a killed container isn't reused. Delete current-batch.ts and
reseed core.test.ts via the DB so it reflects the real process boundary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
These are leftovers from the April-2026 inbound/outbound session-DB
split, marked @deprecated "kept temporarily for test compatibility":
sessionDbPath, openSessionDb, writeSystemResponse (session-manager),
getStuckProcessingIds (session-db, superseded by getProcessingClaims),
getSessionDb (container connection + barrel re-export), and the dead
registerResponseHandler/onShutdown re-exports from index.ts. The only
real callers are two dev scripts, repointed to inboundDbPath /
getInboundDb.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The /debug skill tells operators to rm -rf a session folder to reset a
stuck session, but the sessions row survives. The next message then takes
the existing-session path and opens inbound.db in a directory that no
longer exists — better-sqlite3 throws and the message is dropped forever.
writeSessionMessage now calls the idempotent initSessionFolder when the
inbound.db path is missing, so the documented reset actually re-provisions.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove scaffold-era vocabulary in the ncl protocol that no code produces:
the duplicate `Access` union in crud.ts (now imported from registry.ts),
the unused `'hidden'` access level, and the never-emitted `'permission-denied'`
and `'not-found'` error codes. Also refresh three stale docstrings.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
These config exports in src/config.ts have zero readers and advertise
controls that v2 removed:
- CONTAINER_TIMEOUT / CONTAINER_MAX_OUTPUT_SIZE / IDLE_TIMEOUT /
MAX_CONCURRENT_CONTAINERS / MAX_MESSAGES_PER_PROMPT
- the trigger block (escapeRegex, buildTriggerPattern, DEFAULT_TRIGGER,
getTriggerPattern, TRIGGER_PATTERN)
Also remove the `auth` script from package.json — it pointed at the
deleted src/whatsapp-auth.ts, so `pnpm run auth` only errored — and fix
the stale MAX_MESSAGES_PER_PROMPT reference in the container's
messages-in.ts comment (the value comes from container.json's
maxMessagesPerPrompt).
Grep confirms no importers in src/, container/, scripts/, setup/, or
tests. Precedent: 0283391.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Egress-lockdown and CPU/memory limits were read from process.env only, but
the shipped launchd/systemd service sets just PATH+HOME and the host never
loads .env into process.env — so these knobs could not be turned on the
supported way. Route them through the same readEnvFile path as the other
config keys, keeping process.env precedence so dev-mode/nohup installs are
unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The dispatcher trimmed exactly one trailing dash-segment to recover the
target id, so any generated id containing dashes (UUIDs, sess-*, appr-*)
never matched a command and failed unknown-command; only --id worked.
Resolve by longest registered prefix instead: split the dash-joined
command, take the longest prefix that lookup() resolves as the command,
and treat the re-joined remainder as args.id. Server-side only, no wire
or client change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add '/start' back to the host FILTERED set (a Telegram fix from 866b791
was silently undone when host gating was added) so it is dropped instead
of reaching the agent as a normal message. Replace the inline isAdmin
query and its hasTable('user_roles') fail-open guard with a call to
hasAdminPrivilege; user_roles always exists (core migration 001-initial),
so the guard only ever masked a missing check. Add a focused command-gate
test covering both.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Approval cards render every button with the same neutral style, so Approve
and Reject read identically at a glance. The chat SDK's Button already
accepts style ('primary' | 'danger' | 'default') and @chat-adapter/slack
maps primary→green and danger→red Block Kit styles (Telegram ignores it),
but the ask_question pipeline dropped the field.
- ask-question.ts: add OptionStyle and an optional style field to
OptionInput/NormalizedOption; normalizeOption whitelists the value.
Bare-string options stay unstyled.
- chat-sdk-bridge.ts: forward opt.style into Button() on ask_question
cards. Persisted options_json tolerates the extra key
(getAskQuestionRender only reads label/selectedLabel/value).
- Producers: module approvals (Approve/Reject), sender approvals
(Allow/Deny), channel approvals (Connect/Reject), and OneCLI credential
cards (Approve/Reject) annotate primary/danger. Multi-choice picker
options stay unstyled — a list of equals.
Purely additive: options without style render exactly as before.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>