Files
nanoclaw/docs/v1-vs-v2/remote-control.md
gavrielc 47950671fa docs: add v1→v2 action-items analysis + SDK signal probe tool
- 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>
2026-04-20 01:00:04 +03:00

91 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 16124) 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 92215) handles credential-gated action approval. OneCLI gateway intercepts HTTP, delivers ask_question card to approver, persists `pending_approvals` row (line 173196). |
| Approver selection (admin → owner chain) | **access.ts** | ✅ Moved | `pickApprover()` (access.ts:5572) 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:83101) walks approver list, prefers same channel kind via `channelTypeOf()` (line 112115), 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:148167) with Approve/Reject buttons, routes via `deliveryAdapter.deliver()` with action_id for button callbacks. |
| Button click → approval resolution | **onecli-approvals.ts** | ✅ Moved | `resolveOneCLIApproval()` (line 6883) 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 200211); `expireApproval()` (line 217226) edits card to "Expired (reason)" and deletes row. Startup sweep cleans stale rows (line 247255). |
| 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 178186). |
| 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
1. **CLI subprocess spawning** — v2 has no `claude remote-control` equivalent. Agents run in Docker containers, not standalone CLI processes. The OneCLI agent sandbox is managed by the agent-runner container, not the host.
2. **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.
3. **Per-message URL polling with regex extraction** — v2's webhook server is push-based (HTTP handler), not pull-based polling of stdout files.
4. **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.
5. **State file on disk** (`remote-control.json`) — v2 stores all session state in SQLite central DB and per-session `inbound.db` / `outbound.db`.
## Behavioral Discrepancies
1. **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 8794). Multiple approvers can be reached, decoupling approval from message sender.
2. **Session multiplicity**:
- v1: One active `RemoteControlSession` per host at a time.
- v2: Unlimited concurrent sessions, each with independent state (`inbound.db`, `outbound.db`, heartbeat).
3. **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.
4. **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:1218`
- v1 tests: `/Users/gavriel/nanoclaw4/src/v1/remote-control.test.ts:1379`
- v2 access control: `/Users/gavriel/nanoclaw4/src/access.ts:29115` (pickApprover, pickApprovalDelivery, canAccessAgentGroup)
- v2 approval handler: `/Users/gavriel/nanoclaw4/src/onecli-approvals.ts:50270` (handleRequest, resolveOneCLIApproval, sweepStaleApprovals)
- v2 webhook server: `/Users/gavriel/nanoclaw4/src/webhook-server.ts:73124` (registerWebhookAdapter, ensureServer)
- v2 router: `/Users/gavriel/nanoclaw4/src/router.ts:1950` (inbound access gate, unknown_sender_policy)