@agentvault/claude-bridge 0.3.3 → 0.4.0

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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plan-c-crux-harness.d.ts.map
@@ -0,0 +1,25 @@
1
+ import type { ServiceSpec } from "./spec.js";
2
+ export interface ServiceBackend {
3
+ install(spec: ServiceSpec): void;
4
+ uninstall(label: string): void;
5
+ status(label: string): {
6
+ loaded: boolean;
7
+ detail: string;
8
+ };
9
+ }
10
+ export interface BackendDeps {
11
+ home: string;
12
+ uid: number;
13
+ user: string;
14
+ exec: (cmd: string, args: string[]) => {
15
+ code: number;
16
+ stdout: string;
17
+ };
18
+ writeFile: (path: string, data: string) => void;
19
+ mkdir: (path: string) => void;
20
+ rm: (path: string) => void;
21
+ exists: (path: string) => boolean;
22
+ }
23
+ export declare function defaultDeps(): BackendDeps;
24
+ export declare function selectBackend(platform: NodeJS.Platform, deps?: BackendDeps): ServiceBackend;
25
+ //# sourceMappingURL=backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/service/backend.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;IACjC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,SAAS,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9B,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AAED,wBAAgB,WAAW,IAAI,WAAW,CAmBzC;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,cAAc,CAU3F"}
@@ -0,0 +1,17 @@
1
+ import type { ServiceSpec } from "./spec.js";
2
+ import type { BackendDeps, ServiceBackend } from "./backend.js";
3
+ /** Render a launchd LaunchAgent plist for a bridge ServiceSpec. Pure string. */
4
+ export declare function renderLaunchdPlist(spec: ServiceSpec): string;
5
+ export declare class LaunchdBackend implements ServiceBackend {
6
+ private deps;
7
+ constructor(deps: BackendDeps);
8
+ private plistPath;
9
+ private domain;
10
+ install(spec: ServiceSpec): void;
11
+ uninstall(label: string): void;
12
+ status(label: string): {
13
+ loaded: boolean;
14
+ detail: string;
15
+ };
16
+ }
17
+ //# sourceMappingURL=launchd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launchd.d.ts","sourceRoot":"","sources":["../../src/service/launchd.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAShE,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAkC5D;AAED,qBAAa,cAAe,YAAW,cAAc;IACvC,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,WAAW;IAErC,OAAO,CAAC,SAAS;IAGjB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAWhC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAM9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;CAI3D"}
@@ -0,0 +1,18 @@
1
+ import type { BridgeConfig } from "../config.js";
2
+ export interface ServiceSpec {
3
+ label: string;
4
+ programArgs: string[];
5
+ env: Record<string, string>;
6
+ workingDir: string;
7
+ logOut: string;
8
+ logErr: string;
9
+ keepAliveUnlessCleanExit: boolean;
10
+ runAtLoad: boolean;
11
+ }
12
+ export interface BuildSpecOptions {
13
+ nodePath: string;
14
+ entrypoint: string;
15
+ home: string;
16
+ }
17
+ export declare function buildServiceSpec(cfg: BridgeConfig, opts: BuildSpecOptions): ServiceSpec;
18
+ //# sourceMappingURL=spec.d.ts.map
@@ -0,0 +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;AAEjD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,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,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAID,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,gBAAgB,GAAG,WAAW,CAwBvF"}
@@ -0,0 +1,11 @@
1
+ import { type ServiceBackend } from "./backend.js";
2
+ export interface ServiceCmdDeps {
3
+ backend: ServiceBackend;
4
+ entrypoint: string;
5
+ log: (msg: string) => void;
6
+ }
7
+ /** Handle `agentvault-claude-bridge <install|uninstall|status|logs>` before boot.
8
+ * Returns true when handled (caller should exit). Secrets are never placed on
9
+ * the service command line — config flows via the plist env; the token is omitted. */
10
+ export declare function maybeRunServiceSubcommand(argv: string[], env: NodeJS.ProcessEnv, deps?: ServiceCmdDeps): boolean;
11
+ //# sourceMappingURL=subcommand.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subcommand.d.ts","sourceRoot":"","sources":["../../src/service/subcommand.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAElE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,cAAc,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5B;AAQD;;uFAEuF;AACvF,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,CAAC,UAAU,EACtB,IAAI,CAAC,EAAE,cAAc,GACpB,OAAO,CA+CT"}
@@ -0,0 +1,19 @@
1
+ import type { ServiceSpec } from "./spec.js";
2
+ import type { BackendDeps, ServiceBackend } from "./backend.js";
3
+ /** Render a systemd --user service unit for a bridge ServiceSpec. Pure string.
4
+ * Restart=on-failure + SuccessExitStatus=0 is the KeepAlive={SuccessfulExit:false}
5
+ * equivalent: a clean exit(0) (revoked device) is treated as success => NOT
6
+ * restarted; a non-zero crash is restarted. WantedBy=default.target is RunAtLoad. */
7
+ export declare function renderSystemdUnit(spec: ServiceSpec): string;
8
+ export declare class SystemdBackend implements ServiceBackend {
9
+ private deps;
10
+ constructor(deps: BackendDeps);
11
+ private unitPath;
12
+ install(spec: ServiceSpec): void;
13
+ uninstall(label: string): void;
14
+ status(label: string): {
15
+ loaded: boolean;
16
+ detail: string;
17
+ };
18
+ }
19
+ //# sourceMappingURL=systemd.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"systemd.d.ts","sourceRoot":"","sources":["../../src/service/systemd.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAkBhE;;;sFAGsF;AACtF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAsB3D;AAED,qBAAa,cAAe,YAAW,cAAc;IACvC,OAAO,CAAC,IAAI;gBAAJ,IAAI,EAAE,WAAW;IAErC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAmBhC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAQ9B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;CAQ3D"}
package/dist/session.d.ts CHANGED
@@ -55,6 +55,22 @@ export interface SessionOpts {
55
55
  model?: string;
56
56
  systemPrompt?: string;
57
57
  queryImpl?: QueryFn;
58
+ /** When true, session runs as a worker agent: full toolset on owner DMs,
59
+ * project context loaded, workspace cwd set, AV data dir fenced off. */
60
+ worker?: boolean;
61
+ /** Workspace directory to set as cwd in worker mode. */
62
+ workspaceDir?: string;
63
+ /** Claude permission mode for worker turns (default: "auto"). */
64
+ permissionMode?: "auto" | "acceptEdits" | "bypassPermissions";
65
+ /** AgentVault data directory — fenced off from worker tool access. */
66
+ dataDir?: string;
67
+ /** Slice 2 Plan B — audit self-report context. When present and the turn is
68
+ * armed, each tool decision is fire-and-forget reported to /audit/entries. */
69
+ roomId?: string;
70
+ agentId?: string | null;
71
+ apiUrl?: string;
72
+ /** Lazy getter so a JWT refreshed after construction is still picked up. */
73
+ deviceJwt?: () => string | null;
58
74
  }
59
75
  export declare class PersistentClaudeSession {
60
76
  private opts;
@@ -63,10 +79,30 @@ export declare class PersistentClaudeSession {
63
79
  /** Reply sink bound to the message currently being processed. Set as each
64
80
  * message is handed to the model so the say tool routes to the right place. */
65
81
  private activeReply?;
66
- /** Per-turn state for the #416 DM auto-reply fallback. */
82
+ /**
83
+ * Per-turn state for the #416 DM auto-reply fallback and the worker tool gate.
84
+ *
85
+ * LOAD-BEARING (one of two tool-enabling signals): `currentAutoReply` is true
86
+ * ONLY on owner DM turns (`autoReplyOnText:true` from wireBridge) and false on
87
+ * room turns (`autoReplyOnText:false`). The gate allows tools when EITHER
88
+ * `currentAutoReply` (owner DM) OR `currentArmedGetter()` (armed room turn, Slice 2)
89
+ * is true. A room-origin turn MUST keep `currentAutoReply` false; an unarmed
90
+ * room turn's `currentArmedGetter()` MUST return false — or untrusted input gains full
91
+ * tool access. See worker-permission.test.ts for the covering assertion.
92
+ */
67
93
  private currentAutoReply;
94
+ /** Per-turn: a LIVE getter answering "is this turn's room armed right now?"
95
+ * (Slice 2 Plan C D5). Independent of currentAutoReply (which is owner-DM-only
96
+ * and also drives the #416 plain-text fallback). The gate allows tools when
97
+ * EITHER currentAutoReply OR this getter is true. CRITICAL: the gate CALLS this
98
+ * getter at each tool-call decision (not a boolean captured once), so a mid-turn
99
+ * disarm denies the very next tool call. wireBridge binds it to the ArmingState
100
+ * for this turn's room; an unarmed room turn's getter returns false. */
101
+ private currentArmedGetter;
68
102
  private saidThisTurn;
69
103
  private turnText;
104
+ /** In-process room MCP server — hoisted so buildSdkOptions can reference it. */
105
+ private roomServer;
70
106
  constructor(opts: SessionOpts);
71
107
  /**
72
108
  * Queue an inbound message for the model.
@@ -74,15 +110,28 @@ export declare class PersistentClaudeSession {
74
110
  * reply): if the turn produces assistant text but never calls the say tool,
75
111
  * send that text as the reply instead of silently dropping it (#416). Rooms
76
112
  * omit this so the agent can still choose silence.
113
+ * @param opts.armed — for armed room turns (Slice 2 Plan C): a LIVE getter that
114
+ * the gate calls at each tool-call decision. When it returns true this room
115
+ * turn is granted full worker tool access (same as an owner DM turn). Reading
116
+ * it live (not capturing a boolean) is what lets a mid-turn disarm deny the
117
+ * next tool call. Bound by wireBridge to the ArmingState for this turn's room.
77
118
  */
78
119
  push(text: string, reply?: ReplySink, opts?: {
79
120
  autoReplyOnText?: boolean;
121
+ armed?: () => boolean;
80
122
  }): void;
81
123
  /** Route a say-tool message to the reply bound to the in-flight message, falling
82
124
  * back to opts.onSay. This is what closes the DM→room leak: the destination is
83
125
  * the one captured for the message being answered, not a live global target. */
84
126
  deliver(text: string): Promise<void>;
85
127
  private input;
128
+ /**
129
+ * Build the SDK options for this session. Locked mode (default) is safe for
130
+ * untrusted inbound room messages. Worker mode (opts.worker=true) enables full
131
+ * toolset on owner DMs, project context, workspace cwd, and an AV data-dir
132
+ * fence via canUseTool.
133
+ */
134
+ private buildSdkOptions;
86
135
  start(): Promise<void>;
87
136
  }
88
137
  export {};
@@ -0,0 +1,32 @@
1
+ import type { CanUseTool, HookCallbackMatcher } from "@anthropic-ai/claude-agent-sdk";
2
+ /**
3
+ * PRIMARY enforcement: a PreToolUse hook. Unlike canUseTool (the SDK's "ask"
4
+ * path, which `permissionMode:"auto"` BYPASSES by auto-approving benign tools via
5
+ * its classifier — verified live: the canUseTool gate never fired in auto mode),
6
+ * a PreToolUse `deny` fires on EVERY tool call regardless of permission mode.
7
+ * This is what actually enforces the gate at runtime.
8
+ */
9
+ export declare function makeWorkerPreToolUseHook(opts: {
10
+ dataDir: string;
11
+ isToolTurn: () => boolean;
12
+ /** Slice 2 Plan B: true when this turn comes from an armed room. When set,
13
+ * each tool decision (allow AND deny) is forwarded to onToolDecision for
14
+ * audit self-reporting. room_say is always excluded (it is the reply
15
+ * mechanism, not a user-initiated tool action). */
16
+ isArmedTurn?: () => boolean;
17
+ onToolDecision?: (d: {
18
+ tool: string;
19
+ decision: "allow" | "deny";
20
+ reason?: string;
21
+ }) => void;
22
+ }): HookCallbackMatcher;
23
+ /**
24
+ * SECONDARY (defense-in-depth): the canUseTool callback covers the SDK's "ask"
25
+ * path (permissionMode `"default"`/`"dontAsk"`). Shares gateDecision with the
26
+ * PreToolUse hook above, so a deny in one is a deny in the other.
27
+ */
28
+ export declare function makeWorkerPermission(opts: {
29
+ dataDir: string;
30
+ isToolTurn: () => boolean;
31
+ }): CanUseTool;
32
+ //# sourceMappingURL=worker-permission.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentvault/claude-bridge",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
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",
@@ -8,6 +8,9 @@
8
8
  "bin": {
9
9
  "agentvault-claude-bridge": "dist/index.js"
10
10
  },
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
11
14
  "files": [
12
15
  "dist",
13
16
  "README.md",