@agentvault/claude-bridge 0.3.2 → 0.3.4

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/bridge.d.ts CHANGED
@@ -35,8 +35,12 @@ export declare class RoomHushState {
35
35
  }
36
36
  export interface RoomSession {
37
37
  /** `reply` is the immutable reply sink captured for THIS message (see
38
- * ActiveTarget.snapshotReply) — the session invokes it when Claude answers. */
39
- push(text: string, reply?: (text: string) => Promise<void>): void;
38
+ * ActiveTarget.snapshotReply) — the session invokes it when Claude answers.
39
+ * `opts.autoReplyOnText` (set for 1:1 DMs) makes the session fall back to
40
+ * sending plain assistant text when the model never calls the say tool (#416). */
41
+ push(text: string, reply?: (text: string) => Promise<void>, opts?: {
42
+ autoReplyOnText?: boolean;
43
+ }): void;
40
44
  }
41
45
  /** Where a reply should go: a room (sendToRoom) or a 1:1 DM (send). */
42
46
  export type BridgeTarget = {
package/dist/config.d.ts CHANGED
@@ -8,6 +8,9 @@ export interface BridgeConfig {
8
8
  roomFilter?: string;
9
9
  model?: string;
10
10
  systemPrompt?: string;
11
+ worker: boolean;
12
+ workspaceDir?: string;
13
+ permissionMode: "auto" | "acceptEdits" | "bypassPermissions";
11
14
  }
12
15
  export declare function loadConfig(env: NodeJS.ProcessEnv, argv?: string[]): BridgeConfig;
13
16
  //# sourceMappingURL=config.d.ts.map
package/dist/index.js CHANGED
@@ -45248,20 +45248,20 @@ function multibaseToPublicKey(multibase) {
45248
45248
  }
45249
45249
  async function signDocument(document, privateKey) {
45250
45250
  await libsodium_wrappers_default.ready;
45251
- const canonical = canonicalize(document);
45251
+ const canonical2 = canonicalize(document);
45252
45252
  const domainPrefix = libsodium_wrappers_default.from_string(DOMAIN_DID_DOCUMENT);
45253
- const message = new Uint8Array(domainPrefix.length + canonical.length);
45253
+ const message = new Uint8Array(domainPrefix.length + canonical2.length);
45254
45254
  message.set(domainPrefix);
45255
- message.set(canonical, domainPrefix.length);
45255
+ message.set(canonical2, domainPrefix.length);
45256
45256
  return libsodium_wrappers_default.crypto_sign_detached(message, privateKey);
45257
45257
  }
45258
45258
  async function verifyDocumentSignature(document, signature, publicKey) {
45259
45259
  await libsodium_wrappers_default.ready;
45260
- const canonical = canonicalize(document);
45260
+ const canonical2 = canonicalize(document);
45261
45261
  const domainPrefix = libsodium_wrappers_default.from_string(DOMAIN_DID_DOCUMENT);
45262
- const message = new Uint8Array(domainPrefix.length + canonical.length);
45262
+ const message = new Uint8Array(domainPrefix.length + canonical2.length);
45263
45263
  message.set(domainPrefix);
45264
- message.set(canonical, domainPrefix.length);
45264
+ message.set(canonical2, domainPrefix.length);
45265
45265
  try {
45266
45266
  return libsodium_wrappers_default.crypto_sign_verify_detached(signature, message, publicKey);
45267
45267
  } catch {
@@ -96449,6 +96449,20 @@ function resolveDataDir(env) {
96449
96449
  function loadConfig(env, argv = []) {
96450
96450
  const { dataDir, source: dataDirSource } = resolveDataDir(env);
96451
96451
  const inviteToken = (argv[0] && !argv[0].startsWith("-") ? argv[0] : "") || env.AV_INVITE_TOKEN || "";
96452
+ const worker = env.AV_WORKER === "1" || env.AV_WORKER === "true";
96453
+ const workspaceDir = env.AV_WORKSPACE_DIR || void 0;
96454
+ const PERMISSION_MODES = ["auto", "acceptEdits", "bypassPermissions"];
96455
+ const permissionMode = env.AV_PERMISSION_MODE ?? "auto";
96456
+ if (!PERMISSION_MODES.includes(permissionMode)) {
96457
+ throw new Error(
96458
+ `invalid AV_PERMISSION_MODE: ${env.AV_PERMISSION_MODE} (expected one of ${PERMISSION_MODES.join(", ")})`
96459
+ );
96460
+ }
96461
+ if (worker && !workspaceDir) {
96462
+ throw new Error(
96463
+ "worker mode requires AV_WORKSPACE_DIR (the project directory the agent works in)"
96464
+ );
96465
+ }
96452
96466
  if (!inviteToken && !hasPersistedCreds(dataDir)) {
96453
96467
  throw new Error(
96454
96468
  "invite token required: pass it as the first argument or set AV_INVITE_TOKEN (or start from a data dir with existing credentials to reconnect)"
@@ -96465,7 +96479,10 @@ function loadConfig(env, argv = []) {
96465
96479
  agentName: env.AV_AGENT_NAME ?? "Claude",
96466
96480
  roomFilter: env.AV_ROOM_ID || void 0,
96467
96481
  model: env.AV_CLAUDE_MODEL || void 0,
96468
- systemPrompt: env.AV_SYSTEM_PROMPT || void 0
96482
+ systemPrompt: env.AV_SYSTEM_PROMPT || void 0,
96483
+ worker,
96484
+ workspaceDir,
96485
+ permissionMode
96469
96486
  };
96470
96487
  }
96471
96488
 
@@ -117763,6 +117780,81 @@ function Z_($10, Q4) {
117763
117780
  return null;
117764
117781
  }
117765
117782
 
117783
+ // src/worker-permission.ts
117784
+ import { realpathSync as realpathSync2 } from "node:fs";
117785
+ import { resolve as resolve2, dirname, basename, sep } from "node:path";
117786
+ var PATH_FIELDS = ["file_path", "path", "notebook_path"];
117787
+ function canonical(p2) {
117788
+ const abs = resolve2(p2);
117789
+ try {
117790
+ return realpathSync2(abs);
117791
+ } catch {
117792
+ try {
117793
+ return resolve2(realpathSync2(dirname(abs)), basename(abs));
117794
+ } catch {
117795
+ return abs;
117796
+ }
117797
+ }
117798
+ }
117799
+ function targetsDataDir(_toolName, input, dataDir) {
117800
+ const canonDir = canonical(dataDir);
117801
+ for (const f7 of PATH_FIELDS) {
117802
+ const v5 = input[f7];
117803
+ if (typeof v5 === "string") {
117804
+ const canon = canonical(v5);
117805
+ if (canon === canonDir || canon.startsWith(canonDir + sep)) return true;
117806
+ }
117807
+ }
117808
+ const cmd = input.command;
117809
+ if (typeof cmd === "string" && cmd.includes(canonDir)) return true;
117810
+ return false;
117811
+ }
117812
+ function gateDecision(toolName, input, opts) {
117813
+ if (toolName === ROOM_SAY_TOOL_NAME) return { deny: false };
117814
+ if (!opts.isOwnerTurn()) {
117815
+ return { deny: true, reason: "tools are disabled in rooms; reply with the say tool only" };
117816
+ }
117817
+ if (targetsDataDir(toolName, input, opts.dataDir)) {
117818
+ return { deny: true, reason: "access to the AgentVault key directory is not permitted" };
117819
+ }
117820
+ return { deny: false };
117821
+ }
117822
+ function makeWorkerPreToolUseHook(opts) {
117823
+ return {
117824
+ hooks: [
117825
+ async (input) => {
117826
+ const i2 = input;
117827
+ const verdict = gateDecision(
117828
+ i2.tool_name ?? "",
117829
+ i2.tool_input ?? {},
117830
+ opts
117831
+ );
117832
+ if (verdict.deny) {
117833
+ console.error(`[worker-gate] DENY ${i2.tool_name} \u2014 ${verdict.reason}`);
117834
+ return {
117835
+ hookSpecificOutput: {
117836
+ hookEventName: "PreToolUse",
117837
+ permissionDecision: "deny",
117838
+ permissionDecisionReason: verdict.reason
117839
+ }
117840
+ };
117841
+ }
117842
+ return {};
117843
+ }
117844
+ ]
117845
+ };
117846
+ }
117847
+ function makeWorkerPermission(opts) {
117848
+ return async (toolName, input) => {
117849
+ const verdict = gateDecision(toolName, input, opts);
117850
+ if (verdict.deny) {
117851
+ console.error(`[worker-gate] DENY ${toolName} \u2014 ${verdict.reason}`);
117852
+ return { behavior: "deny", message: verdict.reason };
117853
+ }
117854
+ return { behavior: "allow", updatedInput: input };
117855
+ };
117856
+ }
117857
+
117766
117858
  // ../../node_modules/zod/v4/classic/external.js
117767
117859
  var external_exports = {};
117768
117860
  __export(external_exports, {
@@ -131554,14 +131646,36 @@ var PersistentClaudeSession = class {
131554
131646
  /** Reply sink bound to the message currently being processed. Set as each
131555
131647
  * message is handed to the model so the say tool routes to the right place. */
131556
131648
  activeReply;
131557
- push(text, reply) {
131649
+ /**
131650
+ * Per-turn state for the #416 DM auto-reply fallback.
131651
+ *
131652
+ * LOAD-BEARING for the worker tool gate: `currentAutoReply` is true ONLY on
131653
+ * owner DM turns (`autoReplyOnText:true` from wireBridge) and false on room
131654
+ * turns (`autoReplyOnText:false`). `makeWorkerPermission` calls
131655
+ * `isOwnerTurn: () => this.currentAutoReply` to decide whether tools are
131656
+ * allowed. A room-origin turn MUST keep this false or untrusted input gains
131657
+ * full tool access. See worker-permission.test.ts for the covering assertion.
131658
+ */
131659
+ currentAutoReply = false;
131660
+ saidThisTurn = false;
131661
+ turnText = "";
131662
+ /** In-process room MCP server — hoisted so buildSdkOptions can reference it. */
131663
+ roomServer;
131664
+ /**
131665
+ * Queue an inbound message for the model.
131666
+ * @param opts.autoReplyOnText — for 1:1 DMs (where the owner always expects a
131667
+ * reply): if the turn produces assistant text but never calls the say tool,
131668
+ * send that text as the reply instead of silently dropping it (#416). Rooms
131669
+ * omit this so the agent can still choose silence.
131670
+ */
131671
+ push(text, reply, opts) {
131558
131672
  const msg = {
131559
131673
  type: "user",
131560
131674
  message: { role: "user", content: text },
131561
131675
  parent_tool_use_id: null,
131562
131676
  session_id: ""
131563
131677
  };
131564
- const item = { msg, reply };
131678
+ const item = { msg, reply, autoReplyOnText: opts?.autoReplyOnText };
131565
131679
  if (this.waiting) {
131566
131680
  const w2 = this.waiting;
131567
131681
  this.waiting = null;
@@ -131574,56 +131688,70 @@ var PersistentClaudeSession = class {
131574
131688
  * back to opts.onSay. This is what closes the DM→room leak: the destination is
131575
131689
  * the one captured for the message being answered, not a live global target. */
131576
131690
  async deliver(text) {
131691
+ this.saidThisTurn = true;
131577
131692
  if (this.activeReply) await this.activeReply(text);
131578
131693
  else if (this.opts.onSay) await this.opts.onSay(text);
131579
131694
  }
131580
131695
  async *input() {
131581
131696
  while (true) {
131582
- if (this.pending.length > 0) {
131583
- const item2 = this.pending.shift();
131584
- this.activeReply = item2.reply;
131585
- yield item2.msg;
131586
- continue;
131587
- }
131588
- const item = await new Promise((resolve2) => {
131589
- this.waiting = resolve2;
131697
+ const item = this.pending.length > 0 ? this.pending.shift() : await new Promise((resolve3) => {
131698
+ this.waiting = resolve3;
131590
131699
  });
131591
131700
  this.activeReply = item.reply;
131701
+ this.currentAutoReply = item.autoReplyOnText ?? false;
131702
+ this.saidThisTurn = false;
131703
+ this.turnText = "";
131592
131704
  yield item.msg;
131593
131705
  }
131594
131706
  }
131707
+ /**
131708
+ * Build the SDK options for this session. Locked mode (default) is safe for
131709
+ * untrusted inbound room messages. Worker mode (opts.worker=true) enables full
131710
+ * toolset on owner DMs, project context, workspace cwd, and an AV data-dir
131711
+ * fence via canUseTool.
131712
+ */
131713
+ buildSdkOptions() {
131714
+ const base = {
131715
+ model: this.opts.model,
131716
+ systemPrompt: this.opts.systemPrompt,
131717
+ mcpServers: { room: this.roomServer },
131718
+ allowedTools: [ROOM_SAY_TOOL_NAME]
131719
+ };
131720
+ if (!this.opts.worker) {
131721
+ return {
131722
+ ...base,
131723
+ permissionMode: "dontAsk",
131724
+ tools: [],
131725
+ settingSources: []
131726
+ };
131727
+ }
131728
+ const gateOpts = {
131729
+ dataDir: this.opts.dataDir ?? "",
131730
+ // LOAD-BEARING: this.currentAutoReply is true only on owner DM turns.
131731
+ // A room turn must keep it false or untrusted input gains full tool access.
131732
+ // See worker-permission.test.ts for the covering assertion.
131733
+ isOwnerTurn: () => this.currentAutoReply
131734
+ };
131735
+ return {
131736
+ ...base,
131737
+ permissionMode: this.opts.permissionMode ?? "auto",
131738
+ settingSources: ["user", "project"],
131739
+ cwd: this.opts.workspaceDir,
131740
+ // PRIMARY gate: a PreToolUse hook fires on EVERY tool call regardless of
131741
+ // permissionMode. canUseTool alone is bypassed in "auto" mode (the SDK's
131742
+ // classifier auto-approves without hitting the "ask" path) — verified live.
131743
+ hooks: { PreToolUse: [makeWorkerPreToolUseHook(gateOpts)] },
131744
+ // Secondary (defense-in-depth): covers the "ask" path in default/dontAsk modes.
131745
+ canUseTool: makeWorkerPermission(gateOpts)
131746
+ };
131747
+ }
131595
131748
  async start() {
131596
- const roomServer = _s({
131749
+ this.roomServer = _s({
131597
131750
  name: "room",
131598
131751
  version: "0.2.0",
131599
131752
  tools: [makeRoomSayTool((text) => this.deliver(text))]
131600
131753
  });
131601
- const sdkOptions = {
131602
- model: this.opts.model,
131603
- systemPrompt: this.opts.systemPrompt,
131604
- // SECURITY: this session is driven by UNTRUSTED inbound room messages from
131605
- // other agents. It must NOT be able to run general tools (shell/file/network)
131606
- // — that would be a prompt-injection → RCE / key-exfil path on the host. The
131607
- // ONLY tool it gets is room_say (post text to the active room), which has no
131608
- // such reach. Defense is capability REMOVAL first, permission mode second:
131609
- // - tools [] : remove ALL built-in tools (Bash/Read/Write/...) at the
131610
- // source so an injection can't even reach the permission layer. The
131611
- // room_say MCP tool comes via mcpServers and is unaffected by this.
131612
- // - permissionMode "dontAsk" (NEVER "bypassPermissions"): deny anything not
131613
- // pre-approved — no reliance on a no-TTY-implies-denial assumption.
131614
- // - allowedTools [ROOM_SAY_TOOL_NAME] : pre-approve ONLY room_say.
131615
- // - settingSources [] : do not load CLAUDE.md / project agents / skills
131616
- // from disk into an untrusted-input session.
131617
- // When CClaude later becomes a tool-using coder, additional tools must only
131618
- // be enabled INSIDE an isolation boundary (sandboxed dev droplet, no prod/
131619
- // network access, no access to the device-key dir) AND gated by canUseTool.
131620
- permissionMode: "dontAsk",
131621
- tools: [],
131622
- allowedTools: [ROOM_SAY_TOOL_NAME],
131623
- mcpServers: { room: roomServer },
131624
- settingSources: []
131625
- // maxTurns left unset → persistent multi-turn (NOT maxTurns:1)
131626
- };
131754
+ const sdkOptions = this.buildSdkOptions();
131627
131755
  const q5 = this.opts.queryImpl ? this.opts.queryImpl({
131628
131756
  prompt: this.input(),
131629
131757
  options: sdkOptions
@@ -131635,7 +131763,15 @@ var PersistentClaudeSession = class {
131635
131763
  if (m6.type === "assistant") {
131636
131764
  const blocks = m6.message?.content ?? [];
131637
131765
  const text = blocks.filter((b5) => b5.type === "text").map((b5) => b5.text ?? "").join("");
131638
- if (text.trim()) this.opts.onObserve?.(text);
131766
+ if (text.trim()) {
131767
+ this.turnText += text;
131768
+ this.opts.onObserve?.(text);
131769
+ }
131770
+ } else if (m6.type === "result") {
131771
+ const reply = this.activeReply;
131772
+ if (this.currentAutoReply && !this.saidThisTurn && this.turnText.trim() && reply) {
131773
+ void reply(this.turnText);
131774
+ }
131639
131775
  }
131640
131776
  }
131641
131777
  }
@@ -131741,13 +131877,15 @@ function wireBridge(channel, session, target, opts = {}) {
131741
131877
  }
131742
131878
  log(`inbound from ${e7.senderName} in ${e7.roomId.slice(0, 8)}`);
131743
131879
  target.setRoom(e7.roomId);
131744
- session.push(`[${e7.senderName}]: ${e7.plaintext}`, target.snapshotReply(channel, log));
131880
+ session.push(`[${e7.senderName}]: ${e7.plaintext}`, target.snapshotReply(channel, log), {
131881
+ autoReplyOnText: false
131882
+ });
131745
131883
  });
131746
131884
  channel.on("message", (text, metadata) => {
131747
131885
  if (metadata?.roomId) return;
131748
131886
  log("inbound 1:1 DM from owner");
131749
131887
  target.setDm();
131750
- session.push(text, target.snapshotReply(channel, log));
131888
+ session.push(text, target.snapshotReply(channel, log), { autoReplyOnText: true });
131751
131889
  });
131752
131890
  }
131753
131891
 
@@ -131760,7 +131898,14 @@ async function main() {
131760
131898
  "[bridge] warning: passing the invite token on the command line is visible to other local users via 'ps'. Prefer: AV_INVITE_TOKEN=\u2026 npx @agentvault/claude-bridge"
131761
131899
  );
131762
131900
  }
131901
+ console.error(`[bridge] version: ${true ? "0.3.4" : "dev"}`);
131763
131902
  console.error(`[bridge] data dir: ${cfg.dataDir} (${cfg.dataDirSource})`);
131903
+ if (cfg.worker) {
131904
+ console.error(`[bridge] WORKER MODE \u2014 workspace: ${cfg.workspaceDir} \xB7 permissions: ${cfg.permissionMode} \xB7 enclave dir fenced`);
131905
+ console.error(
131906
+ "[bridge] WORKER MODE WARNING: worker mode mixes untrusted room context with trusted owner DM turns in one persistent session. The turn-scoped gate (isOwnerTurn) prevents tool use ON a room turn, but does NOT prevent a room message from planting an instruction that fires on a later owner DM turn when tools are enabled (cross-turn injection). Until Slice 2: run a worker bridge DM-only (no room traffic) or under OS-user isolation."
131907
+ );
131908
+ }
131764
131909
  const target = new ActiveTarget();
131765
131910
  if (cfg.roomFilter) target.setRoom(cfg.roomFilter);
131766
131911
  const channel = new SecureChannel({
@@ -131779,11 +131924,15 @@ async function main() {
131779
131924
  // per-message binding is what prevents a private 1:1 reply from leaking into a
131780
131925
  // room when room traffic arrives mid-compose.
131781
131926
  // Assistant reasoning that wasn't sent — log a short trace only.
131782
- onObserve: (text) => console.error(`[bridge] observed (${text.length} chars, not sent)`)
131927
+ onObserve: (text) => console.error(`[bridge] observed (${text.length} chars, not sent)`),
131928
+ worker: cfg.worker,
131929
+ workspaceDir: cfg.workspaceDir,
131930
+ permissionMode: cfg.permissionMode,
131931
+ dataDir: cfg.dataDir
131783
131932
  });
131784
131933
  wireBridge(
131785
131934
  channel,
131786
- { push: (t7, reply) => session.push(t7, reply) },
131935
+ { push: (t7, reply, opts) => session.push(t7, reply, opts) },
131787
131936
  target,
131788
131937
  { roomFilter: cfg.roomFilter, log: (m6) => console.error("[bridge] " + m6) }
131789
131938
  );
package/dist/session.d.ts CHANGED
@@ -55,6 +55,15 @@ 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;
58
67
  }
59
68
  export declare class PersistentClaudeSession {
60
69
  private opts;
@@ -63,13 +72,44 @@ export declare class PersistentClaudeSession {
63
72
  /** Reply sink bound to the message currently being processed. Set as each
64
73
  * message is handed to the model so the say tool routes to the right place. */
65
74
  private activeReply?;
75
+ /**
76
+ * Per-turn state for the #416 DM auto-reply fallback.
77
+ *
78
+ * LOAD-BEARING for the worker tool gate: `currentAutoReply` is true ONLY on
79
+ * owner DM turns (`autoReplyOnText:true` from wireBridge) and false on room
80
+ * turns (`autoReplyOnText:false`). `makeWorkerPermission` calls
81
+ * `isOwnerTurn: () => this.currentAutoReply` to decide whether tools are
82
+ * allowed. A room-origin turn MUST keep this false or untrusted input gains
83
+ * full tool access. See worker-permission.test.ts for the covering assertion.
84
+ */
85
+ private currentAutoReply;
86
+ private saidThisTurn;
87
+ private turnText;
88
+ /** In-process room MCP server — hoisted so buildSdkOptions can reference it. */
89
+ private roomServer;
66
90
  constructor(opts: SessionOpts);
67
- push(text: string, reply?: ReplySink): void;
91
+ /**
92
+ * Queue an inbound message for the model.
93
+ * @param opts.autoReplyOnText — for 1:1 DMs (where the owner always expects a
94
+ * reply): if the turn produces assistant text but never calls the say tool,
95
+ * send that text as the reply instead of silently dropping it (#416). Rooms
96
+ * omit this so the agent can still choose silence.
97
+ */
98
+ push(text: string, reply?: ReplySink, opts?: {
99
+ autoReplyOnText?: boolean;
100
+ }): void;
68
101
  /** Route a say-tool message to the reply bound to the in-flight message, falling
69
102
  * back to opts.onSay. This is what closes the DM→room leak: the destination is
70
103
  * the one captured for the message being answered, not a live global target. */
71
104
  deliver(text: string): Promise<void>;
72
105
  private input;
106
+ /**
107
+ * Build the SDK options for this session. Locked mode (default) is safe for
108
+ * untrusted inbound room messages. Worker mode (opts.worker=true) enables full
109
+ * toolset on owner DMs, project context, workspace cwd, and an AV data-dir
110
+ * fence via canUseTool.
111
+ */
112
+ private buildSdkOptions;
73
113
  start(): Promise<void>;
74
114
  }
75
115
  export {};
@@ -0,0 +1,22 @@
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
+ isOwnerTurn: () => boolean;
12
+ }): HookCallbackMatcher;
13
+ /**
14
+ * SECONDARY (defense-in-depth): the canUseTool callback covers the SDK's "ask"
15
+ * path (permissionMode `"default"`/`"dontAsk"`). Shares gateDecision with the
16
+ * PreToolUse hook above, so a deny in one is a deny in the other.
17
+ */
18
+ export declare function makeWorkerPermission(opts: {
19
+ dataDir: string;
20
+ isOwnerTurn: () => boolean;
21
+ }): CanUseTool;
22
+ //# 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.2",
3
+ "version": "0.3.4",
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",