@pushary/agent-hooks 0.95.8 → 0.98.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 +27 -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 +19 -22
  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 +45 -40
  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-GOPIMNVC.js → chunk-2GLOHBYB.js} +1 -1
  39. package/dist/{chunk-BQL7UDIB.js → chunk-3RVCQDC4.js} +1 -1
  40. package/dist/chunk-53QCTSWY.js +202 -0
  41. package/dist/{chunk-3YFJGKJQ.js → chunk-5B5QV3KC.js} +8 -8
  42. package/dist/{chunk-FRXZMCVA.js → chunk-5KLAETSU.js} +1 -1
  43. package/dist/{chunk-SDNTRS76.js → chunk-6C6MS57P.js} +1 -1
  44. package/dist/{chunk-A22VQ5ZL.js → chunk-6CYB77VD.js} +111 -55
  45. package/dist/{chunk-QD73WATQ.js → chunk-6LRGZJFB.js} +21 -21
  46. package/dist/{chunk-Z4JVEL7R.js → chunk-A76GNRIK.js} +191 -2
  47. package/dist/{chunk-RE25RJ5A.js → chunk-B35OCFXH.js} +4 -4
  48. package/dist/{chunk-UKMLSQXA.js → chunk-CP34FI4B.js} +2 -2
  49. package/dist/{chunk-I7LKI4WO.js → chunk-DOD3A6EU.js} +6 -8
  50. package/dist/{chunk-N6FAXQDD.js → chunk-FAJ4R7IB.js} +1 -1
  51. package/dist/{chunk-U5RIXR7I.js → chunk-HB2KB7QL.js} +1 -1
  52. package/dist/{chunk-QV266EGS.js → chunk-HFQ7WSHZ.js} +11 -14
  53. package/dist/{chunk-QJPYJVL3.js → chunk-IRLTW6KF.js} +1 -1
  54. package/dist/{chunk-Y6SCMXR6.js → chunk-J44RPHUZ.js} +2 -2
  55. package/dist/{chunk-R6LBGKWQ.js → chunk-JQU6PMH4.js} +17 -15
  56. package/dist/{chunk-4DUEIUUO.js → chunk-K6CH5NVW.js} +1 -1
  57. package/dist/{chunk-XZWUOCJZ.js → chunk-LBNAGGLV.js} +1 -1
  58. package/dist/{chunk-XS33QBJA.js → chunk-LGXUGYUH.js} +50 -1
  59. package/dist/{chunk-XO3JR3OP.js → chunk-LX4NNYII.js} +2 -2
  60. package/dist/{chunk-XEVOPOKT.js → chunk-M4TVEMLH.js} +1 -1
  61. package/dist/{chunk-TY6IJY6I.js → chunk-NIXVMMJM.js} +1 -1
  62. package/dist/{chunk-VERXHIQF.js → chunk-NQGYPTJQ.js} +1 -1
  63. package/dist/{chunk-EMGGUO2Q.js → chunk-OBYC6SN4.js} +1 -1
  64. package/dist/{chunk-XUTSZYYJ.js → chunk-OPR2BPD4.js} +1 -4
  65. package/dist/{chunk-MC5WUOJJ.js → chunk-OUAE3GKO.js} +75 -1
  66. package/dist/{chunk-ATKGKAP6.js → chunk-QXIF6R4P.js} +16 -13
  67. package/dist/chunk-SI7GYBQP.js +15 -0
  68. package/dist/{chunk-VMQU7YEJ.js → chunk-SJLGOVC6.js} +1 -1
  69. package/dist/{chunk-54W7ELDQ.js → chunk-TZHDKRMG.js} +2 -2
  70. package/dist/{chunk-F4TMY6XI.js → chunk-V47GITZV.js} +3 -3
  71. package/dist/{chunk-L7NOG547.js → chunk-VZD2GEKB.js} +1 -1
  72. package/dist/{chunk-R3OZ5ISU.js → chunk-W2MG2P46.js} +4 -4
  73. package/dist/{chunk-E47WV7BJ.js → chunk-WU6LSXJ6.js} +2 -2
  74. package/dist/{chunk-NOVA7KZ2.js → chunk-XL3RKJYQ.js} +308 -35
  75. package/dist/{chunk-5GKNAQTG.js → chunk-XOOWC62T.js} +1 -1
  76. package/dist/{chunk-SKPXQRKN.js → chunk-XPPXOKIG.js} +1 -1
  77. package/dist/{chunk-HUGIKB5O.js → chunk-YJDRHQLG.js} +6 -6
  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
@@ -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
  };
@@ -15,10 +15,10 @@ import {
15
15
  } from "./chunk-EYRGBYZG.js";
16
16
  import {
17
17
  reportedInstallSource
18
- } from "./chunk-L7NOG547.js";
18
+ } from "./chunk-VZD2GEKB.js";
19
19
  import {
20
20
  isAnswerSource
21
- } from "./chunk-MC5WUOJJ.js";
21
+ } from "./chunk-OUAE3GKO.js";
22
22
 
23
23
  // src/reach.ts
24
24
  var reachVerdict = (channels) => {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  registerTranscript
3
- } from "./chunk-3YFJGKJQ.js";
3
+ } from "./chunk-5B5QV3KC.js";
4
4
 
5
5
  // src/admission.ts
6
6
  import { readFileSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readConfigFileKey
3
- } from "./chunk-VMQU7YEJ.js";
3
+ } from "./chunk-SJLGOVC6.js";
4
4
 
5
5
  // src/bell/fleet.ts
6
6
  import { closeSync, constants, futimesSync, lstatSync, mkdirSync, openSync, readdirSync, unlinkSync } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  readTranscriptPreference
3
- } from "./chunk-VMQU7YEJ.js";
3
+ } from "./chunk-SJLGOVC6.js";
4
4
 
5
5
  // src/transcript-setting.ts
6
6
  var transcriptEnvironmentPreference = () => {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  writeJsonAtomicDurable
3
- } from "./chunk-L7NOG547.js";
3
+ } from "./chunk-VZD2GEKB.js";
4
4
 
5
5
  // src/transcript-recipient-pin.ts
6
6
  import { existsSync, mkdirSync, readFileSync, rmSync } from "fs";
@@ -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-OUAE3GKO.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,
@@ -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 = "I approved this command in Pushary after the auto-reviewer denied it. If it is denied again, stop and tell me.";
1401
+ var codexRescueRetryInstruction = (command) => command.length <= CODEX_RESCUE_COMMAND_MAX_LENGTH ? `Run it again now, exactly as before: ${command}` : `Run it again now, exactly as before. 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,17 @@
1
1
  import {
2
2
  CLAUDE_HOOK_EVENTS,
3
3
  supportedClaudeEvents
4
- } from "./chunk-QV266EGS.js";
4
+ } from "./chunk-HFQ7WSHZ.js";
5
5
  import {
6
- guardBinary
7
- } from "./chunk-3AJIK5AO.js";
6
+ guardBinary,
7
+ holdBudgetOf,
8
+ isProtectedByScope,
9
+ registerHookEntry
10
+ } from "./chunk-XL3RKJYQ.js";
8
11
  import {
9
- isProtectedByScope
10
- } from "./chunk-NOVA7KZ2.js";
11
- import {
12
- HOOK_LOCATOR_BINARY
13
- } from "./chunk-MC5WUOJJ.js";
12
+ HOOK_LOCATOR_BINARY,
13
+ withHookHoldBudget
14
+ } from "./chunk-OUAE3GKO.js";
14
15
 
15
16
  // src/claude-config.ts
16
17
  import { join } from "path";
@@ -126,12 +127,14 @@ var addPusharyHooks = (settings, binDir, version = null) => {
126
127
  const hooks = ensureRecord(settings, "hooks");
127
128
  const supported = new Map(supportedClaudeEvents(version).map((hook) => [hook.event, hook]));
128
129
  for (const known of CLAUDE_HOOK_EVENTS) {
129
- const current = hooks[known.event];
130
- const others = (Array.isArray(current) ? current : []).filter((item) => !isPusharyHook(item));
130
+ const current = Array.isArray(hooks[known.event]) ? hooks[known.event] : [];
131
131
  const definition = supported.get(known.event);
132
132
  if (definition) {
133
- hooks[known.event] = [...others, entry(definition, resolve)];
134
- } else if (others.length > 0) {
133
+ hooks[known.event] = registerHookEntry(current, isPusharyHook, entry(definition, resolve));
134
+ continue;
135
+ }
136
+ const others = current.filter((item) => !isPusharyHook(item));
137
+ if (others.length > 0) {
135
138
  hooks[known.event] = others;
136
139
  } else {
137
140
  delete hooks[known.event];
@@ -142,7 +145,7 @@ var entry = (definition, resolve) => ({
142
145
  ...definition.matcher ? { matcher: definition.matcher } : {},
143
146
  hooks: [{
144
147
  type: "command",
145
- command: resolve(definition.binary),
148
+ command: withHookHoldBudget(resolve(definition.binary), holdBudgetOf(definition)),
146
149
  timeout: definition.timeout
147
150
  }]
148
151
  });
@@ -0,0 +1,15 @@
1
+ import {
2
+ HOOK_HOLD_BUDGET_ENV,
3
+ hookHoldBudgetProof
4
+ } from "./chunk-OUAE3GKO.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
+ };
@@ -4,7 +4,7 @@ import {
4
4
  resolveInstallSourceFor,
5
5
  writeFileAtomic,
6
6
  writeJsonAtomic
7
- } from "./chunk-L7NOG547.js";
7
+ } from "./chunk-VZD2GEKB.js";
8
8
  import {
9
9
  configFilePath
10
10
  } from "./chunk-2UMNXADU.js";
@@ -2,10 +2,10 @@ import {
2
2
  resolveBinary,
3
3
  resolveGlobalPackageVersion,
4
4
  resolvePackageVersionFromBinary
5
- } from "./chunk-L4ZCFMWW.js";
5
+ } from "./chunk-XL3RKJYQ.js";
6
6
  import {
7
7
  MANAGED_PROVIDERS
8
- } from "./chunk-MC5WUOJJ.js";
8
+ } from "./chunk-OUAE3GKO.js";
9
9
 
10
10
  // src/live-capabilities.ts
11
11
  var CLAUDE_BRIDGE_CAPABILITY = "claude-bridge";
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  openCodeConfigDirFrom
3
- } from "./chunk-U5RIXR7I.js";
3
+ } from "./chunk-HB2KB7QL.js";
4
4
  import {
5
5
  codexHomeFrom
6
- } from "./chunk-NOVA7KZ2.js";
6
+ } from "./chunk-XL3RKJYQ.js";
7
7
 
8
8
  // src/vscode-config.ts
9
9
  import { homedir } from "os";
@@ -264,7 +264,7 @@ You have Pushary MCP tools that reach the user on their phone. Use them proactiv
264
264
 
265
265
  - When you need the user to make a decision or answer a clarifying question, call \`ask_user\` instead of guessing or stalling. Use type "confirm" for yes/no, "select" for a fixed set of options, and "input" for free text. Read \`answered\`, \`status\` and the returned handoff instead of assuming the tool blocked. Silence is not consent; choice/text answers do not authorize a separate action.
266
266
  - Ask once, on one channel. If you also have a built-in way to prompt the user, use that or \`ask_user\` for a given question, never both. \`ask_user\` is the one that reaches the user when they are away from this machine, so prefer it. Raising both strands the user in whichever prompt you are not waiting on.
267
- - Honor authorization already granted in this session. Ask before a risky or irreversible action outside that authorization, or when an enforced host policy requires it. Do not ask again for an already authorized step, and never bypass an enforced gate.
267
+ - Honor authorization already granted in this session. Ask before a risky or irreversible action outside that authorization, or when an enforced host policy requires it. Do not ask again for an already authorized step, and never bypass an enforced gate. When a Pushary hook result says the user approved a specific command after a denial, rerunning that exact command is the approved path and not a bypass of that gate.
268
268
  - Call \`propose_scope\` only for an unresolved or requested file boundary. A ratified scope narrows what may proceed; it does not bypass other approval rules. Do not add a plan approval merely because work spans several files, and do not propose a new scope mid-run just to widen it. Let the installed gate request any scope expansion before the edit executes.
269
269
  - When you are blocked, stuck, or hit an error you cannot resolve on your own, call \`send_notification\` so the user knows, and call \`ask_user\` if you need a decision to continue.
270
270
  - When meaningful work finishes while the user is away, or they requested an alert, call \`send_notification\` so the user knows it is done. Skip trivial completions and duplicate reports to someone already reading. Always pass \`context\` with a \`type\` of "task_complete", "error" or "info": it is what tells Pushary this is a task update, so the user's own setting for where updates land can route it.
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2UMNXADU.js";
4
4
  import {
5
5
  normalizeInstallSource
6
- } from "./chunk-MC5WUOJJ.js";
6
+ } from "./chunk-OUAE3GKO.js";
7
7
 
8
8
  // src/cli/fsx.ts
9
9
  import { copyFileSync, existsSync, fsyncSync, mkdirSync, openSync, closeSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "fs";
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  AGENT_CHOICES
3
- } from "./chunk-XZWUOCJZ.js";
3
+ } from "./chunk-LBNAGGLV.js";
4
4
  import {
5
5
  isBridgeLocator
6
- } from "./chunk-QJPYJVL3.js";
6
+ } from "./chunk-IRLTW6KF.js";
7
7
  import {
8
8
  pusharyHookExecutable
9
- } from "./chunk-QD73WATQ.js";
9
+ } from "./chunk-6LRGZJFB.js";
10
10
  import {
11
11
  BELL_HOOK_BINARY
12
- } from "./chunk-GOPIMNVC.js";
12
+ } from "./chunk-2GLOHBYB.js";
13
13
 
14
14
  // src/setup/hook-ownership.ts
15
15
  var commandsIn = (value) => {
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  pusharyDir
3
- } from "./chunk-NOVA7KZ2.js";
3
+ } from "./chunk-XL3RKJYQ.js";
4
4
  import {
5
5
  writeFileAtomic
6
- } from "./chunk-L7NOG547.js";
6
+ } from "./chunk-VZD2GEKB.js";
7
7
 
8
8
  // src/identity.ts
9
9
  import { randomUUID } from "crypto";