@pushary/agent-hooks 1.9.65 → 1.9.67

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 (129) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/data/SKILL.md +1 -1
  3. package/data/cursor-plugin/skills/pushary/SKILL.md +1 -1
  4. package/data/vscode-plugin/skills/pushary/SKILL.md +1 -1
  5. package/dist/{api-32B3XLJR.js → api-3WIPD5GC.js} +12 -10
  6. package/dist/bin/pushary-bell-hook.js +25 -27
  7. package/dist/bin/pushary-bell.js +10 -10
  8. package/dist/bin/pushary-claude.js +26 -28
  9. package/dist/bin/pushary-clean.js +74 -77
  10. package/dist/bin/pushary-codex-bridge.js +21 -22
  11. package/dist/bin/pushary-codex-hook.js +30 -32
  12. package/dist/bin/pushary-codex-session.js +8 -8
  13. package/dist/bin/pushary-codex.js +13 -13
  14. package/dist/bin/pushary-connect.js +28 -28
  15. package/dist/bin/pushary-cowork.js +5 -5
  16. package/dist/bin/pushary-daemon.js +40 -41
  17. package/dist/bin/pushary-disconnect.js +25 -26
  18. package/dist/bin/pushary-doctor.js +118 -76
  19. package/dist/bin/pushary-elicitation-hook.js +25 -27
  20. package/dist/bin/pushary-gemini-bridge.js +15 -16
  21. package/dist/bin/pushary-gemini-hook.js +27 -29
  22. package/dist/bin/pushary-hook.js +29 -31
  23. package/dist/bin/pushary-login.js +20 -19
  24. package/dist/bin/pushary-logout.js +26 -28
  25. package/dist/bin/pushary-mcp.js +11 -11
  26. package/dist/bin/pushary-mode.js +16 -15
  27. package/dist/bin/pushary-notification-hook.js +24 -26
  28. package/dist/bin/pushary-opencode-hook.js +26 -28
  29. package/dist/bin/pushary-permission-denied-hook.js +29 -31
  30. package/dist/bin/pushary-permission-hook.js +32 -31
  31. package/dist/bin/pushary-post-hook.js +24 -26
  32. package/dist/bin/pushary-prompt-hook.js +24 -26
  33. package/dist/bin/pushary-session-end-hook.js +26 -28
  34. package/dist/bin/pushary-session-start-hook.js +34 -35
  35. package/dist/bin/pushary-setup.js +111 -99
  36. package/dist/bin/pushary-stats.js +5 -5
  37. package/dist/bin/pushary-status.js +37 -36
  38. package/dist/bin/pushary-stop-hook.js +24 -26
  39. package/dist/bin/pushary-stopfailure-hook.js +24 -26
  40. package/dist/bin/pushary-suggestions.js +15 -14
  41. package/dist/bin/pushary-transcript-register.js +16 -18
  42. package/dist/bin/pushary-transcripts.js +18 -17
  43. package/dist/bin/pushary-upgrade.js +45 -39
  44. package/dist/bin/pushary-wait.js +26 -20
  45. package/dist/bin/pushary.js +7 -7
  46. package/dist/{chunk-Y64JGYTB.js → chunk-32OSSYGZ.js} +1 -1
  47. package/dist/{chunk-UHS5MMAI.js → chunk-3THDNNVL.js} +3 -3
  48. package/dist/{chunk-OA6EADFT.js → chunk-564IPPBL.js} +3 -3
  49. package/dist/chunk-5MOJCOCF.js +25 -0
  50. package/dist/{chunk-RRHHWYPX.js → chunk-6H4E5UBA.js} +6 -6
  51. package/dist/{chunk-UM2X7HL6.js → chunk-6LXXKMV6.js} +42 -16
  52. package/dist/chunk-76SWHCAR.js +299 -0
  53. package/dist/{chunk-AN7C3CHH.js → chunk-77L2TNJW.js} +1 -1
  54. package/dist/{chunk-XJIIU36L.js → chunk-7YJSDXLM.js} +1 -1
  55. package/dist/{chunk-XZCJC3QO.js → chunk-B3V2WOJX.js} +285 -50
  56. package/dist/{chunk-RABLE5R6.js → chunk-BBFGDYYI.js} +1 -1
  57. package/dist/{chunk-GTBWWCCM.js → chunk-BFF3ABFV.js} +115 -9
  58. package/dist/{chunk-NOH3EJFH.js → chunk-C4EDAHXI.js} +1 -1
  59. package/dist/{chunk-ITYKN2K3.js → chunk-CHYBOTQN.js} +2 -2
  60. package/dist/{chunk-FFSIYBBU.js → chunk-CJZLIAJF.js} +27 -10
  61. package/dist/{chunk-UMTMFDL2.js → chunk-CPLQ5OHX.js} +1 -1
  62. package/dist/{chunk-DTVHZDYM.js → chunk-D3U6CN7T.js} +26 -12
  63. package/dist/{chunk-ICFHMJTW.js → chunk-D7Z4USSB.js} +27 -8
  64. package/dist/{chunk-DXMUJTYF.js → chunk-DP6LU6BB.js} +2 -2
  65. package/dist/{chunk-GA257T2E.js → chunk-DXCHIKAN.js} +8 -8
  66. package/dist/{chunk-PP7FC3Y7.js → chunk-EQR6JSFE.js} +9 -7
  67. package/dist/{chunk-Z3M627TQ.js → chunk-FONWVVDD.js} +1 -1
  68. package/dist/{chunk-3KXBRSL3.js → chunk-GMHQYX3D.js} +1 -1
  69. package/dist/{chunk-WY6YAMZW.js → chunk-ICQCQAUO.js} +5 -4
  70. package/dist/{chunk-CZ65D6Q5.js → chunk-IPG3RS23.js} +2 -2
  71. package/dist/{chunk-JCWHOB36.js → chunk-IRV2MNMA.js} +53 -3
  72. package/dist/{chunk-HIYLAC3C.js → chunk-IVB5O6NY.js} +3 -3
  73. package/dist/{chunk-DM24VZOI.js → chunk-IXTVUINH.js} +3 -3
  74. package/dist/{chunk-BEQ3MEE3.js → chunk-JBRMIADM.js} +2 -2
  75. package/dist/{chunk-CU2PXLZ6.js → chunk-JHB5SR5B.js} +4 -4
  76. package/dist/{chunk-ED2SWTPC.js → chunk-JOBSKQ2Z.js} +4 -4
  77. package/dist/{chunk-W5L2ZLHE.js → chunk-JOD2325S.js} +3 -3
  78. package/dist/{chunk-CSKABVSH.js → chunk-JSPCGPSH.js} +3 -3
  79. package/dist/{chunk-R4FL5JHK.js → chunk-KE4TSJUD.js} +4 -4
  80. package/dist/{chunk-3LF7T3NW.js → chunk-KIXRHNCW.js} +6 -6
  81. package/dist/{chunk-BKCI53W6.js → chunk-KNUYUQBP.js} +4 -4
  82. package/dist/{chunk-4LUWNBGD.js → chunk-KTLE7NIK.js} +23 -8
  83. package/dist/{chunk-ADXHPGBZ.js → chunk-LJHDUKBB.js} +6 -6
  84. package/dist/{chunk-U2P2LQ5B.js → chunk-LS42NV6E.js} +15 -6
  85. package/dist/{chunk-QNZ6G3LY.js → chunk-LYTWK5TF.js} +5 -5
  86. package/dist/{chunk-2VADJ6JH.js → chunk-METUT7V4.js} +2 -2
  87. package/dist/{chunk-P5HZEGT7.js → chunk-NSS2AIGI.js} +6 -6
  88. package/dist/{chunk-QSW3HAKI.js → chunk-NWN6GGJL.js} +61 -2
  89. package/dist/{chunk-7ILQX47W.js → chunk-P4RXSZDT.js} +3 -3
  90. package/dist/{chunk-FWS2KLV2.js → chunk-PIBWX3EF.js} +2 -2
  91. package/dist/{chunk-F56GTJFG.js → chunk-PO2P3XDC.js} +5 -5
  92. package/dist/{chunk-EEVE7H5Z.js → chunk-R2BZH2NK.js} +4 -4
  93. package/dist/{chunk-I2UBF2SS.js → chunk-RO2B4YBF.js} +1 -1
  94. package/dist/{chunk-MNKIR2ZB.js → chunk-RQOT6QL3.js} +67 -6
  95. package/dist/{chunk-Q2BD4IBX.js → chunk-SB6TYV4M.js} +11 -13
  96. package/dist/{chunk-ATPJGHUP.js → chunk-TI4JYPGQ.js} +7 -3
  97. package/dist/{chunk-LMC5UPL7.js → chunk-UC2J5RR4.js} +1 -1
  98. package/dist/{chunk-IZVEBBDH.js → chunk-UQIPWXBL.js} +3 -3
  99. package/dist/{chunk-VHKK3US3.js → chunk-UXN73YAE.js} +3 -3
  100. package/dist/{chunk-WOHP7EL6.js → chunk-VEYFXRZ6.js} +7 -6
  101. package/dist/{chunk-2Z7I7DHT.js → chunk-VHMZPN3N.js} +1 -1
  102. package/dist/{chunk-HVOQUPSO.js → chunk-VKZ3RJ5U.js} +1 -1
  103. package/dist/chunk-VVIQWGS7.js +2298 -0
  104. package/dist/{chunk-SCMEVDAR.js → chunk-WJLHZ6E6.js} +3 -3
  105. package/dist/{chunk-BAGBCSXA.js → chunk-X2MTVD4W.js} +1 -1
  106. package/dist/{chunk-3UNAPXED.js → chunk-XFDWLDHG.js} +9 -9
  107. package/dist/{chunk-6CZX6YOE.js → chunk-XNUWKKGS.js} +3 -3
  108. package/dist/{chunk-NUEPJ5W6.js → chunk-XZKSUJ55.js} +3 -5
  109. package/dist/{chunk-HDGB6SQ4.js → chunk-Y4MRHAE5.js} +2 -2
  110. package/dist/{chunk-SZBMTOUZ.js → chunk-Y6ZN454E.js} +1 -1
  111. package/dist/{chunk-TK4JU2F7.js → chunk-YSD4SMHE.js} +1 -1
  112. package/dist/{chunk-SROWUG5W.js → chunk-ZK5WYAMI.js} +1 -1
  113. package/dist/{chunk-JFKMPLNF.js → chunk-ZPIYJLQ3.js} +2 -2
  114. package/dist/{codex-app-cards-switch-HK4RMOBI.js → codex-app-cards-switch-25TDTMUZ.js} +11 -11
  115. package/dist/{codex-desktop-launch-OCO6N7SA.js → codex-desktop-launch-JDKUCD2Y.js} +1 -1
  116. package/dist/{codex-session-launch-ETZQTVFS.js → codex-session-launch-6N35AAEO.js} +18 -19
  117. package/dist/{config-YY54G3CC.js → config-OBGYURFX.js} +3 -3
  118. package/dist/{global-upgrade-5DN5G3ZQ.js → global-upgrade-2QNQD5OM.js} +10 -10
  119. package/dist/{identity-EGL6HAWG.js → identity-CUPX2U7Q.js} +4 -4
  120. package/dist/{live-capabilities-WU3FYJJG.js → live-capabilities-CUEIWMSM.js} +5 -5
  121. package/dist/{paths-ZG6KDM6Y.js → paths-BHLBZ6FY.js} +7 -3
  122. package/dist/{reapply-AQLNCCTE.js → reapply-4GXLWZ5M.js} +27 -27
  123. package/dist/src/index.js +30 -20
  124. package/dist/{upgrade-lock-BIRXIKHJ.js → upgrade-lock-RDKTAHWR.js} +4 -4
  125. package/dist/{usage-43C2DHDU.js → usage-KX2HN47E.js} +3 -3
  126. package/package.json +1 -1
  127. package/dist/chunk-ANP3DIST.js +0 -118
  128. package/dist/chunk-ONBL2GCU.js +0 -615
  129. package/dist/chunk-TJUJAK5L.js +0 -62
@@ -4,19 +4,24 @@ import {
4
4
  outcomeOfFailedAsk,
5
5
  reportMachineDenial,
6
6
  storedWaitDeadline
7
- } from "./chunk-RRHHWYPX.js";
7
+ } from "./chunk-6H4E5UBA.js";
8
8
  import {
9
9
  isGatingMoment,
10
10
  recordKeylessMoment
11
- } from "./chunk-XJIIU36L.js";
11
+ } from "./chunk-7YJSDXLM.js";
12
+ import {
13
+ isAdmitted,
14
+ readAdmissionRules
15
+ } from "./chunk-EQR6JSFE.js";
12
16
  import {
13
17
  isDeferAnswer,
14
18
  oncePerSession
15
- } from "./chunk-UMTMFDL2.js";
19
+ } from "./chunk-CPLQ5OHX.js";
16
20
  import {
17
21
  activeHookSource,
18
- activeIdentity
19
- } from "./chunk-WY6YAMZW.js";
22
+ activeIdentity,
23
+ namedSource
24
+ } from "./chunk-ICQCQAUO.js";
20
25
  import {
21
26
  DEFAULT_SESSION,
22
27
  UNNAMED_ACTION,
@@ -40,10 +45,10 @@ import {
40
45
  reportEvent,
41
46
  scopePathFor,
42
47
  settleQuestionsForToolUse
43
- } from "./chunk-3UNAPXED.js";
48
+ } from "./chunk-XFDWLDHG.js";
44
49
  import {
45
50
  repoKeyFor
46
- } from "./chunk-3KXBRSL3.js";
51
+ } from "./chunk-GMHQYX3D.js";
47
52
  import {
48
53
  askUser,
49
54
  cancelQuestion,
@@ -58,21 +63,22 @@ import {
58
63
  requestGate,
59
64
  sendNotification,
60
65
  waitForQuestion,
61
- waitsForThePhone
62
- } from "./chunk-DTVHZDYM.js";
66
+ waitsForThePhone,
67
+ withdrawQuestion
68
+ } from "./chunk-D3U6CN7T.js";
63
69
  import {
64
70
  getMachineId
65
- } from "./chunk-FWS2KLV2.js";
71
+ } from "./chunk-PIBWX3EF.js";
66
72
  import {
67
73
  claudeConfigDir
68
- } from "./chunk-ATPJGHUP.js";
74
+ } from "./chunk-TI4JYPGQ.js";
69
75
  import {
70
76
  getApiKey,
71
77
  getBaseUrl
72
- } from "./chunk-Y64JGYTB.js";
78
+ } from "./chunk-32OSSYGZ.js";
73
79
  import {
74
80
  NPX_LATEST_COMMAND
75
- } from "./chunk-BAGBCSXA.js";
81
+ } from "./chunk-X2MTVD4W.js";
76
82
  import {
77
83
  AGENT_QUESTION_STRATEGY,
78
84
  AGENT_QUESTION_TOOL,
@@ -81,11 +87,13 @@ import {
81
87
  GATE_WIRE,
82
88
  HANDED_BACK_REASON,
83
89
  PLAN_DECLINED_REASON,
90
+ PRETOOLUSE_HANDLED_TOOLS,
84
91
  QUESTION_LIMIT_PROMPT_REASON,
85
92
  SCOPE_BATCH_REASON,
86
93
  SOLE_GATE_SIGNED_OUT_REASON,
87
94
  SOLE_GATE_SUBSCRIPTION_REASON,
88
95
  SOLE_GATE_UNDECIDED_REASON,
96
+ TERMINAL_AND_PHONE_REASON,
89
97
  UNREADABLE_CALL_REASON,
90
98
  UNVERIFIABLE_APPROVAL_REASON,
91
99
  WITHDRAWN_APPROVAL_REASON,
@@ -99,6 +107,7 @@ import {
99
107
  claudePermissionRequestOutput,
100
108
  claudePermissionRulesFromSettings,
101
109
  claudePreToolUseOutput,
110
+ claudePromptTakesTheCall,
102
111
  claudeStoppedPermissionRequestOutput,
103
112
  claudeStoppedPreToolUseOutput,
104
113
  denyReasonFrom,
@@ -114,17 +123,113 @@ import {
114
123
  isCursorHookPayload,
115
124
  isPermissionRequestOwnedByPreToolUse,
116
125
  isPusharyOwnTool,
126
+ isRecord,
117
127
  parseAgentQuestions,
118
128
  questionHandedToTheTerminal,
119
129
  questionLimitHandBack,
120
130
  refusesWhenUnanswered,
121
131
  resolveGate,
132
+ terminalLiveWait,
133
+ terminalStaysLiveForTheCall,
122
134
  timeoutActionWithoutLocalPrompt,
123
135
  unansweredRefusalReason
124
- } from "./chunk-MNKIR2ZB.js";
136
+ } from "./chunk-RQOT6QL3.js";
137
+
138
+ // src/terminal-answer-watch.ts
139
+ import { closeSync, openSync, readSync, statSync } from "fs";
140
+ import { StringDecoder } from "string_decoder";
141
+ var INITIAL_TAIL_BYTES = 1048576;
142
+ var DEFAULT_POLL_MS = 500;
143
+ var sortedKeys = (value) => {
144
+ if (Array.isArray(value)) return value.map(sortedKeys);
145
+ if (!isRecord(value)) return value;
146
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortedKeys(value[key])]));
147
+ };
148
+ var stableJson = (value) => JSON.stringify(sortedKeys(value) ?? null);
149
+ var parsedLine = (line) => {
150
+ try {
151
+ return JSON.parse(line);
152
+ } catch {
153
+ return void 0;
154
+ }
155
+ };
156
+ var messageParts = (line) => {
157
+ const row = parsedLine(line);
158
+ const content = isRecord(row) && isRecord(row.message) ? row.message.content : void 0;
159
+ return Array.isArray(content) ? content.filter(isRecord) : [];
160
+ };
161
+ var isToolUse = (part) => part.type === "tool_use" && typeof part.id === "string" && typeof part.name === "string";
162
+ var isToolResultFor = (toolUseId) => (part) => part.type === "tool_result" && part.tool_use_id === toolUseId;
163
+ var matchingToolUseIds = (lines, call) => {
164
+ const wanted = stableJson(call.toolInput);
165
+ return lines.flatMap(messageParts).filter(isToolUse).filter((part) => part.name === call.toolName && stableJson(part.input ?? {}) === wanted).map((part) => part.id);
166
+ };
167
+ var toolUseIdOf = (lines, call) => matchingToolUseIds(lines, call).at(-1);
168
+ var toolResultArrived = (lines, toolUseId) => lines.some((line) => messageParts(line).some(isToolResultFor(toolUseId)));
169
+ var writtenAtMs = (line) => {
170
+ const row = parsedLine(line);
171
+ return isRecord(row) && typeof row.timestamp === "string" ? Date.parse(row.timestamp) : Number.NaN;
172
+ };
173
+ var answeredBefore = (lines, toolUseId, startedAtMs) => lines.some((line) => messageParts(line).some(isToolResultFor(toolUseId)) && !(writtenAtMs(line) >= startedAtMs));
174
+ var openToolUseIdOf = (lines, call, startedAtMs) => matchingToolUseIds(lines, call).filter((id) => !answeredBefore(lines, id, startedAtMs)).at(-1);
175
+ var readTranscriptFrom = (path, offset) => {
176
+ try {
177
+ const size = statSync(path).size;
178
+ if (size <= offset) return { bytes: Buffer.alloc(0), endOffset: size };
179
+ const descriptor = openSync(path, "r");
180
+ try {
181
+ const buffer = Buffer.alloc(size - offset);
182
+ readSync(descriptor, buffer, 0, buffer.length, offset);
183
+ return { bytes: buffer, endOffset: size };
184
+ } finally {
185
+ closeSync(descriptor);
186
+ }
187
+ } catch {
188
+ return null;
189
+ }
190
+ };
191
+ var startingOffset = (path) => {
192
+ try {
193
+ return Math.max(statSync(path).size - INITIAL_TAIL_BYTES, 0);
194
+ } catch {
195
+ return 0;
196
+ }
197
+ };
198
+ var watchForTerminalAnswer = (transcriptPath, call, startedAtMs, signal, pollMs = DEFAULT_POLL_MS) => new Promise((resolve) => {
199
+ if (!transcriptPath || signal.aborted) return;
200
+ let offset = startingOffset(transcriptPath);
201
+ const decoder = new StringDecoder("utf8");
202
+ let carry = "";
203
+ let readingHistory = true;
204
+ let toolUseId;
205
+ let timer;
206
+ const stop = () => {
207
+ if (timer) clearTimeout(timer);
208
+ };
209
+ signal.addEventListener("abort", stop, { once: true });
210
+ const tick = () => {
211
+ if (signal.aborted) return;
212
+ const chunk = readTranscriptFrom(transcriptPath, offset);
213
+ if (chunk) {
214
+ offset = chunk.endOffset;
215
+ const pieces = `${carry}${decoder.write(chunk.bytes)}`.split("\n");
216
+ carry = pieces.pop() ?? "";
217
+ toolUseId = toolUseId ?? (readingHistory ? openToolUseIdOf(pieces, call, startedAtMs) : toolUseIdOf(pieces, call));
218
+ readingHistory = false;
219
+ if (toolUseId && toolResultArrived(pieces, toolUseId)) {
220
+ signal.removeEventListener("abort", stop);
221
+ resolve();
222
+ return;
223
+ }
224
+ }
225
+ timer = setTimeout(tick, pollMs);
226
+ timer.unref?.();
227
+ };
228
+ tick();
229
+ });
125
230
 
126
231
  // src/claude-permission-rules.ts
127
- import { readFileSync, statSync } from "fs";
232
+ import { readFileSync, statSync as statSync2 } from "fs";
128
233
  import { homedir } from "os";
129
234
  import { join } from "path";
130
235
  var MANAGED_CLAUDE_SETTINGS_BY_PLATFORM = {
@@ -146,15 +251,15 @@ var claudeSettingsSources = (location) => [
146
251
  { path: join(location.projectDir, ".claude", "settings.local.json"), trust: "grants" }
147
252
  ] : []
148
253
  ];
149
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
254
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
150
255
  var restrictionsOnly = (document) => {
151
- if (!isRecord(document) || !isRecord(document.permissions)) return null;
256
+ if (!isRecord2(document) || !isRecord2(document.permissions)) return null;
152
257
  const { ask: ask2, deny: deny2 } = document.permissions;
153
258
  return { permissions: { ask: ask2, deny: deny2 } };
154
259
  };
155
260
  var readSettingsDocument = (path) => {
156
261
  try {
157
- const stats = statSync(path);
262
+ const stats = statSync2(path);
158
263
  if (!stats.isFile() || stats.size > MAX_CLAUDE_SETTINGS_BYTES) return null;
159
264
  return JSON.parse(readFileSync(path, "utf8"));
160
265
  } catch {
@@ -191,34 +296,45 @@ var reportDecisionEpisode = async (apiKey, input) => {
191
296
 
192
297
  // src/hook.ts
193
298
  import { basename } from "path";
194
- var START_MS = Date.now();
195
- var ACTIVE_AGENT = activeIdentity(process.argv);
196
- var ACTIVE_SOURCE = activeHookSource(process.argv);
197
- var PUSHARY_IS_SOLE_GATE = ACTIVE_SOURCE !== null && hookCompatibleProfileBySource(ACTIVE_SOURCE)?.soleGate != null;
198
- var agentNameIn = (projectName) => `${ACTIVE_AGENT.label} - ${projectName}`;
299
+
300
+ // src/pre-tool-use-output.ts
199
301
  var allow = (reason) => claudePreToolUseOutput("allow", reason);
200
302
  var deny = (reason) => claudePreToolUseOutput("deny", reason);
201
- var allowWithInput = (updatedInput) => claudePreToolUseOutput("allow", void 0, updatedInput);
202
303
  var ask = (reason) => claudePreToolUseOutput("ask", reason);
203
304
  var halted = (reason) => claudeStoppedPreToolUseOutput(reason);
204
- var undecidedCall = (reason) => PUSHARY_IS_SOLE_GATE ? deny(reason) : void 0;
305
+
306
+ // src/permission-ask-outcome.ts
205
307
  var questionLimitOutput = (handBack) => handBack.kind === "refuse" ? deny(handBack.reason) : ask(QUESTION_LIMIT_PROMPT_REASON);
206
- var outcomeOfFailedPermissionAsk = (error, sessionId, questionLimitFacts) => {
308
+ var outcomeOfFailedPermissionAsk = (error, sessionId, questionLimitFacts, subscriptionRequiredOutput) => {
207
309
  if (isQuestionLimitRefusal(error)) return questionLimitOutput(questionLimitHandBack(questionLimitFacts));
208
- return outcomeOfFailedAsk(error, sessionId, halted(UNVERIFIABLE_APPROVAL_REASON)) ?? undecidedCall(SOLE_GATE_SUBSCRIPTION_REASON);
310
+ return outcomeOfFailedAsk(error, sessionId, halted(UNVERIFIABLE_APPROVAL_REASON)) ?? subscriptionRequiredOutput;
209
311
  };
210
- var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, timeoutAction, sessionId, machineId, toolName, toolTarget, decision, signals, agentPromptFollows) => {
312
+ var handlePushOnly = async (apiKey, call, timeoutSeconds, timeoutAction, signals) => {
313
+ const {
314
+ description,
315
+ projectName,
316
+ agentName,
317
+ sessionId,
318
+ machineId,
319
+ toolName,
320
+ toolTarget,
321
+ decision,
322
+ agentPromptFollows,
323
+ startMs,
324
+ subscriptionRequiredOutput,
325
+ announceWait: announceWait2
326
+ } = call;
211
327
  const effectiveWait = effectiveWaitSeconds(timeoutAction, timeoutSeconds, "claude");
212
328
  const now = Date.now();
213
- const requestedDeadline = hookWaitDeadline(START_MS, effectiveWait, "claude", now);
214
- const clampCut = hookWaitClamped(START_MS, effectiveWait, "claude", now);
329
+ const requestedDeadline = hookWaitDeadline(startMs, effectiveWait, "claude", now);
330
+ const clampCut = hookWaitClamped(startMs, effectiveWait, "claude", now);
215
331
  let result;
216
332
  try {
217
333
  result = await askUser(apiKey, {
218
334
  question: `Allow ${description || UNNAMED_ACTION}?`,
219
335
  type: "confirm",
220
336
  context: `Agent wants to run this in ${projectName}`,
221
- agentName: agentNameIn(projectName),
337
+ agentName,
222
338
  sessionId,
223
339
  machineId,
224
340
  toolName,
@@ -230,11 +346,11 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
230
346
  return outcomeOfFailedPermissionAsk(error, sessionId, {
231
347
  refusesUnanswered: refusesWhenUnanswered({ mode: "push_only", timeoutAction }),
232
348
  agentPromptFollows
233
- });
349
+ }, subscriptionRequiredOutput);
234
350
  }
235
351
  if (isDecisionStopped(result)) return halted(DECISION_STOP_REASON);
236
352
  const deadline = storedWaitDeadline(result, requestedDeadline);
237
- if (waitsForThePhone(result)) announceWait(effectiveWait, result.answerUrl, sessionId);
353
+ if (waitsForThePhone(result)) announceWait2(effectiveWait, result.answerUrl, sessionId);
238
354
  const answer = await waitForQuestion(apiKey, result, deadline);
239
355
  if (isDecisionStopped(answer)) return halted(DECISION_STOP_REASON);
240
356
  if (!answer.answered && isLocalQuestionHandoff(result)) return ask("Approve in the current client.");
@@ -255,18 +371,29 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
255
371
  return ask("No push response, asking in terminal");
256
372
  }
257
373
  };
258
- var handleTerminalOnly = () => {
259
- return ask();
260
- };
261
- var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds, sessionId, machineId, toolName, toolTarget, decision, signals, agentPromptFollows) => {
262
- const requestedDeadline = hookWaitDeadline(START_MS, pushFirstSeconds, "claude", Date.now());
374
+ var handlePushFirst = async (apiKey, call, pushFirstSeconds, signals) => {
375
+ const {
376
+ description,
377
+ projectName,
378
+ agentName,
379
+ sessionId,
380
+ machineId,
381
+ toolName,
382
+ toolTarget,
383
+ decision,
384
+ agentPromptFollows,
385
+ startMs,
386
+ subscriptionRequiredOutput,
387
+ announceWait: announceWait2
388
+ } = call;
389
+ const requestedDeadline = hookWaitDeadline(startMs, pushFirstSeconds, "claude", Date.now());
263
390
  let result;
264
391
  try {
265
392
  result = await askUser(apiKey, {
266
393
  question: `Allow ${description || UNNAMED_ACTION}?`,
267
394
  type: "confirm",
268
395
  context: `Agent wants to run this in ${projectName}`,
269
- agentName: agentNameIn(projectName),
396
+ agentName,
270
397
  sessionId,
271
398
  machineId,
272
399
  toolName,
@@ -275,10 +402,10 @@ var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds,
275
402
  ...decision
276
403
  });
277
404
  } catch (error) {
278
- return outcomeOfFailedPermissionAsk(error, sessionId, { refusesUnanswered: false, agentPromptFollows });
405
+ return outcomeOfFailedPermissionAsk(error, sessionId, { refusesUnanswered: false, agentPromptFollows }, subscriptionRequiredOutput);
279
406
  }
280
407
  if (isDecisionStopped(result)) return halted(DECISION_STOP_REASON);
281
- if (waitsForThePhone(result)) announceWait(pushFirstSeconds, result.answerUrl, sessionId);
408
+ if (waitsForThePhone(result)) announceWait2(pushFirstSeconds, result.answerUrl, sessionId);
282
409
  const deadline = storedWaitDeadline(result, requestedDeadline);
283
410
  const answer = await waitForQuestion(apiKey, result, deadline, 1500);
284
411
  if (isDecisionStopped(answer)) return halted(DECISION_STOP_REASON);
@@ -289,6 +416,20 @@ var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds,
289
416
  }
290
417
  return ask(WITHDRAWN_APPROVAL_REASON);
291
418
  };
419
+
420
+ // src/hook.ts
421
+ var START_MS = Date.now();
422
+ var ACTIVE_AGENT = activeIdentity(process.argv);
423
+ var ACTIVE_SOURCE = activeHookSource(process.argv);
424
+ var HANDED_DOWN_BY_THE_MAC_BRIDGE = namedSource(process.argv) !== void 0;
425
+ var CLAUDE_SESSION_ATTENDED_ENV = "CLAUDE_CODE_SESSION_ATTENDED";
426
+ var PUSHARY_IS_SOLE_GATE = ACTIVE_SOURCE !== null && hookCompatibleProfileBySource(ACTIVE_SOURCE)?.soleGate != null;
427
+ var agentNameIn = (projectName) => `${ACTIVE_AGENT.label} - ${projectName}`;
428
+ var allowWithInput = (updatedInput) => claudePreToolUseOutput("allow", void 0, updatedInput);
429
+ var undecidedCall = (reason) => PUSHARY_IS_SOLE_GATE ? deny(reason) : void 0;
430
+ var handleTerminalOnly = () => {
431
+ return ask();
432
+ };
292
433
  var handleNotifyOnly = async (apiKey, description, projectName, sessionId, machineId) => {
293
434
  try {
294
435
  await sendNotification(apiKey, {
@@ -302,6 +443,64 @@ var handleNotifyOnly = async (apiKey, description, projectName, sessionId, machi
302
443
  }
303
444
  return ask();
304
445
  };
446
+ var TERMINAL_ANSWERED = /* @__PURE__ */ Symbol("terminal-answered");
447
+ var terminalAnswered = (input, signal) => watchForTerminalAnswer(input.transcript_path, { toolName: input.tool_name, toolInput: input.tool_input ?? {} }, START_MS, signal).then(() => TERMINAL_ANSWERED);
448
+ var terminalLiveTimeoutOutput = (onTimeout, question) => {
449
+ switch (onTimeout) {
450
+ case "allow":
451
+ return allow();
452
+ case "deny":
453
+ return deny(unansweredRefusalReason(isAwayPaused(question)));
454
+ case "leave_to_terminal":
455
+ return ask(WITHDRAWN_APPROVAL_REASON);
456
+ }
457
+ };
458
+ var outcomeOfFailedTerminalLiveAsk = (error, sessionId, refusesUnanswered) => isQuestionLimitRefusal(error) ? questionLimitOutput(questionLimitHandBack({ refusesUnanswered, agentPromptFollows: true })) : outcomeOfFailedAsk(error, sessionId, void 0);
459
+ var handleTerminalLive = async (apiKey, input, description, projectName, toolPolicy, holdBudgetSeconds, machineId, toolTarget, decision, signals) => {
460
+ const plan = terminalLiveWait(toolPolicy, holdBudgetSeconds);
461
+ const sessionId = input.session_id;
462
+ const stop = new AbortController();
463
+ const terminal = terminalAnswered(input, stop.signal);
464
+ let question;
465
+ const servedEndsAt = Date.now() + plan.waitSeconds * 1e3;
466
+ const requestedDeadline = Math.min(servedEndsAt, START_MS + plan.holdSeconds * 1e3);
467
+ try {
468
+ question = await askUser(apiKey, {
469
+ question: `Allow ${description || UNNAMED_ACTION}?`,
470
+ type: "confirm",
471
+ context: `Agent wants to run this in ${projectName}`,
472
+ agentName: agentNameIn(projectName),
473
+ sessionId,
474
+ machineId,
475
+ toolName: input.tool_name,
476
+ toolTarget,
477
+ waitEndsAt: plan.onTimeout === "leave_to_terminal" ? new Date(requestedDeadline).toISOString() : void 0,
478
+ ...decision
479
+ });
480
+ } catch (error) {
481
+ stop.abort();
482
+ return outcomeOfFailedTerminalLiveAsk(error, sessionId, plan.onTimeout === "deny");
483
+ }
484
+ if (isDecisionStopped(question)) {
485
+ stop.abort();
486
+ return void 0;
487
+ }
488
+ const deadline = storedWaitDeadline(question, requestedDeadline);
489
+ const first = await Promise.race([waitForQuestion(apiKey, question, deadline, 2e3, stop.signal), terminal]).finally(() => stop.abort());
490
+ if (first === TERMINAL_ANSWERED) {
491
+ await withdrawQuestion(apiKey, question.correlationId);
492
+ return void 0;
493
+ }
494
+ if (isDecisionStopped(first)) return void 0;
495
+ if (!first.answered && isLocalQuestionHandoff(question)) return ask("Approve in the current client.");
496
+ if (first.answered) {
497
+ if (isDeferAnswer(first.value)) return ask(HANDED_BACK_REASON);
498
+ signals.humanDecision = true;
499
+ return first.value === "yes" ? allow() : deny(denyReasonFrom(first.value, first.note));
500
+ }
501
+ return terminalLiveTimeoutOutput(deadline < servedEndsAt ? "leave_to_terminal" : plan.onTimeout, question);
502
+ };
503
+ var pushesToThePhone = (mode) => mode !== "terminal_only" && mode !== "notify_only";
305
504
  var emitDecisionEpisode = (apiKey, input, toolPolicy, machineId, toolTarget, decision, result) => {
306
505
  const verdict = result?.hookSpecificOutput?.permissionDecision;
307
506
  const outcome = verdict === "allow" ? "approve" : verdict === "deny" ? "deny" : null;
@@ -330,7 +529,7 @@ var emitDecisionEpisode = (apiKey, input, toolPolicy, machineId, toolTarget, dec
330
529
  machineId
331
530
  });
332
531
  };
333
- var dispatchModeHandler = async (apiKey, input, toolPolicy, machineId, trainingConsent = false, scopeReason, scopePath) => {
532
+ var dispatchModeHandler = async (apiKey, input, toolPolicy, machineId, trainingConsent = false, scopeReason, scopePath, terminalLive) => {
334
533
  const description = describeToolCall(input.tool_name, input.tool_input, "hook");
335
534
  const projectName = basename(input.cwd ?? process.cwd());
336
535
  const sessionId = input.session_id;
@@ -357,18 +556,36 @@ var dispatchModeHandler = async (apiKey, input, toolPolicy, machineId, trainingC
357
556
  permissionMode: input.permission_mode
358
557
  };
359
558
  const signals = {};
559
+ const permissionAsk = {
560
+ description,
561
+ projectName,
562
+ agentName: agentNameIn(projectName),
563
+ sessionId,
564
+ machineId,
565
+ toolName: input.tool_name,
566
+ toolTarget,
567
+ decision,
568
+ agentPromptFollows,
569
+ startMs: START_MS,
570
+ subscriptionRequiredOutput: undecidedCall(SOLE_GATE_SUBSCRIPTION_REASON),
571
+ announceWait
572
+ };
360
573
  const runHandler = () => {
361
- switch (ownPrompt ? toolPolicy.mode : approvalModeWithoutLocalPrompt(toolPolicy.mode)) {
574
+ const mode = ownPrompt ? toolPolicy.mode : approvalModeWithoutLocalPrompt(toolPolicy.mode);
575
+ if (terminalLive && pushesToThePhone(mode)) {
576
+ return handleTerminalLive(apiKey, input, description, projectName, { ...toolPolicy, mode }, terminalLive.holdBudgetSeconds, machineId, toolTarget, decision, signals);
577
+ }
578
+ switch (mode) {
362
579
  case "push_only":
363
- return handlePushOnly(apiKey, description, projectName, toolPolicy.timeoutSeconds, ownPrompt ? toolPolicy.timeoutAction : timeoutActionWithoutLocalPrompt(toolPolicy), sessionId, machineId, input.tool_name, toolTarget, decision, signals, agentPromptFollows);
580
+ return handlePushOnly(apiKey, permissionAsk, toolPolicy.timeoutSeconds, ownPrompt ? toolPolicy.timeoutAction : timeoutActionWithoutLocalPrompt(toolPolicy), signals);
364
581
  case "terminal_only":
365
582
  return Promise.resolve(handleTerminalOnly());
366
583
  case "push_first":
367
- return handlePushFirst(apiKey, description, projectName, toolPolicy.pushFirstSeconds, sessionId, machineId, input.tool_name, toolTarget, decision, signals, agentPromptFollows);
584
+ return handlePushFirst(apiKey, permissionAsk, toolPolicy.pushFirstSeconds, signals);
368
585
  case "notify_only":
369
586
  return handleNotifyOnly(apiKey, description, projectName, sessionId, machineId);
370
587
  default:
371
- return handlePushFirst(apiKey, description, projectName, toolPolicy.pushFirstSeconds, sessionId, machineId, input.tool_name, toolTarget, decision, signals, agentPromptFollows);
588
+ return handlePushFirst(apiKey, permissionAsk, toolPolicy.pushFirstSeconds, signals);
372
589
  }
373
590
  };
374
591
  const result = await runHandler();
@@ -403,6 +620,16 @@ var keylessNoticeOnce = (sessionId) => {
403
620
  }
404
621
  };
405
622
  var shouldDeferToNativeMode = defersToNativeMode;
623
+ var terminalLiveFactsOf = (input) => ({
624
+ source: ACTIVE_SOURCE,
625
+ sessionAttended: process.env[CLAUDE_SESSION_ATTENDED_ENV] === "1",
626
+ permissionHookAdmitted: isAdmitted(readAdmissionRules(), "claude", "PermissionRequest"),
627
+ handedDownByTheMacBridge: HANDED_DOWN_BY_THE_MAC_BRIDGE,
628
+ fromCursor: isCursorHookPayload(input),
629
+ permissionMode: input.permission_mode,
630
+ toolName: input.tool_name
631
+ });
632
+ var preToolUseHandsOff = (input, gateInput, verdict) => claudePromptTakesTheCall(terminalLiveFactsOf(input), gateInput, verdict);
406
633
  var handleAskUserQuestion = async (apiKey, input, modeState) => {
407
634
  if (questionHandedToTheTerminal(input.tool_name, modeState)) return void 0;
408
635
  const questions = parseAgentQuestions(input.tool_input ?? {});
@@ -599,6 +826,7 @@ var handlePreToolUse = async (input, canonical) => {
599
826
  case "defer":
600
827
  return undecidedCall(SOLE_GATE_UNDECIDED_REASON);
601
828
  default:
829
+ if (preToolUseHandsOff(input, gateInput, verdict)) return ask(TERMINAL_AND_PHONE_REASON);
602
830
  return await dispatchModeHandler(apiKey, input, verdict.policy, getMachineId(), modeState.trainingConsent, verdict.scopeReason, verdict.scopePath);
603
831
  }
604
832
  } catch (error) {
@@ -616,9 +844,9 @@ var toPermissionRequestOutput = (hookOutput) => {
616
844
  if (d.permissionDecision === "deny") return permReqDeny(d.permissionDecisionReason);
617
845
  return void 0;
618
846
  };
619
- var handlePermissionRequest = async (input, canonical) => {
847
+ var handlePermissionRequest = async (input, canonical, holdBudgetSeconds) => {
620
848
  const claudeRules = claudePermissionRulesFor(input);
621
- if (canonical === void 0 && !claudeRulesMayCover(input, claudeRules) && isPermissionRequestOwnedByPreToolUse(input.tool_name, input.permission_mode, shouldDeferToNativeMode)) return void 0;
849
+ if (canonical === void 0 && !claudeRulesMayCover(input, claudeRules) && isPermissionRequestOwnedByPreToolUse(input.tool_name, input.permission_mode, shouldDeferToNativeMode) && !terminalStaysLiveForTheCall(terminalLiveFactsOf(input))) return void 0;
622
850
  if (isPusharyOwnTool(input.tool_name)) return void 0;
623
851
  let apiKey;
624
852
  try {
@@ -642,7 +870,14 @@ var handlePermissionRequest = async (input, canonical) => {
642
870
  if (canonical === null) return permReqDeny(UNREADABLE_CALL_REASON);
643
871
  if (agentQuestionStrategy(ACTIVE_SOURCE, "PermissionRequest", input.tool_name) === AGENT_QUESTION_STRATEGY.native) return void 0;
644
872
  if (verdict.kind === "gate" && verdict.scopeReason && (canonical?.toolInputs.length ?? 0) > 1) return permReqDeny(SCOPE_BATCH_REASON);
645
- if (!(verdict.kind === "gate" && verdict.scopeReason) && !claudeAllowlistOwnsCall(gateInput, verdict, input.permission_mode, claudeRules) && isPermissionRequestOwnedByPreToolUse(input.tool_name, input.permission_mode, shouldDeferToNativeMode)) return void 0;
873
+ const handedOff = preToolUseHandsOff(input, gateInput, verdict);
874
+ const terminalLive = terminalStaysLiveForTheCall(terminalLiveFactsOf(input));
875
+ const scopeQuestion = verdict.kind === "gate" && Boolean(verdict.scopeReason);
876
+ const preToolUseRanForTheCall = PRETOOLUSE_HANDLED_TOOLS.has(input.tool_name) && isAdmitted(readAdmissionRules(), "claude", "PreToolUse");
877
+ const preToolUseAskedTheScopeQuestion = scopeQuestion && terminalLive && preToolUseRanForTheCall;
878
+ const preToolUseOwnsTheCall = !scopeQuestion && !claudeAllowlistOwnsCall(gateInput, verdict, input.permission_mode, claudeRules) && isPermissionRequestOwnedByPreToolUse(input.tool_name, input.permission_mode, shouldDeferToNativeMode);
879
+ if (!handedOff && (preToolUseAskedTheScopeQuestion || preToolUseOwnsTheCall)) return void 0;
880
+ const terminalLiveDispatch = handedOff ? { holdBudgetSeconds } : void 0;
646
881
  switch (verdict.kind) {
647
882
  case "allow":
648
883
  return permReqAllow();
@@ -651,7 +886,7 @@ var handlePermissionRequest = async (input, canonical) => {
651
886
  return void 0;
652
887
  default:
653
888
  return toPermissionRequestOutput(
654
- await dispatchModeHandler(apiKey, input, verdict.policy, getMachineId(), modeState.trainingConsent, verdict.scopeReason, verdict.scopePath)
889
+ await dispatchModeHandler(apiKey, input, verdict.policy, getMachineId(), modeState.trainingConsent, verdict.scopeReason, verdict.scopePath, terminalLiveDispatch)
655
890
  );
656
891
  }
657
892
  } catch (error) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isFile
3
- } from "./chunk-SCMEVDAR.js";
3
+ } from "./chunk-WJLHZ6E6.js";
4
4
 
5
5
  // src/claude-native-install.ts
6
6
  import { accessSync, constants } from "fs";