@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.
@@ -0,0 +1,40 @@
1
+ import type { ReplySink } from "./session.js";
2
+ import type { WorkerTask } from "./worker-queue.js";
3
+ /** The push interface wireBridge drives (matches PersistentClaudeSession.push). */
4
+ export interface PushLike {
5
+ push(text: string, reply?: ReplySink, opts?: {
6
+ autoReplyOnText?: boolean;
7
+ replyExpected?: boolean;
8
+ armed?: () => boolean;
9
+ }): void;
10
+ }
11
+ export interface RouterDeps {
12
+ /**
13
+ * Task 4 (C-1/H-3): live, fail-closed device-level tool capability ("Work").
14
+ * MUST be read live at each push (never snapshotted) so a mid-connection flip
15
+ * and the fail-closed reconnect window both take effect immediately. Gates
16
+ * BOTH tool-eligible lanes — owner-DM and armed-room: Work off → the turn
17
+ * falls through to the tools-absent listener (structural fail-safe), making
18
+ * `work_allowed` the single on/off control for tool capability. The
19
+ * session-level isToolTurn AND remains the hard backstop for both lanes.
20
+ */
21
+ workAllowed: () => boolean;
22
+ /** Always-on locked listener (tools:[]) for untrusted/non-tool turns. */
23
+ listener: PushLike;
24
+ /** Serial queue of tool-eligible turns. */
25
+ queue: {
26
+ enqueue(task: WorkerTask): void;
27
+ };
28
+ }
29
+ /**
30
+ * Route each inbound turn to EXACTLY ONE lane (facet B). Tool-eligible turns —
31
+ * owner DMs (autoReplyOnText) and armed room turns — go to an isolated ephemeral
32
+ * worker via the queue; everything else stays on the locked listener. A room
33
+ * turn's `armed` getter is read once here at routing time to pick the lane; the
34
+ * same live getter is threaded to the worker task so a mid-turn disarm still
35
+ * denies the next tool call (Facet A D5). `work_allowed` is the single control
36
+ * gating BOTH lanes: an armed room turn is only routed to the worker queue when
37
+ * workAllowed() is also true, so Work off always falls through to the listener.
38
+ */
39
+ export declare function makeRouter(deps: RouterDeps): PushLike;
40
+ //# sourceMappingURL=router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../../src/service/spec.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB,EAAE,OAAO,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,gBAAgB,GAAG,WAAW,CA2BvF"}
1
+ {"version":3,"file":"spec.d.ts","sourceRoot":"","sources":["../../src/service/spec.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAGjD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB,EAAE,OAAO,CAAC;IAClC,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,gBAAgB,GAAG,WAAW,CA0BvF"}
package/dist/session.d.ts CHANGED
@@ -45,7 +45,7 @@ export type QueryFn = (args: {
45
45
  type: string;
46
46
  [k: string]: unknown;
47
47
  }>;
48
- type ReplySink = (text: string) => void | Promise<void>;
48
+ export type ReplySink = (text: string) => void | Promise<void>;
49
49
  export interface SessionOpts {
50
50
  /** Fallback reply sink when a message carries no per-message reply (e.g. a
51
51
  * proactive say). Per-message replies passed to push() take precedence. */
@@ -73,6 +73,19 @@ export interface SessionOpts {
73
73
  apiUrl?: string;
74
74
  /** Lazy getter so a JWT refreshed after construction is still picked up. */
75
75
  deviceJwt?: () => string | null;
76
+ /** Single-shot ephemeral worker: process exactly the pushed message(s) then
77
+ * terminate the input stream so start() resolves. Used by the worker queue so
78
+ * a per-task query() completes and its state is discarded. */
79
+ ephemeral?: boolean;
80
+ /** Turn cap for a worker query() (bounds a runaway tool loop). */
81
+ maxTurns?: number;
82
+ /**
83
+ * Task 4 (C-1/H-3): live, fail-closed device-level tool capability ("Work").
84
+ * Folded into `isToolTurn` as a top-level AND — the HARD backstop that makes
85
+ * the fail-safe real: NO tool runs (owner DM OR armed room) when this returns
86
+ * false, even if the router mis-routed. Read LIVE at each tool decision (never
87
+ * snapshotted). Absent getter ⇒ `?? false` ⇒ fail-closed (no tools). */
88
+ workAllowed?: () => boolean;
76
89
  }
77
90
  export declare class PersistentClaudeSession {
78
91
  private opts;
@@ -101,10 +114,21 @@ export declare class PersistentClaudeSession {
101
114
  * disarm denies the very next tool call. wireBridge binds it to the ArmingState
102
115
  * for this turn's room; an unarmed room turn's getter returns false. */
103
116
  private currentArmedGetter;
117
+ /** Per-turn: does this turn's sender expect a reply? DECOUPLED from
118
+ * `currentAutoReply` on purpose — it drives ONLY the #416 plain-text fallback
119
+ * (deliver assistant text when the model never calls say), NOT the tool gate.
120
+ * True for owner 1:1 DMs (defaults from autoReplyOnText) AND for a HUMAN/owner
121
+ * speaking in a room (wireBridge sets replyExpected from !senderIsAgent). An
122
+ * agent-authored room turn keeps this false → the agent may still stay silent,
123
+ * which also prevents agent↔agent reply loops. Tool access is unaffected: it
124
+ * stays gated on currentAutoReply (owner DM) OR currentArmedGetter (armed room). */
125
+ private currentReplyExpected;
104
126
  private saidThisTurn;
105
127
  private turnText;
106
128
  /** In-process room MCP server — hoisted so buildSdkOptions can reference it. */
107
129
  private roomServer;
130
+ /** AbortController for the in-flight query(); abort() triggers it (queue timeout). */
131
+ private _abort?;
108
132
  constructor(opts: SessionOpts);
109
133
  /**
110
134
  * Queue an inbound message for the model.
@@ -120,12 +144,15 @@ export declare class PersistentClaudeSession {
120
144
  */
121
145
  push(text: string, reply?: ReplySink, opts?: {
122
146
  autoReplyOnText?: boolean;
147
+ replyExpected?: boolean;
123
148
  armed?: () => boolean;
124
149
  }): void;
125
150
  /** Route a say-tool message to the reply bound to the in-flight message, falling
126
151
  * back to opts.onSay. This is what closes the DM→room leak: the destination is
127
152
  * the one captured for the message being answered, not a live global target. */
128
153
  deliver(text: string): Promise<void>;
154
+ /** Abort the in-flight query() — used by the worker queue on a per-task timeout. */
155
+ abort(): void;
129
156
  private input;
130
157
  /**
131
158
  * Build the SDK options for this session. Locked mode (default) is safe for
@@ -136,5 +163,4 @@ export declare class PersistentClaudeSession {
136
163
  private buildSdkOptions;
137
164
  start(): Promise<void>;
138
165
  }
139
- export {};
140
166
  //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1,46 @@
1
+ import type { PersistentClaudeSession, ReplySink } from "./session.js";
2
+ /** One tool-eligible turn to run in an isolated ephemeral worker. */
3
+ export type WorkerTask = {
4
+ /** The single instruction that seeds the worker's conversation. */
5
+ instruction: string;
6
+ /** Where this task's reply (room_say / #416 fallback) is sent. Per-task. */
7
+ reply: ReplySink;
8
+ /** Owner-DM turns want a plain-text reply fallback (#416). */
9
+ autoReplyOnText: boolean;
10
+ /** A human/owner sender expects a reply: if the worker answers in plain text
11
+ * without calling say, deliver that text instead of dropping it. Decoupled from
12
+ * autoReplyOnText so it delivers on a tool-capable room turn WITHOUT enabling
13
+ * tools (arming governs tools). Absent/false for agent-authored turns. */
14
+ replyExpected?: boolean;
15
+ /** Armed-room turns: live getter the gate reads per tool decision (mid-turn
16
+ * disarm denies the next call). Absent for owner DMs. */
17
+ armed?: () => boolean;
18
+ };
19
+ export interface WorkerQueueDeps {
20
+ /** Build a fresh ephemeral worker session for this task (not yet started). */
21
+ makeSession: (task: WorkerTask) => PersistentClaudeSession;
22
+ /** Per-task wall-clock cap; on breach the worker is aborted and the queue advances. */
23
+ timeoutMs: number;
24
+ log: (m: string) => void;
25
+ }
26
+ /**
27
+ * Serial FIFO of tool-eligible turns. One worker runs at a time (concurrent tool
28
+ * calls against one device/workspace can interleave destructively; the owner is a
29
+ * single actor). Each task runs in a fresh ephemeral worker seeded with only its
30
+ * instruction; on error or timeout the worker is torn down, a generic message is
31
+ * sent to the task's reply, and the queue advances — a task can never wedge the
32
+ * queue (which would starve the owner's DM lane).
33
+ */
34
+ export declare class WorkerQueue {
35
+ private deps;
36
+ private q;
37
+ private loop;
38
+ private running;
39
+ constructor(deps: WorkerQueueDeps);
40
+ enqueue(task: WorkerTask): void;
41
+ /** Resolves when the queue has processed everything enqueued so far. */
42
+ whenDrained(): Promise<void>;
43
+ private drain;
44
+ private runOne;
45
+ }
46
+ //# sourceMappingURL=worker-queue.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.5.7",
3
+ "version": "0.5.10",
4
4
  "type": "module",
5
5
  "description": "AgentVault Claude Bridge — daemon for bridging a Claude agent into secure E2E-encrypted AgentVault 1:1 direct messages and rooms.",
6
6
  "main": "dist/index.js",