Follow-up to #2467. The trailing "anything outside these tags is also
treated as scratchpad" clause contradicted the rest of the system prompt,
which requires bare text to be wrapped in `<message>` blocks. Removing it
keeps the description focused on what `<internal>` actually does.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The welcome skill told the agent to send the greeting via `send_message`,
but the destinations system prompt also requires the final response to
be wrapped in `<message to="…">` blocks (since 1d4d920). The agent
followed both, sending the greeting once via the MCP tool and once via
the wrapped final output.
- welcome/SKILL.md: drop the mechanism — "send a short, warm greeting"
lets the system prompt steer how it's delivered.
- destinations.ts: reframe `<message>` blocks and `send_message` as the
same delivery surface, with the explicit note that each call/block
lands as its own message — so they compose into a sequence rather than
reading as additive duplicates of the same content.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Without files:read, @chat-adapter/slack cannot download attachments —
Slack returns an HTML login page in place of file bytes and the adapter
throws a NetworkError. Bundles files:write for symmetric outbound
(files.uploadV2).
Closes#2457
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The parenthetical "(single-destination: just write)" was stale after
9db39b2 removed the bare-text routing fallback. Agents following this
hint had their responses silently dropped to scratchpad.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The upstream install script supports ONECLI_VERSION; use it to avoid
pulling an untested gateway release during setup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Platforms like Teams send userIds in "29:xxx" format which already
include a colon. Blindly prefixing with channelType produced double-
namespaced ids (e.g. "teams:29:xxx") that never matched the users
table, causing all approval clicks to be rejected. Mirror the
resolveOrCreateUser logic: only prefix when the raw id has no colon.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the agent outputs bare text without <message to="..."> blocks,
nothing gets delivered — silent failure. Now the poll-loop pushes a
one-shot correction back into the active query telling the agent to
re-send with proper wrapping. Capped at once per user turn to avoid
loops; resets when a new follow-up message arrives.
Also updates destination instructions to require explicit <internal>
wrapping for scratchpad instead of treating bare text as implicit
scratchpad.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tell the compactor to include the <message to="name"> wrapping reminder
verbatim at the END of the summary so it's the last thing the agent sees
after compaction. Previously the instruction just asked to "preserve"
routing info, which the compactor could place anywhere or summarize away.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The compacted event handler injected a system-tagged reminder into the
live query after SDK auto-compaction, which caused the agent to send
an unintended message. Reverts the four changes from #2327:
- Remove `compacted` variant from ProviderEvent union
- Restore `result` yield for compact_boundary in ClaudeProvider
- Remove compacted event handler and getAllDestinations import in poll-loop
- Remove compaction integration tests and CompactingProvider helper
Closes#2325 differently — the reminder approach is not viable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The container opens inbound.db read-only, so it can't ALTER TABLE.
If the host hasn't run migrateMessagesInTable yet (e.g., container
rebuilt before host restart), the on_wake column won't exist and
the query crashes, causing a restart loop.
Detect the column via PRAGMA table_info and conditionally include
the filter clause.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sweep of outbound strings, doc URLs, comments, and clone instructions
that were missed in the original org rename. One both-match case in
setup/lib/channels-remote.sh (URL detection) accepts either name so
existing forks with a `qwibitai` remote continue to resolve cleanly;
everywhere else is a straight rename.
Historical mentions left intact:
- CHANGELOG.md (v2.0.0 entry, frozen history)
- .claude/skills/add-gmail-tool/SKILL.md (pre-v2 qwibitai skill — historical)
- repo-tokens/badge.svg (auto-regenerated by update-tokens.yml)
Addresses review feedback on this branch:
- Fix TS2352 build error in dispatch.ts: `getSession()` returns `Session`,
which has no index signature, so `(s as Record<string, unknown>)` is rejected
by tsc. `Session.agent_group_id` exists — read it directly.
- Fix a regression introduced by dropping the `groupField in data` guard:
the post-handler scope check now runs for *every* command under a whitelisted
resource, including custom ops, which return ad-hoc shapes. `ncl groups config
get` (access:open, reachable by a group-scoped agent) returns a config object
with no `id` field → `data['id'] !== ctx.agentGroupId` → `forbidden`, even on
the agent's own config. Fix: tag the auto-generated list/get handlers with
`generic: 'list' | 'get'` on `CommandDef` (set in `registerResource`) and run
the post-handler check only when `cmd.generic` is set. Generic handlers return
raw DB rows that carry `scopeField`; custom ops are already pinned to the
caller's group by the pre-handler `--id` auto-fill or the approval gate.
Fail-closed-when-`scopeField`-missing is preserved (now scoped to generic
list/get).
- Tests: `dispatch.test.ts` mocks `getResource` (the real resources aren't
registered in this unit), tags the two post-handler test commands as `generic`,
and adds coverage for: custom op returning a non-row object not being rejected;
`sessions-get` pre-handler returning "session not found" for foreign and
non-existent UUIDs (no existence oracle) and allowing the caller's own session;
generic list/get failing closed when a resource declares no `scopeField`.
Full suite: 323 passing.
- Remove FORK.md from the PR diff — it's the fork's personal README, carried in
because the branch was cut from the fork's `main` rather than upstream.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 patch versions ahead. The 2.1.120 binary baseline introduced a
number of plugin and skill behaviors that have since landed in the
public Claude Code docs: ${CLAUDE_EFFORT} substitution, settled
`arguments` field in skill frontmatter, plugin `channels` field.
No breaking changes for nanoclaw's runtime contract. Verified by
running container/skills/{agent-browser,vercel-cli,slack-formatting}
under the bumped image; all three load and execute as expected.
SDK at ^0.2.116 (caret) remains compatible with claude-code 2.1.128.
Bumping CLAUDE_CODE_VERSION invalidates the pnpm install layer in
container/Dockerfile and triggers a full rebuild of the agent image.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>