- docs/v1-vs-v2/: full v1→v2 regression analysis (SUMMARY + 21 per-module docs + ACTION-ITEMS rollup with decisions + timezone recreation spec). - container/agent-runner/scripts/sdk-signal-probe.ts: empirical harness used to characterise Claude Agent SDK event/hook/stderr timing for the stuck-detection design in item 9. - src/channels/chat-sdk-bridge.ts: document the conversations Map staleness in a code comment; fix deferred to when dynamic group registration lands (ACTION-ITEMS item 17). No runtime behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.2 KiB
remote-control: v1 vs v2
Scope
v1:
/Users/gavriel/nanoclaw4/src/v1/remote-control.ts(218 lines)/Users/gavriel/nanoclaw4/src/v1/remote-control.test.ts(379 lines)- Integrated into v1 host via
restoreRemoteControl()call at startup (v1/index.ts:42)
v2 Counterparts:
/Users/gavriel/nanoclaw4/src/access.ts(115 lines) — privilege/approval routing/Users/gavriel/nanoclaw4/src/onecli-approvals.ts(269 lines) — OneCLI credential-gated action approval/Users/gavriel/nanoclaw4/src/webhook-server.ts(134 lines) — HTTP webhook ingress for Chat SDK adapters/Users/gavriel/nanoclaw4/src/router.ts(start of file) — inbound message routing with access gates
Capability Map
| v1 Behavior | v2 Location | Status | Notes |
|---|---|---|---|
Start claude remote-control child process, extract URL |
Removed | ❌ Removed | v2 has no equivalent. The claude remote-control CLI was a v1-only mechanism tied to individual Telegram chats. |
| Session state persistence (PID, URL, metadata) | Removed | ❌ Removed | v2 is stateless at the host level — all per-session state lives in inbound.db / outbound.db. |
| Auto-accept "Enable Remote Control?" prompt via stdin | Removed | ❌ Removed | v1 quirk tied to Claude CLI's interactive mode; no equivalent in v2. |
| Restore session from disk on startup | Removed | ❌ Removed | v2 has no startup recovery loop for stale processes. Sessions are created on-demand. |
| Detect dead process by signal check | Removed | ❌ Removed | v2 uses per-session heartbeat file (/workspace/.heartbeat) and inactivity detection via 60s sweep. |
| HTTP URL polling + timeout handling | Webhook server | ✅ Moved | v2's webhook-server.ts (line 16–124) runs a persistent HTTP server (default port 3000) for Chat SDK adapter webhooks. Routes via /webhook/{adapterName} (not URL-in-stdout polling). |
| Single active session per host | Per-agent-group sessions | ✅ Evolved | v2 supports unlimited concurrent sessions. Each (agent_group, messaging_group, thread) tuple is a separate session with its own container. |
getActiveSession() getter |
Removed | ❌ Removed | No global session concept. v2 queries sessions via getSession(sessionId) in db/sessions.ts. |
| Credential access approval | OneCLI approval handler | ✅ Moved | v2's onecli-approvals.ts (line 92–215) handles credential-gated action approval. OneCLI gateway intercepts HTTP, delivers ask_question card to approver, persists pending_approvals row (line 173–196). |
| Approver selection (admin → owner chain) | access.ts | ✅ Moved | pickApprover() (access.ts:55–72) returns ordered list: agent-group admins → global admins → owners. Same preference order as v1 logic. |
| Approval delivery to DM (same channel kind preferred) | access.ts + user-dm.ts | ✅ Moved | pickApprovalDelivery() (access.ts:83–101) walks approver list, prefers same channel kind via channelTypeOf() (line 112–115), falls back to any reachable DM. Uses ensureUserDm() for cold-DM resolution (user-dm.ts). |
| Ask_question card delivery | onecli-approvals.ts | ✅ Moved | v2 builds ask_question card (onecli-approvals.ts:148–167) with Approve/Reject buttons, routes via deliveryAdapter.deliver() with action_id for button callbacks. |
| Button click → approval resolution | onecli-approvals.ts | ✅ Moved | resolveOneCLIApproval() (line 68–83) matches approval_id, resolves Promise, updates status to approved/rejected, deletes pending_approvals row. |
| Approval expiry + cleanup | onecli-approvals.ts | ✅ Moved | Expiry timer fires just before gateway's TTL (line 200–211); expireApproval() (line 217–226) edits card to "Expired (reason)" and deletes row. Startup sweep cleans stale rows (line 247–255). |
| Rate limiting | Not implemented | ❌ Missing | Neither v1 nor v2 has rate limiting on remote-control or approval requests. |
| Audit logging | Partial | ⚠️ Partial | v1: logger.info() on session start/stop. v2: log.info() on approval resolved (onecli-approvals.ts:81), stale sweeps (line 250), expiry (line 225). Payload stored in pending_approvals.payload for audit (line 178–186). |
| Error recovery (process death) | Minimal | ⚠️ Minimal | v1: restores from disk, kills stale PID. v2: no equivalent — dead container is detected by stale heartbeat, then respawned via wakeContainer(). |
| Transport | HTTP via stdout polling | HTTP via standard webhook server | v1 is ephemeral per session; v2 is persistent, multi-tenant. |
| Auth | None (CLI subprocess) | OneCLI gateway (credential-gated via HTTP) | v1 has no auth; v2 gates on agent identity + OneCLI decision. |
Missing from v2
-
CLI subprocess spawning — v2 has no
claude remote-controlequivalent. Agents run in Docker containers, not standalone CLI processes. The OneCLI agent sandbox is managed by the agent-runner container, not the host. -
Process-level lifecycle management — v1 tracks individual process PIDs and signal-kills them. v2 uses container IDs + heartbeat file, handled by host-sweep (host-sweep.ts) and container-runner.ts.
-
Per-message URL polling with regex extraction — v2's webhook server is push-based (HTTP handler), not pull-based polling of stdout files.
-
Direct user-to-bot communication model — v1's remote-control was tied to a single Telegram JID + chat. v2 decouples messaging groups from agent groups, allowing one agent to serve multiple channels with different isolation levels.
-
State file on disk (
remote-control.json) — v2 stores all session state in SQLite central DB and per-sessioninbound.db/outbound.db.
Behavioral Discrepancies
-
Approval delivery model:
- v1: Remote control was tied to a single message sender; approvals implicitly went to the initiator's contact or a hardcoded owner.
- v2: Approvals route to admins of the originating agent group, with tie-break by channel kind (pickApprovalDelivery line 87–94). Multiple approvers can be reached, decoupling approval from message sender.
-
Session multiplicity:
- v1: One active
RemoteControlSessionper host at a time. - v2: Unlimited concurrent sessions, each with independent state (
inbound.db,outbound.db, heartbeat).
- v1: One active
-
Timeout & cleanup:
- v1: Explicit timeout on URL polling (30s), then kill process. No ongoing monitoring.
- v2: Heartbeat-based inactivity detection (60s sweep), graceful cleanup on stale. Approval expiry tied to OneCLI gateway TTL, not a fixed timeout.
-
Error transparency:
- v1: Polling errors logged to stdout/stderr files; user doesn't see unless they debug.
- v2: All approval errors logged centrally; card is edited to "Expired" on failure, so approver sees state change.
Worth Preserving?
No — v2 supersedes v1's remote-control model.
v1's remote-control was a bridge between Telegram chats and a single Claude CLI session. v2 achieves equivalent (and superior) remote operation via:
- OneCLI credential approvals (onecli-approvals.ts): Admins approve API/credential requests from agents, just as v1 surfaced sensitive actions.
- Approval routing (access.ts): Automatically picks the right admin on the right channel, with fallback to any reachable DM.
- Multi-tenant agent groups: Agents can serve multiple channels with different approval chains, not just one chat JID.
Users still get on-demand approval for sensitive actions; they just don't manage a CLI subprocess anymore. The host handles container lifecycle, and the container agent is managed by OneCLI.
Citation Summary
- v1 remote-control:
/Users/gavriel/nanoclaw4/src/v1/remote-control.ts:1–218 - v1 tests:
/Users/gavriel/nanoclaw4/src/v1/remote-control.test.ts:1–379 - v2 access control:
/Users/gavriel/nanoclaw4/src/access.ts:29–115(pickApprover, pickApprovalDelivery, canAccessAgentGroup) - v2 approval handler:
/Users/gavriel/nanoclaw4/src/onecli-approvals.ts:50–270(handleRequest, resolveOneCLIApproval, sweepStaleApprovals) - v2 webhook server:
/Users/gavriel/nanoclaw4/src/webhook-server.ts:73–124(registerWebhookAdapter, ensureServer) - v2 router:
/Users/gavriel/nanoclaw4/src/router.ts:19–50(inbound access gate, unknown_sender_policy)