@pushary/agent-hooks 0.95.2 → 0.95.5

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 (98) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1 -1
  3. package/data/cursor-plugin/.cursor-plugin/plugin.json +1 -1
  4. package/data/cursor-plugin/CHANGELOG.md +5 -0
  5. package/data/cursor-plugin/README.md +6 -8
  6. package/data/cursor-plugin/hooks/hooks.json +56 -1
  7. package/data/cursor-plugin/scripts/pushary-gate.mjs +120 -71
  8. package/data/vscode-plugin/.claude-plugin/plugin.json +1 -1
  9. package/data/vscode-plugin/CHANGELOG.md +5 -0
  10. package/data/vscode-plugin/README.md +7 -15
  11. package/data/vscode-plugin/hooks/hooks.json +49 -0
  12. package/data/vscode-plugin/scripts/pushary-gate.mjs +208 -88
  13. package/dist/bin/pushary-bell-hook.js +20 -22
  14. package/dist/bin/pushary-bell.js +9 -11
  15. package/dist/bin/pushary-claude.js +58 -46
  16. package/dist/bin/pushary-clean.js +21 -20
  17. package/dist/bin/pushary-codex-bridge.js +120 -25
  18. package/dist/bin/pushary-codex-hook.js +43 -63
  19. package/dist/bin/pushary-codex.js +11 -10
  20. package/dist/bin/pushary-connect.js +8 -10
  21. package/dist/bin/pushary-cowork.js +5 -5
  22. package/dist/bin/pushary-daemon.js +26 -28
  23. package/dist/bin/pushary-disconnect.js +11 -10
  24. package/dist/bin/pushary-doctor.js +25 -25
  25. package/dist/bin/pushary-elicitation-hook.js +30 -31
  26. package/dist/bin/pushary-gemini-bridge.js +9 -11
  27. package/dist/bin/pushary-gemini-hook.js +45 -79
  28. package/dist/bin/pushary-hook.js +27 -29
  29. package/dist/bin/pushary-login.js +57 -31
  30. package/dist/bin/pushary-logout.js +6 -8
  31. package/dist/bin/pushary-mode.js +5 -5
  32. package/dist/bin/pushary-notification-hook.js +22 -24
  33. package/dist/bin/pushary-opencode-hook.js +38 -74
  34. package/dist/bin/pushary-permission-denied-hook.js +25 -27
  35. package/dist/bin/pushary-permission-hook.js +25 -27
  36. package/dist/bin/pushary-post-hook.js +22 -24
  37. package/dist/bin/pushary-prompt-hook.js +22 -24
  38. package/dist/bin/pushary-session-end-hook.js +22 -24
  39. package/dist/bin/pushary-session-start-hook.js +25 -27
  40. package/dist/bin/pushary-setup.js +78 -55
  41. package/dist/bin/pushary-stats.js +4 -4
  42. package/dist/bin/pushary-status.js +10 -12
  43. package/dist/bin/pushary-stop-hook.js +22 -24
  44. package/dist/bin/pushary-stopfailure-hook.js +22 -24
  45. package/dist/bin/pushary-suggestions.js +2 -2
  46. package/dist/bin/pushary-transcript-register.js +18 -20
  47. package/dist/bin/pushary-transcripts.js +7 -9
  48. package/dist/bin/pushary-upgrade.js +17 -19
  49. package/dist/bin/pushary-wait.js +5 -5
  50. package/dist/bin/pushary.js +1 -1
  51. package/dist/{chunk-7LUFYRH4.js → chunk-2D7BAOMY.js} +7 -7
  52. package/dist/{chunk-3ZUTNTZA.js → chunk-2ZTL3PGN.js} +1 -1
  53. package/dist/{chunk-EPFDN2ZT.js → chunk-32RMQO5Z.js} +1 -1
  54. package/dist/{chunk-EZIEXHYM.js → chunk-3AJIK5AO.js} +5 -2
  55. package/dist/{chunk-IPHOUBTU.js → chunk-4PX4QK2Z.js} +1 -1
  56. package/dist/{chunk-UW4LWAWY.js → chunk-4RF52LSU.js} +8 -8
  57. package/dist/{chunk-UOTSWS5N.js → chunk-67YNQ3QA.js} +43 -98
  58. package/dist/{chunk-G4TL4YWH.js → chunk-6AEM5RCS.js} +1 -1
  59. package/dist/{chunk-G24SEVCI.js → chunk-6VANQN5Q.js} +3 -7
  60. package/dist/{chunk-CKOLDI47.js → chunk-BDI2MXTT.js} +2 -2
  61. package/dist/{chunk-YKLCWWEI.js → chunk-BMANRGJQ.js} +1 -1
  62. package/dist/{chunk-2XUPP4WM.js → chunk-CUEZMPLJ.js} +1 -1
  63. package/dist/{chunk-A4R6BZCK.js → chunk-D62EONG3.js} +1 -1
  64. package/dist/{chunk-ZDAT5KMU.js → chunk-D6PRC3YQ.js} +1 -1
  65. package/dist/{chunk-MOY5X6HJ.js → chunk-DLLAWCQB.js} +1 -1
  66. package/dist/{chunk-DSTLA2TC.js → chunk-GOPIMNVC.js} +1 -1
  67. package/dist/{chunk-CH6DTGQR.js → chunk-HBHEX3JQ.js} +3 -3
  68. package/dist/{chunk-DNVDKHQD.js → chunk-HCMM3BOW.js} +226 -58
  69. package/dist/{chunk-76XICHYT.js → chunk-HGUZOUB2.js} +4 -4
  70. package/dist/{chunk-E3NF5CL2.js → chunk-HQIVR7MM.js} +6 -3
  71. package/dist/{chunk-JDFB3YKG.js → chunk-I6GZX3FT.js} +3 -3
  72. package/dist/{chunk-3OXHYPZG.js → chunk-IM2A5H33.js} +4 -1
  73. package/dist/{chunk-V27AHID3.js → chunk-IVQMNLSA.js} +3 -14
  74. package/dist/{chunk-4LL6LG5X.js → chunk-KAVDAZ7A.js} +43 -1
  75. package/dist/chunk-KBITFQST.js +6 -0
  76. package/dist/{chunk-AMZJ3CKW.js → chunk-KI4PA7MQ.js} +1 -1
  77. package/dist/{chunk-IAEXPTXA.js → chunk-N4VNLISN.js} +2 -2
  78. package/dist/{chunk-VX3BWMC5.js → chunk-OEKFD5BC.js} +1 -1
  79. package/dist/{chunk-QF4RI5CD.js → chunk-OI3DJA7N.js} +1 -1
  80. package/dist/{chunk-WB2C7VPF.js → chunk-OW6AZDY2.js} +3 -3
  81. package/dist/{chunk-4XXUHWIK.js → chunk-QHWXUNMN.js} +1 -1
  82. package/dist/{chunk-M7WWEFG6.js → chunk-QQGNFHIK.js} +2 -2
  83. package/dist/{chunk-YJAQRKH5.js → chunk-RLXVW4AM.js} +1 -1
  84. package/dist/{chunk-GUVCYYDK.js → chunk-S5CMLR35.js} +94 -15
  85. package/dist/{chunk-EUAQ4YAN.js → chunk-UCS65VVR.js} +1 -1
  86. package/dist/{chunk-WM5XMCID.js → chunk-VOSGZ6PH.js} +4 -4
  87. package/dist/{chunk-QUE76J4H.js → chunk-VTO3TNIB.js} +1 -1
  88. package/dist/{chunk-6FAHPGCZ.js → chunk-WDKHZRAS.js} +183 -25
  89. package/dist/{chunk-5GLPYGKS.js → chunk-WRDLHR2F.js} +2 -2
  90. package/dist/{chunk-RVSEGFPD.js → chunk-XLOELLAS.js} +9 -4
  91. package/dist/chunk-XS33QBJA.js +112 -0
  92. package/dist/{chunk-FQUV5KTM.js → chunk-Z2UWFEJW.js} +8 -8
  93. package/dist/src/index.d.ts +8 -1
  94. package/dist/src/index.js +11 -12
  95. package/package.json +1 -1
  96. package/dist/chunk-BC3VCZ3E.js +0 -86
  97. package/dist/chunk-RER3FRTD.js +0 -265
  98. package/dist/chunk-X7YWD6IS.js +0 -44
@@ -8,7 +8,7 @@ import {
8
8
  describeIdleWindow,
9
9
  startProcessOwnership,
10
10
  withSpawnProcessOwnership
11
- } from "../chunk-M7WWEFG6.js";
11
+ } from "../chunk-QQGNFHIK.js";
12
12
  import {
13
13
  isDeferAnswer
14
14
  } from "../chunk-YHG74UFF.js";
@@ -17,70 +17,73 @@ import {
17
17
  loadSessionKey,
18
18
  registerTranscript,
19
19
  registeredTranscriptEnabled
20
- } from "../chunk-UW4LWAWY.js";
21
- import "../chunk-YKLCWWEI.js";
22
- import "../chunk-WM5XMCID.js";
23
- import "../chunk-3ZUTNTZA.js";
24
- import "../chunk-2XUPP4WM.js";
25
- import "../chunk-CH6DTGQR.js";
20
+ } from "../chunk-4RF52LSU.js";
21
+ import "../chunk-BMANRGJQ.js";
22
+ import "../chunk-VOSGZ6PH.js";
23
+ import "../chunk-2ZTL3PGN.js";
24
+ import "../chunk-CUEZMPLJ.js";
25
+ import "../chunk-HBHEX3JQ.js";
26
26
  import {
27
27
  ensureDaemonRunning
28
- } from "../chunk-RVSEGFPD.js";
29
- import "../chunk-7LUFYRH4.js";
30
- import "../chunk-DSTLA2TC.js";
31
- import "../chunk-EZIEXHYM.js";
28
+ } from "../chunk-XLOELLAS.js";
29
+ import "../chunk-2D7BAOMY.js";
30
+ import "../chunk-GOPIMNVC.js";
31
+ import "../chunk-3AJIK5AO.js";
32
32
  import {
33
33
  encryptRecord
34
- } from "../chunk-IPHOUBTU.js";
34
+ } from "../chunk-4PX4QK2Z.js";
35
35
  import "../chunk-EQRS3SGM.js";
36
36
  import {
37
37
  transcriptsEnabled
38
- } from "../chunk-QF4RI5CD.js";
38
+ } from "../chunk-OI3DJA7N.js";
39
39
  import {
40
40
  needsShell,
41
41
  spawnClaude
42
42
  } from "../chunk-XHKBHWLX.js";
43
43
  import "../chunk-L4ZCFMWW.js";
44
44
  import {
45
+ DEFAULT_SESSION,
45
46
  fetchModeState,
46
47
  getPolicy,
47
- phoneDrivenEnv,
48
- repoKeyFor,
49
- reportEvent
50
- } from "../chunk-DNVDKHQD.js";
51
- import {
52
- DEFAULT_SESSION,
53
- askUser,
54
- cancelQuestion,
55
48
  markPendingCommandDelivered,
56
49
  markPendingCommandFailed,
50
+ phoneDrivenEnv,
57
51
  readPendingCommand,
58
52
  readPendingCommandById,
59
53
  removePendingCommand,
60
- savePendingCommand,
61
- waitForAnswer
62
- } from "../chunk-RER3FRTD.js";
54
+ repoKeyFor,
55
+ reportEvent,
56
+ savePendingCommand
57
+ } from "../chunk-HCMM3BOW.js";
58
+ import {
59
+ DECISION_STOP_REASON,
60
+ askUser,
61
+ cancelQuestion,
62
+ isDecisionStopped,
63
+ isLocalQuestionHandoff,
64
+ nextLongPollMs,
65
+ waitForAnswer,
66
+ withdrawQuestion
67
+ } from "../chunk-XS33QBJA.js";
63
68
  import {
64
69
  UNNAMED_ACTION,
65
70
  deriveToolTarget,
66
71
  describeToolCall,
67
72
  scopePathFor
68
- } from "../chunk-CKOLDI47.js";
69
- import "../chunk-V27AHID3.js";
73
+ } from "../chunk-BDI2MXTT.js";
74
+ import "../chunk-IVQMNLSA.js";
70
75
  import {
71
76
  getMachineId
72
- } from "../chunk-IAEXPTXA.js";
73
- import "../chunk-WB2C7VPF.js";
77
+ } from "../chunk-N4VNLISN.js";
78
+ import "../chunk-OW6AZDY2.js";
74
79
  import "../chunk-GMXKITVA.js";
75
80
  import "../chunk-DINDL2CF.js";
76
- import "../chunk-GUVCYYDK.js";
77
- import "../chunk-BC3VCZ3E.js";
81
+ import "../chunk-S5CMLR35.js";
78
82
  import "../chunk-EYRGBYZG.js";
79
83
  import "../chunk-SAF6HGAA.js";
80
84
  import "../chunk-7QLSKOSU.js";
81
85
  import "../chunk-KZERVKTD.js";
82
- import "../chunk-X7YWD6IS.js";
83
- import "../chunk-4LL6LG5X.js";
86
+ import "../chunk-KAVDAZ7A.js";
84
87
  import {
85
88
  getApiKey,
86
89
  getBaseUrl
@@ -89,7 +92,7 @@ import {
89
92
  KILL_REASON,
90
93
  redactSecrets,
91
94
  resolveGate
92
- } from "../chunk-6FAHPGCZ.js";
95
+ } from "../chunk-WDKHZRAS.js";
93
96
 
94
97
  // src/wrapper/localPassthrough.ts
95
98
  var SIGNAL_NUMBERS = {
@@ -448,7 +451,11 @@ var createRemoteApprover = (deps) => {
448
451
  const waitForPhone = async (entry, correlationId, windowMs) => {
449
452
  const deadline = now() + windowMs;
450
453
  while (now() < deadline && !entry.controller.signal.aborted) {
451
- const remaining = Math.min(Math.max(deadline - now(), 1e3), 3e4);
454
+ const hold = nextLongPollMs(deadline, now());
455
+ if (hold === null) {
456
+ await sleep(Math.min(500, Math.max(0, deadline - now())));
457
+ continue;
458
+ }
452
459
  const abortPromise = new Promise((resolve) => {
453
460
  entry.controller.signal.addEventListener("abort", () => resolve({ answered: false, aborted: true }), {
454
461
  once: true
@@ -457,18 +464,15 @@ var createRemoteApprover = (deps) => {
457
464
  let outcome;
458
465
  try {
459
466
  outcome = await Promise.race([
460
- waitForAnswer2(deps.apiKey, correlationId, remaining),
467
+ waitForAnswer2(deps.apiKey, correlationId, hold, AbortSignal.any([entry.controller.signal, AbortSignal.timeout(Math.max(1, deadline - now()))])),
461
468
  abortPromise
462
469
  ]);
463
470
  } catch {
464
- if (now() + 1e3 >= deadline) break;
465
- await sleep(1e3);
466
- continue;
471
+ return { answered: false, status: "unavailable" };
467
472
  }
468
- if ("aborted" in outcome) return { answered: false };
469
- if (outcome.answered) return outcome;
470
- if (now() + 500 >= deadline) break;
471
- await sleep(500);
473
+ if ("aborted" in outcome) return { answered: false, status: "cancelled" };
474
+ if (isDecisionStopped(outcome) || outcome.answered) return outcome;
475
+ await sleep(Math.min(500, Math.max(0, deadline - now())));
472
476
  }
473
477
  return { answered: false };
474
478
  };
@@ -524,23 +528,31 @@ var createRemoteApprover = (deps) => {
524
528
  repoKey: repoKeyFor(deps.cwd)
525
529
  });
526
530
  } catch {
527
- return applyTimeout(policy, input);
531
+ return deny("Pushary could not create a verifiable approval. Retry the action.");
528
532
  }
533
+ if (isDecisionStopped(question)) return deny(DECISION_STOP_REASON);
529
534
  entry.correlationId = question.correlationId;
530
- if (question.noDevices) return applyTimeout(policy, input);
531
- const answer = await waitForPhone(entry, question.correlationId, windowFor(policy));
535
+ const localHandoff = isLocalQuestionHandoff(question);
536
+ let answer = question.answered ? { ...question, answered: true } : localHandoff ? { answered: false } : await waitForPhone(entry, question.correlationId, windowFor(policy));
537
+ if (isDecisionStopped(answer) || entry.controller.signal.aborted) return deny(DECISION_STOP_REASON);
538
+ if (!answer.answered) {
539
+ answer = await withdrawQuestion(deps.apiKey, question.correlationId, { cancelQuestion: cancelQuestion2, waitForAnswer: waitForAnswer2 });
540
+ entry.correlationId = void 0;
541
+ if (isDecisionStopped(answer)) return deny(DECISION_STOP_REASON);
542
+ }
532
543
  answered = answer.answered;
533
544
  if (answer.answered) {
534
545
  if (isDeferAnswer(answer.value)) return deny("Deferred \u2014 handle it on the machine");
535
546
  return answer.value === "yes" ? allow(input) : deny("Denied from your phone");
536
547
  }
548
+ if (localHandoff) return deny("This remote session has no local approval UI. Continue in an interactive session or choose a mode that permits remote approval.");
537
549
  return applyTimeout(policy, input);
538
550
  } catch {
539
551
  return deny("Approval unavailable; denied by default");
540
552
  } finally {
541
553
  if (options?.signal) options.signal.removeEventListener("abort", outerAbort);
542
554
  if (entry.correlationId && !answered && !tornDown) {
543
- void cancelQuestion2(deps.apiKey, entry.correlationId).catch(() => {
555
+ await cancelQuestion2(deps.apiKey, entry.correlationId).catch(() => {
544
556
  });
545
557
  }
546
558
  pending.delete(entry);
@@ -6,12 +6,13 @@ import {
6
6
  resolveHermesPython,
7
7
  runHermesPythonScript
8
8
  } from "../chunk-N6FAXQDD.js";
9
+ import "../chunk-KBITFQST.js";
9
10
  import {
10
11
  removeInstructionBlock,
11
12
  shortenHome,
12
13
  unregisterPluginLocation,
13
14
  vscodeSettingsTargets
14
- } from "../chunk-G24SEVCI.js";
15
+ } from "../chunk-6VANQN5Q.js";
15
16
  import {
16
17
  OPENCODE_PLUGIN_SIBLING,
17
18
  OPENCODE_PLUGIN_SIBLING_CONTENTS,
@@ -21,39 +22,39 @@ import {
21
22
  openCodePluginDir,
22
23
  openCodePluginFile,
23
24
  removeOpenCodeMcpServer
24
- } from "../chunk-E3NF5CL2.js";
25
+ } from "../chunk-HQIVR7MM.js";
25
26
  import {
26
27
  isClaudeMcpSnapshot,
27
28
  removeClaudeMcpServers,
28
29
  removePusharySettings,
29
30
  restoreClaudeMcpServer
30
- } from "../chunk-76XICHYT.js";
31
+ } from "../chunk-HGUZOUB2.js";
31
32
  import {
32
33
  hookOwnerOf,
33
34
  isHookTakeoverReceipt,
34
35
  restoreHookTakeover,
35
36
  restoreMcpServer
36
- } from "../chunk-WM5XMCID.js";
37
- import "../chunk-3ZUTNTZA.js";
38
- import "../chunk-2XUPP4WM.js";
37
+ } from "../chunk-VOSGZ6PH.js";
38
+ import "../chunk-2ZTL3PGN.js";
39
+ import "../chunk-CUEZMPLJ.js";
39
40
  import {
40
41
  pusharyHookExecutable
41
- } from "../chunk-CH6DTGQR.js";
42
+ } from "../chunk-HBHEX3JQ.js";
42
43
  import {
43
44
  disableDaemonService
44
- } from "../chunk-RVSEGFPD.js";
45
+ } from "../chunk-XLOELLAS.js";
45
46
  import {
46
47
  removeGeminiSettings
47
- } from "../chunk-7LUFYRH4.js";
48
- import "../chunk-DSTLA2TC.js";
49
- import "../chunk-EZIEXHYM.js";
48
+ } from "../chunk-2D7BAOMY.js";
49
+ import "../chunk-GOPIMNVC.js";
50
+ import "../chunk-3AJIK5AO.js";
50
51
  import "../chunk-XHKBHWLX.js";
51
52
  import {
52
53
  execNpm
53
54
  } from "../chunk-L4ZCFMWW.js";
54
- import "../chunk-CKOLDI47.js";
55
- import "../chunk-V27AHID3.js";
56
- import "../chunk-IAEXPTXA.js";
55
+ import "../chunk-BDI2MXTT.js";
56
+ import "../chunk-IVQMNLSA.js";
57
+ import "../chunk-N4VNLISN.js";
57
58
  import {
58
59
  claudeJson,
59
60
  claudeMcpReceipt,
@@ -79,28 +80,28 @@ import {
79
80
  restoreCodexHookTrust,
80
81
  vscodePluginDir,
81
82
  vscodePluginMcp
82
- } from "../chunk-WB2C7VPF.js";
83
+ } from "../chunk-OW6AZDY2.js";
83
84
  import {
84
85
  exitOnHelpFlag
85
- } from "../chunk-MOY5X6HJ.js";
86
- import "../chunk-ZDAT5KMU.js";
86
+ } from "../chunk-DLLAWCQB.js";
87
+ import "../chunk-D6PRC3YQ.js";
87
88
  import {
88
89
  rejectUnknownFlags
89
90
  } from "../chunk-GMXKITVA.js";
90
91
  import "../chunk-DINDL2CF.js";
91
92
  import {
92
93
  removeClaudeAlias
93
- } from "../chunk-BC3VCZ3E.js";
94
+ } from "../chunk-S5CMLR35.js";
94
95
  import "../chunk-7QLSKOSU.js";
95
96
  import {
96
97
  EXIT
97
98
  } from "../chunk-KZERVKTD.js";
98
- import "../chunk-4LL6LG5X.js";
99
+ import "../chunk-KAVDAZ7A.js";
99
100
  import "../chunk-2UMNXADU.js";
100
101
  import {
101
102
  CURSOR_GATE_EVENTS,
102
103
  HOOK_LOCATOR_BINARY
103
- } from "../chunk-6FAHPGCZ.js";
104
+ } from "../chunk-WDKHZRAS.js";
104
105
 
105
106
  // bin/pushary-clean.ts
106
107
  import { existsSync, readFileSync, copyFileSync, readdirSync } from "fs";
@@ -1,24 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import "../chunk-3OXHYPZG.js";
2
+ import "../chunk-IM2A5H33.js";
3
3
  import {
4
4
  LiveTranscriptRelay,
5
5
  liveSessionSleep,
6
6
  postLiveSession,
7
7
  postLiveSessionUntilAccepted,
8
8
  startLiveSessionHeartbeat
9
- } from "../chunk-4XXUHWIK.js";
9
+ } from "../chunk-QHWXUNMN.js";
10
10
  import {
11
11
  SESSION_IDLE_TIMEOUT_MS,
12
12
  describeIdleWindow,
13
13
  idleDeadlinePassed,
14
14
  startProcessOwnership,
15
15
  withSpawnProcessOwnership
16
- } from "../chunk-M7WWEFG6.js";
17
- import "../chunk-IPHOUBTU.js";
16
+ } from "../chunk-QQGNFHIK.js";
17
+ import "../chunk-4PX4QK2Z.js";
18
18
  import "../chunk-EQRS3SGM.js";
19
19
  import {
20
20
  transcriptsEnabled
21
- } from "../chunk-QF4RI5CD.js";
21
+ } from "../chunk-OI3DJA7N.js";
22
22
  import {
23
23
  spawnClaude
24
24
  } from "../chunk-XHKBHWLX.js";
@@ -27,40 +27,42 @@ import {
27
27
  } from "../chunk-L4ZCFMWW.js";
28
28
  import {
29
29
  getMachineId
30
- } from "../chunk-IAEXPTXA.js";
31
- import "../chunk-WB2C7VPF.js";
32
- import "../chunk-GUVCYYDK.js";
33
- import "../chunk-BC3VCZ3E.js";
34
- import "../chunk-X7YWD6IS.js";
35
- import "../chunk-4LL6LG5X.js";
30
+ } from "../chunk-N4VNLISN.js";
31
+ import "../chunk-OW6AZDY2.js";
32
+ import "../chunk-S5CMLR35.js";
33
+ import {
34
+ callMcpTool
35
+ } from "../chunk-EYRGBYZG.js";
36
+ import "../chunk-SAF6HGAA.js";
37
+ import "../chunk-7QLSKOSU.js";
38
+ import "../chunk-KZERVKTD.js";
39
+ import "../chunk-KAVDAZ7A.js";
36
40
  import {
37
41
  getApiKey,
38
42
  getBaseUrl
39
43
  } from "../chunk-2UMNXADU.js";
40
- import "../chunk-6FAHPGCZ.js";
44
+ import {
45
+ parseAgentQuestion,
46
+ parseAgentQuestionAnswers
47
+ } from "../chunk-WDKHZRAS.js";
41
48
 
42
49
  // bin/pushary-codex-bridge.ts
43
50
  import { existsSync } from "fs";
44
51
 
45
52
  // src/codex-bridge.ts
53
+ import { randomUUID } from "crypto";
46
54
  import { spawn } from "child_process";
47
55
  import { basename } from "path";
48
56
  var MAX_RPC_LINE = 1024 * 1024;
49
57
  var STDERR_TAIL_LIMIT = 2e3;
50
58
  var FAILURE_MESSAGE_MAX = 240;
51
59
  var MAX_PENDING_RPC = 8;
60
+ var LOCAL_QUESTION_UNAVAILABLE = "Pushary handed this question to the current client, but this Codex bridge has no local question UI. Continue in an interactive Codex session, or choose a Pushary mode that permits remote questions.";
52
61
  var MAX_TRANSCRIPT_TEXT = 32 * 1024;
53
62
  var MAX_MESSAGE_BUFFERS = 16;
54
63
  var JsonRpcClient = class {
55
- child;
56
- pending = /* @__PURE__ */ new Map();
57
- notifications = /* @__PURE__ */ new Set();
58
- nextId = 1;
59
- buffer = "";
60
- closed = false;
61
- stderrTail = "";
62
- failure = null;
63
- constructor(command, cwd, timeoutMs) {
64
+ constructor(command, cwd, timeoutMs, bridgeOptions) {
65
+ this.bridgeOptions = bridgeOptions;
64
66
  const options = { cwd, env: process.env, stdio: ["pipe", "pipe", "pipe"] };
65
67
  this.child = process.platform === "win32" ? spawnClaude(command.command, command.args ?? [], options) : spawn(command.command, command.args ?? [], options);
66
68
  this.child.stdout?.setEncoding("utf8");
@@ -71,6 +73,15 @@ var JsonRpcClient = class {
71
73
  this.child.once("exit", (code, signal) => this.fail(new Error(this.exitMessage(code, signal))));
72
74
  this.timeoutMs = timeoutMs;
73
75
  }
76
+ child;
77
+ pending = /* @__PURE__ */ new Map();
78
+ notifications = /* @__PURE__ */ new Set();
79
+ serverRequests = /* @__PURE__ */ new Map();
80
+ nextId = 1;
81
+ buffer = "";
82
+ closed = false;
83
+ stderrTail = "";
84
+ failure = null;
74
85
  keepStderrTail(chunk) {
75
86
  this.stderrTail = (this.stderrTail + chunk).slice(-STDERR_TAIL_LIMIT);
76
87
  }
@@ -109,6 +120,8 @@ var JsonRpcClient = class {
109
120
  async close() {
110
121
  if (this.closed) return;
111
122
  this.closed = true;
123
+ for (const request of this.serverRequests.values()) request.abort();
124
+ this.serverRequests.clear();
112
125
  this.child.stdin?.end();
113
126
  if (this.child.exitCode != null) return;
114
127
  await Promise.race([
@@ -147,8 +160,28 @@ var JsonRpcClient = class {
147
160
  } catch {
148
161
  return;
149
162
  }
150
- if (typeof message.id === "number" && message.method) {
151
- const result = message.method === "item/commandExecution/requestApproval" || message.method === "item/fileChange/requestApproval" ? { decision: "decline" } : message.method === "applyPatchApproval" || message.method === "execCommandApproval" ? { decision: { denied: { rejection: "No interactive Codex client is attached" } } } : message.method === "item/tool/requestUserInput" ? { answers: {} } : null;
163
+ if (message.id !== void 0 && message.method === "item/tool/requestUserInput") {
164
+ const id = message.id;
165
+ if (this.serverRequests.has(id)) return;
166
+ if (this.serverRequests.size >= MAX_PENDING_RPC) {
167
+ this.write({ id, error: { code: -32e3, message: "Too many pending user questions" } });
168
+ return;
169
+ }
170
+ const controller = new AbortController();
171
+ this.serverRequests.set(id, controller);
172
+ const signal = AbortSignal.any([controller.signal, this.bridgeOptions.signal]);
173
+ void answerCodexQuestions(this.bridgeOptions, message.params ?? {}, signal).then(
174
+ (result) => {
175
+ if (!signal.aborted && !this.closed) this.write({ id, result });
176
+ },
177
+ (error) => {
178
+ if (!signal.aborted && !this.closed) this.write({ id, error: { code: -32e3, message: error instanceof Error && error.message === LOCAL_QUESTION_UNAVAILABLE ? LOCAL_QUESTION_UNAVAILABLE : "No user answer was received through Pushary" } });
179
+ }
180
+ ).finally(() => this.serverRequests.delete(id));
181
+ return;
182
+ }
183
+ if (message.id !== void 0 && message.method) {
184
+ const result = message.method === "item/commandExecution/requestApproval" || message.method === "item/fileChange/requestApproval" ? { decision: "decline" } : message.method === "applyPatchApproval" || message.method === "execCommandApproval" ? { decision: { denied: { rejection: "No interactive Codex client is attached" } } } : null;
152
185
  this.write(result ? { id: message.id, result } : { id: message.id, error: { code: -32601, message: "Unsupported server request" } });
153
186
  return;
154
187
  }
@@ -161,6 +194,10 @@ var JsonRpcClient = class {
161
194
  else pending.resolve(message.result);
162
195
  return;
163
196
  }
197
+ if (message.method === "serverRequest/resolved") {
198
+ const id = message.params?.requestId;
199
+ if (typeof id === "number" || typeof id === "string") this.serverRequests.get(id)?.abort();
200
+ }
164
201
  if (message.method) {
165
202
  for (const listener of this.notifications) listener(message);
166
203
  }
@@ -169,6 +206,8 @@ var JsonRpcClient = class {
169
206
  if (this.closed) return;
170
207
  this.closed = true;
171
208
  this.failure = error;
209
+ for (const request of this.serverRequests.values()) request.abort();
210
+ this.serverRequests.clear();
172
211
  this.rejectPending(error);
173
212
  if (this.child.exitCode == null) this.child.kill();
174
213
  }
@@ -188,11 +227,67 @@ var rpcTurnId = (result) => {
188
227
  const response = object(result);
189
228
  return string(object(response?.turn)?.id) ?? string(response?.turnId);
190
229
  };
230
+ var answerCodexQuestions = async (options, params, signal) => {
231
+ if (!Array.isArray(params.questions) || !params.questions.length || params.questions.length > 3) throw new Error("Invalid questions");
232
+ const answers = /* @__PURE__ */ Object.create(null);
233
+ const deadline = Date.now() + Math.min(12e4, typeof params.autoResolutionMs === "number" ? Math.max(0, params.autoResolutionMs) : 12e4);
234
+ for (const raw of params.questions) {
235
+ if (signal.aborted || Date.now() >= deadline) throw new Error("Question expired");
236
+ const question = object(raw);
237
+ const id = string(question?.id);
238
+ const prompt = string(question?.question);
239
+ if (!id || !prompt || id in answers || question?.isSecret === true) throw new Error("Unsupported question");
240
+ const labels = Array.isArray(question?.options) ? question.options.map((option) => string(object(option)?.label)).filter((label) => !!label) : [];
241
+ const structured = labels.length >= 2 ? parseAgentQuestion({ ...question, multiSelect: false, allowOther: question?.isOther === true }) : void 0;
242
+ let correlationId = null;
243
+ let settled = false;
244
+ const call = (name, args, timeoutMs, waitSignal = signal) => callMcpTool(options.apiKey, name, args, { baseUrl: options.baseUrl, signal: waitSignal, timeoutMs, maxRetries: 1 });
245
+ try {
246
+ signal.throwIfAborted();
247
+ if (Date.now() >= deadline) throw new Error("Question expired");
248
+ let result = await call("ask_user", {
249
+ requestId: randomUUID(),
250
+ question: prompt,
251
+ type: labels.length >= 2 ? "select" : "input",
252
+ ...labels.length >= 2 ? { options: labels } : {},
253
+ ...structured ? { questions: [structured] } : {},
254
+ context: string(question?.header),
255
+ agentName: `Codex - ${basename(options.cwd)}`,
256
+ sessionId: params.threadId,
257
+ machineId: options.machineId,
258
+ toolName: "request_user_input",
259
+ wait: false,
260
+ waitEndsAt: new Date(deadline).toISOString()
261
+ }, Math.min(1e4, deadline - Date.now()));
262
+ correlationId = string(result.correlationId);
263
+ while (!result.answered) {
264
+ if (result.status === "terminal" || result.status === "notified" || result.suppressed || result.mode === "terminal_only" || result.mode === "notify_only") throw new Error(LOCAL_QUESTION_UNAVAILABLE);
265
+ if (!correlationId || result.handoffAction === "stop" || result.noDevices || result.status && result.status !== "pending") throw new Error("Question unavailable");
266
+ const remaining = deadline - Date.now();
267
+ if (remaining <= 0) throw new Error("Question expired");
268
+ result = await call("wait_for_answer", { correlationId, timeoutMs: Math.min(25e3, remaining) }, Math.min(27e3, remaining));
269
+ if (!result.answered) await liveSessionSleep(100, signal);
270
+ }
271
+ if (signal.aborted || Date.now() >= deadline) throw new Error("Question expired");
272
+ settled = true;
273
+ const rawValue = string(result.value);
274
+ const value = rawValue && structured ? (parseAgentQuestionAnswers([structured], rawValue) ?? parseAgentQuestionAnswers([structured], JSON.stringify({ [prompt]: rawValue })))?.[prompt] : rawValue;
275
+ if (!value || value === "defer" || labels.length && question?.isOther !== true && !labels.includes(value)) throw new Error("Invalid answer");
276
+ answers[id] = { answers: [value] };
277
+ } finally {
278
+ if (correlationId && !settled) {
279
+ await call("cancel_question", { correlationId }, 3e3, AbortSignal.timeout(3e3)).catch(() => {
280
+ });
281
+ }
282
+ }
283
+ }
284
+ return { answers };
285
+ };
191
286
  var runCodexBridge = async (options) => {
192
287
  const timeoutMs = options.requestTimeoutMs ?? 1e4;
193
288
  const now = options.now ?? Date.now;
194
289
  const idleTimeoutMs = options.idleTimeoutMs ?? SESSION_IDLE_TIMEOUT_MS;
195
- const rpc = new JsonRpcClient(options.appServer, options.cwd, timeoutMs);
290
+ const rpc = new JsonRpcClient(options.appServer, options.cwd, timeoutMs, options);
196
291
  let threadId = options.resumeThreadId ?? null;
197
292
  let activeTurnId = null;
198
293
  let lastCompletedTurnId = null;
@@ -249,7 +344,7 @@ var runCodexBridge = async (options) => {
249
344
  try {
250
345
  await rpc.request("initialize", {
251
346
  clientInfo: { name: "pushary", title: "Pushary", version: "1" },
252
- capabilities: { experimentalApi: false, requestAttestation: false }
347
+ capabilities: { experimentalApi: true, requestAttestation: false }
253
348
  });
254
349
  rpc.notify("initialized");
255
350
  const thread = options.resumeThreadId ? await rpc.request("thread/resume", { threadId: options.resumeThreadId, cwd: options.cwd, excludeTurns: true }) : await rpc.request("thread/start", { cwd: options.cwd, model: options.model ?? null });