Commit Graph

9 Commits

Author SHA1 Message Date
NanoClaw bot user
0d7458c6f3 fix(skills): replace sqlite3 CLI with in-tree better-sqlite3 wrapper
Setup deliberately avoids the sqlite3 CLI (`setup/verify.ts:5` calls
this out: "Uses better-sqlite3 directly (no sqlite3 CLI)") and never
installs or probes for the binary. Despite that, 13 skills shelled out
to `sqlite3 ...` directly, breaking on hosts where the CLI isn't
preinstalled — the same root cause as #2191 but spread across the
skill surface.

Add `scripts/q.ts`, a ~30-LOC wrapper over the `better-sqlite3` dep
that setup already installs and verifies. Default output matches
`sqlite3 -list` (pipe-separated, no header) so existing skill text
reads identically — only the binary changes. SELECT/WITH queries go
through `db.prepare().all()`; everything else (INSERT/UPDATE/DELETE,
including compound statements) goes through `db.exec()`.

Migrate every in-tree caller:

- 17 hardcoded invocations across 8 SKILL.md files (init-first-agent,
  add-deltachat, add-signal, add-emacs, add-whatsapp, add-ollama-provider,
  debug, add-parallel) plus add-deltachat/VERIFY.md.
- `manage-channels/SKILL.md` shows canonical SQL but never prescribed
  a tool, so the assistant defaulted to `sqlite3` and silently failed.
  Add a one-line wrapper hint above the SQL block.
- `migrate-v2.sh` schema/count probes (was the original #2191 case).
  Replace `.tables` with `SELECT name FROM sqlite_master`.
- Document the wrapper convention in root `CLAUDE.md` under "Central DB".

Add `scripts/q.test.ts` with 6 vitest cases covering both modes,
NULL rendering, empty-result, compound mutations, and arg validation.
Wire `scripts/**/*.test.ts` into `vitest.config.ts`.

Out of scope (flagged for follow-up):
- `debug` and `add-parallel` still reference the v1-only path
  `store/messages.db`. Routing through the wrapper now produces a
  cleaner "no such file" error, but the surrounding sections are
  v1-era throughout — a v1-content cleanup is its own PR.
- `cleanup-sessions.sh` is being addressed in #1889 (different style,
  hard-fail rather than wrap); left untouched here to avoid stepping
  on that author's work.

Closes #2191.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 19:38:33 +02:00
gavrielc
40ddc94d0a Revert "fix(init-first-agent): seed welcome via inbound.db; drop --no-cli-bonus"
This reverts commit 9fe529984a.
2026-04-21 15:20:06 +03:00
Koshkoshinsk
9fe529984a fix(init-first-agent): seed welcome via inbound.db; drop --no-cli-bonus
The welcome DM used to be handed to the running service over the CLI
admin socket, which stamped `cli:local` as the sender. On a strict
messaging group (any fresh DM wired by init-first-agent), that tripped
the unknown-sender approval gate — the operator's own bootstrap script
ended up requesting its own approval. Fix by writing the welcome
directly into the session's inbound.db with a `System` sender; the
running service's host-sweep wakes the container on its next pass.

Also drop `--no-cli-bonus`. Now that init-cli-agent always wires
cli/local to the scratch CLI agent, every caller of init-first-agent
had to pass --no-cli-bonus to avoid double-wiring; the flag has become
mandatory, so it's just removed. The cli-bonus branch goes with it.

Skill docs updated in lockstep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 08:50:59 +00:00
gavrielc
6c26c0413a feat(router,cli): replyTo override + CLI admin-transport flows
- InboundEvent gains an optional replyTo; router stamps the row's address
  fields from it when set, so replies can route to a different channel than
  the one the inbound came in on.
- ChannelSetup adds onInboundEvent for admin-transport adapters that build
  the full event themselves.
- CLI wire format accepts {text, to, reply_to}. Routed messages go through
  onInboundEvent and do not evict an active chat client.
- init-first-agent hands the DM welcome to the running service via
  data/cli.sock — synchronous wake, no sweep wait. Fails loudly if the
  service is down; no silent fallback.
- Split the CLI scratch-agent bootstrap into scripts/init-cli-agent.ts;
  init-first-agent is DM-only.

Agents cannot set replyTo: it lives only on the inbound/router seam and is
consumed once when writing messages_in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 23:30:47 +03:00
gavrielc
c37609ffc8 docs(skills): drop "v2" from skill content + src/index.ts log lines
Cleans up the prose-level v2 references that the rename commit didn't
touch. Skills now describe themselves and the codebase without "v2"
versioning language. /add-X-v2 cross-references in setup, init-first-agent,
and manage-channels updated to /add-X.

Runtime path identifiers (data/v2.db, data/v2-sessions/, container name
nanoclaw-v2) deliberately left as-is — renaming them breaks live installs
without commensurate benefit.

Verified: pnpm run build clean, 326 host tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:42:55 +03:00
Koshkoshinsk
77321984ba feat(pair-telegram): emit REMINDER_TO_ASSISTANT in status block
Move the "print the pairing code as plain text" directive from three
skill docs into the CLI output itself. Every caller of pair-telegram
(init-first-agent, manage-channels, add-telegram-v2, future callers)
now sees the reminder directly in the PAIR_TELEGRAM_ISSUED and
PAIR_TELEGRAM_NEW_CODE blocks. Skill docs shortened to point at it.

Also add a short pre-tool-call sentence in init-first-agent step 3b
instructing the assistant to extract the code and ask the user to send
it in Telegram.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 11:50:25 +00:00
Koshkoshinsk
1eaf4f88d9 Revert "docs(skills): print pairing code as final message; drop log-tail verify"
This reverts commit bd6a26a002.
2026-04-15 10:48:17 +00:00
Koshkoshinsk
bd6a26a002 docs(skills): print pairing code as final message; drop log-tail verify
- Reword pair-code instruction across add-telegram-v2, manage-channels,
  and init-first-agent so the very last user-visible message after
  generating the code MUST be a plain-text print of it.
- Replace init-first-agent's tail -f based verify step with a plain-text
  prompt asking the user to confirm receipt of the welcome DM, falling
  back to DB-based diagnostics only on non-arrival. Avoids harness
  blocks on long leading sleeps and fragile log-string greps.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:31:46 +00:00
gavrielc
0d3326aae5 feat(v2): user-level privilege model + cold DM infra + init-first-agent skill
Replaces the agent-group-centric "main group" concept with user-level
privileges and adds the cold-DM infrastructure needed for proactive
outbound messaging (pairing, approvals, welcome flows).

Privilege model
- New tables: users, user_roles (owner global-only; admin global or
  scoped to an agent_group), agent_group_members (explicit non-
  privileged access; admin/owner imply membership), user_dms (cold-DM
  resolution cache).
- Removed agent_groups.is_admin, messaging_groups.admin_user_id. Replaced
  with messaging_groups.unknown_sender_policy (strict | request_approval
  | public) for per-chat unknown-sender gating.
- src/access.ts: canAccessAgentGroup, pickApprover, pickApprovalDelivery.
- src/router.ts: access gate on every inbound, honoring
  unknown_sender_policy for unknown senders.
- src/channels/telegram.ts: pairing interceptor upserts the paired user
  and promotes them to owner if hasAnyOwner() is false (first-pair-wins).

Cold DM infrastructure
- ChannelAdapter.openDM?(handle) — optional method. Chat-SDK-bridge wires
  it to chat.openDM() for resolution-required channels (Discord, Slack,
  Teams, Webex, gChat); direct-addressable channels (Telegram, WhatsApp,
  iMessage, Matrix, Resend) fall through to the handle directly.
- src/user-dm.ts: ensureUserDm(userId) — resolves + caches via user_dms.

Approval routing
- onecli-approvals + delivery use pickApprover + pickApprovalDelivery:
  scoped admins → global admins → owners (dedup), first reachable via
  ensureUserDm, same-channel-kind tie-break. Approvals land in the
  approver's DM, not the origin chat.

Delivery fixes
- delivery.ts ACL rejection now throws instead of returning undefined —
  the outer loop previously marked rejected messages as delivered.
- Implicit-origin allow: session.messaging_group_id === target skips the
  destination check.
- createMessagingGroupAgent auto-creates the companion agent_destinations
  row (normalized local_name from the messaging group's name, collision-
  broken within the agent's namespace).

Container
- container-runner.ts: /workspace/global always read-only; drops
  NANOCLAW_IS_ADMIN; adds NANOCLAW_ADMIN_USER_IDS (owners + global admins
  + scoped admins for this agent group). Agent-runner poll-loop gates
  slash commands against that set.

New skill: /init-first-agent
- Walks the operator through standing up the first agent for a channel:
  channel pick → identity lookup (reads each channel SKILL.md's
  ## Channel Info > how-to-find-id) → DM platform_id resolution (direct-
  addressable, cold-DM via "user DMs bot first + sqlite lookup", or
  Telegram pair-code fallback) → run scripts/init-first-agent.ts →
  verify via tail of nanoclaw.log.
- scripts/init-first-agent.ts: parameterized helper that upserts the
  user + grants owner (if none), creates dm-with-<display-name> agent
  group + initGroupFilesystem, reuses/creates the DM messaging_group,
  wires it (auto-creates destination), resolves the session, and writes
  a kind:'chat' / sender:'system' welcome message into inbound.db. Host
  sweep wakes the container and the agent DMs the operator via the
  normal delivery path.

/manage-channels rewrite
- Drops --is-main / --jid / main-vs-non-main isolation references.
- First-channel flow delegates to /init-first-agent.
- Explains createMessagingGroupAgent auto-creates destinations.
- Adds a privileged-users show section.

setup/
- register.ts: drop --is-main, --jid, --local-name, --trigger
  requiresTrigger defaults; call initGroupFilesystem; normalize to
  v2 schema (no is_admin, no admin_user_id, sets unknown_sender_policy
  'strict'); let createMessagingGroupAgent handle the destination row.
- pair-telegram.ts: emit PAIRED_USER_ID (namespaced "telegram:<id>")
  instead of ADMIN_USER_ID; update header comment.
- register.test.ts deleted — was v1-only, tested a registered_groups
  table that no longer exists.

Docs
- v2-architecture-diagram.{md,html}: ER diagram updated to drop
  is_admin/admin_user_id, add unknown_sender_policy, and include
  users/user_roles/agent_group_members/user_dms.
- v2-architecture-draft.md: approval-routing paragraph rewritten for
  pickApprover/pickApprovalDelivery/ensureUserDm; SQL schema block
  updated; admin-verification paragraph references
  NANOCLAW_ADMIN_USER_IDS.
- v2-setup-wiring.md: entity-model sketch rewritten.
- v2-checklist.md: marked privilege refactor / container filtering /
  approval routing / unknown-sender gating done; removed obsolete
  admin_user_id and main-vs-non-main items.

Scripts
- scripts/init-first-agent.ts (new) replaces scripts/welcome-owner-dm.ts
  (removed; welcome-owner was a Discord-specific one-off).
- test-v2-host.ts, test-v2-channel-e2e.ts, seed-discord.ts: drop
  is_admin + admin_user_id, use unknown_sender_policy.

Tests
- src/access.test.ts (new): 14 tests for canAccessAgentGroup, role
  helpers, pickApprover, ensureUserDm, pickApprovalDelivery.
- src/db/db-v2.test.ts: adds 3 tests for the auto-created
  agent_destinations row (normalized name, no duplicates, collision
  break within an agent group).
- host-core.test.ts, channel-registry.test.ts: updated fixtures to
  use unknown_sender_policy: 'public' where the test exercises routing
  rather than the access gate.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 00:03:51 +03:00