@pushary/agent-hooks 0.95.7 → 0.97.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.
Files changed (82) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/bin/pushary-bell-hook.js +19 -21
  3. package/dist/bin/pushary-bell.js +6 -8
  4. package/dist/bin/pushary-claude.js +21 -23
  5. package/dist/bin/pushary-clean.js +67 -32
  6. package/dist/bin/pushary-codex-bridge.js +13 -14
  7. package/dist/bin/pushary-codex-hook.js +452 -104
  8. package/dist/bin/pushary-codex.js +8 -8
  9. package/dist/bin/pushary-connect.js +6 -6
  10. package/dist/bin/pushary-cowork.js +3 -3
  11. package/dist/bin/pushary-daemon.js +25 -27
  12. package/dist/bin/pushary-disconnect.js +6 -8
  13. package/dist/bin/pushary-doctor.js +28 -23
  14. package/dist/bin/pushary-elicitation-hook.js +19 -21
  15. package/dist/bin/pushary-gemini-bridge.js +11 -12
  16. package/dist/bin/pushary-gemini-hook.js +22 -24
  17. package/dist/bin/pushary-hook.js +24 -26
  18. package/dist/bin/pushary-login.js +7 -8
  19. package/dist/bin/pushary-logout.js +4 -4
  20. package/dist/bin/pushary-mode.js +3 -3
  21. package/dist/bin/pushary-notification-hook.js +21 -23
  22. package/dist/bin/pushary-opencode-hook.js +22 -24
  23. package/dist/bin/pushary-permission-denied-hook.js +28 -27
  24. package/dist/bin/pushary-permission-hook.js +24 -26
  25. package/dist/bin/pushary-post-hook.js +21 -23
  26. package/dist/bin/pushary-prompt-hook.js +21 -23
  27. package/dist/bin/pushary-session-end-hook.js +21 -23
  28. package/dist/bin/pushary-session-start-hook.js +24 -26
  29. package/dist/bin/pushary-setup.js +34 -38
  30. package/dist/bin/pushary-stats.js +2 -2
  31. package/dist/bin/pushary-status.js +10 -11
  32. package/dist/bin/pushary-stop-hook.js +21 -23
  33. package/dist/bin/pushary-stopfailure-hook.js +21 -23
  34. package/dist/bin/pushary-transcript-register.js +17 -19
  35. package/dist/bin/pushary-transcripts.js +5 -5
  36. package/dist/bin/pushary-upgrade.js +17 -20
  37. package/dist/bin/pushary-wait.js +3 -3
  38. package/dist/{chunk-WAUAYRUQ.js → chunk-3SNTEHOH.js} +4 -4
  39. package/dist/{chunk-WPS5IM7B.js → chunk-4AXWBCCO.js} +4 -4
  40. package/dist/{chunk-IZVK6FKD.js → chunk-5ODZEFP3.js} +1 -1
  41. package/dist/{chunk-BGX4QUQF.js → chunk-67RNDRKL.js} +261 -31
  42. package/dist/{chunk-HAXZ6CBF.js → chunk-6VXZKOBB.js} +8 -8
  43. package/dist/{chunk-CNUDZS4V.js → chunk-6WJKYD3Q.js} +8 -8
  44. package/dist/{chunk-EMGGUO2Q.js → chunk-A4AIISZZ.js} +1 -1
  45. package/dist/{chunk-N6FAXQDD.js → chunk-AXSDMDJI.js} +1 -1
  46. package/dist/{chunk-XO3JR3OP.js → chunk-B3557BDL.js} +2 -2
  47. package/dist/{chunk-QJPYJVL3.js → chunk-BYH75I7Y.js} +1 -1
  48. package/dist/{chunk-VERXHIQF.js → chunk-C2FYSFTQ.js} +1 -1
  49. package/dist/{chunk-SKPXQRKN.js → chunk-CDSA3NTB.js} +1 -1
  50. package/dist/{chunk-XJQOVWBQ.js → chunk-DGUMR5LZ.js} +6 -6
  51. package/dist/{chunk-54W7ELDQ.js → chunk-DNLFSRCF.js} +2 -2
  52. package/dist/{chunk-6W2WGNO5.js → chunk-DOKFLZWL.js} +10 -14
  53. package/dist/{chunk-U5RIXR7I.js → chunk-EHQC3GWL.js} +1 -1
  54. package/dist/{chunk-GQRU4KJF.js → chunk-FU5SQKAY.js} +15 -4
  55. package/dist/{chunk-TY6IJY6I.js → chunk-GGYH6LIE.js} +1 -1
  56. package/dist/{chunk-XZWUOCJZ.js → chunk-H4VNAOCG.js} +1 -1
  57. package/dist/{chunk-MC5WUOJJ.js → chunk-HUZW6SEO.js} +75 -1
  58. package/dist/{chunk-7RNY3UHJ.js → chunk-IBRX7LRE.js} +8 -10
  59. package/dist/{chunk-UKMLSQXA.js → chunk-ICUBJNFL.js} +2 -2
  60. package/dist/{chunk-4OSQZ5TB.js → chunk-IZKCA35M.js} +2 -2
  61. package/dist/{chunk-NKUE5SJB.js → chunk-K7MLXXPY.js} +2 -2
  62. package/dist/{chunk-PQGLDN4V.js → chunk-KDPYXMUI.js} +2 -2
  63. package/dist/{chunk-XS33QBJA.js → chunk-LGXUGYUH.js} +50 -1
  64. package/dist/{chunk-VMZ4SWQD.js → chunk-LYWTLNMG.js} +6 -8
  65. package/dist/chunk-OYTOLQ3N.js +15 -0
  66. package/dist/{chunk-XUTSZYYJ.js → chunk-QFAZ6QCT.js} +1 -4
  67. package/dist/{chunk-FRXZMCVA.js → chunk-ROTIPRM4.js} +1 -1
  68. package/dist/{chunk-BQL7UDIB.js → chunk-SW37HB27.js} +1 -1
  69. package/dist/{chunk-Z4JVEL7R.js → chunk-VCKQ45QA.js} +191 -2
  70. package/dist/{chunk-SDNTRS76.js → chunk-VDTC5DEM.js} +1 -1
  71. package/dist/{chunk-VMQU7YEJ.js → chunk-VF7YOT2W.js} +1 -1
  72. package/dist/{chunk-5GKNAQTG.js → chunk-VOC55PQV.js} +1 -1
  73. package/dist/{chunk-L7NOG547.js → chunk-VTUN7WSG.js} +1 -1
  74. package/dist/{chunk-GOPIMNVC.js → chunk-WIKEADOS.js} +1 -1
  75. package/dist/chunk-XDMHLQMH.js +202 -0
  76. package/dist/{chunk-USWT4PTR.js → chunk-YRXVC4HL.js} +1 -1
  77. package/dist/{chunk-VIUOGMT5.js → chunk-YX3U2CLP.js} +111 -55
  78. package/dist/src/index.js +11 -11
  79. package/package.json +1 -1
  80. package/dist/chunk-3AJIK5AO.js +0 -57
  81. package/dist/chunk-L4ZCFMWW.js +0 -177
  82. package/dist/chunk-VDI645ZX.js +0 -18
@@ -18,26 +18,26 @@ import {
18
18
  summarizeStop,
19
19
  taskTitleFrom,
20
20
  toolResultErrorText
21
- } from "./chunk-Z4JVEL7R.js";
21
+ } from "./chunk-VCKQ45QA.js";
22
22
  import {
23
23
  NOTIFICATION_PUSH_TYPES,
24
24
  POSTTOOLUSE_MIRROR_ONLY_TOOLS
25
- } from "./chunk-XUTSZYYJ.js";
25
+ } from "./chunk-QFAZ6QCT.js";
26
26
  import {
27
27
  cancelQuestion,
28
28
  isPolicyConfig,
29
29
  sendNotification,
30
30
  waitForAnswer
31
- } from "./chunk-XS33QBJA.js";
31
+ } from "./chunk-LGXUGYUH.js";
32
32
  import {
33
33
  getMachineId
34
- } from "./chunk-4OSQZ5TB.js";
34
+ } from "./chunk-IZKCA35M.js";
35
35
  import {
36
36
  withRetry
37
37
  } from "./chunk-EYRGBYZG.js";
38
38
  import {
39
39
  reportedInstallSource
40
- } from "./chunk-L7NOG547.js";
40
+ } from "./chunk-VTUN7WSG.js";
41
41
  import {
42
42
  getApiKey,
43
43
  getBaseUrl
@@ -54,7 +54,7 @@ import {
54
54
  pendingCommandEvents,
55
55
  resolveAutoResolveOrigin,
56
56
  resolvePolicy
57
- } from "./chunk-MC5WUOJJ.js";
57
+ } from "./chunk-HUZW6SEO.js";
58
58
 
59
59
  // src/policy.ts
60
60
  import { createHash } from "crypto";
@@ -2,10 +2,10 @@ import {
2
2
  resolveBinary,
3
3
  resolveGlobalPackageVersion,
4
4
  resolvePackageVersionFromBinary
5
- } from "./chunk-L4ZCFMWW.js";
5
+ } from "./chunk-67RNDRKL.js";
6
6
  import {
7
7
  MANAGED_PROVIDERS
8
- } from "./chunk-MC5WUOJJ.js";
8
+ } from "./chunk-HUZW6SEO.js";
9
9
 
10
10
  // src/live-capabilities.ts
11
11
  var CLAUDE_BRIDGE_CAPABILITY = "claude-bridge";
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  detectClaudeVersion
3
- } from "./chunk-USWT4PTR.js";
3
+ } from "./chunk-YRXVC4HL.js";
4
4
  import {
5
5
  addClaudeMcpServer,
6
6
  addPusharyHooks,
7
7
  addPusharyToolPermissions
8
- } from "./chunk-CNUDZS4V.js";
8
+ } from "./chunk-6WJKYD3Q.js";
9
9
  import {
10
10
  claudeWired,
11
11
  codexWired,
12
12
  geminiWired
13
- } from "./chunk-GQRU4KJF.js";
13
+ } from "./chunk-FU5SQKAY.js";
14
14
  import {
15
15
  CLAUDE_HOOK_EVENTS,
16
16
  GEMINI_HOOK_BINARY,
@@ -21,14 +21,7 @@ import {
21
21
  needsReregistration,
22
22
  registrationSignatures,
23
23
  supportedClaudeEvents
24
- } from "./chunk-7RNY3UHJ.js";
25
- import {
26
- guardBinary
27
- } from "./chunk-3AJIK5AO.js";
28
- import {
29
- resolveGlobalBinDir,
30
- resolveGlobalBinary
31
- } from "./chunk-L4ZCFMWW.js";
24
+ } from "./chunk-IBRX7LRE.js";
32
25
  import {
33
26
  CODEX_HOOK_BINARY,
34
27
  addCodexHookTrust,
@@ -38,16 +31,19 @@ import {
38
31
  codexConfigToml,
39
32
  codexHookPositions,
40
33
  codexHooksJson,
34
+ guardBinary,
41
35
  hasCodexHooks,
36
+ resolveGlobalBinDir,
37
+ resolveGlobalBinary,
42
38
  vscodePluginMcp
43
- } from "./chunk-BGX4QUQF.js";
39
+ } from "./chunk-67RNDRKL.js";
44
40
  import {
45
41
  readJsonSafe,
46
42
  writeJsonAtomic
47
- } from "./chunk-L7NOG547.js";
43
+ } from "./chunk-VTUN7WSG.js";
48
44
  import {
49
45
  HOOK_LOCATOR_BINARY
50
- } from "./chunk-MC5WUOJJ.js";
46
+ } from "./chunk-HUZW6SEO.js";
51
47
 
52
48
  // src/reapply.ts
53
49
  import { join } from "path";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  HOOK_BUDGETS,
3
3
  OPENCODE_APPROVAL_EVENT
4
- } from "./chunk-MC5WUOJJ.js";
4
+ } from "./chunk-HUZW6SEO.js";
5
5
 
6
6
  // src/opencode-plugin.ts
7
7
  var OPENCODE_PLUGIN_MARKER = "pushary-opencode-plugin w1";
@@ -2,15 +2,16 @@ import {
2
2
  CLAUDE_HOOK_EVENTS,
3
3
  GEMINI_HOOK_EVENTS,
4
4
  hasGeminiHooks
5
- } from "./chunk-7RNY3UHJ.js";
5
+ } from "./chunk-IBRX7LRE.js";
6
6
  import {
7
7
  CODEX_HOOK_EVENTS,
8
8
  hasCodexHooks
9
- } from "./chunk-BGX4QUQF.js";
9
+ } from "./chunk-67RNDRKL.js";
10
10
  import {
11
11
  CURSOR_GATE_EVENTS,
12
- HOOK_LOCATOR_BINARY
13
- } from "./chunk-MC5WUOJJ.js";
12
+ HOOK_LOCATOR_BINARY,
13
+ hookCommandHoldBudget
14
+ } from "./chunk-HUZW6SEO.js";
14
15
 
15
16
  // src/diagnostics/wiring.ts
16
17
  var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
@@ -111,6 +112,14 @@ var claudeOtherHolders = (settings) => otherHolders(record(settings)?.hooks, CLA
111
112
  var codexOtherHolders = (hooks) => otherHolders(record(hooks)?.hooks, CODEX_GATE_EVENTS, "nested", "seconds");
112
113
  var geminiOtherHolders = (settings) => otherHolders(record(settings)?.hooks, GEMINI_GATE_EVENTS, "nested", "milliseconds");
113
114
  var cursorOtherHolders = (userHooks) => otherHolders(record(userHooks)?.hooks, CURSOR_GATE_EVENTS, "flat", "seconds");
115
+ var holdEvents = (table) => table.filter((hook) => hook.holds).map((hook) => hook.event);
116
+ var unprovenHolds = (hooks, events) => {
117
+ const table = record(hooks);
118
+ if (!table) return [];
119
+ return events.filter((event) => registeredHooks(table[event], "nested", "seconds").some((hook) => pusharyHookExecutable(hook.command) !== null && hookCommandHoldBudget(hook.command) === void 0));
120
+ };
121
+ var claudeUnprovenHolds = (settings) => unprovenHolds(record(settings)?.hooks, holdEvents(CLAUDE_HOOK_EVENTS));
122
+ var codexUnprovenHolds = (hooks) => unprovenHolds(record(hooks)?.hooks, holdEvents(CODEX_HOOK_EVENTS));
114
123
  var holdsLong = (holder) => holder.timeoutSeconds !== null && holder.timeoutSeconds > LONG_HOLD_SECONDS;
115
124
  var SHELLS = /* @__PURE__ */ new Set(["/bin/sh", "/bin/bash", "/bin/zsh", "/usr/bin/env"]);
116
125
  var holderExecutable = (command) => {
@@ -139,6 +148,8 @@ export {
139
148
  codexOtherHolders,
140
149
  geminiOtherHolders,
141
150
  cursorOtherHolders,
151
+ claudeUnprovenHolds,
152
+ codexUnprovenHolds,
142
153
  holdsLong,
143
154
  describeOtherHolder,
144
155
  describeHoldSentence
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFileKey
3
- } from "./chunk-VMQU7YEJ.js";
3
+ } from "./chunk-VF7YOT2W.js";
4
4
 
5
5
  // src/bell/fleet.ts
6
6
  import { closeSync, constants, futimesSync, lstatSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-7QLSKOSU.js";
7
7
  import {
8
8
  isValidApiKey
9
- } from "./chunk-MC5WUOJJ.js";
9
+ } from "./chunk-HUZW6SEO.js";
10
10
 
11
11
  // src/setup/options.ts
12
12
  var AGENT_CHOICES = ["claude_code", "codex", "gemini_cli", "hermes", "cursor", "vscode", "opencode", "custom"];
@@ -293,6 +293,26 @@ var HOOK_BUDGETS = {
293
293
  opencode: { budgetSeconds: 120, guardSeconds: 10 }
294
294
  };
295
295
  var hookMaxWaitSeconds = (agent) => Math.max(HOOK_BUDGETS[agent].budgetSeconds - HOOK_BUDGETS[agent].guardSeconds, 1);
296
+ var HOOK_HOLD_BUDGET_FLAG = "--hold-budget";
297
+ var HOOK_HOLD_BUDGET_ENV = "PUSHARY_HOLD_BUDGET_SECONDS";
298
+ var HOOK_BRIDGE_SOURCE_FLAG = "--source";
299
+ var HOOK_HOLD_BUDGET_SECONDS = /^[1-9]\d{0,5}$/;
300
+ var hookHoldBudgetSeconds = (text) => text !== void 0 && HOOK_HOLD_BUDGET_SECONDS.test(text) ? Number(text) : void 0;
301
+ var hookHoldBudgetArgument = (seconds) => `${HOOK_HOLD_BUDGET_FLAG}=${seconds}`;
302
+ var withHookHoldBudget = (command, holdBudgetSeconds) => holdBudgetSeconds === void 0 ? command : `${command} ${hookHoldBudgetArgument(holdBudgetSeconds)}`;
303
+ var registeredHookHoldBudget = (args) => {
304
+ const prefix = `${HOOK_HOLD_BUDGET_FLAG}=`;
305
+ const proofs = args.filter((arg) => arg.startsWith(prefix));
306
+ return proofs.length === 1 ? hookHoldBudgetSeconds(proofs[0].slice(prefix.length)) : void 0;
307
+ };
308
+ var hookCommandHoldBudget = (command) => registeredHookHoldBudget(command.trim().split(/\s+/));
309
+ var hookHoldBudgetProof = (args, env) => {
310
+ const handedDown = hookHoldBudgetSeconds(env[HOOK_HOLD_BUDGET_ENV]);
311
+ const registered = args.includes(HOOK_BRIDGE_SOURCE_FLAG) ? void 0 : registeredHookHoldBudget(args);
312
+ if (handedDown === void 0) return registered;
313
+ return registered === void 0 ? handedDown : Math.min(handedDown, registered);
314
+ };
315
+ var hookHoldWindowMs = (agent, holdBudgetSeconds) => holdBudgetSeconds === void 0 ? 0 : Math.max((Math.min(holdBudgetSeconds, HOOK_BUDGETS[agent].budgetSeconds) - HOOK_BUDGETS[agent].guardSeconds) * 1e3, 0);
296
316
  var hookWaitDeadline = (startMs, policyWaitSeconds, agent, nowMs) => {
297
317
  const maxWait = hookMaxWaitSeconds(agent);
298
318
  return Math.min(
@@ -1275,6 +1295,30 @@ var shouldDeferToNativeMode = (permissionMode, toolName, options = {}) => {
1275
1295
  var ASKED_IN_EVERY_MODE_TOOLS = ["AskUserQuestion", "ExitPlanMode"];
1276
1296
  var ASKED_IN_EVERY_MODE = new Set(ASKED_IN_EVERY_MODE_TOOLS);
1277
1297
  var isAskedInEveryMode = (toolName) => ASKED_IN_EVERY_MODE.has(toolName);
1298
+ var NATIVE_BLOCK_EVENTS = /* @__PURE__ */ new Set(["PermissionDenied"]);
1299
+ var isNativeBlockEvent = (event) => typeof event === "string" && NATIVE_BLOCK_EVENTS.has(event);
1300
+ var CODEX_RESCUE_TOOL_NAME = "Bash";
1301
+ var isBlockedActionEvent = ({ source, event, blockedBy }) => isNativeBlockEvent(event) || source === "codex" && event === "Stop" && blockedBy === "auto_review";
1302
+ var blockedActionRetryReachesGate = (source, toolName) => source === "claude" ? PRETOOLUSE_HANDLED_TOOLS.has(toolName) && !isAskedInEveryMode(toolName) : source === "codex" && toolName === CODEX_RESCUE_TOOL_NAME;
1303
+ var isBlockedActionRequest = (request) => isBlockedActionEvent(request) && blockedActionRetryReachesGate(request.source, request.toolName);
1304
+ var canonicalJsonText = (value) => {
1305
+ if (Array.isArray(value)) return `[${value.map(canonicalJsonText).join(",")}]`;
1306
+ if (value !== null && typeof value === "object") {
1307
+ const record = value;
1308
+ return `{${Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalJsonText(record[key])}`).join(",")}}`;
1309
+ }
1310
+ return JSON.stringify(value) ?? "null";
1311
+ };
1312
+ var BLOCKED_ACTION_IDENTITY_VERSION = "blocked-action/v1";
1313
+ var CLAUDE_BLOCKED_ACTION_IDENTITY_VERSION = "blocked-action/v2";
1314
+ var BLOCKED_ACTION_GRANT_TTL_SECONDS = 120;
1315
+ var BLOCKED_ACTION_PRESENTATION_ONLY_KEYS = ["description"];
1316
+ var isPresentationOnlyInputKey = (key) => BLOCKED_ACTION_PRESENTATION_ONLY_KEYS.includes(key);
1317
+ var identifyingBlockedActionInput = (toolInput) => Object.fromEntries(Object.entries(toolInput).filter(([key]) => !isPresentationOnlyInputKey(key)));
1318
+ var blockedActionIdentity = (subject) => {
1319
+ const { sessionId, toolName, toolInput } = subject;
1320
+ return subject.source === "claude" ? canonicalJsonText([CLAUDE_BLOCKED_ACTION_IDENTITY_VERSION, sessionId, subject.cwd, toolName, identifyingBlockedActionInput(toolInput)]) : canonicalJsonText([BLOCKED_ACTION_IDENTITY_VERSION, sessionId, toolName, identifyingBlockedActionInput(toolInput)]);
1321
+ };
1278
1322
  var PRETOOLUSE_GATED_TOOLS = [
1279
1323
  "Bash",
1280
1324
  "Write",
@@ -1286,6 +1330,7 @@ var PRETOOLUSE_GATED_TOOLS = [
1286
1330
  ];
1287
1331
  var PRETOOLUSE_HANDLED_TOOLS = /* @__PURE__ */ new Set([...PRETOOLUSE_GATED_TOOLS, ...ASKED_IN_EVERY_MODE_TOOLS]);
1288
1332
  var isPermissionRequestOwnedByPreToolUse = (toolName, permissionMode, defers = shouldDeferToNativeMode) => PRETOOLUSE_HANDLED_TOOLS.has(toolName) && (isAskedInEveryMode(toolName) || !defers(permissionMode, toolName));
1333
+ var CLAUDE_PRETOOLUSE_MATCHER = [...PRETOOLUSE_HANDLED_TOOLS].join("|");
1289
1334
  var resolveGate = (input) => {
1290
1335
  const { modeState, config, toolName, toolInputs, cwd, repoKey, sessionId } = input;
1291
1336
  if (isPusharyOwnTool(toolName)) {
@@ -1342,6 +1387,19 @@ var modeStateFromResponse = (data) => {
1342
1387
  askHandoff: data.askHandoff === true
1343
1388
  };
1344
1389
  };
1390
+ var GATE_WIRE = 1;
1391
+ var GATE_MAX_TOOL_INPUTS = 32;
1392
+ var GATE_BLOCK_REASON_MAX_LENGTH = 200;
1393
+ var claudePermissionDeniedOutput = (decision) => ({
1394
+ hookSpecificOutput: {
1395
+ hookEventName: "PermissionDenied",
1396
+ retry: decision === "allow"
1397
+ }
1398
+ });
1399
+ var CODEX_RESCUE_COMMAND_MAX_LENGTH = 2e3;
1400
+ var CODEX_RESCUE_APPROVED = "The user approved this command from Pushary after the auto-reviewer denied it.";
1401
+ var codexRescueRetryInstruction = (command) => command.length <= CODEX_RESCUE_COMMAND_MAX_LENGTH ? `Run it again exactly as before, unchanged: ${command}` : `Run it again exactly as before, unchanged. It is too long to repeat in full here and begins: ${command.slice(0, CODEX_RESCUE_COMMAND_MAX_LENGTH)}`;
1402
+ var codexStopRescueOutput = (decision, command) => decision === "allow" ? { decision: "block", reason: `${CODEX_RESCUE_APPROVED} ${codexRescueRetryInstruction(command)}` } : { continue: true };
1345
1403
  var stoppedOutput = (output, stopReason) => ({ ...output, continue: false, stopReason });
1346
1404
  var claudePreToolUseOutput = (decision, reason, updatedInput) => ({
1347
1405
  hookSpecificOutput: {
@@ -1387,6 +1445,8 @@ var gateOutputFor = (source, event, decision, reason) => {
1387
1445
  output = claudePreToolUseOutput(decision, reason);
1388
1446
  } else if (event === "PermissionRequest" && decision !== "ask") {
1389
1447
  output = claudePermissionRequestOutput(decision, reason);
1448
+ } else if (source === "claude" && event === "PermissionDenied" && decision !== "ask") {
1449
+ output = claudePermissionDeniedOutput(decision);
1390
1450
  }
1391
1451
  } else if (source === "codex") {
1392
1452
  if (event === "PermissionRequest" && decision !== "ask") {
@@ -1582,6 +1642,11 @@ export {
1582
1642
  parseAgentQuestionAnswers,
1583
1643
  normalizeInstallSource,
1584
1644
  HOOK_BUDGETS,
1645
+ HOOK_HOLD_BUDGET_ENV,
1646
+ withHookHoldBudget,
1647
+ hookCommandHoldBudget,
1648
+ hookHoldBudgetProof,
1649
+ hookHoldWindowMs,
1585
1650
  hookWaitDeadline,
1586
1651
  hookWaitClamped,
1587
1652
  effectiveWaitSeconds,
@@ -1605,11 +1670,20 @@ export {
1605
1670
  KILL_REASON,
1606
1671
  isPusharyOwnTool,
1607
1672
  shouldDeferToNativeMode,
1608
- ASKED_IN_EVERY_MODE_TOOLS,
1673
+ CODEX_RESCUE_TOOL_NAME,
1674
+ isBlockedActionRequest,
1675
+ BLOCKED_ACTION_GRANT_TTL_SECONDS,
1676
+ blockedActionIdentity,
1609
1677
  PRETOOLUSE_GATED_TOOLS,
1610
1678
  isPermissionRequestOwnedByPreToolUse,
1679
+ CLAUDE_PRETOOLUSE_MATCHER,
1611
1680
  resolveGate,
1612
1681
  modeStateFromResponse,
1682
+ GATE_WIRE,
1683
+ GATE_MAX_TOOL_INPUTS,
1684
+ GATE_BLOCK_REASON_MAX_LENGTH,
1685
+ claudePermissionDeniedOutput,
1686
+ codexStopRescueOutput,
1613
1687
  stoppedOutput,
1614
1688
  claudePreToolUseOutput,
1615
1689
  PENDING_COMMAND_PREFIX,
@@ -1,16 +1,15 @@
1
1
  import {
2
2
  NOTIFICATION_MATCHED_TYPES,
3
- POSTTOOLUSE_MIRRORED_TOOLS,
4
- PRETOOLUSE_PHONE_ANSWERED_TOOLS
5
- } from "./chunk-XUTSZYYJ.js";
3
+ POSTTOOLUSE_MIRRORED_TOOLS
4
+ } from "./chunk-QFAZ6QCT.js";
6
5
  import {
7
6
  isProtectedByScope
8
- } from "./chunk-BGX4QUQF.js";
7
+ } from "./chunk-67RNDRKL.js";
9
8
  import {
9
+ CLAUDE_PRETOOLUSE_MATCHER,
10
10
  HOOK_BUDGETS,
11
- HOOK_LOCATOR_BINARY,
12
- PRETOOLUSE_GATED_TOOLS
13
- } from "./chunk-MC5WUOJJ.js";
11
+ HOOK_LOCATOR_BINARY
12
+ } from "./chunk-HUZW6SEO.js";
14
13
 
15
14
  // src/gemini-config.ts
16
15
  var GEMINI_HOOK_BINARY = "pushary-gemini-hook";
@@ -128,12 +127,11 @@ var atLeast = (version, floor) => compareVersion(version, floor) >= 0;
128
127
  var formatVersion = (version) => version.join(".");
129
128
 
130
129
  // src/claude-events.ts
131
- var GATED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, ...PRETOOLUSE_PHONE_ANSWERED_TOOLS].join("|");
132
130
  var MIRRORED_TOOLS = POSTTOOLUSE_MIRRORED_TOOLS.join("|");
133
131
  var ESTABLISHED = [1, 0, 0];
134
132
  var OBSERVED = [2, 0, 0];
135
133
  var CLAUDE_HOOK_EVENTS = [
136
- { event: "PreToolUse", binary: "pushary-hook", matcher: GATED_TOOLS, timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
134
+ { event: "PreToolUse", binary: "pushary-hook", matcher: CLAUDE_PRETOOLUSE_MATCHER, timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
137
135
  { event: "PostToolUse", binary: "pushary-post-hook", matcher: MIRRORED_TOOLS, timeout: 10, delivery: "telemetry", since: ESTABLISHED },
138
136
  { event: "Stop", binary: "pushary-stop-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
139
137
  { event: "UserPromptSubmit", binary: "pushary-prompt-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
@@ -142,7 +140,7 @@ var CLAUDE_HOOK_EVENTS = [
142
140
  { event: "SessionEnd", binary: "pushary-session-end-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
143
141
  { event: "StopFailure", binary: "pushary-stopfailure-hook", timeout: 10, delivery: "telemetry", since: ESTABLISHED },
144
142
  { event: "PermissionRequest", binary: "pushary-permission-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", since: ESTABLISHED },
145
- { event: "PermissionDenied", binary: "pushary-permission-denied-hook", timeout: 60, delivery: "telemetry", since: ESTABLISHED },
143
+ { event: "PermissionDenied", binary: "pushary-permission-denied-hook", timeout: HOOK_BUDGETS.claude.budgetSeconds, delivery: "approval", holds: true, since: ESTABLISHED },
146
144
  // Observability only. Each carries session shape the island cannot get any other
147
145
  // way, and none of them decides anything.
148
146
  { event: "SubagentStart", binary: "pushary-session-start-hook", timeout: 10, delivery: "telemetry", since: OBSERVED },
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  SETUP_FLAGS
3
- } from "./chunk-XZWUOCJZ.js";
3
+ } from "./chunk-H4VNAOCG.js";
4
4
  import {
5
5
  parseFlags
6
6
  } from "./chunk-GMXKITVA.js";
@@ -14,7 +14,7 @@ import {
14
14
  CLAUDE_CONNECTOR_URL,
15
15
  COWORK_CONNECTOR_LIMIT,
16
16
  COWORK_STANDING_INSTRUCTIONS
17
- } from "./chunk-MC5WUOJJ.js";
17
+ } from "./chunk-HUZW6SEO.js";
18
18
 
19
19
  // src/cli/cowork.ts
20
20
  import { readFileSync } from "fs";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-BGX4QUQF.js";
3
+ } from "./chunk-67RNDRKL.js";
4
4
  import {
5
5
  writeFileAtomic
6
- } from "./chunk-L7NOG547.js";
6
+ } from "./chunk-VTUN7WSG.js";
7
7
 
8
8
  // src/identity.ts
9
9
  import { randomUUID } from "crypto";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  openCodeConfigDirFrom
3
- } from "./chunk-U5RIXR7I.js";
3
+ } from "./chunk-EHQC3GWL.js";
4
4
  import {
5
5
  codexHomeFrom
6
- } from "./chunk-BGX4QUQF.js";
6
+ } from "./chunk-67RNDRKL.js";
7
7
 
8
8
  // src/vscode-config.ts
9
9
  import { homedir } from "os";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-BGX4QUQF.js";
3
+ } from "./chunk-67RNDRKL.js";
4
4
  import {
5
5
  readJsonSafe
6
- } from "./chunk-L7NOG547.js";
6
+ } from "./chunk-VTUN7WSG.js";
7
7
 
8
8
  // src/process-ownership.ts
9
9
  import { createHash, randomUUID } from "crypto";
@@ -4,6 +4,9 @@ import {
4
4
  import {
5
5
  installProxyDispatcher
6
6
  } from "./chunk-SAF6HGAA.js";
7
+ import {
8
+ getBaseUrl
9
+ } from "./chunk-2UMNXADU.js";
7
10
 
8
11
  // src/api.ts
9
12
  import { randomUUID } from "crypto";
@@ -24,6 +27,21 @@ var isWaitForAnswerResponse = (data) => {
24
27
  const d = data;
25
28
  return typeof d.answered === "boolean";
26
29
  };
30
+ var GATE_REPLY_KINDS = ["kill", "allow", "deny", "ask", "no_opinion"];
31
+ var isSettlementOutputs = (value) => {
32
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
33
+ const outputs = value;
34
+ return (outputs.approve === void 0 || typeof outputs.approve === "string") && (outputs.deny === void 0 || typeof outputs.deny === "string");
35
+ };
36
+ var isGateReply = (data) => {
37
+ if (!data || typeof data !== "object") return false;
38
+ const d = data;
39
+ return GATE_REPLY_KINDS.includes(d.kind) && (d.output === void 0 || typeof d.output === "string") && (d.correlationId === void 0 || typeof d.correlationId === "string") && (d.deadlineSeconds === void 0 || typeof d.deadlineSeconds === "number") && (d.noDevices === void 0 || typeof d.noDevices === "boolean") && (d.suppressed === void 0 || typeof d.suppressed === "boolean") && (d.settlementOutputs === void 0 || isSettlementOutputs(d.settlementOutputs));
40
+ };
41
+ var isBlockedActionClaim = (data) => {
42
+ if (!data || typeof data !== "object") return false;
43
+ return typeof data.granted === "boolean";
44
+ };
27
45
 
28
46
  // src/api.ts
29
47
  installProxyDispatcher();
@@ -96,6 +114,34 @@ var waitForQuestion = async (apiKey, question, deadlineMs, pollInterval = 2e3) =
96
114
  var sendNotification = async (apiKey, params, timeoutMs = 15e3, signal) => {
97
115
  await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: signal ? 1 : 3, timeoutMs, signal });
98
116
  };
117
+ var GATE_PATH = "/api/agent/gate";
118
+ var BLOCKED_ACTION_CLAIM_PATH = "/api/agent/blocked-action/claim";
119
+ var postAgentRequest = async (apiKey, path, body, timeoutMs) => {
120
+ const response = await fetch(`${getBaseUrl()}${path}`, {
121
+ method: "POST",
122
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
123
+ body: JSON.stringify(body),
124
+ signal: AbortSignal.timeout(timeoutMs)
125
+ });
126
+ if (!response.ok) throw new Error(`${path} rejected (${response.status})`);
127
+ return response.json();
128
+ };
129
+ var requestGate = async (apiKey, request, timeoutMs = 15e3) => {
130
+ try {
131
+ const reply = await postAgentRequest(apiKey, GATE_PATH, request, timeoutMs);
132
+ return isGateReply(reply) ? reply : null;
133
+ } catch {
134
+ return null;
135
+ }
136
+ };
137
+ var claimBlockedAction = async (apiKey, request, timeoutMs) => {
138
+ try {
139
+ const reply = await postAgentRequest(apiKey, BLOCKED_ACTION_CLAIM_PATH, request, timeoutMs);
140
+ return isBlockedActionClaim(reply) ? reply.granted : null;
141
+ } catch {
142
+ return null;
143
+ }
144
+ };
99
145
 
100
146
  export {
101
147
  isPolicyConfig,
@@ -105,8 +151,11 @@ export {
105
151
  askUser,
106
152
  waitForAnswer,
107
153
  nextLongPollMs,
154
+ pollForAnswer,
108
155
  cancelQuestion,
109
156
  withdrawQuestion,
110
157
  waitForQuestion,
111
- sendNotification
158
+ sendNotification,
159
+ requestGate,
160
+ claimBlockedAction
112
161
  };
@@ -2,27 +2,25 @@ import {
2
2
  needsShell,
3
3
  spawnClaude
4
4
  } from "./chunk-XHKBHWLX.js";
5
- import {
6
- resolveGlobalBinary
7
- } from "./chunk-L4ZCFMWW.js";
8
5
  import {
9
6
  getMachineId
10
- } from "./chunk-4OSQZ5TB.js";
7
+ } from "./chunk-IZKCA35M.js";
11
8
  import {
12
- pusharyDir
13
- } from "./chunk-BGX4QUQF.js";
9
+ pusharyDir,
10
+ resolveGlobalBinary
11
+ } from "./chunk-67RNDRKL.js";
14
12
  import {
15
13
  getPackageVersion
16
14
  } from "./chunk-DINDL2CF.js";
17
15
  import {
18
16
  readControlMode
19
- } from "./chunk-VMQU7YEJ.js";
17
+ } from "./chunk-VF7YOT2W.js";
20
18
  import {
21
19
  readJsonSafe,
22
20
  writeFileAtomic,
23
21
  writeJsonAtomic,
24
22
  writeJsonAtomicDurable
25
- } from "./chunk-L7NOG547.js";
23
+ } from "./chunk-VTUN7WSG.js";
26
24
  import {
27
25
  getApiKey
28
26
  } from "./chunk-2UMNXADU.js";
@@ -0,0 +1,15 @@
1
+ import {
2
+ HOOK_HOLD_BUDGET_ENV,
3
+ hookHoldBudgetProof
4
+ } from "./chunk-HUZW6SEO.js";
5
+
6
+ // src/hold-budget.ts
7
+ var takeHookHoldBudget = (args = process.argv.slice(2), env = process.env) => {
8
+ const proof = hookHoldBudgetProof(args, env);
9
+ delete env[HOOK_HOLD_BUDGET_ENV];
10
+ return proof;
11
+ };
12
+
13
+ export {
14
+ takeHookHoldBudget
15
+ };
@@ -1,10 +1,8 @@
1
1
  import {
2
- ASKED_IN_EVERY_MODE_TOOLS,
3
2
  PRETOOLUSE_GATED_TOOLS
4
- } from "./chunk-MC5WUOJJ.js";
3
+ } from "./chunk-HUZW6SEO.js";
5
4
 
6
5
  // src/claude-tools.ts
7
- var PRETOOLUSE_PHONE_ANSWERED_TOOLS = ASKED_IN_EVERY_MODE_TOOLS;
8
6
  var POSTTOOLUSE_MIRROR_ONLY_TOOLS = ["TodoWrite", "Task", "WebFetch", "WebSearch"];
9
7
  var POSTTOOLUSE_MIRRORED_TOOLS = [...PRETOOLUSE_GATED_TOOLS, ...POSTTOOLUSE_MIRROR_ONLY_TOOLS];
10
8
 
@@ -34,7 +32,6 @@ var NOTIFICATION_PUSH_TYPES = /* @__PURE__ */ new Set([
34
32
  ]);
35
33
 
36
34
  export {
37
- PRETOOLUSE_PHONE_ANSWERED_TOOLS,
38
35
  POSTTOOLUSE_MIRROR_ONLY_TOOLS,
39
36
  POSTTOOLUSE_MIRRORED_TOOLS,
40
37
  NOTIFICATION_MATCHED_TYPES,
@@ -3,7 +3,7 @@ import {
3
3
  decodePublicKey,
4
4
  generateSessionKey,
5
5
  wrapSessionKey
6
- } from "./chunk-SKPXQRKN.js";
6
+ } from "./chunk-CDSA3NTB.js";
7
7
  import {
8
8
  fetchTranscriptRecipients,
9
9
  uploadTranscriptRecords
@@ -13,7 +13,7 @@ import {
13
13
  import {
14
14
  isValidApiKey,
15
15
  normalizeKeyScope
16
- } from "./chunk-MC5WUOJJ.js";
16
+ } from "./chunk-HUZW6SEO.js";
17
17
 
18
18
  // src/session.ts
19
19
  var samePusharyAccount = (left, right) => left.kind === "ok" && right.kind === "ok" && left.identity.workspaceId !== "" && left.identity.workspaceId === right.identity.workspaceId && left.identity.siteId === right.identity.siteId;