Commit Graph

883 Commits

Author SHA1 Message Date
exe.dev user
96d7656112 refactor(new-setup): rewrite probe in pure bash, drop unavailable fallback
The probe now returns a real snapshot from second zero, so every step
consults real probe fields instead of falling back to "run every step
blindly" when Node isn't installed. Also drops the redundant
CLI_AGENT_WIRED field (it gated the last step on its own end-state) and
scopes timezone out of the probe (timezone is not part of /new-setup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:40:53 +00:00
Koshkoshinsk
5542107b9e fix(new-setup): align onecli health path and rework auth flow
onecli step:
- Poll /api/health (was /health) so the step's health check matches
  the probe's. On hosted OneCLI (app.onecli.sh) the old path returned
  non-ok, flagging the gateway as "degraded" even though install
  succeeded.
- Drop the "try `onecli start`" hint — no such subcommand exists and
  it sent the skill off chasing fabricated commands. A failed health
  poll is demoted to a soft warning; the auth step surfaces a real
  outage via `onecli secrets list`.

SKILL.md step 4: rewrite to match the /setup skill's pattern — the
user generates the token themselves, picks dashboard or CLI to
register it with OneCLI, and the skill verifies via `auth --check`.
Tokens no longer travel through chat.

Co-Authored-By: Koshkoshinsk <daniel.milliner@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:10:21 +00:00
Koshkoshinsk
0992979c5a feat(new-setup): probe host-deps and skip bootstrap when already installed
Probe now emits HOST_DEPS (ok|missing) based on whether
node_modules/better-sqlite3/build/Release/better_sqlite3.node exists
— the canonical proof that `pnpm install` ran and the native build
step succeeded. Step 1 (Node bootstrap) skips when HOST_DEPS=ok
instead of always re-running setup.sh. Probe now genuinely routes
step 1 the same way it routes every other step.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:01:05 +00:00
Koshkoshinsk
f553c8126c refactor(new-setup): add step-4 join barrier and drop scripted one-liners
Two flow fixes:

1. Add "Ordering and parallelism" section making explicit that step 4
   (auth) must block until step 3 (OneCLI) is complete — auth writes
   the secret into the vault, so firing an AskUserQuestion while
   OneCLI is still installing asks the user for a credential the
   system can't store. Step 2 (container build) is safe to run past
   step 4, joined before step 6 (first CLI agent).

2. Drop the per-step quoted one-liners. They duplicated Claude's own
   natural narration ("While those build, let's get your credential
   set up." → immediately echoed by the scripted "Your agent needs an
   Anthropic credential..."). Each step now has a short description
   instead; Claude narrates in its own voice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:50:00 +00:00
Koshkoshinsk
77fec6c7c3 fix(new-setup): avoid double-bootstrap and corepack EACCES on system Node
Two fixes to the fresh-install path:

1. setup.sh: when `corepack enable` runs as a non-root user against a
   system-wide Node install (apt-installed to /usr/bin), it fails EACCES
   trying to symlink /usr/bin/pnpm, leaving pnpm off PATH. Retry with
   sudo when pnpm is still missing — gated to Linux/WSL so macOS
   Homebrew prefixes aren't polluted with root-owned shims.

2. SKILL.md step 1: if the probe reports STATUS: unavailable (Node not
   installed), install Node BEFORE invoking `bash setup.sh`. The old
   flow ran setup.sh first as a diagnostic, which always failed fast,
   installed Node, then re-ran — two bootstraps for no reason.

Combined: fresh Linux box now goes Node install -> single setup.sh run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:37:03 +00:00
Koshkoshinsk
77624d7854 fix(new-setup): wrap probe in shell script for single-command permission check
The chained `&& / ||` inline command tripped Claude Code's per-operation
permission check. Move the Node-missing fallback into setup/probe.sh so
the skill's `!` block is a single `bash setup/probe.sh` call.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:05:54 +00:00
Koshkoshinsk
b3e8b2e047 fix(new-setup): run probe before pnpm is installed
Port probe to zero-dep plain ESM (setup/probe.mjs) so /new-setup can
inject dynamic context on a fresh machine where pnpm/node_modules
don't yet exist. Skill falls back to a STATUS: unavailable block if
Node itself isn't on PATH, and the flow treats that as "run every
step from 1" (each step is idempotent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 11:03:49 +00:00
Koshkoshinsk
f6ddd20636 feat(new-setup): add skill definition
Shortest path from zero to a working two-way agent chat via the CLI
channel. Renders `!`pnpm exec tsx setup/index.ts --step probe`` at the
top for dynamic context injection — Claude sees current system state
before generating its first response and routes each subsequent step
(skip/ask/run) off the probe snapshot. Pre-approves the Bash patterns
it needs via `allowed-tools` so setup runs without per-step prompts.

Lives alongside /setup for now; will replace it once proven.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:43:41 +00:00
Koshkoshinsk
6db81554bf feat(new-setup): add probe step for dynamic context injection
Single upfront parallel scan the SKILL.md renders via `!`...`` so Claude
sees system state before generating its first response. Each field maps
to a routing decision (skip/run/ask) for a downstream step.

Reports: OS, SHELL, DOCKER + IMAGE_PRESENT, ONECLI_STATUS + ONECLI_URL,
ANTHROPIC_SECRET, SERVICE_STATUS, CLI_AGENT_WIRED, INFERRED_DISPLAY_NAME,
TZ_STATUS + TZ_ENV + TZ_SYSTEM. Runs in ~200ms on a fully-set-up host.

Not a replacement for per-step idempotency — each step keeps its own
checks since probe is a snapshot and can go stale by execution time.

Uses /api/health (OneCLI's actual endpoint). Anthropic secret check
uses the CLI client so it works whenever onecli is installed, even if
the direct HTTP health probe fails (different network paths).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:43:38 +00:00
Koshkoshinsk
01389ff8fc feat(new-setup): add onecli, auth, and cli-agent dispatcher steps
Aggregates the loose OneCLI install, secret registration, and first-agent
wiring commands from /setup into three new dispatcher steps. Adds
--cli-only mode to init-first-agent so /new-setup can reach a working
2-way CLI chat with the bare minimum.

- setup/onecli.ts: idempotent install + PATH + api-host + .env, polls /health
- setup/auth.ts: --check verifies secret; --create --value registers it
- setup/cli-agent.ts: wraps init-first-agent --cli-only
- scripts/init-first-agent.ts: --cli-only mode; DM mode unchanged

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:43:35 +00:00
gavrielc
5ed5b72f10 docs: consolidate refactor follow-ups into a single REFACTOR.md
Single forward-looking reference that replaces the two untracked planning
docs (REFACTOR_PLAN.md + REFACTOR_EXECUTION.md) which had become a mix of
historical PR timeline and still-relevant decisions.

Keeps only what's actionable going forward:
- Module tiers, the four registries, and the module distribution model
  (architecture summary).
- Remaining work: Phase 5 (v2 → main) and the modules-branch decision.
- Operational patterns worth preserving (standing checks, TDZ rule,
  branch-sync file-presence diff procedure, prettier drift pattern).
- 17 curated open questions across design, distribution, core slotting,
  and documentation.

Canonical references (docs/module-contract.md, docs/architecture.md, etc.)
are linked but not duplicated. This doc is transient — retire when the
refactor is fully behind us.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:14:09 +03:00
gavrielc
60ffe34396 Merge pull request #1853 from qwibitai/refactor/pr9-cli-channel
feat(channels): add CLI channel — talk to your agent from the terminal
2026-04-18 22:02:02 +03:00
gavrielc
131fc99700 feat(channels): add CLI channel — talk to your agent from the terminal
First default channel that ships with main. Unix-socket adapter + thin
client; plugs into the running daemon rather than spawning its own host.

## src/channels/cli.ts

- ChannelAdapter with channelType='cli', platformId='local'.
- setup() unlinks any stale socket, listens on $DATA_DIR/cli.sock (mode 0600
  so only the local user can connect).
- On client connect: reads newline-delimited JSON ({"text": "..."}) and
  calls config.onInbound('local', null, {id, kind:'chat', content, ts}).
- deliver() writes {"text": <body>} back to the connected socket; silently
  no-ops when no client is attached (outbound row still persists).
- Single-client policy: a second connection supersedes the first with a
  [superseded] notice.
- teardown() closes the client, closes the server, removes the socket file.

## scripts/chat.ts + pnpm run chat

One-shot client:
- pnpm run chat <message...>
- Connects to the socket, writes one JSON line with the message.
- Reads replies; exits 2s after the first reply lands (hard timeout 120s).
- ENOENT/ECONNREFUSED prints a hint to start the daemon.

## scripts/init-first-agent.ts

- Fix stale imports after earlier module extractions (permissions +
  agent-to-agent moved their DB helpers into modules/).
- After wiring the DM channel, also create cli/local messaging_group
  (unknown_sender_policy='public' — local socket perms handle auth) and
  wire it to the same agent. User can `pnpm run chat` immediately.

## package.json

- Add "chat": "tsx scripts/chat.ts" script.

## Validation

- pnpm run build clean.
- pnpm test — 137 host tests pass.
- bun test in container/agent-runner — 17 pass.
- Service boot logs: "CLI channel listening" + "Channel adapter started
  channel=cli type=cli". Clean SIGTERM shutdown; socket file removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 21:51:04 +03:00
gavrielc
e5319b3e1e Merge pull request #1851 from qwibitai/refactor/pr8-slim-and-cli
refactor: relocate outbox I/O, dead-code sweep, add CLI harness
2026-04-18 21:34:37 +03:00
gavrielc
7169c25e70 refactor: relocate outbox I/O to session-manager + dead-code sweep
## Outbox extraction (delivery.ts → session-manager.ts)

File I/O for outbound attachments now lives in session-manager.ts alongside
the symmetric inbound extractAttachmentFiles. delivery.ts no longer touches
the filesystem — it hands buffers to the adapter and calls clearOutbox on
success.

- New `readOutboxFiles(agentGroupId, sessionId, messageId, filenames)` and
  `clearOutbox(agentGroupId, sessionId, messageId)` in session-manager.ts.
- deliverMessage in delivery.ts loses ~35 lines of fs/path code and its
  `fs`/`path` imports.

## Dead-code sweep

TypeScript's --noUnusedLocals surfaced several cruft imports. Fixed:

- src/container-runner.ts: drop unused `markContainerIdle` import; drop
  unused `session` parameter from `buildContainerArgs` signature.
- src/delivery.ts: drop unused `getSession`, `writeSessionMessage`,
  `wakeContainer` imports.
- src/host-sweep.ts: drop unused `updateSession`, `outboundDbPath` imports.
- container/agent-runner/src/poll-loop.ts: drop unused `config`,
  `processingIds` params from `processQuery`.
- Test files: drop unused imports in channel-registry.test, db-v2.test,
  host-core.test.

Skipped: `conversations` state in chat-sdk-bridge.ts (never read but
tangled with public `updateConversations` method; cleaning it risks a
merge conflict with the channels branch at the next sync).

## Validation

- `pnpm run build` clean
- `pnpm test` — 137 host tests pass
- `bun test` in container/agent-runner — 17 tests pass
- Service boots (`NanoClaw running`, `OneCLI approval handler started`)
  and shuts down cleanly on SIGTERM

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 21:34:08 +03:00
gavrielc
3d945db6eb Merge pull request #1849 from qwibitai/refactor/pr7-retier-approvals
refactor(modules): re-tier approvals as default, extract self-mod as optional
2026-04-18 19:54:49 +03:00
gavrielc
95fdec335a refactor(modules): re-tier approvals as default; extract self-mod as optional
Promotes approvals to the default tier with a public API (requestApproval +
registerApprovalHandler) that other modules consume. Self-modification
(install_packages / request_rebuild / add_mcp_server) moves into a new
optional module that registers delivery actions + matching approval handlers
via the new API.

## Approvals (default tier)

- Adds `src/modules/approvals/primitive.ts` exporting `requestApproval`,
  `registerApprovalHandler`, `notifyAgent`. Absorbs `pickApprover` /
  `pickApprovalDelivery` / `channelTypeOf` from the deleted `src/access.ts`.
- Rewrites `response-handler.ts` to dispatch to registered approval handlers
  on approve (action-keyed Map). Reject path is centralized.
- Drops the three self-mod-specific delivery-action registrations from
  `approvals/index.ts`; they belong to self-mod now.
- `onecli-approvals.ts` now imports picks from the primitive instead of
  `src/access.ts`.

## Self-mod (optional tier)

- New `src/modules/self-mod/` with request handlers (validate input + call
  requestApproval) and apply handlers (orchestration on approve).
- `apply.ts` owns updateContainerConfig + buildAgentGroupImage + killContainer
  calls. Self-mod depends on approvals (via registerApprovalHandler +
  requestApproval + notifyAgent) and on core (container-runner, container-config).
- Registers 3 delivery actions + 3 approval handlers at import time.

## Other changes

- `src/access.ts` and `src/access.test.ts` deleted. Tests split across
  `src/modules/approvals/picks.test.ts` (approver selection) and
  `src/modules/permissions/permissions.test.ts` (access + roles + DM).
- `src/modules/index.ts` barrel: approvals loads before self-mod so
  registerApprovalHandler is bound when self-mod registers at import time.

## Validation

- `pnpm run build` clean
- `pnpm test` — 137 host tests pass
- `bun test` in container/agent-runner — 17 tests pass
- Service starts; boot log shows `OneCLI approval handler started`,
  `NanoClaw running`; clean SIGTERM shutdown

Resolves the transitional tier violation flagged in PR #5 where core
imported from the permissions optional module via `src/access.ts`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:41:26 +03:00
gavrielc
1f179e07f2 Merge pull request #1848 from qwibitai/refactor/pr6-agent-to-agent
refactor(modules): extract agent-to-agent as registry-based module
2026-04-18 19:12:17 +03:00
gavrielc
46b19dcf9c refactor(modules): extract agent-to-agent as registry-based module
Last extraction of Phase 3. Moves inter-agent messaging + create_agent +
destination projection into src/modules/agent-to-agent/. Core retains:

- `channel_type === 'agent'` dispatch in delivery.ts, guarded by
  hasTable('agent_destinations') + dynamic import into module.
- Channel-permission ACL in delivery.ts, guarded by hasTable, with
  inlined SQL (no module import from core).
- writeDestinations call in container-runner.ts, guarded by hasTable +
  dynamic import into module.
- createMessagingGroupAgent's destination side effect in db/messaging-groups.ts,
  guarded by hasTable. This is a documented transitional tier violation
  (core imports from optional module), analogous to src/access.ts.

Migration `004-agent-destinations.ts` renamed to `module-agent-to-agent-
destinations.ts` preserving `name: 'agent-destinations'` so existing DBs
don't re-run it.

delivery.ts: 600 → 449 lines. handleSystemAction's last switch case gone
(just registry + default log-and-drop). notifyAgent helper removed (only
create_agent used it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:00:10 +03:00
gavrielc
c80a23e24f Merge pull request #1847 from qwibitai/refactor/pr5-permissions
refactor(modules): extract permissions as optional module
2026-04-18 18:41:52 +03:00
gavrielc
32bcc2c5ae refactor(permissions): preserve pre-PR behavior in three spots
PR #5 review flagged three behavior changes that shouldn't have slipped
in. This commit reverts each to match the pre-refactor behavior exactly.

1. User upsert ordering. Split the router hook into two setters:
   setSenderResolver (runs before agent resolution) and setAccessGate
   (runs after). Restores the pre-PR sequence where the users row is
   upserted even if the message is dropped by wiring or trigger rules.

2. dropped_messages audit. Moved src/modules/permissions/db/dropped-messages.ts
   back to src/db/dropped-messages.ts. The table is core audit infra, not
   permissions-specific. Router re-writes rows for no_agent_wired and
   no_trigger_match; the access gate writes rows for policy refusals.

3. Permissionless container fallback. Dropped. poll-loop restores the
   original deny-all check when NANOCLAW_ADMIN_USER_IDS is empty.

Module contract doc updated with the two-hook shape.

Validation: host build clean, 137/137 host tests, 17/17 container
tests, typecheck clean, service boots to "NanoClaw running" with
permissions module registering both hooks and clean SIGTERM shutdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 18:00:10 +03:00
gavrielc
7cc4ecc3be refactor(modules): extract permissions as optional module
Moves user-roles / users / agent-group-members / user-dms /
dropped-messages / user-dm / canAccessAgentGroup into
src/modules/permissions/. Module registers a single inbound-gate that
owns sender resolution, access decision, unknown-sender policy, and
drop-audit recording.

Router slimmed from 357 → 179 lines; the inline fallback chain
(extractAndUpsertUser / enforceAccess / handleUnknownSender /
recordDroppedMessage) is gone — without the permissions module core
defaults to allow-all with userId=null.

container-runner's admin-ID query is now inline SQL guarded by
sqlite_master on user_roles, keeping core free of any import from the
permissions module. The container-side formatter falls back to
permissionless mode when NANOCLAW_ADMIN_USER_IDS is empty: every sender
with an identifiable senderId is treated as admin.

Module contract doc formalizes the tier model and the dependency rule
(core ← default modules ← optional modules). One transitional violation
flagged: src/access.ts (core) imports from the permissions module for
its remaining approver-picking helpers; resolves in the planned PR #7
re-tier.

Validation: host build clean, 137/137 host tests, 17/17 container
tests, typecheck clean, service boots to "NanoClaw running" with
permissions module registering its gate and clean SIGTERM shutdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:42:14 +03:00
gavrielc
e75af5e44d Merge pull request #1842 from qwibitai/refactor/pr4-scheduling
refactor: extract scheduling as registry-based module (PR #4)
2026-04-18 17:23:27 +03:00
gavrielc
473f766585 refactor(modules): extract scheduling as registry-based module
Moves the scheduling surface — 5 delivery actions (schedule_task,
cancel_task, pause_task, resume_task, update_task), handleRecurrence,
applyPreTaskScripts, and task DB helpers — out of core and into
src/modules/scheduling/ (host) and container/agent-runner/src/scheduling/
(container).

First PR to fill the MODULE-HOOK markers introduced in PR #2:
  - src/host-sweep.ts MODULE-HOOK:scheduling-recurrence now dynamically
    imports handleRecurrence from the module each sweep tick.
  - container/agent-runner/src/poll-loop.ts MODULE-HOOK:scheduling-pre-task
    dynamically imports applyPreTaskScripts before the provider call.
    When the marker block is empty (scheduling uninstalled), `keep`
    falls back to `normalMessages` so non-task messages still flow.

The 5 task cases are removed from delivery.ts's handleSystemAction
switch — the registry now routes them. Task DB helpers moved out of
src/db/session-db.ts (which kept `nextEvenSeq` as a named export so
the module can uphold the host-writes-even-seq invariant). Test suite
split to match: scheduling-specific tests live in the module.

No migration — tasks are messages_in rows with kind='task'.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 16:17:47 +03:00
gavrielc
71aab8c316 Merge pull request #1840 from qwibitai/refactor/pr3-approvals-interactive
refactor(modules): extract approvals + interactive as registry-based modules
2026-04-18 15:57:29 +03:00
gavrielc
626c565a70 fix(modules): break circular import TDZ between index.ts and modules
PR #3 introduced a circular-import temporal-dead-zone bug that didn't
surface in unit tests but crashed the service at startup:

  src/index.ts imports './modules/index.js' for side effects
  → src/modules/interactive/index.ts calls registerResponseHandler()
  → that function is declared in src/index.ts
  → but src/index.ts's const responseHandlers = [] hasn't been
    initialized yet (we're in the middle of its module-init)
  → ReferenceError: Cannot access 'responseHandlers' before initialization

Same issue for registerResponseHandler itself (the function reference
resolves to undefined) and for onShutdown in the approvals module.

Caught when the operator started the service and systemd flagged the
process as crashing in auto-restart loop.

Fix: extract responseHandlers + registerResponseHandler + shutdownCallbacks
+ onShutdown into src/response-registry.ts, which has no dependencies on
src/index.ts or on modules. index.ts re-exports the same surface for any
existing consumers; modules import directly from response-registry.js.

The bug was latent because:
- Unit tests import pieces, never src/index.ts's main() flow.
- Host builds clean because TypeScript doesn't catch runtime circular
  init order.
- Only surfaces when the ES module loader actually executes src/index.ts
  as the entry point.

Verified: service boots on Linux host with approvals + interactive
loaded; OneCLI handler starts via onDeliveryAdapterReady callback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:48:43 +03:00
gavrielc
a4573395d9 refactor(modules): extract approvals + interactive as registry-based modules
Phase 2 / PR #3 of the module refactor. Moves the approval and interactive-
question flows out of core and into src/modules/, wired through the response
dispatcher and delivery action registries.

New modules:
- src/modules/interactive/ — registers a response handler that claims
  pending_questions rows, writes question_response to the session DB, wakes
  the container. createPendingQuestion call stays inline in delivery.ts
  (guarded by hasTable) per plan.
- src/modules/approvals/ — registers 3 delivery actions (install_packages,
  request_rebuild, add_mcp_server), a response handler for pending_approvals
  (including OneCLI action fall-through), an adapter-ready hook that boots
  the OneCLI manual-approval handler, and a shutdown hook that stops it.
  OneCLI implementation (src/onecli-approvals.ts) moves into the module.

Core lifecycle hooks added (narrow, not registries):
- onDeliveryAdapterReady(cb) in delivery.ts — fires when setDeliveryAdapter
  runs (or immediately if already set). Used by approvals for OneCLI boot.
- onShutdown(cb) in index.ts — fires on SIGTERM/SIGINT. Used by approvals
  for OneCLI teardown.
- getDeliveryAdapter() getter in delivery.ts — for live-flow adapter access
  in registered delivery actions.

Core shrinks: delivery.ts 911 → 665 lines, index.ts 405 → 224 lines.
dispatchResponse now logs "Unclaimed response" instead of falling through
to an inline handler — the inline fallback moved into the two modules.

Migration files renamed to the module-<name>-<short>.ts convention:
- 003-pending-approvals.ts → module-approvals-pending-approvals.ts
- 007-pending-approvals-title-options.ts → module-approvals-title-options.ts
Migration.name fields unchanged so existing DBs treat them as already-applied.

Degradation verified: emptying the modules barrel builds clean and 137/137
tests pass. Actions would log "Unknown system action"; button clicks would
log "Unclaimed response".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:16:53 +03:00
gavrielc
a612c2ca24 Merge pull request #1839 from qwibitai/refactor/pr2-scaffolding
refactor: scaffold module registries + default-module layout (PR #2)
2026-04-18 14:48:50 +03:00
gavrielc
4202041d0b refactor: scaffold module registries and default-module layout
Additive change — existing code paths still run via inline fallbacks.
Prepares core for per-module extractions in PR #3 onward.

Four registries added with empty defaults:
  - delivery action handlers (delivery.ts)
  - router inbound gate (router.ts)
  - response dispatcher (index.ts)
  - MCP tool self-registration (container/agent-runner/src/mcp-tools/server.ts)

Default modules moved to src/modules/ for signaling:
  - src/modules/typing/       (extracted from delivery.ts)
  - src/modules/mount-security/ (moved from src/mount-security.ts)

Both are imported directly by core — no hook, no registry. Removal
requires editing core imports.

Migrator now keys applied rows by name (uniqueness) so module
migrations can pick arbitrary version numbers. Stored version column
is auto-assigned as an applied-order sequence.

sqlite_master guards added around core calls into module-owned tables
(user_roles, agent_destinations, pending_questions). No-ops today;
load-bearing after the owning modules are extracted.

MODULE-HOOK markers placed at scheduling's two skill-edit sites
(host-sweep.ts recurrence call, poll-loop.ts pre-task gate). PR #4
replaces the marked blocks when scheduling moves to its module.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:46:19 +03:00
gavrielc
1888ecc1e9 Merge pull request #1838 from qwibitai/refactor/pr1-prep
refactor: PR #1 prep — v1 removal, module contract, sdk bump
2026-04-18 14:26:30 +03:00
gavrielc
a1b227269e chore(deps): bump @onecli-sh/sdk to 0.3.1
Lockfile was pinned to 0.2.0 while package.json already declared
^0.3.1. The code depends on types added in 0.3.x (ApprovalRequest,
ManualApprovalHandle, configureManualApproval), so the host build
was failing on v2. Refreshing the lockfile resolves it.

0.3.1 was published 2026-04-10, well clear of minimumReleaseAge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:24:04 +03:00
gavrielc
53e8135102 docs: add module contract for refactor
Codifies the interface between core and modules: the four registries
(delivery actions, inbound gate, response dispatcher, MCP tool
self-registration), default modules (typing, mount-security),
guarded-inline fallbacks, MODULE-HOOK skill-edit markers, and module
migration naming.

Authoritative reference for downstream extraction PRs and install
skills. See REFACTOR_PLAN.md for broader context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:23:56 +03:00
gavrielc
86becf8dea chore: delete v1 reference code
Removes src/v1/ (37 files) and container/agent-runner/src/v1/ (3 files)
along with the v1 reference note in CLAUDE.md and the now-obsolete
tsconfig exclude. v1 was already out of the runtime path; this just
removes the dead weight.

~8,800 LOC removed, zero runtime change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 14:23:47 +03:00
gavrielc
27c52205f9 fix(channels): bridge openDM delegates to adapter directly
chat.openDM dispatches via inferAdapterFromUserId, which only recognizes
Discord/Slack/Teams/gChat formats and throws for everything else —
breaking approval delivery on Telegram (numeric IDs) and the other
direct-addressable channels the bridge now wraps. Delegate straight to
adapter.openDM + channelIdFromThreadId, and only expose openDM when the
underlying adapter implements it. Preserves the adapter's native
platform_id encoding (e.g. "telegram:<chatId>") so user_dms caches align
with the messaging_groups rows onInbound wrote.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 18:30:38 +03:00
gavrielc
e93292de2a fix(agent-runner): spawn built-in MCP server with bun, not node
The Bun migration (c5d0ef8) dropped the in-image tsc build step, so
/app/src/mcp-tools/index.js never exists — only index.ts. The spawn
config in container/agent-runner/src/index.ts still pointed at
index.js and invoked it with `node`, which can't execute TypeScript
anyway. Net effect: every session failed to start the `nanoclaw`
MCP server, so scheduling, send_to_agent, interactive questions,
and self-mod tools were silently absent from the agent's toolset.

Matches entrypoint.sh and src/container-runner.ts, which already
use `exec bun run /app/src/index.ts` for the same reason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 17:17:45 +03:00
gavrielc
bd659fd7d6 style(v2/delivery): prettier reflow of test import
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:17:07 +03:00
gavrielc
ac9535baa8 fix(v2/delivery): prevent double-send from overlapping delivery polls
The active poll (1s, running sessions) and sweep poll (60s, all active
sessions) both call deliverSessionMessages, and a running session is in
both result sets. Without locking they race on the same outbound row:
both read it as undelivered, both call the channel adapter, both
markDelivered. INSERT OR IGNORE hides the DB collision but the user has
already received the message twice.

Adds a per-session inflight guard; the second concurrent caller skips
and picks up any leftover work on the next poll tick.

Also makes outbox cleanup in deliverMessage best-effort: the message is
already on the user's screen, so a cleanup throw must not propagate to
the retry path (which would resend).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 15:17:06 +03:00
gavrielc
bfc626be82 docs: drop v2 framing across CLAUDE.md and 12 docs
Renamed 12 docs/v2-*.md → docs/*.md (already in index from earlier git mv).
Rewrote CLAUDE.md to describe the codebase as just "the codebase" rather
than "v2"; added a "Channels and Providers (skill-installed)" section
reflecting the new model and updated the docs index links.

Agent (general-purpose) cleaned the 12 doc bodies:
- Dropped "NanoClaw v2" / "v2 schema" / "(v2)" prose throughout
- Rewrote inter-doc cross-references docs/v2-X.md → docs/X.md
- Architecture, agent-runner-details: collapsed v1↔v2 comparison tables
  into present-tense facts; added notes that trunk only ships `claude`
  and that channel adapters are skill-installed from the `channels` branch
- Setup-wiring, checklist: dropped v1→v2 migration items that no longer
  apply
- Frozen runtime paths preserved: data/v2.db, data/v2-sessions/,
  container name nanoclaw-v2

git grep confirms remaining `\bv2\b` matches in docs/ are only those
runtime paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:53:21 +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
gavrielc
00fb1bee4a chore(skills): rename add-*-v2 → add-* and drop dead v1 channel skills
Renamed 13 skill folders to drop the -v2 suffix (the v2/v1 distinction
isn't load-bearing anymore — there is no v1 runtime). Deleted the four
v1 channel skills that occupied the rename target paths (add-discord,
add-slack, add-telegram, add-whatsapp); they targeted src/v1 which is
reference-only per CLAUDE.md.

Skill content still says "v2" in places — that's a follow-up commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:38:19 +03:00
gavrielc
4857512267 refactor(v2): move setup/groups.ts off trunk + drop pino dep
setup/groups.ts is whatsapp-only — its inline syncScript imports baileys
and pino to fetch group metadata via Baileys.groupFetchAllParticipating.
On trunk it was a no-op for non-whatsapp users (returned early without
auth) and the only thing keeping pino alive.

Removed:
- setup/groups.ts (lives on `channels` branch; restored by /add-whatsapp-v2)
- `groups` STEPS entry from setup/index.ts
- pino from package.json (no longer used outside the moved file)

/add-whatsapp-v2 skill updated to copy setup/groups.ts and register both
groups + whatsapp-auth in setup/index.ts STEPS, install pino@9.6.0 along
with baileys + qrcode.

Verified: build clean, 326 host tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:34:16 +03:00
gavrielc
712720eef4 refactor(v2): drop @chat-adapter/shared dep — duck-type NetworkError
The only use was channel-registry.ts checking `err instanceof NetworkError`
to retry transient setup failures. Switched to a duck-type predicate
(`err.name === 'NetworkError'`) so the dep is no longer needed at trunk
level. Channel skills bring it in transitively when they install their
Chat SDK adapter package.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:33:22 +03:00
gavrielc
a3376c25df docs(channel-skills): rewrite all 13 /add-*-v2 skills for copy-from-channels-branch pattern
Each skill now:
- Pre-flight: checks for file present + import line + dep listed (idempotent)
- git fetch origin channels
- git show origin/channels:<paths> > <paths> to copy adapter (and helpers/tests/setup-step where applicable)
- Append `import './<chan>.js';` to src/channels/index.ts
- pnpm install <pkg>@<pinned-version>
- pnpm run build

Telegram additionally copies 4 helper/test files + setup/pair-telegram.ts
and registers `'pair-telegram':` in setup/index.ts STEPS.

WhatsApp (native) additionally copies setup/whatsapp-auth.ts and
registers `'whatsapp-auth':` in setup/index.ts STEPS, installs
@whiskeysockets/baileys + qrcode + @types/qrcode pinned.

All credential / next-steps / channel-info / troubleshooting sections
preserved verbatim.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:24:20 +03:00
gavrielc
437ba63a2f refactor(v2): move channel adapters off v2 trunk
v2 ships with no channels baked in. All channel adapters (discord, slack,
telegram + helpers, whatsapp, whatsapp-cloud, gchat, github, imessage,
linear, matrix, resend, teams, webex) and their channel-specific setup
steps (pair-telegram, whatsapp-auth) now live on the `channels` branch
and get copied in via /add-*-v2 skills.

Removed:
- src/channels/{discord,slack,telegram*,whatsapp*,gchat,github,imessage,linear,matrix,resend,teams,webex}.ts
- setup/{pair-telegram,whatsapp-auth}.ts
- 14 channel-specific deps from package.json (@chat-adapter/*, @beeper/*,
  @bitbasti/*, @resend/chat-sdk-adapter, @whiskeysockets/baileys,
  chat-adapter-imessage, qrcode, @chat-adapter/state-memory unused)
- Their corresponding STEPS entries from setup/index.ts
- Channel imports from src/channels/index.ts

Kept:
- Channel infra: adapter.ts, channel-registry.ts (+ test), chat-sdk-bridge.ts,
  ask-question.ts, an empty-imports index.ts
- Chat SDK runtime (`chat`) for channels that copy in via Chat SDK bridge
- @chat-adapter/shared promoted from transitive to direct dep
  (channel-registry.ts uses NetworkError from it)

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:20:28 +03:00
gavrielc
32a973f1cd docs(add-opencode): rewrite skill for copy-from-providers-branch pattern
v2 no longer ships opencode on trunk. The skill now:
- Fetches origin/providers
- Copies opencode source files to their target paths
- Appends self-registration imports to both provider barrels
- Adds @opencode-ai/sdk@1.4.3 as a pinned agent-runner dep
- Adds OPENCODE_VERSION ARG + opencode-ai pnpm global install to Dockerfile
- Rebuilds host + container

All steps idempotent. Credential/env/Zen docs unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:14:10 +03:00
gavrielc
03dd559786 style: trim trailing whitespace in providers barrel
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:12:35 +03:00
gavrielc
e0258e8c1b refactor(v2): move opencode provider off v2 trunk
v2 ships with only claude baked in. opencode now lives on the `providers`
branch and gets copied in via the /add-opencode skill.

Removed:
- src/providers/opencode.ts
- container/agent-runner/src/providers/{opencode,mcp-to-opencode}.ts + test
- @opencode-ai/sdk from agent-runner package.json + bun.lock
- opencode-ai global install + OPENCODE_VERSION ARG from Dockerfile
- opencode self-registration imports from both provider barrels
- opencode test case from factory.test.ts

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 14:10:56 +03:00
gavrielc
2529c3e387 Merge pull request #1776 from talmosko-code/feat/add-opencode-v2
feat(v2): OpenCode agent provider
2026-04-17 12:22:48 +03:00
gavrielc
f18d0561a6 Merge pull request #1814 from qwibitai/refactor/provider-barrel-v2
refactor(v2/providers): self-registration barrel + host container-config registry
2026-04-17 12:22:12 +03:00
Tal Moskovich
c9dd6e050e docs: update OpenCode Zen integration details in SKILL.md
- Clarified the use of `x-api-key` for Zen's HTTP API, addressing common issues with Bearer tokens.
- Added configuration examples for `.env` and OneCLI registration for Zen keys.
- Provided guidance on naming conventions for OpenCode agent and provider settings.
- Included a note on the difference in authentication methods between OpenCode and OpenRouter.
2026-04-17 12:20:22 +03:00