@pushary/agent-hooks 1.9.44 → 1.9.45

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 (106) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +1 -1
  3. package/dist/bin/pushary-bell-hook.js +25 -26
  4. package/dist/bin/pushary-bell.js +9 -9
  5. package/dist/bin/pushary-claude.js +32 -34
  6. package/dist/bin/pushary-clean.js +30 -31
  7. package/dist/bin/pushary-codex-bridge.js +22 -690
  8. package/dist/bin/pushary-codex-hook.js +32 -34
  9. package/dist/bin/pushary-codex-session.js +437 -41
  10. package/dist/bin/pushary-codex.js +12 -13
  11. package/dist/bin/pushary-connect.js +24 -25
  12. package/dist/bin/pushary-cowork.js +5 -5
  13. package/dist/bin/pushary-daemon.js +42 -43
  14. package/dist/bin/pushary-disconnect.js +24 -25
  15. package/dist/bin/pushary-doctor.js +35 -36
  16. package/dist/bin/pushary-elicitation-hook.js +25 -26
  17. package/dist/bin/pushary-gemini-bridge.js +22 -22
  18. package/dist/bin/pushary-gemini-hook.js +29 -31
  19. package/dist/bin/pushary-hook.js +30 -31
  20. package/dist/bin/pushary-login.js +13 -13
  21. package/dist/bin/pushary-logout.js +24 -25
  22. package/dist/bin/pushary-mcp.js +9 -9
  23. package/dist/bin/pushary-mode.js +12 -12
  24. package/dist/bin/pushary-notification-hook.js +24 -25
  25. package/dist/bin/pushary-opencode-hook.js +28 -30
  26. package/dist/bin/pushary-permission-denied-hook.js +29 -30
  27. package/dist/bin/pushary-permission-hook.js +28 -29
  28. package/dist/bin/pushary-post-hook.js +24 -25
  29. package/dist/bin/pushary-prompt-hook.js +24 -25
  30. package/dist/bin/pushary-session-end-hook.js +24 -25
  31. package/dist/bin/pushary-session-start-hook.js +29 -30
  32. package/dist/bin/pushary-setup.js +37 -38
  33. package/dist/bin/pushary-stats.js +5 -5
  34. package/dist/bin/pushary-status.js +27 -28
  35. package/dist/bin/pushary-stop-hook.js +24 -25
  36. package/dist/bin/pushary-stopfailure-hook.js +24 -25
  37. package/dist/bin/pushary-suggestions.js +12 -12
  38. package/dist/bin/pushary-transcript-register.js +17 -17
  39. package/dist/bin/pushary-transcripts.js +7 -7
  40. package/dist/bin/pushary-upgrade.js +31 -32
  41. package/dist/bin/pushary-wait.js +12 -12
  42. package/dist/bin/pushary.js +6 -6
  43. package/dist/{chunk-MIBCRQWK.js → chunk-2DORVRDB.js} +2 -2
  44. package/dist/{chunk-KILK6SWZ.js → chunk-2FUHSKXH.js} +4 -4
  45. package/dist/{chunk-UY5E2UUW.js → chunk-2SXXH6JY.js} +5 -5
  46. package/dist/{chunk-UJXLVH66.js → chunk-335ZDJ7R.js} +1 -1
  47. package/dist/{chunk-OZ7MLVDM.js → chunk-3PM62C2R.js} +8 -8
  48. package/dist/{chunk-GRAPJKDK.js → chunk-46F4BRUQ.js} +1 -1
  49. package/dist/{chunk-WMO2XVG4.js → chunk-4A6VUVXV.js} +1 -1
  50. package/dist/{chunk-T3KMJI4W.js → chunk-57M4ERJK.js} +2 -2
  51. package/dist/{chunk-SI2NC6UU.js → chunk-5DCZL3BC.js} +3 -3
  52. package/dist/chunk-6JPDAUFP.js +866 -0
  53. package/dist/{chunk-S2ALW446.js → chunk-6TYKZ4DN.js} +3 -3
  54. package/dist/{chunk-WBD7HZEZ.js → chunk-7SGYCU5C.js} +1 -1
  55. package/dist/{chunk-KYLY6UMN.js → chunk-7URSJMAB.js} +1 -1
  56. package/dist/{chunk-T4CKP47U.js → chunk-ASK2MSD2.js} +5 -5
  57. package/dist/{chunk-W2TA4EVZ.js → chunk-AUOSFZJ3.js} +7 -7
  58. package/dist/{chunk-TWATOPHK.js → chunk-BT57FUMT.js} +2 -2
  59. package/dist/{chunk-GOEIW2VH.js → chunk-CDERSTBO.js} +182 -39
  60. package/dist/{chunk-M2L7625S.js → chunk-CFMCV6Z7.js} +2 -2
  61. package/dist/{chunk-FI2F5KZJ.js → chunk-DSGB5JLG.js} +4 -4
  62. package/dist/{chunk-2NVXFWQY.js → chunk-ECFEYB6Z.js} +1 -1
  63. package/dist/{chunk-DD6IBKEJ.js → chunk-EH5ZDG6V.js} +4 -4
  64. package/dist/{chunk-PQAFH4DF.js → chunk-EKGQEDQ4.js} +2 -2
  65. package/dist/{chunk-OWQOMGBF.js → chunk-EWC4P4ZL.js} +1 -1
  66. package/dist/{chunk-LEHBWBCG.js → chunk-FOUK4CBO.js} +4 -4
  67. package/dist/{chunk-4WBAHVBK.js → chunk-GQ5KCVGK.js} +6 -6
  68. package/dist/{chunk-BKK3JCTD.js → chunk-GZSSAY6N.js} +12 -14
  69. package/dist/{chunk-PGHN5D4N.js → chunk-H4ODQD3Y.js} +2 -2
  70. package/dist/{chunk-CAJCCQ7N.js → chunk-HHX6IRSH.js} +1 -1
  71. package/dist/{chunk-3IRC3XFG.js → chunk-IFT3GU7Y.js} +2 -2
  72. package/dist/{chunk-UC4PNLMK.js → chunk-IKYI63RF.js} +6 -6
  73. package/dist/{chunk-4JHEZ2PV.js → chunk-IOVJVGIT.js} +4 -4
  74. package/dist/{chunk-JNU3LV47.js → chunk-K7EGWMDU.js} +18 -3
  75. package/dist/{chunk-CESLE4UN.js → chunk-KS7NA5CK.js} +7 -7
  76. package/dist/{chunk-IWHDNMM3.js → chunk-N2K3BSCA.js} +5 -5
  77. package/dist/{chunk-HV4TEKNP.js → chunk-NJN5NRHZ.js} +4 -4
  78. package/dist/{chunk-6EV3B5CD.js → chunk-OHFC3GJY.js} +1 -1
  79. package/dist/{chunk-SWDPCDLT.js → chunk-P2F3JUFX.js} +4 -4
  80. package/dist/{chunk-TKTS3SIM.js → chunk-PWUIPQ2I.js} +3 -3
  81. package/dist/{chunk-ALY7ZBNB.js → chunk-QQXP3CQA.js} +1 -1
  82. package/dist/{chunk-FYNMFSIZ.js → chunk-R7ZQHAVL.js} +2 -2
  83. package/dist/{chunk-FKDZU6YX.js → chunk-RBJWPUI6.js} +4 -4
  84. package/dist/{chunk-MRQJCIFO.js → chunk-RFQWB5ZV.js} +1 -1
  85. package/dist/{chunk-XPQDBTW4.js → chunk-S6EGJBI5.js} +1 -1
  86. package/dist/{chunk-53UZN7JW.js → chunk-SFQSOBLM.js} +3 -3
  87. package/dist/{chunk-75M7NFFC.js → chunk-SIN7NJD7.js} +1 -1
  88. package/dist/{chunk-OSMNLNPF.js → chunk-U45OINYS.js} +1 -1
  89. package/dist/{chunk-E76QCZIA.js → chunk-U6Z56IQS.js} +2 -2
  90. package/dist/{chunk-GBCR2FH2.js → chunk-VO6XI6AX.js} +3 -3
  91. package/dist/{chunk-7UM4NCUI.js → chunk-WDIX4XZH.js} +2 -2
  92. package/dist/{chunk-CEZTYTBN.js → chunk-WM76AN4X.js} +11 -13
  93. package/dist/{chunk-L2ZM7LXN.js → chunk-WPPKXVV3.js} +2 -2
  94. package/dist/{chunk-RGBTKOCC.js → chunk-XFHISCJM.js} +5 -5
  95. package/dist/{chunk-TJZUQRMH.js → chunk-YK7FGPYS.js} +3 -3
  96. package/dist/{chunk-42CVAOC5.js → chunk-YWIFK2UB.js} +2 -2
  97. package/dist/{chunk-UZVF6MP2.js → chunk-YXGWG5PO.js} +5 -5
  98. package/dist/{chunk-OR6KJOHE.js → chunk-ZSY6BWUD.js} +2 -2
  99. package/dist/{global-upgrade-QSSDYZCZ.js → global-upgrade-U6NWZA5W.js} +6 -6
  100. package/dist/{paths-55DDFLY4.js → paths-IQTB3ZZI.js} +2 -2
  101. package/dist/{reapply-MEU66ALO.js → reapply-LILGWJO3.js} +21 -22
  102. package/dist/src/index.js +19 -20
  103. package/dist/{upgrade-lock-YLXAU4AD.js → upgrade-lock-2BVFIDU4.js} +3 -3
  104. package/package.json +1 -1
  105. package/dist/chunk-5ZSBQHCM.js +0 -161
  106. package/dist/chunk-SVCT5D7K.js +0 -196
@@ -1,36 +1,50 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  exitOnHelpFlag
4
- } from "../chunk-OR6KJOHE.js";
4
+ } from "../chunk-ZSY6BWUD.js";
5
5
  import {
6
- CodexNativeApprovals
7
- } from "../chunk-SVCT5D7K.js";
8
- import "../chunk-PQAFH4DF.js";
6
+ CodexNativeApprovals,
7
+ answerCodexElicitation,
8
+ answerCodexQuestions,
9
+ claimOwnedQuestion,
10
+ closesOwnedQuestion,
11
+ codexQuestionOwner,
12
+ planCodexElicitation,
13
+ reportOwnedQuestion
14
+ } from "../chunk-6JPDAUFP.js";
15
+ import "../chunk-EKGQEDQ4.js";
9
16
  import {
10
17
  resolveProviderBinary
11
- } from "../chunk-L2ZM7LXN.js";
18
+ } from "../chunk-WPPKXVV3.js";
12
19
  import "../chunk-RIVNNBEK.js";
13
- import "../chunk-75M7NFFC.js";
14
- import "../chunk-MRQJCIFO.js";
20
+ import "../chunk-H4ODQD3Y.js";
21
+ import "../chunk-SIN7NJD7.js";
22
+ import "../chunk-RFQWB5ZV.js";
23
+ import "../chunk-XHKBHWLX.js";
15
24
  import "../chunk-6ICPEJY5.js";
16
- import "../chunk-GOEIW2VH.js";
25
+ import {
26
+ writeDegraded
27
+ } from "../chunk-CDERSTBO.js";
17
28
  import "../chunk-POFBE6ZY.js";
18
- import "../chunk-53UZN7JW.js";
19
- import "../chunk-TKTS3SIM.js";
20
- import "../chunk-S2ALW446.js";
29
+ import "../chunk-SFQSOBLM.js";
30
+ import "../chunk-PWUIPQ2I.js";
31
+ import "../chunk-6TYKZ4DN.js";
21
32
  import {
22
33
  getMachineId
23
- } from "../chunk-WMO2XVG4.js";
34
+ } from "../chunk-4A6VUVXV.js";
24
35
  import "../chunk-7QLSKOSU.js";
25
36
  import "../chunk-KZERVKTD.js";
26
- import "../chunk-4JHEZ2PV.js";
37
+ import "../chunk-IOVJVGIT.js";
27
38
  import "../chunk-AQFEP65J.js";
28
39
  import {
29
40
  getApiKey,
30
41
  getBaseUrl
31
- } from "../chunk-OWQOMGBF.js";
32
- import "../chunk-T3KMJI4W.js";
33
- import "../chunk-JNU3LV47.js";
42
+ } from "../chunk-EWC4P4ZL.js";
43
+ import "../chunk-57M4ERJK.js";
44
+ import {
45
+ CODEX_NATIVE_APPROVAL_METHODS,
46
+ canonicalJsonText
47
+ } from "../chunk-K7EGWMDU.js";
34
48
 
35
49
  // src/codex-session-launch.ts
36
50
  import { spawn as spawn2 } from "child_process";
@@ -44,6 +58,94 @@ import { createServer } from "http";
44
58
  import { tmpdir } from "os";
45
59
  import { join } from "path";
46
60
  import WebSocket, { WebSocketServer } from "ws";
61
+
62
+ // src/codex-shared-questions.ts
63
+ import { createHash, randomUUID } from "crypto";
64
+ var CodexSharedQuestions = class {
65
+ constructor(options) {
66
+ this.options = options;
67
+ }
68
+ generation = randomUUID();
69
+ pending = /* @__PURE__ */ new Map();
70
+ retired = /* @__PURE__ */ new Set();
71
+ tasks = /* @__PURE__ */ new Set();
72
+ closed = false;
73
+ handle(message, threadId) {
74
+ if (this.closed || !message.method) return;
75
+ const params = message.params ?? {};
76
+ if (message.id === void 0) {
77
+ for (const [id2, pending2] of this.pending) {
78
+ const closed = pending2.dispatch ? closesOwnedQuestion(pending2.dispatch.owner, message.method, params) : message.method === "serverRequest/resolved" && params.requestId === id2 || params.threadId === pending2.threadId && ["thread/closed", "thread/archived", "turn/completed"].includes(message.method);
79
+ if (closed) {
80
+ pending2.controller.abort();
81
+ this.pending.delete(id2);
82
+ this.retired.add(id2);
83
+ if (pending2.dispatch) {
84
+ const task2 = reportOwnedQuestion(this.options, pending2.dispatch, "request_closed").finally(() => this.tasks.delete(task2));
85
+ this.tasks.add(task2);
86
+ }
87
+ }
88
+ }
89
+ return;
90
+ }
91
+ const id = message.id;
92
+ if (typeof id !== "string" && !(typeof id === "number" && Number.isSafeInteger(id))) return;
93
+ if (this.retired.has(id)) return;
94
+ const subjectHash = createHash("sha256").update(canonicalJsonText(params)).digest("hex");
95
+ const existing = this.pending.get(id);
96
+ if (existing) {
97
+ if (existing.subjectHash !== subjectHash) {
98
+ existing.controller.abort();
99
+ this.pending.delete(id);
100
+ this.retired.add(id);
101
+ }
102
+ return;
103
+ }
104
+ if (this.pending.size >= 8) return;
105
+ const isQuestion = message.method === "item/tool/requestUserInput";
106
+ if (!isQuestion && message.method !== "mcpServer/elicitation/request") return;
107
+ const owner = isQuestion ? codexQuestionOwner(params, id, { machineId: this.options.machineId, generation: this.generation, threadId }) : null;
108
+ if (isQuestion ? !owner : !planCodexElicitation(params, threadId)) return;
109
+ const duration = params.autoResolutionMs;
110
+ if (duration != null && (typeof duration !== "number" || !Number.isFinite(duration) || duration < 0)) return;
111
+ const dispatch = owner ? { owner, correlationIds: [], deadline: typeof duration === "number" ? Date.now() + duration : null } : void 0;
112
+ const pending = { controller: new AbortController(), dispatch, threadId, subjectHash };
113
+ this.pending.set(id, pending);
114
+ const task = this.answer(id, params, pending).finally(() => this.tasks.delete(task));
115
+ this.tasks.add(task);
116
+ }
117
+ async answer(id, params, pending) {
118
+ const signal = AbortSignal.any([this.options.signal, pending.controller.signal]);
119
+ const options = { ...this.options, shared: true, signal };
120
+ const dispatch = pending.dispatch;
121
+ try {
122
+ const result = dispatch ? await answerCodexQuestions(options, params, signal, "request_user_input", dispatch) : await answerCodexElicitation(options, params, pending.threadId, signal);
123
+ if (signal.aborted || this.closed || "action" in result && result.action !== "accept") return;
124
+ if (dispatch) await claimOwnedQuestion(options, dispatch);
125
+ if (signal.aborted || this.closed || this.pending.get(id) !== pending) return;
126
+ if (dispatch?.deadline != null && Date.now() >= dispatch.deadline) throw new Error("Question expired");
127
+ this.options.reply(id, result);
128
+ if (dispatch) await reportOwnedQuestion(options, dispatch, "reply_queued");
129
+ } catch {
130
+ if (!signal.aborted) writeDegraded("questions", { reason: "shared_codex_question_unavailable", fallback: "native client", event: "question" });
131
+ } finally {
132
+ if (dispatch && (!dispatch.claimId || signal.aborted)) await reportOwnedQuestion(options, dispatch, "unavailable");
133
+ if (!dispatch?.claimId || signal.aborted) {
134
+ this.pending.delete(id);
135
+ this.retired.add(id);
136
+ }
137
+ }
138
+ }
139
+ async close() {
140
+ this.closed = true;
141
+ for (const pending of this.pending.values()) pending.controller.abort();
142
+ await Promise.allSettled(this.tasks);
143
+ await Promise.allSettled([...this.pending.values()].flatMap((pending) => pending.dispatch ? [reportOwnedQuestion(this.options, pending.dispatch, "unavailable")] : []));
144
+ this.pending.clear();
145
+ }
146
+ };
147
+
148
+ // src/codex-shared-session.ts
47
149
  var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
48
150
  var requestId = (value) => typeof value === "string" || typeof value === "number" && Number.isSafeInteger(value);
49
151
  var MAX_MESSAGE_BYTES = 16 * 1024 * 1024;
@@ -66,14 +168,14 @@ var startCodexSharedSession = async (options) => {
66
168
  detached: true
67
169
  });
68
170
  child.stderr.resume();
69
- const exited = new Promise((resolve2) => {
171
+ const exited = new Promise((resolve3) => {
70
172
  child.once("exit", () => {
71
173
  controller.abort(new Error("Codex app-server stopped"));
72
- resolve2();
174
+ resolve3();
73
175
  });
74
176
  child.once("error", () => {
75
177
  controller.abort(new Error("Could not start Codex app-server"));
76
- resolve2();
178
+ resolve3();
77
179
  });
78
180
  });
79
181
  let terminal = null;
@@ -84,16 +186,20 @@ var startCodexSharedSession = async (options) => {
84
186
  if (signal.aborted || child.stdin.destroyed) throw new Error("Codex connection closed");
85
187
  child.stdin.write(JSON.stringify(message) + "\n");
86
188
  };
87
- const approvals = new CodexNativeApprovals({ ...options, signal, shared: true, reply: (id, result) => {
189
+ const reply = (id, result) => {
88
190
  if (!unanswered.delete(id)) throw new Error("Native request already answered");
89
191
  write({ id, result });
90
- } });
192
+ };
193
+ const approvals = new CodexNativeApprovals({ ...options, signal, shared: true, reply });
194
+ const questions = new CodexSharedQuestions({ ...options, signal, reply });
91
195
  const observeNative = (message) => {
92
196
  if (typeof message.method !== "string") return;
93
197
  if (requestId(message.id)) unanswered.add(message.id);
94
198
  if (!record(message.params)) return;
95
199
  if (message.method === "serverRequest/resolved" && requestId(message.params.requestId)) unanswered.delete(message.params.requestId);
96
- approvals.handle({ id: message.id, method: message.method, params: message.params }, typeof message.params.threadId === "string" ? message.params.threadId : null);
200
+ const event = { id: message.id, method: message.method, params: message.params };
201
+ const threadId = typeof message.params.threadId === "string" ? message.params.threadId : null;
202
+ if (!approvals.handle(event, threadId)) questions.handle(event, threadId);
97
203
  };
98
204
  child.stdout.setEncoding("utf8");
99
205
  child.stdout.on("data", (chunk) => {
@@ -135,6 +241,7 @@ var startCodexSharedSession = async (options) => {
135
241
  if (requestId(message.id) && !("method" in message)) {
136
242
  if (!unanswered.delete(message.id)) return;
137
243
  approvals.handle({ method: "serverRequest/resolved", params: { requestId: message.id } }, null);
244
+ questions.handle({ method: "serverRequest/resolved", params: { requestId: message.id } }, null);
138
245
  }
139
246
  write(message);
140
247
  } catch {
@@ -155,7 +262,7 @@ var startCodexSharedSession = async (options) => {
155
262
  const stopServer = async () => {
156
263
  signalServerGroup("SIGTERM");
157
264
  const deadline = Date.now() + 2e3;
158
- while (signalServerGroup(0) && Date.now() < deadline) await new Promise((resolve2) => setTimeout(resolve2, 25));
265
+ while (signalServerGroup(0) && Date.now() < deadline) await new Promise((resolve3) => setTimeout(resolve3, 25));
159
266
  if (signalServerGroup(0)) signalServerGroup("SIGKILL");
160
267
  await exited;
161
268
  };
@@ -167,7 +274,7 @@ var startCodexSharedSession = async (options) => {
167
274
  websocket.close();
168
275
  http.close();
169
276
  try {
170
- await Promise.all([approvals.close(), stopServer()]);
277
+ await Promise.all([approvals.close(), questions.close(), stopServer()]);
171
278
  } finally {
172
279
  await rm(directory, { recursive: true, force: true });
173
280
  }
@@ -178,9 +285,9 @@ var startCodexSharedSession = async (options) => {
178
285
  void close();
179
286
  }, { once: true });
180
287
  try {
181
- await new Promise((resolve2, reject) => {
288
+ await new Promise((resolve3, reject) => {
182
289
  http.once("error", reject);
183
- http.listen(socketPath, resolve2);
290
+ http.listen(socketPath, resolve3);
184
291
  });
185
292
  signal.throwIfAborted();
186
293
  return { endpoint: "unix://" + socketPath, signal, close };
@@ -200,7 +307,7 @@ var launchCodexSession = async (options) => {
200
307
  if (!(await stat(options.cwd)).isDirectory()) throw new Error("Codex needs a project directory");
201
308
  const session = await startCodexSharedSession(options);
202
309
  try {
203
- process.stderr.write("[pushary] Managed Codex session. Supported native permissions can reach Pushary; other prompts stay in this terminal.\n");
310
+ process.stderr.write("[pushary] Managed Codex session. Supported questions and native permissions can reach Pushary; other prompts stay in this terminal.\n");
204
311
  const tui = spawn2(options.binary, ["--remote", session.endpoint], {
205
312
  cwd: options.cwd,
206
313
  env: { ...options.env ?? process.env, PUSHARY_CODEX_BRIDGE: "1" },
@@ -212,9 +319,9 @@ var launchCodexSession = async (options) => {
212
319
  session.signal.addEventListener("abort", stop, { once: true });
213
320
  if (session.signal.aborted) stop();
214
321
  try {
215
- return await new Promise((resolve2, reject) => {
322
+ return await new Promise((resolve3, reject) => {
216
323
  tui.once("error", reject);
217
- tui.once("close", (code) => resolve2(code ?? 1));
324
+ tui.once("close", (code) => resolve3(code ?? 1));
218
325
  });
219
326
  } finally {
220
327
  session.signal.removeEventListener("abort", stop);
@@ -224,25 +331,314 @@ var launchCodexSession = async (options) => {
224
331
  }
225
332
  };
226
333
 
334
+ // src/codex-desktop-launch.ts
335
+ import { spawn as spawn4, execFileSync } from "child_process";
336
+ import { existsSync } from "fs";
337
+ import { mkdtemp as mkdtemp2, readFile, rm as rm2, writeFile, access } from "fs/promises";
338
+ import { constants } from "fs";
339
+ import { tmpdir as tmpdir2 } from "os";
340
+ import { join as join2, resolve as resolve2 } from "path";
341
+
342
+ // src/codex-desktop-proxy.ts
343
+ import { spawn as spawn3 } from "child_process";
344
+ import { isAbsolute } from "path";
345
+ import { PassThrough, Transform } from "stream";
346
+ var record2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
347
+ var requestId2 = (value) => typeof value === "string" && value.length > 0 || typeof value === "number" && Number.isSafeInteger(value);
348
+ var THREAD_METHODS = ["thread/start", "thread/resume", "thread/fork", "thread/read"];
349
+ var MAX_MESSAGE_BYTES2 = 16 * 1024 * 1024;
350
+ var MAX_CONTEXTS = 1024;
351
+ var CodexDesktopApprovals = class {
352
+ threads = /* @__PURE__ */ new Map();
353
+ requests = /* @__PURE__ */ new Map();
354
+ answered = /* @__PURE__ */ new Map();
355
+ approvals;
356
+ closing;
357
+ constructor(options) {
358
+ this.approvals = new CodexNativeApprovals({ ...options, cwd: "/", shared: true, reply: (id, result) => {
359
+ if (this.closing || this.answered.get(id) !== false) throw new Error("Desktop already resolved this permission");
360
+ this.answered.set(id, true);
361
+ options.reply({ id, result });
362
+ } });
363
+ }
364
+ client(message) {
365
+ if (requestId2(message.id) && !("method" in message)) {
366
+ if (this.answered.get(message.id) === true) return false;
367
+ if (this.answered.has(message.id)) {
368
+ this.answered.set(message.id, true);
369
+ this.approvals.handle({ method: "serverRequest/resolved", params: { requestId: message.id } }, null);
370
+ }
371
+ }
372
+ if (this.closing) return true;
373
+ if (requestId2(message.id) && typeof message.method === "string" && (THREAD_METHODS.includes(message.method) || message.method === "turn/start")) {
374
+ if (this.requests.size >= MAX_CONTEXTS) {
375
+ void this.close();
376
+ return true;
377
+ }
378
+ this.requests.set(message.id, message);
379
+ const p = message.params;
380
+ if (message.method === "turn/start" && record2(p) && typeof p.threadId === "string" && p.cwd != null) this.forgetThread(p.threadId);
381
+ }
382
+ return true;
383
+ }
384
+ server(message) {
385
+ if (this.closing) return;
386
+ if (requestId2(message.id) && !("method" in message)) {
387
+ const request = this.requests.get(message.id);
388
+ this.requests.delete(message.id);
389
+ if (request && record2(message.result)) {
390
+ this.rememberThread(message.result.thread);
391
+ const p2 = request.params;
392
+ if (request.method === "turn/start" && record2(p2)) this.rememberThread({ id: p2.threadId, cwd: p2.cwd });
393
+ }
394
+ return;
395
+ }
396
+ if (typeof message.method !== "string" || !record2(message.params)) return;
397
+ const p = message.params;
398
+ if (message.method === "thread/started") this.rememberThread(p.thread);
399
+ if (message.method === "thread/closed" || message.method === "thread/archived") {
400
+ if (typeof p.threadId === "string") this.forgetThread(p.threadId);
401
+ }
402
+ if (message.method === "serverRequest/resolved" && requestId2(p.requestId) && this.answered.has(p.requestId)) this.answered.set(p.requestId, true);
403
+ const threadId = typeof p.threadId === "string" ? p.threadId : null;
404
+ const cwd = threadId ? this.threads.get(threadId) : void 0;
405
+ if (message.id !== void 0) {
406
+ if (!cwd || !requestId2(message.id) || !CODEX_NATIVE_APPROVAL_METHODS.some((method) => method === message.method)) return;
407
+ if (this.answered.has(message.id)) return;
408
+ if (this.answered.size >= 1e4) {
409
+ void this.close();
410
+ return;
411
+ }
412
+ this.answered.set(message.id, false);
413
+ }
414
+ this.approvals.handle({ id: message.id, method: message.method, params: p }, threadId, cwd);
415
+ }
416
+ close() {
417
+ if (!this.closing) this.closing = this.approvals.close();
418
+ return this.closing;
419
+ }
420
+ forgetThread(id) {
421
+ this.threads.delete(id);
422
+ this.approvals.handle({ method: "thread/closed", params: { threadId: id } }, id);
423
+ }
424
+ rememberThread(thread) {
425
+ if (!record2(thread) || typeof thread.id !== "string" || typeof thread.cwd !== "string" || !isAbsolute(thread.cwd)) return;
426
+ if (this.threads.has(thread.id) && this.threads.get(thread.id) !== thread.cwd) this.forgetThread(thread.id);
427
+ if (this.threads.size >= MAX_CONTEXTS && !this.threads.has(thread.id)) return;
428
+ this.threads.set(thread.id, thread.cwd);
429
+ }
430
+ };
431
+ var codexProtocolLines = (observe, unavailable) => {
432
+ let buffered = Buffer.alloc(0), passthrough = false;
433
+ const forward = (stream, line) => {
434
+ try {
435
+ const message = JSON.parse(line.toString("utf8"));
436
+ if (!record2(message)) throw new Error("Unknown Codex protocol message");
437
+ if (observe(message)) stream.push(line);
438
+ } catch {
439
+ unavailable();
440
+ passthrough = true;
441
+ stream.push(line);
442
+ }
443
+ };
444
+ return new Transform({
445
+ transform(chunk, _encoding, callback) {
446
+ if (passthrough) {
447
+ this.push(chunk);
448
+ callback();
449
+ return;
450
+ }
451
+ buffered = Buffer.concat([buffered, chunk]);
452
+ if (buffered.length > MAX_MESSAGE_BYTES2) {
453
+ unavailable();
454
+ passthrough = true;
455
+ this.push(buffered);
456
+ buffered = Buffer.alloc(0);
457
+ callback();
458
+ return;
459
+ }
460
+ let end;
461
+ while ((end = buffered.indexOf(10)) >= 0) {
462
+ const line = buffered.subarray(0, end + 1);
463
+ buffered = buffered.subarray(end + 1);
464
+ if (passthrough || !line.toString("utf8").trim()) this.push(line);
465
+ else forward(this, line);
466
+ }
467
+ callback();
468
+ },
469
+ flush(callback) {
470
+ if (buffered.length) {
471
+ if (passthrough) this.push(buffered);
472
+ else forward(this, buffered);
473
+ }
474
+ callback();
475
+ }
476
+ });
477
+ };
478
+ var runCodexDesktopProxy = async (binary, args2, readAccount) => {
479
+ const stdioServer = args2[0] === "app-server" && !args2.some((arg) => arg === "--listen" || arg.startsWith("--listen="));
480
+ const child = spawn3(binary, [...args2], {
481
+ stdio: ["pipe", "pipe", "inherit"],
482
+ detached: true,
483
+ env: { ...process.env, CODEX_CLI_PATH: binary }
484
+ });
485
+ const account = stdioServer ? readAccount() : null;
486
+ const abort = new AbortController();
487
+ let approvals;
488
+ let timer;
489
+ let killTimer;
490
+ const unavailable = () => {
491
+ abort.abort();
492
+ clearInterval(timer);
493
+ void approvals?.close();
494
+ process.stderr.write("[pushary] Desktop forwarding unavailable; answer permissions in Codex.\n");
495
+ };
496
+ if (account && stdioServer) {
497
+ approvals = new CodexDesktopApprovals({ ...account, signal: abort.signal, reply: (message) => {
498
+ if (child.stdin.destroyed) throw new Error("Codex engine stopped");
499
+ child.stdin.write(JSON.stringify(message) + "\n");
500
+ } });
501
+ timer = setInterval(() => {
502
+ const current = readAccount();
503
+ if (!current || current.apiKey !== account.apiKey || current.baseUrl !== account.baseUrl || current.machineId !== account.machineId) unavailable();
504
+ }, 1e3);
505
+ }
506
+ const signalChild = (signal) => {
507
+ if (!child.pid) return;
508
+ try {
509
+ process.kill(-child.pid, signal);
510
+ } catch (error) {
511
+ if (!record2(error) || error.code !== "ESRCH") throw error;
512
+ }
513
+ };
514
+ const stop = () => {
515
+ if (killTimer) return;
516
+ abort.abort();
517
+ signalChild("SIGTERM");
518
+ killTimer = setTimeout(() => signalChild("SIGKILL"), 2e3);
519
+ };
520
+ const input = stdioServer ? codexProtocolLines((message) => approvals?.client(message) ?? true, unavailable) : new PassThrough();
521
+ const output = stdioServer ? codexProtocolLines((message) => {
522
+ approvals?.server(message);
523
+ return true;
524
+ }, unavailable) : new PassThrough();
525
+ process.stdin.pipe(input).pipe(child.stdin);
526
+ child.stdout.pipe(output).pipe(process.stdout, { end: false });
527
+ if (stdioServer) process.stdin.once("end", stop);
528
+ for (const stream of [process.stdin, input, child.stdin, child.stdout, output, process.stdout]) stream.on("error", stop);
529
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, stop);
530
+ try {
531
+ return await new Promise((resolve3, reject) => {
532
+ child.once("error", reject);
533
+ child.once("close", (code) => resolve3(code ?? 1));
534
+ });
535
+ } finally {
536
+ abort.abort();
537
+ clearInterval(timer);
538
+ clearTimeout(killTimer);
539
+ signalChild("SIGKILL");
540
+ process.stdin.unpipe(input);
541
+ child.stdout.unpipe(output);
542
+ output.unpipe(process.stdout);
543
+ process.stdin.pause();
544
+ process.stdin.off("end", stop);
545
+ for (const stream of [process.stdin, input, child.stdin, child.stdout, output, process.stdout]) stream.off("error", stop);
546
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.off(signal, stop);
547
+ await approvals?.close();
548
+ }
549
+ };
550
+
551
+ // src/codex-desktop-launch.ts
552
+ var record3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
553
+ var quote = (value) => "'" + value.replace(/'/g, "'\\''") + "'";
554
+ var TESTED_ENGINE_VERSION = "0.162.0-alpha.2";
555
+ var desktopEngineFromManifest = (app, manifest) => {
556
+ if (!record3(manifest) || manifest.layoutVersion !== 1 || manifest.entrypoint !== "bin/codex" || manifest.version !== TESTED_ENGINE_VERSION) {
557
+ throw new Error(`Experimental Desktop forwarding requires bundled Codex ${TESTED_ENGINE_VERSION}. Launch Codex normally for this version.`);
558
+ }
559
+ return join2(app, "Contents/Resources/codex-cli/bin/codex");
560
+ };
561
+ var writeDesktopProxy = async (entry, app, directory) => {
562
+ const shim = join2(directory, "codex");
563
+ await writeFile(shim, "#!/bin/sh\nexec " + [...entry, "--desktop-proxy", app, "--"].map(quote).join(" ") + ' "$@"\n', { mode: 448, flag: "wx" });
564
+ return shim;
565
+ };
566
+ var desktopEngine = async (app) => {
567
+ const manifest = JSON.parse(await readFile(join2(app, "Contents/Resources/codex-cli/codex-package.json"), "utf8"));
568
+ const engine = desktopEngineFromManifest(app, manifest);
569
+ await access(engine, constants.X_OK);
570
+ return engine;
571
+ };
572
+ var desktopExecutable = (app) => {
573
+ const plist = join2(app, "Contents/Info.plist");
574
+ const read = (key) => execFileSync("/usr/bin/plutil", ["-extract", key, "raw", plist], { encoding: "utf8" }).trim();
575
+ if (read("CFBundleIdentifier") !== "com.openai.codex") throw new Error("Choose the Codex Desktop application");
576
+ const name = read("CFBundleExecutable");
577
+ if (!name || name.includes("/") || name === "." || name === "..") throw new Error("Invalid Desktop executable");
578
+ try {
579
+ execFileSync("/usr/bin/pgrep", ["-x", name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")], { stdio: "ignore" });
580
+ } catch (error) {
581
+ if (record3(error) && error.status === 1) return join2(app, "Contents/MacOS", name);
582
+ throw error;
583
+ }
584
+ throw new Error("Codex Desktop is already running. Finish active work and quit it yourself, then retry. Pushary will not restart it.");
585
+ };
586
+ var codexDesktopCommand = async (args2, entry, readAccount) => {
587
+ if (args2[0] !== "--desktop" && args2[0] !== "--desktop-proxy") return null;
588
+ if (process.platform !== "darwin") throw new Error("Experimental Codex Desktop forwarding currently requires macOS");
589
+ if (args2[0] === "--desktop-proxy") {
590
+ if (!args2[1] || args2[2] !== "--") throw new Error("Invalid Desktop proxy invocation");
591
+ return runCodexDesktopProxy(await desktopEngine(args2[1]), args2.slice(3), readAccount);
592
+ }
593
+ if (args2.length > 2) throw new Error("Usage: pushary codex --desktop [path/to/Codex.app]");
594
+ if (!readAccount()) throw new Error("Connect Pushary before starting Desktop forwarding");
595
+ const app = resolve2(args2[1] ?? ["/Applications/ChatGPT.app", "/Applications/Codex.app"].find((candidate) => existsSync(join2(candidate, "Contents/Resources/codex-cli/codex-package.json"))) ?? "/Applications/Codex.app");
596
+ await desktopEngine(app);
597
+ const executable = desktopExecutable(app);
598
+ const directory = await mkdtemp2(join2(tmpdir2(), "pushary-codex-desktop-"));
599
+ try {
600
+ const shim = await writeDesktopProxy(entry, app, directory);
601
+ process.stderr.write("[pushary] Experimental Desktop launch. Supported approvals can reach Pushary; other prompts stay in Codex. Quit and open Codex normally to disable.\n");
602
+ const child = spawn4(executable, [], { stdio: "ignore", detached: true, env: { ...process.env, CODEX_CLI_PATH: shim } });
603
+ return await new Promise((resolve3, reject) => {
604
+ child.once("error", reject);
605
+ child.once("close", (code) => resolve3(code ?? 1));
606
+ });
607
+ } finally {
608
+ await rm2(directory, { recursive: true, force: true });
609
+ }
610
+ };
611
+
227
612
  // bin/pushary-codex-session.ts
228
- exitOnHelpFlag("codex");
229
613
  var args = process.argv.slice(2);
230
614
  if (args[0] === "codex") args.shift();
615
+ if (args[0] !== "--desktop-proxy") exitOnHelpFlag("codex");
616
+ var desktopAccount = () => {
617
+ try {
618
+ return { apiKey: getApiKey(), baseUrl: getBaseUrl(), machineId: getMachineId() };
619
+ } catch {
620
+ return null;
621
+ }
622
+ };
231
623
  {
232
624
  const abort = new AbortController();
233
625
  const stop = () => abort.abort();
234
- for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, stop);
626
+ if (!args[0]?.startsWith("--desktop")) for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) process.on(signal, stop);
235
627
  try {
236
- const binary = resolveProviderBinary("codex");
237
- if (!binary) throw new Error("Install Codex first, then run pushary codex");
238
- process.exitCode = await launchCodexSession({
239
- binary,
240
- cwd: codexSessionDirectory(args, process.cwd()),
241
- apiKey: getApiKey(),
242
- baseUrl: getBaseUrl(),
243
- machineId: getMachineId(),
244
- signal: abort.signal
245
- });
628
+ const desktop = await codexDesktopCommand(args, [process.execPath, ...process.execArgv, process.argv[1], "codex"], desktopAccount);
629
+ if (desktop !== null) process.exitCode = desktop;
630
+ else {
631
+ const binary = resolveProviderBinary("codex");
632
+ if (!binary) throw new Error("Install Codex first, then run pushary codex");
633
+ process.exitCode = await launchCodexSession({
634
+ binary,
635
+ cwd: codexSessionDirectory(args, process.cwd()),
636
+ apiKey: getApiKey(),
637
+ baseUrl: getBaseUrl(),
638
+ machineId: getMachineId(),
639
+ signal: abort.signal
640
+ });
641
+ }
246
642
  } catch (error) {
247
643
  process.stderr.write(`[pushary] ${error instanceof Error ? error.message : "Codex session failed"}
248
644
  `);
@@ -1,33 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  reportEvent
4
- } from "../chunk-BKK3JCTD.js";
5
- import "../chunk-CAJCCQ7N.js";
4
+ } from "../chunk-GZSSAY6N.js";
5
+ import "../chunk-HHX6IRSH.js";
6
+ import "../chunk-CFMCV6Z7.js";
6
7
  import {
7
8
  askUser,
8
9
  isDecisionStopped,
9
10
  waitForAnswer
10
- } from "../chunk-5ZSBQHCM.js";
11
- import "../chunk-M2L7625S.js";
12
- import "../chunk-GOEIW2VH.js";
11
+ } from "../chunk-CDERSTBO.js";
13
12
  import "../chunk-POFBE6ZY.js";
14
- import "../chunk-53UZN7JW.js";
15
- import "../chunk-TKTS3SIM.js";
16
- import "../chunk-S2ALW446.js";
13
+ import "../chunk-SFQSOBLM.js";
14
+ import "../chunk-PWUIPQ2I.js";
15
+ import "../chunk-6TYKZ4DN.js";
17
16
  import {
18
17
  getMachineId
19
- } from "../chunk-WMO2XVG4.js";
18
+ } from "../chunk-4A6VUVXV.js";
20
19
  import "../chunk-7QLSKOSU.js";
21
20
  import "../chunk-KZERVKTD.js";
22
- import "../chunk-4JHEZ2PV.js";
21
+ import "../chunk-IOVJVGIT.js";
23
22
  import "../chunk-AQFEP65J.js";
24
23
  import {
25
24
  getApiKey
26
- } from "../chunk-OWQOMGBF.js";
25
+ } from "../chunk-EWC4P4ZL.js";
27
26
  import {
28
27
  NPX_LATEST_COMMAND
29
- } from "../chunk-T3KMJI4W.js";
30
- import "../chunk-JNU3LV47.js";
28
+ } from "../chunk-57M4ERJK.js";
29
+ import "../chunk-K7EGWMDU.js";
31
30
 
32
31
  // bin/pushary-codex.ts
33
32
  import { basename } from "path";