@agentvault/claude-bridge 0.5.7 → 0.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/arming.d.ts CHANGED
@@ -10,24 +10,54 @@ export declare class ArmingState {
10
10
  private pending;
11
11
  private consumed;
12
12
  /**
13
- * Replace the armed set. Use ONLY for the launch-env seed (AV_ARM_ROOM), which
14
- * is itself a host-local action and is therefore allowed to arm.
13
+ * Replace the armed set. Use for the host-local launch seed (AV_ARM_ROOM)
14
+ * itself a host-local action and therefore allowed to arm — and as the
15
+ * primitive `applyAuthoritative` below delegates to for non-shell workers.
15
16
  *
16
- * Do NOT use this for the backend connect `arming_snapshot`: that snapshot
17
- * reflects owner *intent* (set at web-request time, before any local approval),
18
- * so arming from it would let a web-session compromise arm a worker on reconnect
19
- * without host access defeating the 2-of-2 local-approval model. The channel
20
- * snapshot path uses `reconcileDisarm` (disarm-only) instead.
17
+ * The B-pure posture (Task 4) treats the backend connect/live
18
+ * `arming_snapshot` the same way see `applyAuthoritative`'s docstring for
19
+ * the current arm-from-snapshot rationale. Shell-capable (OS-isolated)
20
+ * workers do NOT use this path; they stay on `reconcileDisarm` (disarm-only,
21
+ * see below) until #20.
21
22
  */
22
23
  applySnapshot(roomIds: string[]): void;
23
24
  /**
24
- * Disarm-only reconciliation from the backend connect snapshot. Disarms any
25
- * currently-armed room whose intent is no longer armed (i.e. NOT in
26
- * `intendedArmed`) so a disarm issued while the bridge was offline still takes
27
- * effect on reconnect but NEVER arms a room. Arming stays exclusively behind a
28
- * local `approve-arm`. Returns the rooms that were disarmed. In-memory arming
29
- * survives a WS reconnect, so a genuinely-approved arm is unaffected here; only a
30
- * full process restart (which clears this state) requires re-approval on the host.
25
+ * Set the armed set to EXACTLY `intendedArmed`: arms every room in the list
26
+ * that isn't already armed, and disarms every currently-armed room that is
27
+ * absent from it. This is the AUTHORITATIVE arm-from-snapshot path (Task 4,
28
+ * reversing the prior disarm-only safeguard for non-shell workers).
29
+ *
30
+ * Why arming from the snapshot is now safe: `intendedArmed` is derived
31
+ * server-side from `devices.work_allowed`, and that flag can only be flipped
32
+ * by `PUT /devices/{id}/work_allowed`, which REJECTS any device-bound caller
33
+ * outright (C1 guard — a human account owner only; an agent, including a
34
+ * compromised one, cannot self-authorize). So "arm from snapshot" no longer
35
+ * means "a web session can arm a worker" in the old adversarial sense — it
36
+ * means "the verified owner's grant is applied end-to-end, live, without
37
+ * requiring a separate host-local approval step for every reconnect." This is
38
+ * the accepted B-pure posture; see #20 to harden it further with local
39
+ * approval for non-shell workers too. Shell-capable (OS-isolated) workers are
40
+ * carved out of this path entirely (C2) — see the shell-gate in bridge.ts —
41
+ * because a shell worker armed by a web flag is a live RCE surface even under
42
+ * an honest owner (a compromised owner web session, or a compromised backend,
43
+ * would get arbitrary code execution). Those workers stay on `reconcileDisarm`
44
+ * (disarm-only) until #20 delivers local approval for them as well.
45
+ */
46
+ applyAuthoritative(intendedArmed: string[]): void;
47
+ /**
48
+ * Disarm-only reconciliation from the backend connect/live snapshot. Disarms
49
+ * any currently-armed room whose intent is no longer armed (i.e. NOT in
50
+ * `intendedArmed`) — so a disarm issued while the bridge was offline still
51
+ * takes effect on reconnect — but NEVER arms a room. Used for OS-isolated
52
+ * (shell-capable) workers ONLY (C2): a shell worker must never be armed by a
53
+ * web-originated flag, because a compromised owner session or backend would
54
+ * translate directly into host code execution. Those workers can be armed
55
+ * only by the host-local launch seed (`AV_ARM_ROOM`, via `applySnapshot`)
56
+ * until #20 delivers a local-approval path for them too. Non-shell workers
57
+ * use `applyAuthoritative` instead (Task 4) — see its docstring. Returns the
58
+ * rooms that were disarmed. In-memory arming survives a WS reconnect, so a
59
+ * genuinely-armed room is unaffected here; only a full process restart
60
+ * (which clears this state) requires re-arming.
31
61
  */
32
62
  reconcileDisarm(intendedArmed: string[]): string[];
33
63
  isArmed(roomId: string): boolean;
package/dist/bridge.d.ts CHANGED
@@ -3,6 +3,13 @@ export interface RoomMessage {
3
3
  roomId: string;
4
4
  senderName: string;
5
5
  plaintext: string;
6
+ /** True when the sender is another agent (SecureChannel derives this from the
7
+ * room roster). Used to decide reply expectation: a human/owner speaking in a
8
+ * room always expects a reply; agent-authored traffic does not (the agent may
9
+ * stay silent, and it prevents agent↔agent reply loops). Absent on older
10
+ * payloads / tests → treated as "not an agent" is NOT assumed: replyExpected is
11
+ * set only when we affirmatively know the sender is a human (=== false). */
12
+ senderIsAgent?: boolean;
6
13
  }
7
14
  /** Slice 2 Plan C arming events (SecureChannel re-emits these from the backend). */
8
15
  export interface ArmRequested {
@@ -15,6 +22,10 @@ export interface DisarmEvent {
15
22
  }
16
23
  export interface ArmingSnapshot {
17
24
  roomIds: string[];
25
+ /** Task 3 (P1 bridge): the device-level owner grant this snapshot carries
26
+ * (backend `devices.work_allowed`, Task 2). Optional so older/test payloads
27
+ * without it don't break — treated as `false` (fail-closed) when absent. */
28
+ workAllowed?: boolean;
18
29
  }
19
30
  /** Metadata SecureChannel attaches to a 1:1 `message` event. `roomId` is set only
20
31
  * when the `message` actually originated in a room (handled by room_message), so
@@ -37,6 +48,11 @@ export interface RoomChannel {
37
48
  on(ev: "arm_requested", cb: (e: ArmRequested) => void): unknown;
38
49
  on(ev: "disarm", cb: (e: DisarmEvent) => void): unknown;
39
50
  on(ev: "arming_snapshot", cb: (e: ArmingSnapshot) => void): unknown;
51
+ /** Task 3: SecureChannel's connection lifecycle signal. It never emits a
52
+ * bare `"close"` — a WS drop is reported as `state` moving to
53
+ * `"disconnected"` (or `"error"`), and a completed reconnect as `"ready"`
54
+ * (both the dedicated `state` value AND the separate `"ready"` event). */
55
+ on(ev: "state", cb: (s: string) => void): unknown;
40
56
  sendToRoom(roomId: string, text: string): Promise<void>;
41
57
  send(text: string): Promise<void>;
42
58
  /** Optional so legacy/test fakes without arming support don't break wiring. */
@@ -59,9 +75,12 @@ export interface RoomSession {
59
75
  /** `reply` is the immutable reply sink captured for THIS message (see
60
76
  * ActiveTarget.snapshotReply) — the session invokes it when Claude answers.
61
77
  * `opts.autoReplyOnText` (set for 1:1 DMs) makes the session fall back to
62
- * sending plain assistant text when the model never calls the say tool (#416). */
78
+ * sending plain assistant text when the model never calls the say tool (#416).
79
+ * `opts.replyExpected` requests that same fallback for a room turn (a human/owner
80
+ * sender) WITHOUT enabling tools — decoupled from autoReplyOnText on purpose. */
63
81
  push(text: string, reply?: (text: string) => Promise<void>, opts?: {
64
82
  autoReplyOnText?: boolean;
83
+ replyExpected?: boolean;
65
84
  armed?: () => boolean;
66
85
  }): void;
67
86
  }
@@ -148,11 +167,28 @@ export declare function wireBridge(channel: RoomChannel, session: RoomSession, t
148
167
  roomFilter?: string;
149
168
  armRoom?: boolean;
150
169
  log?: (msg: string) => void;
151
- /** Slice 2 Plan C (T11): worker capability + local-approval marker dir. */
152
- worker?: boolean;
170
+ /** Slice 2 Plan C (T11): worker capability (derived from workspaceDir) + local-approval marker dir. */
153
171
  workspaceDir?: string;
154
172
  dataDir?: string;
155
173
  /** Override the approval poll interval (ms) — for tests. */
156
174
  approvalPollMs?: number;
175
+ /**
176
+ * Task 4 (C2 shell-gate): true when this worker runs Bash unconfined
177
+ * (AV_WORKER_OS_ISOLATED=1 — see config.ts/index.ts, threaded from the
178
+ * session's `osIsolated`). When true, the `arming_snapshot` handler stays
179
+ * disarm-only regardless of B1/worker-capability — a shell worker must
180
+ * never be armed by a web-originated flag (RCE risk). Falls back to
181
+ * reading the env directly only if the caller doesn't thread this.
182
+ */
183
+ osIsolated?: boolean;
184
+ /**
185
+ * Task 3 (P1 bridge): out-param that receives the live `workAllowed()`
186
+ * getter once, synchronously, during wiring. Callers (index.ts, Task 4)
187
+ * capture it to thread device-level tool-capability into the router +
188
+ * session gate. Kept as an out-param rather than changing wireBridge's
189
+ * return type so every existing `const arming = wireBridge(...)` caller
190
+ * (index.ts, arming-authoritative.test.ts, bridge.test.ts) stays intact.
191
+ */
192
+ onWorkAllowed?: (getter: () => boolean) => void;
157
193
  }): ArmingState;
158
194
  //# sourceMappingURL=bridge.d.ts.map
package/dist/config.d.ts CHANGED
@@ -14,8 +14,9 @@ export interface BridgeConfig {
14
14
  roomFilter?: string;
15
15
  model?: string;
16
16
  systemPrompt?: string;
17
- worker: boolean;
18
- workspaceDir?: string;
17
+ /** Per-agent filesystem sandbox (Facet-A fence). Always set: auto-defaults
18
+ * to a sibling of the key/data dir (H-2) unless AV_WORKSPACE_DIR overrides it. */
19
+ workspaceDir: string;
19
20
  permissionMode: "auto" | "acceptEdits" | "bypassPermissions";
20
21
  /** Slice 2: arm the pinned room (roomFilter) for worker tools on room turns.
21
22
  * Off by default. Valid only with worker + roomFilter + workspaceDir. */