@pushary/agent-hooks 0.89.10 → 0.89.11

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/CHANGELOG.md CHANGED
@@ -1,5 +1,36 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.89.11
4
+
5
+ ### Phone-spawned Codex and Gemini sessions record their conversation again
6
+
7
+ Both bridges relay their turns by default and stop only when told to. The
8
+ launchers told them to stop on every run: the value they passed was an equality
9
+ test against an environment variable nobody sets, which is false whenever it is
10
+ absent and never the "unset" the bridge was looking for. The relay was therefore
11
+ never built, and a session started from the phone had nothing to show but its
12
+ task title. The opt-out is now passed only when it is asked for.
13
+
14
+ ### An agent standing by stops reading as working
15
+
16
+ `pushary claude` and the Gemini bridge reported that a session had started, that
17
+ it was still alive, and that it had ended, with nothing in between. Their
18
+ heartbeat kept resetting the clock that would otherwise have aged the session out
19
+ of "active", so the phone showed a working agent for one that had been waiting at
20
+ its prompt since its last reply. That is exactly when someone is looking at the
21
+ screen deciding whether to send it something. Both now report the turn boundary
22
+ they already detect, as the Codex bridge always has.
23
+
24
+ ### Taking a session over from your phone says so, and is ready when you ask
25
+
26
+ An instruction from the phone hands the terminal to Pushary so it can drive an
27
+ agent that would otherwise sit idle. That has not changed. The handover is now
28
+ announced before the session is signalled, instead of the terminal appearing to
29
+ lose its agent for no visible reason. The Agent SDK is resolved when the session
30
+ starts rather than during the handover, so the wait is gone, and a machine that
31
+ cannot reach npm reports the instruction as undelivered instead of accepting it
32
+ and then dropping it. An instruction that is dropped now says it was.
33
+
3
34
  ## 0.89.10
4
35
 
5
36
  ### Phone control delivery is crash-safe
@@ -27,7 +27,7 @@ import {
27
27
  getPolicy,
28
28
  repoKeyFor,
29
29
  reportEvent
30
- } from "../chunk-GTZLL7F4.js";
30
+ } from "../chunk-FXIL7RCL.js";
31
31
  import {
32
32
  DEFAULT_SESSION,
33
33
  askUser,
@@ -39,7 +39,7 @@ import {
39
39
  removePendingCommand,
40
40
  savePendingCommand,
41
41
  waitForAnswer
42
- } from "../chunk-UXHMWF4H.js";
42
+ } from "../chunk-JHJHCMAF.js";
43
43
  import {
44
44
  deriveToolTarget,
45
45
  describeToolCall,
@@ -342,9 +342,13 @@ var BoundedInputQueue = class {
342
342
  }
343
343
  // Drop every buffered item without closing the queue. Used when phone control
344
344
  // turns out to be unavailable (offline SDK setup) so a pending instruction does
345
- // not make the local leg switch again into a mode that cannot run.
345
+ // not make the local leg switch again into a mode that cannot run. Returns how
346
+ // many were dropped, because these are somebody's words and the caller has to
347
+ // be able to say they are gone.
346
348
  clear() {
349
+ const dropped = this.buffer.length;
347
350
  this.buffer.length = 0;
351
+ return dropped;
348
352
  }
349
353
  get isClosed() {
350
354
  return this.closed;
@@ -1128,7 +1132,12 @@ var runLocalLeg = (session, deps = {}) => {
1128
1132
  const control = {
1129
1133
  onPhoneCommand: () => {
1130
1134
  if (!exitReason) exitReason = "switch";
1131
- if (!legAbort.signal.aborted) legAbort.abort();
1135
+ if (!legAbort.signal.aborted) {
1136
+ process.stderr.write(
1137
+ "\n[pushary] instruction from your phone \u2014 taking this session over to run it (Ctrl-] hands the terminal back)\n"
1138
+ );
1139
+ legAbort.abort();
1140
+ }
1132
1141
  },
1133
1142
  stop: (reason) => {
1134
1143
  if (!exitReason) exitReason = { type: "exit", code: reason === "signal" ? 130 : 0 };
@@ -1482,6 +1491,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1482
1491
  } else if (message.type === "result") {
1483
1492
  if (typeof message.total_cost_usd === "number") totalCostUsd = message.total_cost_usd;
1484
1493
  process.stderr.write("[pushary] turn complete \u2014 standing by for phone commands\n");
1494
+ void announcePresence("stop", "Turn complete");
1485
1495
  }
1486
1496
  };
1487
1497
  const poller = startCommandPoller({
@@ -1546,6 +1556,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1546
1556
  void announcePresence("session_start", "Session ready \u2014 reachable from your phone");
1547
1557
  ensurePresenceHeartbeat();
1548
1558
  ensureTranscriptSync();
1559
+ void ensureSdk();
1549
1560
  }
1550
1561
  if (startingMode === "remote" && initialPrompt) input.push(userMessage(initialPrompt));
1551
1562
  const exitCode = await driveModeLoop({
@@ -1555,13 +1566,19 @@ var runDualMode = async (binary, args2, startingMode) => {
1555
1566
  const ready = await ensureSdk();
1556
1567
  if (!ready) {
1557
1568
  remoteAvailable = false;
1558
- input.clear();
1569
+ const dropped = input.clear();
1559
1570
  if (!noticedUnavailable) {
1560
1571
  noticedUnavailable = true;
1561
1572
  process.stderr.write(
1562
1573
  "[pushary] could not start phone control (offline?); staying in the terminal. Relaunch when online to enable.\n"
1563
1574
  );
1564
1575
  }
1576
+ if (dropped > 0) {
1577
+ process.stderr.write(
1578
+ `[pushary] ${dropped} instruction(s) from your phone could not be delivered and were dropped.
1579
+ `
1580
+ );
1581
+ }
1565
1582
  return "switch";
1566
1583
  }
1567
1584
  return runRemoteLeg(session, ready);
@@ -371,7 +371,13 @@ var outcome = await withSpawnProcessOwnership(spawnId, () => runCodexBridge({
371
371
  args: ["app-server", "--stdio"]
372
372
  },
373
373
  signal: abort.signal,
374
- transcripts: process.env.PUSHARY_TRANSCRIPTS === "1"
374
+ // The bridge's own default is ON (`transcripts?: boolean`, and it opts out only
375
+ // on an explicit `false`), but `=== '1'` can never produce `undefined`, so an
376
+ // unset env var forced the opt-out on every spawn and the live relay was never
377
+ // built. Pass the opt-out only when the user asks for one. The relay stays a
378
+ // hard no-op until this account's phone has registered a public key, and what
379
+ // it sends is encrypted to that key on this machine.
380
+ ...process.env.PUSHARY_TRANSCRIPTS === "0" ? { transcripts: false } : {}
375
381
  }));
376
382
  for (const signal of signals) process.off(signal, stop);
377
383
  process.exit(outcome === "stopped" ? 0 : 1);
@@ -34,7 +34,7 @@ import {
34
34
  toCodexWire,
35
35
  toPolicyLookup,
36
36
  toPolicyLookups
37
- } from "../chunk-GTZLL7F4.js";
37
+ } from "../chunk-FXIL7RCL.js";
38
38
  import {
39
39
  DEFAULT_SESSION,
40
40
  askUser,
@@ -42,7 +42,7 @@ import {
42
42
  pollForAnswer,
43
43
  savePendingQuestion,
44
44
  sendNotification
45
- } from "../chunk-UXHMWF4H.js";
45
+ } from "../chunk-JHJHCMAF.js";
46
46
  import {
47
47
  deriveActionBody,
48
48
  deriveBlocker,
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  reportEvent
4
- } from "../chunk-GTZLL7F4.js";
4
+ } from "../chunk-FXIL7RCL.js";
5
5
  import {
6
6
  askUser,
7
7
  waitForAnswer
8
- } from "../chunk-UXHMWF4H.js";
8
+ } from "../chunk-JHJHCMAF.js";
9
9
  import "../chunk-TG3T3M4K.js";
10
10
  import {
11
11
  getMachineId
@@ -11,7 +11,7 @@ import {
11
11
  cancelQuestion,
12
12
  pollForAnswer,
13
13
  savePendingQuestion
14
- } from "../chunk-UXHMWF4H.js";
14
+ } from "../chunk-JHJHCMAF.js";
15
15
  import {
16
16
  getMachineId
17
17
  } from "../chunk-DKWM5T3P.js";
@@ -228,6 +228,7 @@ var runGeminiBridge = async (options) => {
228
228
  try {
229
229
  const outcome2 = await turn(text, commandId);
230
230
  if (outcome2 === "aborted") break;
231
+ void report("stop", "Turn completed").catch(() => null);
231
232
  pendingAck = {
232
233
  commandId,
233
234
  sessionId,
@@ -304,7 +305,13 @@ var outcome = await withSpawnProcessOwnership(spawnId, () => runGeminiBridge({
304
305
  resumeSessionId: process.env.PUSHARY_BRIDGE_RESUME_SESSION_ID?.trim() || void 0,
305
306
  command: { command: process.env.PUSHARY_GEMINI_COMMAND?.trim() || resolveBinary("gemini") || "gemini" },
306
307
  signal: abort.signal,
307
- transcripts: process.env.PUSHARY_TRANSCRIPTS === "1"
308
+ // The bridge's own default is ON (`transcripts?: boolean`, and it opts out only
309
+ // on an explicit `false`), but `=== '1'` can never produce `undefined`, so an
310
+ // unset env var forced the opt-out on every spawn and the live relay was never
311
+ // built. Pass the opt-out only when the user asks for one. The relay stays a
312
+ // hard no-op until this account's phone has registered a public key, and what
313
+ // it sends is encrypted to that key on this machine.
314
+ ...process.env.PUSHARY_TRANSCRIPTS === "0" ? { transcripts: false } : {}
308
315
  }));
309
316
  for (const signal of signals) process.off(signal, stop);
310
317
  process.exit(outcome === "stopped" ? 0 : 1);
@@ -21,7 +21,7 @@ import {
21
21
  readLastUserPrompt,
22
22
  repoKeyFor,
23
23
  reportEvent
24
- } from "../chunk-GTZLL7F4.js";
24
+ } from "../chunk-FXIL7RCL.js";
25
25
  import {
26
26
  DEFAULT_SESSION,
27
27
  askUser,
@@ -29,7 +29,7 @@ import {
29
29
  savePendingQuestion,
30
30
  sendNotification,
31
31
  waitForAnswer
32
- } from "../chunk-UXHMWF4H.js";
32
+ } from "../chunk-JHJHCMAF.js";
33
33
  import {
34
34
  deriveActionBody,
35
35
  deriveBlocker,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePreToolUse
4
- } from "../chunk-GSSFSVAQ.js";
4
+ } from "../chunk-J4K75V4D.js";
5
5
  import "../chunk-6EKQZA6R.js";
6
6
  import "../chunk-NCVIWGVA.js";
7
7
  import "../chunk-3HFK3UXM.js";
@@ -9,8 +9,8 @@ import {
9
9
  readHookInput
10
10
  } from "../chunk-6CUUA7HO.js";
11
11
  import "../chunk-YHG74UFF.js";
12
- import "../chunk-GTZLL7F4.js";
13
- import "../chunk-UXHMWF4H.js";
12
+ import "../chunk-FXIL7RCL.js";
13
+ import "../chunk-JHJHCMAF.js";
14
14
  import "../chunk-TG3T3M4K.js";
15
15
  import "../chunk-DKWM5T3P.js";
16
16
  import "../chunk-QA72MKIT.js";
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-6CUUA7HO.js";
5
5
  import {
6
6
  handleNotification
7
- } from "../chunk-GTZLL7F4.js";
8
- import "../chunk-UXHMWF4H.js";
7
+ } from "../chunk-FXIL7RCL.js";
8
+ import "../chunk-JHJHCMAF.js";
9
9
  import "../chunk-TG3T3M4K.js";
10
10
  import "../chunk-DKWM5T3P.js";
11
11
  import "../chunk-QA72MKIT.js";
@@ -19,7 +19,7 @@ import {
19
19
  policyRequestFor,
20
20
  readLastPrompt,
21
21
  repoKeyFor
22
- } from "../chunk-GTZLL7F4.js";
22
+ } from "../chunk-FXIL7RCL.js";
23
23
  import {
24
24
  DEFAULT_SESSION,
25
25
  askUser,
@@ -27,7 +27,7 @@ import {
27
27
  savePendingQuestion,
28
28
  sendNotification,
29
29
  waitForAnswer
30
- } from "../chunk-UXHMWF4H.js";
30
+ } from "../chunk-JHJHCMAF.js";
31
31
  import {
32
32
  HOOK_FALLBACK_MAX,
33
33
  OPENCODE_TOOL_NAMES,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePermissionDenied
4
- } from "../chunk-GSSFSVAQ.js";
4
+ } from "../chunk-J4K75V4D.js";
5
5
  import "../chunk-6EKQZA6R.js";
6
6
  import "../chunk-NCVIWGVA.js";
7
7
  import "../chunk-3HFK3UXM.js";
@@ -9,8 +9,8 @@ import {
9
9
  readHookInput
10
10
  } from "../chunk-6CUUA7HO.js";
11
11
  import "../chunk-YHG74UFF.js";
12
- import "../chunk-GTZLL7F4.js";
13
- import "../chunk-UXHMWF4H.js";
12
+ import "../chunk-FXIL7RCL.js";
13
+ import "../chunk-JHJHCMAF.js";
14
14
  import "../chunk-TG3T3M4K.js";
15
15
  import "../chunk-DKWM5T3P.js";
16
16
  import "../chunk-QA72MKIT.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handlePermissionRequest
4
- } from "../chunk-GSSFSVAQ.js";
4
+ } from "../chunk-J4K75V4D.js";
5
5
  import "../chunk-6EKQZA6R.js";
6
6
  import "../chunk-NCVIWGVA.js";
7
7
  import "../chunk-3HFK3UXM.js";
@@ -9,8 +9,8 @@ import {
9
9
  readHookInput
10
10
  } from "../chunk-6CUUA7HO.js";
11
11
  import "../chunk-YHG74UFF.js";
12
- import "../chunk-GTZLL7F4.js";
13
- import "../chunk-UXHMWF4H.js";
12
+ import "../chunk-FXIL7RCL.js";
13
+ import "../chunk-JHJHCMAF.js";
14
14
  import "../chunk-TG3T3M4K.js";
15
15
  import "../chunk-DKWM5T3P.js";
16
16
  import "../chunk-QA72MKIT.js";
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-6CUUA7HO.js";
5
5
  import {
6
6
  handlePostToolUse
7
- } from "../chunk-GTZLL7F4.js";
8
- import "../chunk-UXHMWF4H.js";
7
+ } from "../chunk-FXIL7RCL.js";
8
+ import "../chunk-JHJHCMAF.js";
9
9
  import "../chunk-TG3T3M4K.js";
10
10
  import "../chunk-DKWM5T3P.js";
11
11
  import "../chunk-QA72MKIT.js";
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-6CUUA7HO.js";
5
5
  import {
6
6
  handleUserPrompt
7
- } from "../chunk-GTZLL7F4.js";
8
- import "../chunk-UXHMWF4H.js";
7
+ } from "../chunk-FXIL7RCL.js";
8
+ import "../chunk-JHJHCMAF.js";
9
9
  import "../chunk-TG3T3M4K.js";
10
10
  import "../chunk-DKWM5T3P.js";
11
11
  import "../chunk-QA72MKIT.js";
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-6CUUA7HO.js";
5
5
  import {
6
6
  handleSessionEnd
7
- } from "../chunk-GTZLL7F4.js";
8
- import "../chunk-UXHMWF4H.js";
7
+ } from "../chunk-FXIL7RCL.js";
8
+ import "../chunk-JHJHCMAF.js";
9
9
  import "../chunk-TG3T3M4K.js";
10
10
  import "../chunk-DKWM5T3P.js";
11
11
  import "../chunk-QA72MKIT.js";
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-6CUUA7HO.js";
5
5
  import {
6
6
  handleSessionStart
7
- } from "../chunk-GTZLL7F4.js";
8
- import "../chunk-UXHMWF4H.js";
7
+ } from "../chunk-FXIL7RCL.js";
8
+ import "../chunk-JHJHCMAF.js";
9
9
  import "../chunk-TG3T3M4K.js";
10
10
  import "../chunk-DKWM5T3P.js";
11
11
  import "../chunk-QA72MKIT.js";
@@ -86,8 +86,8 @@ import {
86
86
  } from "../chunk-X4ZUUFKV.js";
87
87
  import {
88
88
  reportEvent
89
- } from "../chunk-GTZLL7F4.js";
90
- import "../chunk-UXHMWF4H.js";
89
+ } from "../chunk-FXIL7RCL.js";
90
+ import "../chunk-JHJHCMAF.js";
91
91
  import "../chunk-TG3T3M4K.js";
92
92
  import "../chunk-DKWM5T3P.js";
93
93
  import {
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-6CUUA7HO.js";
5
5
  import {
6
6
  handleStop
7
- } from "../chunk-GTZLL7F4.js";
8
- import "../chunk-UXHMWF4H.js";
7
+ } from "../chunk-FXIL7RCL.js";
8
+ import "../chunk-JHJHCMAF.js";
9
9
  import "../chunk-TG3T3M4K.js";
10
10
  import "../chunk-DKWM5T3P.js";
11
11
  import "../chunk-QA72MKIT.js";
@@ -4,8 +4,8 @@ import {
4
4
  } from "../chunk-6CUUA7HO.js";
5
5
  import {
6
6
  handleStopFailure
7
- } from "../chunk-GTZLL7F4.js";
8
- import "../chunk-UXHMWF4H.js";
7
+ } from "../chunk-FXIL7RCL.js";
8
+ import "../chunk-JHJHCMAF.js";
9
9
  import "../chunk-TG3T3M4K.js";
10
10
  import "../chunk-DKWM5T3P.js";
11
11
  import "../chunk-QA72MKIT.js";
@@ -12,7 +12,7 @@ import {
12
12
  savePendingCommand,
13
13
  sendNotification,
14
14
  waitForAnswer
15
- } from "./chunk-UXHMWF4H.js";
15
+ } from "./chunk-JHJHCMAF.js";
16
16
  import {
17
17
  AGENT_IDENTITIES,
18
18
  deriveReceiptMeta,
@@ -20,7 +20,7 @@ import {
20
20
  readLastUserPrompt,
21
21
  repoKeyFor,
22
22
  throttlePass
23
- } from "./chunk-GTZLL7F4.js";
23
+ } from "./chunk-FXIL7RCL.js";
24
24
  import {
25
25
  DEFAULT_SESSION,
26
26
  askUser,
@@ -28,7 +28,7 @@ import {
28
28
  pollForAnswer,
29
29
  savePendingQuestion,
30
30
  sendNotification
31
- } from "./chunk-UXHMWF4H.js";
31
+ } from "./chunk-JHJHCMAF.js";
32
32
  import {
33
33
  deriveAction,
34
34
  deriveActionBody,
@@ -87,7 +87,16 @@ var allow = () => claudePreToolUseOutput("allow");
87
87
  var deny = (reason) => claudePreToolUseOutput("deny", reason);
88
88
  var allowWithInput = (updatedInput) => claudePreToolUseOutput("allow", void 0, updatedInput);
89
89
  var ask = (reason) => claudePreToolUseOutput("ask", reason);
90
+ var storedWaitDeadline = (result, requestedDeadline) => {
91
+ const stored = result.waitEndsAt ? Date.parse(result.waitEndsAt) : Number.NaN;
92
+ return Number.isFinite(stored) ? Math.min(stored, requestedDeadline) : requestedDeadline;
93
+ };
94
+ var returnedAnswer = (result) => result.answered === true ? { answered: true, value: result.value, note: result.note } : null;
90
95
  var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, timeoutAction, sessionId, machineId, toolName, toolTarget, decision, signals) => {
96
+ const effectiveWait = effectiveWaitSeconds(timeoutAction, timeoutSeconds, "claude");
97
+ const now = Date.now();
98
+ const requestedDeadline = hookWaitDeadline(START_MS, effectiveWait, "claude", now);
99
+ const clampCut = hookWaitClamped(START_MS, effectiveWait, "claude", now);
91
100
  let result;
92
101
  try {
93
102
  result = await askUser(apiKey, {
@@ -99,6 +108,7 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
99
108
  machineId,
100
109
  toolName,
101
110
  toolTarget,
111
+ waitEndsAt: timeoutAction !== "approve" && timeoutAction !== "deny" || clampCut ? new Date(requestedDeadline).toISOString() : void 0,
102
112
  ...decision
103
113
  });
104
114
  } catch {
@@ -117,6 +127,8 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
117
127
  return ask(result.suppressed ? "You are at the keyboard, approve here." : "Delivery mode is Terminal, approve here.");
118
128
  }
119
129
  if (result.noDevices) {
130
+ await cancelQuestion(apiKey, result.correlationId).catch(() => {
131
+ });
120
132
  switch (timeoutAction) {
121
133
  case "approve":
122
134
  return allow();
@@ -126,12 +138,9 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
126
138
  return void 0;
127
139
  }
128
140
  }
129
- const effectiveWait = effectiveWaitSeconds(timeoutAction, timeoutSeconds, "claude");
130
- const now = Date.now();
131
- const deadline = hookWaitDeadline(START_MS, effectiveWait, "claude", now);
132
- const clampCut = hookWaitClamped(START_MS, effectiveWait, "claude", now);
141
+ const deadline = storedWaitDeadline(result, requestedDeadline);
133
142
  announceWait(effectiveWait, result.answerUrl, sessionId);
134
- const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
143
+ const answer = returnedAnswer(result) ?? await pollForAnswer(apiKey, result.correlationId, deadline);
135
144
  if (answer.answered) {
136
145
  if (isDeferAnswer(answer.value)) return ask("Handling on your machine");
137
146
  signals.humanDecision = true;
@@ -147,6 +156,7 @@ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, ti
147
156
  case "deny":
148
157
  return deny("No response within timeout");
149
158
  default:
159
+ savePendingQuestion(sessionId || DEFAULT_SESSION, result.correlationId);
150
160
  return ask("No push response, asking in terminal");
151
161
  }
152
162
  };
@@ -154,6 +164,7 @@ var handleTerminalOnly = () => {
154
164
  return ask();
155
165
  };
156
166
  var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds, sessionId, machineId, toolName, toolTarget, decision, signals) => {
167
+ const requestedDeadline = hookWaitDeadline(START_MS, pushFirstSeconds, "claude", Date.now());
157
168
  let result;
158
169
  try {
159
170
  result = await askUser(apiKey, {
@@ -165,6 +176,7 @@ var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds,
165
176
  machineId,
166
177
  toolName,
167
178
  toolTarget,
179
+ waitEndsAt: new Date(requestedDeadline).toISOString(),
168
180
  ...decision
169
181
  });
170
182
  } catch {
@@ -176,11 +188,13 @@ var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds,
176
188
  return ask("You are at the keyboard, approve here.");
177
189
  }
178
190
  if (result.noDevices) {
191
+ await cancelQuestion(apiKey, result.correlationId).catch(() => {
192
+ });
179
193
  return void 0;
180
194
  }
181
195
  announceWait(pushFirstSeconds, result.answerUrl, sessionId);
182
- const deadline = hookWaitDeadline(START_MS, pushFirstSeconds, "claude", Date.now());
183
- const answer = await pollForAnswer(apiKey, result.correlationId, deadline, 1500);
196
+ const deadline = storedWaitDeadline(result, requestedDeadline);
197
+ const answer = returnedAnswer(result) ?? await pollForAnswer(apiKey, result.correlationId, deadline, 1500);
184
198
  if (answer.answered) {
185
199
  if (isDeferAnswer(answer.value)) return ask("Handling on your machine");
186
200
  signals.humanDecision = true;
@@ -330,7 +344,8 @@ var askAsOneCard = async (apiKey, input, parsed, projectName, deadline) => {
330
344
  sessionId: input.session_id,
331
345
  machineId: getMachineId(),
332
346
  toolName: "AskUserQuestion",
333
- toolUseId: input.tool_use_id
347
+ toolUseId: input.tool_use_id,
348
+ waitEndsAt: new Date(deadline).toISOString()
334
349
  });
335
350
  } catch {
336
351
  return void 0;
@@ -340,7 +355,7 @@ var askAsOneCard = async (apiKey, input, parsed, projectName, deadline) => {
340
355
  });
341
356
  return void 0;
342
357
  }
343
- const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
358
+ const answer = returnedAnswer(result) ?? await pollForAnswer(apiKey, result.correlationId, storedWaitDeadline(result, deadline));
344
359
  const values = answer.answered && answer.value ? parseAgentQuestionAnswers(parsed, answer.value) : null;
345
360
  if (!values || isDeferAnswer(answer.value)) {
346
361
  savePendingQuestion(input.session_id || DEFAULT_SESSION, result.correlationId);
@@ -366,7 +381,8 @@ var askQuestionByQuestion = async (apiKey, input, parsed, projectName, deadline)
366
381
  // One AskUserQuestion call raises several questions, and their payload
367
382
  // fingerprints are identical (same tool, no target, no body). Without the
368
383
  // index they would all collapse onto the first decision.
369
- toolUseId: input.tool_use_id ? `${input.tool_use_id}#q${index}` : void 0
384
+ toolUseId: input.tool_use_id ? `${input.tool_use_id}#q${index}` : void 0,
385
+ waitEndsAt: new Date(deadline).toISOString()
370
386
  });
371
387
  } catch {
372
388
  return void 0;
@@ -376,7 +392,7 @@ var askQuestionByQuestion = async (apiKey, input, parsed, projectName, deadline)
376
392
  });
377
393
  return void 0;
378
394
  }
379
- const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
395
+ const answer = returnedAnswer(result) ?? await pollForAnswer(apiKey, result.correlationId, storedWaitDeadline(result, deadline));
380
396
  const legacyValue = answer.answered ? answer.value?.trim() : void 0;
381
397
  const values = legacyValue ? parseAgentQuestionAnswers([question], legacyValue) ?? parseAgentQuestionAnswers([question], JSON.stringify({
382
398
  [question.question]: question.multiSelect ? JSON.stringify([legacyValue]) : legacyValue
@@ -404,6 +420,7 @@ var PLAN_KEEP_PLANNING = "Keep planning";
404
420
  var handleExitPlanMode = async (apiKey, input) => {
405
421
  const toolInput = input.tool_input ?? {};
406
422
  const projectName = basename(input.cwd ?? process.cwd());
423
+ const deadline = hookWaitDeadline(START_MS, effectiveWaitSeconds("wait", 0, "claude"), "claude", Date.now());
407
424
  let result;
408
425
  try {
409
426
  result = await askUser(apiKey, {
@@ -420,7 +437,8 @@ var handleExitPlanMode = async (apiKey, input) => {
420
437
  // so approving from a lock screen is a real decision and not a blind yes.
421
438
  actionBody: deriveActionBody("ExitPlanMode", toolInput),
422
439
  intent: readLastPrompt(input.session_id ?? DEFAULT_SESSION) ?? (input.transcript_path ? readLastUserPrompt(input.transcript_path) : void 0),
423
- blocker: "Waiting for you to approve the plan"
440
+ blocker: "Waiting for you to approve the plan",
441
+ waitEndsAt: new Date(deadline).toISOString()
424
442
  });
425
443
  } catch {
426
444
  return void 0;
@@ -430,8 +448,7 @@ var handleExitPlanMode = async (apiKey, input) => {
430
448
  });
431
449
  return void 0;
432
450
  }
433
- const deadline = hookWaitDeadline(START_MS, effectiveWaitSeconds("wait", 0, "claude"), "claude", Date.now());
434
- const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
451
+ const answer = returnedAnswer(result) ?? await pollForAnswer(apiKey, result.correlationId, storedWaitDeadline(result, deadline));
435
452
  const value = answer.answered ? (answer.value ?? "").trim() : "";
436
453
  if (!value || isDeferAnswer(answer.value)) {
437
454
  savePendingQuestion(input.session_id || DEFAULT_SESSION, result.correlationId);
@@ -17,7 +17,7 @@ var isPolicyConfig = (data) => {
17
17
  var isAskUserResponse = (data) => {
18
18
  if (!data || typeof data !== "object") return false;
19
19
  const d = data;
20
- return typeof d.correlationId === "string" && typeof d.status === "string";
20
+ return typeof d.correlationId === "string" && typeof d.status === "string" && (d.answered === void 0 || typeof d.answered === "boolean") && (d.value === void 0 || typeof d.value === "string") && (d.note === void 0 || typeof d.note === "string") && (d.waitEndsAt === void 0 || typeof d.waitEndsAt === "string");
21
21
  };
22
22
  var isWaitForAnswerResponse = (data) => {
23
23
  if (!data || typeof data !== "object") return false;
@@ -121,6 +121,7 @@ interface AskUserParams {
121
121
  actionBody?: string;
122
122
  scopePath?: string;
123
123
  toolUseId?: string;
124
+ waitEndsAt?: string;
124
125
  requestId?: string;
125
126
  }
126
127
  interface AskUserResponse {
@@ -132,6 +133,10 @@ interface AskUserResponse {
132
133
  };
133
134
  suppressed?: boolean;
134
135
  noDevices?: boolean;
136
+ answered?: boolean;
137
+ value?: string;
138
+ note?: string;
139
+ waitEndsAt?: string;
135
140
  answerUrl?: string;
136
141
  }
137
142
  interface AnswerResult {
package/dist/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-GSSFSVAQ.js";
3
+ } from "../chunk-J4K75V4D.js";
4
4
  import "../chunk-6EKQZA6R.js";
5
5
  import "../chunk-NCVIWGVA.js";
6
6
  import "../chunk-3HFK3UXM.js";
@@ -13,12 +13,12 @@ import {
13
13
  handlePostToolUse,
14
14
  handleStop,
15
15
  reportEvent
16
- } from "../chunk-GTZLL7F4.js";
16
+ } from "../chunk-FXIL7RCL.js";
17
17
  import {
18
18
  askUser,
19
19
  cancelQuestion,
20
20
  waitForAnswer
21
- } from "../chunk-UXHMWF4H.js";
21
+ } from "../chunk-JHJHCMAF.js";
22
22
  import "../chunk-TG3T3M4K.js";
23
23
  import "../chunk-DKWM5T3P.js";
24
24
  import "../chunk-QA72MKIT.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.89.10",
3
+ "version": "0.89.11",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",