The v2 DB seed queried `is_main` from the v1 `registered_groups` table, but
that column was a later v1 addition — older v1 installs (e.g. 1.1.0) don't have
it, so the migration's `1b-db` step crashes with `no such column: is_main` and
v2.db is never created, cascading into the sessions and tasks steps failing.
`is_main` was selected into the V1Group interface but never read anywhere, so
this just drops it from the SELECT and the interface. The accompanying comment
already states the intent ("Query only the columns we know exist in all v1
installs") — the code now matches it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The prior commit moves `chat` to 4.29.0, but main's own install pins were left
behind — and were inconsistent: the 8 /add-<channel> SKILL.md steps pinned
@chat-adapter/*@4.27.0 while the 12 setup/*.sh scripts pinned @4.26.0. Unify all
to @4.29.0 so `/add-<channel>` (and setup:auto) on a main install fetch an
adapter whose ChatInstance matches the bridge.
20 files, version-string only. Shell scripts pass `bash -n`.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`chat` and the `@chat-adapter/*` channel adapters are version-locked — the
adapter's ChatInstance must match the bridge's, so the pair must move together.
Pin `chat` exactly to 4.29.0 (was 4.26.0 via `^4.24.0`); a caret range floats to
4.31.0 and reintroduces the skew.
Host build + full test suite green at 4.29.0 (chat is consumed only as type
imports by the Chat SDK bridge). The channels-branch adapters bump to 4.29.0 in
lockstep; CHANGELOG notes the reinstall migration for existing channel installs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The setup preflight unloads *crash-looping* peers but ignores a more common
leftover: a launchd plist (or systemd unit) whose program no longer exists,
left behind when a NanoClaw checkout is deleted without running the
uninstaller. The health probe can't see these because an unloaded/inactive job
doesn't report via `launchctl print` / `systemctl show`, so they accumulate —
the OS keeps retrying a missing binary forever.
Detect a registration as dead when its `dist/index.js` target is absent on
disk, then unload (best-effort) and delete the orphaned config file. Own-label
and still-valid registrations are never touched.
Adds peer-cleanup.test.ts (the file previously had no tests) covering both
platforms: dead target removed, live target kept, own registration spared,
unrecognized config ignored.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
/update-nanoclaw Step 7 framed skill updates as an optional, "safe to skip"
extra, so an important channel/provider fix — shipped on the channels/providers
branches the host merge never touches — could be silently missed. Reframe it as
part of the update: default into /update-skills, name the installed skills, and
leave one minimal opt-out.
Move the container image rebuild into /update-skills Step 4: when a re-apply
changes files under container/ (e.g. a provider's runtime), rebuild so new
sessions actually run the new code. Living in update-skills covers both the
standalone and via-update-nanoclaw paths; the update-nanoclaw Step 7.5 that
briefly owned this is removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHaa6bp25E62AuUJyW1V5J
Rename the two new migration files to the numbered convention used by the core
migrations (001–016), with matching migrationNNN exports, instead of the
module- prefix. Versions (17, 18) and stable migration `name`s are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review: move the assigned approver from the approval payload to a dedicated
`approver_user_id` column on pending_approvals.
- New migration adds the column; createPendingApproval + requestApproval write it.
- isAuthorizedApprovalClick reads approval.approver_user_id directly (drops the
payload-parsing helper); when set, only that exact user may resolve.
- The gate no longer stuffs `approver` into the payload.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review: drop the owner/global-admin override on assigned approvals. When an
approval names an approver, only that exact user can resolve it. (Non-assigned
approvals are unchanged — still group/owner authorized.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review: pull `approverUserId` into the `opts` destructure in requestApproval,
and `approver` out of `policy` in the gate, instead of accessing the property
twice. (policies.ts already binds args.* to locals.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Remove redundant doc/inline comments where the code speaks for itself; keep only
the non-obvious notes (return-vs-throw consume, ghost-gate cleanup, caller-does-
auth, reject-handled-elsewhere, stored-vs-click payload). Also drops a couple of
now-stale "target admin" descriptions. No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With payload-based click-auth (clicker === approver), the approver no longer
needs to be a group admin — the operator (operator-only command) designates
whoever should approve, and only that user (or an owner) can resolve the card.
Removes the now-redundant hasAdminPrivilege validation and its import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review (no new pending_approvals column): the gate carries `approver` inside
the existing approval `payload`, and isAuthorizedApprovalClick authorizes the
named approver (or an owner/global admin) when an approval names one — reading
the real value at click time, no group re-derivation.
- `ncl policies set --approver` validates the user is an admin/owner of the
source OR target.
- Drops `approverAgentGroupId` and the agent_group_id stamp; `requestApproval`
keeps `approverUserId` only for delivery.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review: destructure the approval payload once instead of repeating
`payload.x`, and narrow `platform_id` up front so it's used directly (drops the
separate `targetAgentGroupId` local).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review, the policy approver is now required, not optional. Every policy
names one specific admin/owner of the target who approves.
- `approver` column is NOT NULL; `AgentMessagePolicy.approver` is non-nullable.
- `ncl policies set --approver <user-id>` is required and validated to be an
admin/owner of the target.
- The gate always delivers the card to `policy.approver`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per review, add an optional `approver` to a policy: a specific admin/owner of
the target who receives the approval card (instead of all target admins). NULL
keeps the default (all target admins/owners).
- `approver` column on agent_message_policies; carried on AgentMessagePolicy.
- `ncl policies set --approver <user-id>` validates the user is an admin/owner
of the target at set-time, so the existing click-auth gate is unchanged.
- `requestApproval` gains `approverUserId` (single) to deliver the card to that
one user; the gate passes `policy.approver`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR review: extract `parseMessageContent` (text + attachment names from
the message content JSON) so `buildGateQuestion` reads as pure formatting, and
name the body-length cap (`GATE_CARD_BODY_MAX`) instead of a bare 1500.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR review: remove the `approvers` option entirely for v1 — the
approver is always the target group's admins/owners. Drops the `approvers`
DB column, the `--approvers` flag + its set-time validation, the now-unused
`approverUserIds` param on requestApproval, and the related tests. The
target-scoped approver pick (`approverAgentGroupId`) stays. Named approvers
can be re-added later via a migration when needed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR review: hoist session.agent_group_id into a named local
`sourceAgentGroupId`, mirroring `targetAgentGroupId`, and use it throughout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Shorten the verbose doc/inline comments added with the approval-policy gate
down to terse one-liners, matching the surrounding style (e.g. agent-destinations,
write-destinations). No behavior change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- policies.ts: drop the 10-line top banner. Sibling resource files carry no
descriptive header (only destinations.ts, and only for a non-obvious
side-effect); the prose already lives in the resource `description`.
- agent-message-policies.ts: remove `listMessagePolicies` — no production
caller (the `ncl policies list` op uses the generic table-based CRUD); only
its own test referenced it.
- message-gate.test.ts: assert the upsert-no-duplicate invariant via a direct
row count instead of the removed helper.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an optional, directed, per-message require-approval gate on top of an
existing agent-to-agent connection. No policy = today's free flow (fully
backward compatible). When a policy exists for A→B, each message A sends to B
is held, an approval card showing the message goes to B's admins, and the
message is delivered on approve / declined on reject. Rejecting one message
never blocks the connection.
- New `agent_message_policies` table (directed from→to; row exists = require
approval; `approvers` JSON, NULL = target admins). Deleted alongside its
connection so a stale rule can't reactivate on re-wire.
- Gate inside `routeAgentMessage` after the self/`hasDestination` checks:
holds the message via `requestApproval` and returns to consume it (like a
system action); the held message rides in the approval payload and is
re-routed by `applyA2aMessageGate` on approve. Self/internal messages are
never gated.
- `requestApproval` gains `approverAgentGroupId` / `approverUserIds` and stamps
`agent_group_id` on the pending row so the target's admins pass the
click-auth gate.
- `ncl policies list/set/remove`, operator-only (not in the container cli_scope
allowlist); `set` validates named approvers are admins/owners of the target.
Reuses the existing requestApproval / pending_approvals / approval-handler
spine (same shape as create_agent). Host-only; no container changes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.agents/skills -> ../.claude/skills and AGENTS.md -> CLAUDE.md so the
agents-convention paths resolve to the canonical ones. Drops the
host/skills indirection in favor of .claude as the single source of truth.
`claude setup-token` runs under script(1) so the browser OAuth flow keeps a
TTY while we capture the printed token. On terminals that wrap long lines
(e.g. sbx), the token lands split across lines with padding spaces, and the
old parser — which stripped only ANSI codes and newlines — matched just the
first fragment and failed the trailing `AA` check. Login succeeded; only our
parse of the human-oriented output failed (`No sk-ant-oat…AA token found`).
Add setup/lib/captured-token.ts: normalize the capture (strip ANSI/control
bytes and all whitespace, un-wrapping the token) then extract. The TS caller
(claude-assist.ts) and the bash registration script now share it, so the
normalization rules can't drift. Placeholder lines like
`export CLAUDE_CODE_OAUTH_TOKEN=<token>` are ignored.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
resolveGroupIpcPath has no production callers (only its own test); IPC was
removed in the v2 architecture (host<->container communicate solely via the two
session DBs). Drop the function, the now-unused DATA_DIR import, and its tests.
The breaking notice said the onecli setup step enforces the pinned versions, which is only true for fresh installs — on an existing install, updating does not upgrade the running gateway. Clarify that the gateway is separate: /update-nanoclaw upgrades it when the pin moves, otherwise upgrade manually per docs/onecli-upgrades.md.
When an update moves the onecli-gateway/onecli-cli pin in versions.json, the running gateway must be upgraded to match — otherwise the new code's @onecli-sh/sdk calls fail (404 on /v1/agents) and agents can't spawn. update-nanoclaw never detected this, so the upgrade was silently skipped. Add a conditional step that follows docs/onecli-upgrades.md before restart when the pin moves.