@pushary/agent-hooks 0.86.0 → 0.87.1

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 (65) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/data/SKILL.md +31 -11
  3. package/data/cursor-plugin/.cursor-plugin/plugin.json +1 -1
  4. package/data/cursor-plugin/rules/pushary.mdc +2 -2
  5. package/data/cursor-plugin/skills/pushary/SKILL.md +30 -10
  6. package/data/vscode-plugin/.claude-plugin/plugin.json +1 -1
  7. package/data/vscode-plugin/skills/pushary/SKILL.md +30 -10
  8. package/dist/bin/pushary-bell-hook.js +4 -4
  9. package/dist/bin/pushary-bell.js +6 -6
  10. package/dist/bin/pushary-claude.js +12 -10
  11. package/dist/bin/pushary-clean.js +6 -6
  12. package/dist/bin/pushary-codex-bridge.js +5 -5
  13. package/dist/bin/pushary-codex-hook.js +13 -29
  14. package/dist/bin/pushary-codex.js +6 -4
  15. package/dist/bin/pushary-connect.js +6 -6
  16. package/dist/bin/pushary-daemon.js +4 -4
  17. package/dist/bin/pushary-disconnect.js +22 -8
  18. package/dist/bin/pushary-doctor.js +21 -15
  19. package/dist/bin/pushary-elicitation-hook.d.ts +1 -0
  20. package/dist/bin/pushary-elicitation-hook.js +210 -0
  21. package/dist/bin/pushary-gemini-bridge.js +5 -5
  22. package/dist/bin/pushary-gemini-hook.js +10 -8
  23. package/dist/bin/pushary-hook.js +6 -5
  24. package/dist/bin/pushary-login.js +4 -4
  25. package/dist/bin/pushary-logout.js +4 -4
  26. package/dist/bin/pushary-mode.js +3 -3
  27. package/dist/bin/pushary-notification-hook.js +4 -3
  28. package/dist/bin/pushary-permission-denied-hook.js +6 -5
  29. package/dist/bin/pushary-permission-hook.js +6 -5
  30. package/dist/bin/pushary-post-hook.js +4 -3
  31. package/dist/bin/pushary-prompt-hook.js +4 -3
  32. package/dist/bin/pushary-session-end-hook.js +4 -3
  33. package/dist/bin/pushary-session-start-hook.js +4 -3
  34. package/dist/bin/pushary-setup.js +19 -15
  35. package/dist/bin/pushary-stats.js +2 -2
  36. package/dist/bin/pushary-status.js +5 -5
  37. package/dist/bin/pushary-stop-hook.js +4 -3
  38. package/dist/bin/pushary-stopfailure-hook.js +4 -3
  39. package/dist/bin/pushary-upgrade.js +8 -7
  40. package/dist/bin/pushary-wait.js +3 -3
  41. package/dist/{chunk-5EUZYWID.js → chunk-2T3GX7WG.js} +1 -1
  42. package/dist/{chunk-GK4CED6Z.js → chunk-5646VKR4.js} +7 -3
  43. package/dist/{chunk-MNQI6ZZU.js → chunk-7MBDFNUC.js} +1 -1
  44. package/dist/{chunk-CY4TZK5O.js → chunk-7YJS2VTG.js} +31 -4
  45. package/dist/{chunk-PZR4XMFU.js → chunk-A3PFIWWN.js} +1 -1
  46. package/dist/{chunk-2TZCZ7CL.js → chunk-A4I5JGCW.js} +1 -1
  47. package/dist/{chunk-VPJAAGD4.js → chunk-B3V2H6SA.js} +37 -17
  48. package/dist/{chunk-XPAGN3SC.js → chunk-ECEUMWBH.js} +1 -1
  49. package/dist/{chunk-2255DZ74.js → chunk-HAUTLCMA.js} +1 -1
  50. package/dist/{chunk-S3UD7O35.js → chunk-HJ44HIUW.js} +12 -28
  51. package/dist/chunk-J3YDT4HM.js +128 -0
  52. package/dist/{chunk-N52CIK47.js → chunk-JAICP7BA.js} +1 -1
  53. package/dist/{chunk-QW27ZFA4.js → chunk-LT2VLPUK.js} +2 -2
  54. package/dist/{chunk-HSGEEBBL.js → chunk-M7RYIZX6.js} +3 -3
  55. package/dist/{chunk-7G5VO76N.js → chunk-OT4YD54Q.js} +1 -1
  56. package/dist/{chunk-VZNDR3Z7.js → chunk-QBWYNVKY.js} +1 -1
  57. package/dist/{chunk-MTKR4YLW.js → chunk-SJVLFIYP.js} +1 -1
  58. package/dist/{chunk-33T5OOKP.js → chunk-V2XPF77V.js} +96 -161
  59. package/dist/{chunk-FOY5FISQ.js → chunk-WLLBFDVY.js} +1 -1
  60. package/dist/{chunk-Q7N3XGWM.js → chunk-XW5CHAQX.js} +2 -2
  61. package/dist/{chunk-7C5CJC3H.js → chunk-Y3UTTFTZ.js} +1 -1
  62. package/dist/{chunk-2C6B6V2U.js → chunk-ZZE6FN7T.js} +1 -1
  63. package/dist/src/index.d.ts +2 -2
  64. package/dist/src/index.js +10 -8
  65. package/package.json +3 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.87.1
4
+
5
+ ### Unanswered approvals hand off once and fail closed
6
+
7
+ The installed agent instructions now distinguish a live timeout from cancelled,
8
+ missing and unavailable questions. They poll once, cancel before moving the
9
+ decision into the current client, reconcile the cancellation race once, and stop
10
+ when Pushary cannot safely fence the question state.
11
+
3
12
  ## 0.86.0
4
13
 
5
14
  ### Approving a plan from your phone
package/data/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pushary
3
- version: 0.9.2
3
+ version: 0.9.3
4
4
  description: Push notifications and human-in-the-loop for AI agents. Use this whenever a running agent needs a human and nobody is at the terminal, such as before an irreversible or destructive action, before spending money, deploying, force-pushing or deleting, when blocked on a decision outside your authority, when running unattended and you hit a genuine ambiguity, when another skill's workflow says to confirm with the user, and when a long task finishes or fails with nobody watching. Also use it when the user says things like keep going and ping me on my phone if you need anything, notify me when my agent needs me, approve from my phone, ask me questions while I am away from the terminal, run this overnight, keep working while I am in a meeting, I am stepping away, do not wait for me, or wants a long task to run unattended. Send alerts when tasks finish or fail, ask questions (yes/no, multiple choice, or free text) via push, and get answers from the user's lock screen. Use these tools proactively - do not wait for the user to ask for notifications. Every question and answer is recorded, so an unattended run stays reviewable afterwards. Works with Claude Code, Codex, Cursor, VS Code, Windsurf, Hermes, Lovable, or any MCP client; no Claude Max subscription required. Pushary is a hosted service, $9.99/mo after a 3-day card-first trial.
5
5
  metadata:
6
6
  hermes:
@@ -202,9 +202,20 @@ Send a question to the user via push notification and wait for their answer. By
202
202
 
203
203
  Always read `answered` rather than assuming the call blocked. It comes back false
204
204
  in three different situations that mean different things: the wait timed out and
205
- the question is still live (`timedOut`), the site policy is notify_only so nothing
206
- was awaited (`status: "notified"`), or you passed `wait: false` yourself
207
- (`status: "pending"`). All three leave a `correlationId` you can poll.
205
+ the question is still live (`timedOut`), the site policy is notify_only so the
206
+ decision belongs in the current client (`status: "notified"`), or you passed
207
+ `wait: false` yourself (`status: "pending"`). Follow `handoffAction` when present,
208
+ otherwise `nextAction`.
209
+
210
+ For backward compatibility, `nextAction` keeps its original two values. A live timeout from `ask_user` says
211
+ `wait_for_answer`; poll once with `timeoutMs: 55000`. An unanswered poll says
212
+ `handoffAction: cancel_then_ask_in_current_client`: cancel the phone question before asking in
213
+ the current chat or client. If cancellation returns `handoffAction: "stop"`, stop.
214
+ Otherwise, if cancellation returns false, poll once for 1 second
215
+ and honor any answer that won the race. A cancelled question says `handoffAction:
216
+ stop` and must not be resurrected. An unavailable state also stops the handoff,
217
+ because the question cannot be safely fenced. Expired and missing questions are
218
+ not live timeouts.
208
219
 
209
220
  Pass `toolName` and `toolTarget` whenever the question is an approval for a tool
210
221
  call. They are what let the user turn a repeated approval into an always-allow
@@ -249,10 +260,12 @@ rule, so an approval you label once is an approval they never see again.
249
260
 
250
261
  Poll for the user's response to a question sent via `ask_user` with `wait: false`, or to one that timed out. Not needed when using the default blocking mode.
251
262
 
252
- A single call waits at most 55 seconds but the question stays answerable for 10
253
- minutes, so one empty return is not a refusal. Retry with the same
254
- `correlationId` up to three times at `timeoutMs: 55000` before treating it as
255
- unanswered.
263
+ A single call waits at most 55 seconds. Use it once after a live `ask_user`
264
+ timeout. If it returns `answered: false`, follow `handoffAction` when present,
265
+ otherwise `nextAction`: ask in the current chat or client only after cancelling a still-pending phone question. If the
266
+ cancellation loses a race, poll once for 1 second and honor the phone answer
267
+ instead. Only `status: "pending"` means the question is still live; cancelled,
268
+ expired, missing, and unavailable are different outcomes.
256
269
 
257
270
  ### cancel_question
258
271
 
@@ -273,6 +286,13 @@ Use glob syntax (`src/**`, `**/*.test.ts`). Shell commands are **not** scoped he
273
286
  the user declined: ask what scope they want, and do **not** proceed as if they had
274
287
  agreed. Not answered means the scope is simply not in force.
275
288
 
289
+ An unanswered proposal returns its `correlationId`. Poll it once; a late phone
290
+ yes ratifies the exact stored proposal. If that poll is still pending, cancel it
291
+ before asking in the current chat whether to continue without an enforced scope.
292
+ If cancellation returns `handoffAction: "stop"`, stop. Otherwise, if cancellation
293
+ returns false, poll once for 1 second and honor the phone answer
294
+ that won the race. A yes in chat is not a server-ratified scope.
295
+
276
296
  Omitting `allowedPaths` proposes no path restriction, and the user is told that
277
297
  plainly as "this agent is asking to touch anything", so omit it only when you mean
278
298
  it.
@@ -303,7 +323,7 @@ Before executing any of the following, you MUST call `ask_user` with type "confi
303
323
  - Network configuration changes (firewall, DNS, proxy)
304
324
  - Any command the user has flagged as dangerous
305
325
 
306
- If `ask_user` returns `answered: false`, do NOT execute the command. Send a notification that the operation was skipped due to no response.
326
+ If `ask_user` returns `answered: false`, do not execute yet and do not call the task blocked. Follow `handoffAction` when present, otherwise `nextAction`: poll once, then cancel a live phone question before asking in the current client. Execute only after an explicit "yes" from the winning surface.
307
327
 
308
328
  This is not optional. Treat it as a hard constraint, not a suggestion.
309
329
 
@@ -323,10 +343,10 @@ result = ask_user({
323
343
  if result.answered:
324
344
  // result.value = "JWT tokens" - proceed with the chosen approach
325
345
  else:
326
- // user did not respond - pick the safe default or notify and skip
346
+ // follow result.handoffAction when present, otherwise result.nextAction
327
347
  ```
328
348
 
329
- If the user answers in chat before the push response arrives, continue normally and call `cancel_question` with the `correlationId` to clean up.
349
+ If the user answers in chat before the push response arrives, call `cancel_question` before acting. If it returns `handoffAction: "stop"`, stop. Otherwise, if it returns false, poll once for 1 second and honor any phone answer that won the race.
330
350
 
331
351
  **A note on how long ask_user blocks:** the wait time and whether it blocks at all are governed by the site's delivery mode, which the user configures (you do not set it). In the default smart mode and push-only mode, ask_user blocks for the policy timeout; in notify-only mode it returns immediately with `answered: false` after sending the push. Always check `answered` rather than assuming the call blocked, and pass `timeoutMs` only when you need a shorter wait than the site policy.
332
352
 
@@ -2,7 +2,7 @@
2
2
  "name": "pushary",
3
3
  "displayName": "Pushary — Control Panel for AI Agents",
4
4
  "description": "Push notifications, human-in-the-loop questions, and permission gating for your AI coding agent. Get a push when a task finishes, answer the agent from your phone, and approve risky commands before they run.",
5
- "version": "0.1.0",
5
+ "version": "0.2.0",
6
6
  "author": { "name": "Pushary", "email": "business@pushary.com" },
7
7
  "homepage": "https://pushary.com",
8
8
  "repository": "https://github.com/Pushary/cursor-plugin",
@@ -16,9 +16,9 @@ When Pushary is connected, it is your channel for reaching the user. Any time yo
16
16
  - A name, a path, a value, or other free text. Use type "input".
17
17
  - Approval of a plan or an approach before you start. Use type "select" with the options the user is choosing between, or "confirm" to approve a single plan.
18
18
 
19
- `ask_user` blocks and returns `{ answered, value }`. Always set `context` to one line on what you are doing, and set `agentName` to "Cursor - {project}". If `answered` is false, you did not get an answer in time: pick the safe default or stop and tell the user. Never assume approval.
19
+ `ask_user` blocks and returns `{ answered, value, nextAction, handoffAction }`. Always set `context` to one line on what you are doing, and set `agentName` to "Cursor - {project}". If `answered` is false, follow `handoffAction` when present, otherwise `nextAction`: poll once when instructed, then cancel a live phone question before asking in the current chat. Never assume approval.
20
20
 
21
- If the user answers in the chat before the push comes back, continue and call `cancel_question` with the correlationId to clean up.
21
+ If the user answers in the chat before the push comes back, call `cancel_question` with the correlationId before acting. If cancellation returns `handoffAction: "stop"`, stop. Otherwise, if cancellation returns false, poll once for 1 second and honor any phone answer that won the race.
22
22
 
23
23
  ## Notify the user
24
24
 
@@ -195,9 +195,20 @@ Send a question to the user via push notification and wait for their answer. By
195
195
 
196
196
  Always read `answered` rather than assuming the call blocked. It comes back false
197
197
  in three different situations that mean different things: the wait timed out and
198
- the question is still live (`timedOut`), the site policy is notify_only so nothing
199
- was awaited (`status: "notified"`), or you passed `wait: false` yourself
200
- (`status: "pending"`). All three leave a `correlationId` you can poll.
198
+ the question is still live (`timedOut`), the site policy is notify_only so the
199
+ decision belongs in the current client (`status: "notified"`), or you passed
200
+ `wait: false` yourself (`status: "pending"`). Follow `handoffAction` when present,
201
+ otherwise `nextAction`.
202
+
203
+ For backward compatibility, `nextAction` keeps its original two values. A live timeout from `ask_user` says
204
+ `wait_for_answer`; poll once with `timeoutMs: 55000`. An unanswered poll says
205
+ `handoffAction: cancel_then_ask_in_current_client`: cancel the phone question before asking in
206
+ the current chat or client. If cancellation returns `handoffAction: "stop"`, stop.
207
+ Otherwise, if cancellation returns false, poll once for 1 second
208
+ and honor any answer that won the race. A cancelled question says `handoffAction:
209
+ stop` and must not be resurrected. An unavailable state also stops the handoff,
210
+ because the question cannot be safely fenced. Expired and missing questions are
211
+ not live timeouts.
201
212
 
202
213
  Pass `toolName` and `toolTarget` whenever the question is an approval for a tool
203
214
  call. They are what let the user turn a repeated approval into an always-allow
@@ -242,10 +253,12 @@ rule, so an approval you label once is an approval they never see again.
242
253
 
243
254
  Poll for the user's response to a question sent via `ask_user` with `wait: false`, or to one that timed out. Not needed when using the default blocking mode.
244
255
 
245
- A single call waits at most 55 seconds but the question stays answerable for 10
246
- minutes, so one empty return is not a refusal. Retry with the same
247
- `correlationId` up to three times at `timeoutMs: 55000` before treating it as
248
- unanswered.
256
+ A single call waits at most 55 seconds. Use it once after a live `ask_user`
257
+ timeout. If it returns `answered: false`, follow `handoffAction` when present,
258
+ otherwise `nextAction`: ask in the current chat or client only after cancelling a still-pending phone question. If the
259
+ cancellation loses a race, poll once for 1 second and honor the phone answer
260
+ instead. Only `status: "pending"` means the question is still live; cancelled,
261
+ expired, missing, and unavailable are different outcomes.
249
262
 
250
263
  ### cancel_question
251
264
 
@@ -266,6 +279,13 @@ Use glob syntax (`src/**`, `**/*.test.ts`). Shell commands are **not** scoped he
266
279
  the user declined: ask what scope they want, and do **not** proceed as if they had
267
280
  agreed. Not answered means the scope is simply not in force.
268
281
 
282
+ An unanswered proposal returns its `correlationId`. Poll it once; a late phone
283
+ yes ratifies the exact stored proposal. If that poll is still pending, cancel it
284
+ before asking in the current chat whether to continue without an enforced scope.
285
+ If cancellation returns `handoffAction: "stop"`, stop. Otherwise, if cancellation
286
+ returns false, poll once for 1 second and honor the phone answer
287
+ that won the race. A yes in chat is not a server-ratified scope.
288
+
269
289
  Omitting `allowedPaths` proposes no path restriction, and the user is told that
270
290
  plainly as "this agent is asking to touch anything", so omit it only when you mean
271
291
  it.
@@ -296,7 +316,7 @@ Before executing any of the following, you MUST call `ask_user` with type "confi
296
316
  - Network configuration changes (firewall, DNS, proxy)
297
317
  - Any command the user has flagged as dangerous
298
318
 
299
- If `ask_user` returns `answered: false`, do NOT execute the command. Send a notification that the operation was skipped due to no response.
319
+ If `ask_user` returns `answered: false`, do not execute yet and do not call the task blocked. Follow `handoffAction` when present, otherwise `nextAction`: poll once, then cancel a live phone question before asking in the current client. Execute only after an explicit "yes" from the winning surface.
300
320
 
301
321
  This is not optional. Treat it as a hard constraint, not a suggestion.
302
322
 
@@ -316,10 +336,10 @@ result = ask_user({
316
336
  if result.answered:
317
337
  // result.value = "JWT tokens" - proceed with the chosen approach
318
338
  else:
319
- // user did not respond - pick the safe default or notify and skip
339
+ // follow result.handoffAction when present, otherwise result.nextAction
320
340
  ```
321
341
 
322
- If the user answers in chat before the push response arrives, continue normally and call `cancel_question` with the `correlationId` to clean up.
342
+ If the user answers in chat before the push response arrives, call `cancel_question` before acting. If it returns `handoffAction: "stop"`, stop. Otherwise, if it returns false, poll once for 1 second and honor any phone answer that won the race.
323
343
 
324
344
  **A note on how long ask_user blocks:** the wait time and whether it blocks at all are governed by the site's delivery mode, which the user configures (you do not set it). In the default smart mode and push-only mode, ask_user blocks for the policy timeout; in notify-only mode it returns immediately with `answered: false` after sending the push. Always check `answered` rather than assuming the call blocked, and pass `timeoutMs` only when you need a shorter wait than the site policy.
325
345
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pushary",
3
3
  "description": "Push notifications, human-in-the-loop questions, and permission gating for your AI agent. Get a push when a task finishes, answer the agent from your phone, and approve risky commands before they run.",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "author": {
6
6
  "name": "Pushary",
7
7
  "email": "business@pushary.com",
@@ -195,9 +195,20 @@ Send a question to the user via push notification and wait for their answer. By
195
195
 
196
196
  Always read `answered` rather than assuming the call blocked. It comes back false
197
197
  in three different situations that mean different things: the wait timed out and
198
- the question is still live (`timedOut`), the site policy is notify_only so nothing
199
- was awaited (`status: "notified"`), or you passed `wait: false` yourself
200
- (`status: "pending"`). All three leave a `correlationId` you can poll.
198
+ the question is still live (`timedOut`), the site policy is notify_only so the
199
+ decision belongs in the current client (`status: "notified"`), or you passed
200
+ `wait: false` yourself (`status: "pending"`). Follow `handoffAction` when present,
201
+ otherwise `nextAction`.
202
+
203
+ For backward compatibility, `nextAction` keeps its original two values. A live timeout from `ask_user` says
204
+ `wait_for_answer`; poll once with `timeoutMs: 55000`. An unanswered poll says
205
+ `handoffAction: cancel_then_ask_in_current_client`: cancel the phone question before asking in
206
+ the current chat or client. If cancellation returns `handoffAction: "stop"`, stop.
207
+ Otherwise, if cancellation returns false, poll once for 1 second
208
+ and honor any answer that won the race. A cancelled question says `handoffAction:
209
+ stop` and must not be resurrected. An unavailable state also stops the handoff,
210
+ because the question cannot be safely fenced. Expired and missing questions are
211
+ not live timeouts.
201
212
 
202
213
  Pass `toolName` and `toolTarget` whenever the question is an approval for a tool
203
214
  call. They are what let the user turn a repeated approval into an always-allow
@@ -242,10 +253,12 @@ rule, so an approval you label once is an approval they never see again.
242
253
 
243
254
  Poll for the user's response to a question sent via `ask_user` with `wait: false`, or to one that timed out. Not needed when using the default blocking mode.
244
255
 
245
- A single call waits at most 55 seconds but the question stays answerable for 10
246
- minutes, so one empty return is not a refusal. Retry with the same
247
- `correlationId` up to three times at `timeoutMs: 55000` before treating it as
248
- unanswered.
256
+ A single call waits at most 55 seconds. Use it once after a live `ask_user`
257
+ timeout. If it returns `answered: false`, follow `handoffAction` when present,
258
+ otherwise `nextAction`: ask in the current chat or client only after cancelling a still-pending phone question. If the
259
+ cancellation loses a race, poll once for 1 second and honor the phone answer
260
+ instead. Only `status: "pending"` means the question is still live; cancelled,
261
+ expired, missing, and unavailable are different outcomes.
249
262
 
250
263
  ### cancel_question
251
264
 
@@ -266,6 +279,13 @@ Use glob syntax (`src/**`, `**/*.test.ts`). Shell commands are **not** scoped he
266
279
  the user declined: ask what scope they want, and do **not** proceed as if they had
267
280
  agreed. Not answered means the scope is simply not in force.
268
281
 
282
+ An unanswered proposal returns its `correlationId`. Poll it once; a late phone
283
+ yes ratifies the exact stored proposal. If that poll is still pending, cancel it
284
+ before asking in the current chat whether to continue without an enforced scope.
285
+ If cancellation returns `handoffAction: "stop"`, stop. Otherwise, if cancellation
286
+ returns false, poll once for 1 second and honor the phone answer
287
+ that won the race. A yes in chat is not a server-ratified scope.
288
+
269
289
  Omitting `allowedPaths` proposes no path restriction, and the user is told that
270
290
  plainly as "this agent is asking to touch anything", so omit it only when you mean
271
291
  it.
@@ -296,7 +316,7 @@ Before executing any of the following, you MUST call `ask_user` with type "confi
296
316
  - Network configuration changes (firewall, DNS, proxy)
297
317
  - Any command the user has flagged as dangerous
298
318
 
299
- If `ask_user` returns `answered: false`, do NOT execute the command. Send a notification that the operation was skipped due to no response.
319
+ If `ask_user` returns `answered: false`, do not execute yet and do not call the task blocked. Follow `handoffAction` when present, otherwise `nextAction`: poll once, then cancel a live phone question before asking in the current client. Execute only after an explicit "yes" from the winning surface.
300
320
 
301
321
  This is not optional. Treat it as a hard constraint, not a suggestion.
302
322
 
@@ -316,10 +336,10 @@ result = ask_user({
316
336
  if result.answered:
317
337
  // result.value = "JWT tokens" - proceed with the chosen approach
318
338
  else:
319
- // user did not respond - pick the safe default or notify and skip
339
+ // follow result.handoffAction when present, otherwise result.nextAction
320
340
  ```
321
341
 
322
- If the user answers in chat before the push response arrives, continue normally and call `cancel_question` with the `correlationId` to clean up.
342
+ If the user answers in chat before the push response arrives, call `cancel_question` before acting. If it returns `handoffAction: "stop"`, stop. Otherwise, if it returns false, poll once for 1 second and honor any phone answer that won the race.
323
343
 
324
344
  **A note on how long ask_user blocks:** the wait time and whether it blocks at all are governed by the site's delivery mode, which the user configures (you do not set it). In the default smart mode and push-only mode, ask_user blocks for the policy timeout; in notify-only mode it returns immediately with `answered: false` after sending the push. Always check `answered` rather than assuming the call blocked, and pass `timeoutMs` only when you need a shorter wait than the site policy.
325
345
 
@@ -1,16 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleBell
4
- } from "../chunk-5EUZYWID.js";
4
+ } from "../chunk-2T3GX7WG.js";
5
5
  import {
6
6
  readHookInput
7
7
  } from "../chunk-6CUUA7HO.js";
8
- import "../chunk-7G5VO76N.js";
8
+ import "../chunk-OT4YD54Q.js";
9
9
  import "../chunk-BC3VCZ3E.js";
10
10
  import "../chunk-6MTNS63X.js";
11
- import "../chunk-7C5CJC3H.js";
11
+ import "../chunk-Y3UTTFTZ.js";
12
12
  import "../chunk-2UMNXADU.js";
13
- import "../chunk-CY4TZK5O.js";
13
+ import "../chunk-7YJS2VTG.js";
14
14
 
15
15
  // bin/pushary-bell-hook.ts
16
16
  var main = async () => {
@@ -5,18 +5,18 @@ import {
5
5
  liveAgentCount,
6
6
  ring,
7
7
  upgradeThreshold
8
- } from "../chunk-5EUZYWID.js";
8
+ } from "../chunk-2T3GX7WG.js";
9
9
  import {
10
10
  guardBinary
11
- } from "../chunk-HSGEEBBL.js";
11
+ } from "../chunk-M7RYIZX6.js";
12
12
  import "../chunk-VT4IVERX.js";
13
13
  import {
14
14
  claudeSettings
15
- } from "../chunk-VPJAAGD4.js";
15
+ } from "../chunk-B3V2H6SA.js";
16
16
  import {
17
17
  createIo
18
18
  } from "../chunk-5RUIFDTP.js";
19
- import "../chunk-7G5VO76N.js";
19
+ import "../chunk-OT4YD54Q.js";
20
20
  import {
21
21
  parseFlags
22
22
  } from "../chunk-GMXKITVA.js";
@@ -30,13 +30,13 @@ import {
30
30
  readJsonSafe,
31
31
  writeJsonAtomic
32
32
  } from "../chunk-6MTNS63X.js";
33
- import "../chunk-7C5CJC3H.js";
33
+ import "../chunk-Y3UTTFTZ.js";
34
34
  import "../chunk-7QLSKOSU.js";
35
35
  import {
36
36
  EXIT
37
37
  } from "../chunk-KZERVKTD.js";
38
38
  import "../chunk-2UMNXADU.js";
39
- import "../chunk-CY4TZK5O.js";
39
+ import "../chunk-7YJS2VTG.js";
40
40
 
41
41
  // src/bell/install.ts
42
42
  import { join } from "path";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ensureDaemonRunning
4
- } from "../chunk-MTKR4YLW.js";
4
+ } from "../chunk-SJVLFIYP.js";
5
5
  import {
6
6
  buildTranscriptRecords,
7
7
  decodePublicKey,
@@ -9,32 +9,34 @@ import {
9
9
  generateSessionKey,
10
10
  uploadTranscriptRecords,
11
11
  wrapSessionKey
12
- } from "../chunk-XPAGN3SC.js";
12
+ } from "../chunk-ECEUMWBH.js";
13
13
  import {
14
14
  startProcessOwnership,
15
15
  withSpawnProcessOwnership
16
- } from "../chunk-VZNDR3Z7.js";
16
+ } from "../chunk-QBWYNVKY.js";
17
17
  import {
18
18
  needsShell,
19
19
  spawnClaude
20
20
  } from "../chunk-XHKBHWLX.js";
21
21
  import "../chunk-VT4IVERX.js";
22
- import "../chunk-VPJAAGD4.js";
22
+ import "../chunk-B3V2H6SA.js";
23
23
  import {
24
24
  isDeferAnswer
25
25
  } from "../chunk-YHG74UFF.js";
26
26
  import {
27
- askUser,
28
- cancelQuestion,
29
27
  deriveToolTarget,
30
28
  describeToolCall,
31
29
  fetchModeState,
32
30
  getPolicy,
33
31
  repoKeyFor,
34
32
  reportEvent,
35
- scopePathFor,
33
+ scopePathFor
34
+ } from "../chunk-V2XPF77V.js";
35
+ import {
36
+ askUser,
37
+ cancelQuestion,
36
38
  waitForAnswer
37
- } from "../chunk-33T5OOKP.js";
39
+ } from "../chunk-J3YDT4HM.js";
38
40
  import {
39
41
  getMachineId
40
42
  } from "../chunk-RN3NOEJF.js";
@@ -42,7 +44,7 @@ import "../chunk-DINDL2CF.js";
42
44
  import "../chunk-6MTNS63X.js";
43
45
  import "../chunk-BSZYIAZL.js";
44
46
  import "../chunk-SAF6HGAA.js";
45
- import "../chunk-7C5CJC3H.js";
47
+ import "../chunk-Y3UTTFTZ.js";
46
48
  import "../chunk-7QLSKOSU.js";
47
49
  import "../chunk-KZERVKTD.js";
48
50
  import {
@@ -52,7 +54,7 @@ import {
52
54
  import {
53
55
  KILL_REASON,
54
56
  resolveGate
55
- } from "../chunk-CY4TZK5O.js";
57
+ } from "../chunk-7YJS2VTG.js";
56
58
 
57
59
  // src/wrapper/claudeBinary.ts
58
60
  import { statSync } from "fs";
@@ -2,18 +2,18 @@
2
2
  import {
3
3
  removeClaudeMcpServers,
4
4
  removePusharySettings
5
- } from "../chunk-GK4CED6Z.js";
5
+ } from "../chunk-5646VKR4.js";
6
6
  import {
7
7
  removeInstructionBlock,
8
8
  shortenHome,
9
9
  unregisterPluginLocation,
10
10
  vscodeSettingsTargets
11
- } from "../chunk-QW27ZFA4.js";
11
+ } from "../chunk-LT2VLPUK.js";
12
12
  import {
13
13
  removeGeminiSettings
14
- } from "../chunk-2C6B6V2U.js";
14
+ } from "../chunk-ZZE6FN7T.js";
15
15
  import "../chunk-ATBKJNWS.js";
16
- import "../chunk-HSGEEBBL.js";
16
+ import "../chunk-M7RYIZX6.js";
17
17
  import {
18
18
  execNpm
19
19
  } from "../chunk-VT4IVERX.js";
@@ -34,7 +34,7 @@ import {
34
34
  pusharyDir,
35
35
  removeCodexHooks,
36
36
  vscodePluginDir
37
- } from "../chunk-VPJAAGD4.js";
37
+ } from "../chunk-B3V2H6SA.js";
38
38
  import {
39
39
  rejectUnknownFlags
40
40
  } from "../chunk-GMXKITVA.js";
@@ -50,7 +50,7 @@ import "../chunk-7QLSKOSU.js";
50
50
  import {
51
51
  EXIT
52
52
  } from "../chunk-KZERVKTD.js";
53
- import "../chunk-CY4TZK5O.js";
53
+ import "../chunk-7YJS2VTG.js";
54
54
 
55
55
  // bin/pushary-clean.ts
56
56
  import { existsSync, readFileSync, copyFileSync, readdirSync } from "fs";
@@ -6,19 +6,19 @@ import {
6
6
  postLiveSession,
7
7
  postLiveSessionUntilAccepted,
8
8
  startLiveSessionHeartbeat
9
- } from "../chunk-FOY5FISQ.js";
10
- import "../chunk-XPAGN3SC.js";
9
+ } from "../chunk-WLLBFDVY.js";
10
+ import "../chunk-ECEUMWBH.js";
11
11
  import {
12
12
  startProcessOwnership,
13
13
  withSpawnProcessOwnership
14
- } from "../chunk-VZNDR3Z7.js";
14
+ } from "../chunk-QBWYNVKY.js";
15
15
  import {
16
16
  spawnClaude
17
17
  } from "../chunk-XHKBHWLX.js";
18
18
  import {
19
19
  resolveBinary
20
20
  } from "../chunk-VT4IVERX.js";
21
- import "../chunk-VPJAAGD4.js";
21
+ import "../chunk-B3V2H6SA.js";
22
22
  import {
23
23
  getMachineId
24
24
  } from "../chunk-RN3NOEJF.js";
@@ -27,7 +27,7 @@ import {
27
27
  getApiKey,
28
28
  getBaseUrl
29
29
  } from "../chunk-2UMNXADU.js";
30
- import "../chunk-CY4TZK5O.js";
30
+ import "../chunk-7YJS2VTG.js";
31
31
 
32
32
  // bin/pushary-codex-bridge.ts
33
33
  import { existsSync } from "fs";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  isGatingMoment,
4
4
  recordKeylessMoment
5
- } from "../chunk-MNQI6ZZU.js";
5
+ } from "../chunk-7MBDFNUC.js";
6
6
  import {
7
7
  readHookInput
8
8
  } from "../chunk-6CUUA7HO.js";
@@ -12,9 +12,6 @@ import {
12
12
  } from "../chunk-YHG74UFF.js";
13
13
  import {
14
14
  CODEX_AGENT,
15
- DEFAULT_SESSION,
16
- askUser,
17
- cancelQuestion,
18
15
  codexAllow,
19
16
  codexDeny,
20
17
  codexPass,
@@ -34,20 +31,25 @@ import {
34
31
  readLastPrompt,
35
32
  repoKeyFor,
36
33
  reportEvent,
37
- savePendingQuestion,
38
34
  scopePathFor,
39
- sendNotification,
40
35
  toCodexWire,
41
36
  toPolicyLookup,
42
- toPolicyLookups,
43
- waitForAnswer
44
- } from "../chunk-33T5OOKP.js";
37
+ toPolicyLookups
38
+ } from "../chunk-V2XPF77V.js";
39
+ import {
40
+ DEFAULT_SESSION,
41
+ askUser,
42
+ cancelQuestion,
43
+ pollForAnswer,
44
+ savePendingQuestion,
45
+ sendNotification
46
+ } from "../chunk-J3YDT4HM.js";
45
47
  import {
46
48
  getMachineId
47
49
  } from "../chunk-RN3NOEJF.js";
48
50
  import "../chunk-BSZYIAZL.js";
49
51
  import "../chunk-SAF6HGAA.js";
50
- import "../chunk-7C5CJC3H.js";
52
+ import "../chunk-Y3UTTFTZ.js";
51
53
  import "../chunk-7QLSKOSU.js";
52
54
  import "../chunk-KZERVKTD.js";
53
55
  import {
@@ -60,7 +62,7 @@ import {
60
62
  hookWaitClamped,
61
63
  hookWaitDeadline,
62
64
  resolveGate
63
- } from "../chunk-CY4TZK5O.js";
65
+ } from "../chunk-7YJS2VTG.js";
64
66
 
65
67
  // bin/pushary-codex-hook.ts
66
68
  import { basename, join } from "path";
@@ -111,24 +113,6 @@ var claimNotify = (toolUseId) => {
111
113
  return true;
112
114
  }
113
115
  };
114
- var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
115
- var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
116
- while (Date.now() < deadlineMs) {
117
- const remaining = Math.min(Math.max(deadlineMs - Date.now(), 1e3), 3e4);
118
- let answer;
119
- try {
120
- answer = await waitForAnswer(apiKey, correlationId, remaining);
121
- } catch {
122
- if (Date.now() + pollInterval >= deadlineMs) break;
123
- await sleep(pollInterval);
124
- continue;
125
- }
126
- if (answer.answered) return answer;
127
- if (Date.now() + pollInterval >= deadlineMs) break;
128
- await sleep(pollInterval);
129
- }
130
- return { answered: false };
131
- };
132
116
  var agentNameFor = (input) => `Codex - ${basename(input.cwd ?? process.cwd())}`;
133
117
  var describeFor = (input, lookup) => input.tool_name === "apply_patch" ? describeApplyPatch(input.tool_input?.command) ?? describeToolCall(lookup.tool, lookup.input, "hook") : describeToolCall(lookup.tool, lookup.input, "hook");
134
118
  var pushQuestion = async (apiKey, input, lookup, waitSeconds, mode) => {
@@ -1,21 +1,23 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ reportEvent
4
+ } from "../chunk-V2XPF77V.js";
2
5
  import {
3
6
  askUser,
4
- reportEvent,
5
7
  waitForAnswer
6
- } from "../chunk-33T5OOKP.js";
8
+ } from "../chunk-J3YDT4HM.js";
7
9
  import {
8
10
  getMachineId
9
11
  } from "../chunk-RN3NOEJF.js";
10
12
  import "../chunk-BSZYIAZL.js";
11
13
  import "../chunk-SAF6HGAA.js";
12
- import "../chunk-7C5CJC3H.js";
14
+ import "../chunk-Y3UTTFTZ.js";
13
15
  import "../chunk-7QLSKOSU.js";
14
16
  import "../chunk-KZERVKTD.js";
15
17
  import {
16
18
  getApiKey
17
19
  } from "../chunk-2UMNXADU.js";
18
- import "../chunk-CY4TZK5O.js";
20
+ import "../chunk-7YJS2VTG.js";
19
21
 
20
22
  // bin/pushary-codex.ts
21
23
  import { basename } from "path";