pushary 1.9.71 → 1.9.73

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 (127) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{api-HEIRLMTZ.js → api-JVWXPKEW.js} +10 -10
  3. package/dist/bin/pushary-bell-hook.js +25 -25
  4. package/dist/bin/pushary-bell.js +10 -10
  5. package/dist/bin/pushary-claude.js +202 -67
  6. package/dist/bin/pushary-clean.js +33 -33
  7. package/dist/bin/pushary-codex-bridge.js +23 -21
  8. package/dist/bin/pushary-codex-hook.js +30 -30
  9. package/dist/bin/pushary-codex-session.js +8 -8
  10. package/dist/bin/pushary-codex.js +13 -13
  11. package/dist/bin/pushary-connect.js +26 -26
  12. package/dist/bin/pushary-cowork.js +5 -5
  13. package/dist/bin/pushary-daemon.js +38 -38
  14. package/dist/bin/pushary-disconnect.js +25 -25
  15. package/dist/bin/pushary-doctor.js +41 -41
  16. package/dist/bin/pushary-elicitation-hook.js +25 -25
  17. package/dist/bin/pushary-gemini-bridge.js +45 -48
  18. package/dist/bin/pushary-gemini-hook.js +27 -27
  19. package/dist/bin/pushary-hook.js +29 -29
  20. package/dist/bin/pushary-login.js +20 -20
  21. package/dist/bin/pushary-logout.js +24 -24
  22. package/dist/bin/pushary-mcp.js +11 -11
  23. package/dist/bin/pushary-mode.js +15 -15
  24. package/dist/bin/pushary-notification-hook.js +24 -24
  25. package/dist/bin/pushary-opencode-hook.js +26 -26
  26. package/dist/bin/pushary-permission-denied-hook.js +29 -29
  27. package/dist/bin/pushary-permission-hook.js +29 -29
  28. package/dist/bin/pushary-post-hook.js +24 -24
  29. package/dist/bin/pushary-prompt-hook.js +24 -24
  30. package/dist/bin/pushary-session-end-hook.js +26 -26
  31. package/dist/bin/pushary-session-start-hook.js +34 -34
  32. package/dist/bin/pushary-setup.js +47 -46
  33. package/dist/bin/pushary-stats.js +5 -5
  34. package/dist/bin/pushary-status.js +31 -31
  35. package/dist/bin/pushary-stop-hook.js +24 -24
  36. package/dist/bin/pushary-stopfailure-hook.js +24 -24
  37. package/dist/bin/pushary-suggestions.js +15 -15
  38. package/dist/bin/pushary-transcript-register.js +16 -16
  39. package/dist/bin/pushary-transcripts.js +20 -19
  40. package/dist/bin/pushary-upgrade.js +39 -39
  41. package/dist/bin/pushary-wait.js +15 -15
  42. package/dist/bin/pushary.js +7 -7
  43. package/dist/{chunk-EVJRITSI.js → chunk-2HFC4W6J.js} +1 -1
  44. package/dist/{chunk-4QPF4BGP.js → chunk-2JFEMKDG.js} +6 -6
  45. package/dist/{chunk-KGZH5RTB.js → chunk-2NG3ANMT.js} +6 -6
  46. package/dist/{chunk-JMACQEPU.js → chunk-2QB7HJVJ.js} +2 -2
  47. package/dist/{chunk-XJMUEOXT.js → chunk-3JJI5O5N.js} +1 -1
  48. package/dist/{chunk-BZ7XGS5L.js → chunk-3QBKMSOZ.js} +1 -1
  49. package/dist/{chunk-AN26S5DC.js → chunk-3RAWW5DH.js} +6 -6
  50. package/dist/{chunk-J7BLYQ7Z.js → chunk-4LAD5A2Q.js} +4 -4
  51. package/dist/{chunk-VSQDVH43.js → chunk-4UIR25GA.js} +1 -1
  52. package/dist/{chunk-AUOVB62W.js → chunk-56GK65Z6.js} +3 -3
  53. package/dist/{chunk-AERRQEWZ.js → chunk-7FROPPIH.js} +2 -2
  54. package/dist/{chunk-GIE7PLZD.js → chunk-7M4AA7QF.js} +4 -4
  55. package/dist/{chunk-SWQTCKLM.js → chunk-7RX5TOU3.js} +6 -6
  56. package/dist/{chunk-SMIU5URV.js → chunk-7S46LCKD.js} +2 -2
  57. package/dist/{chunk-IYMYGKFB.js → chunk-AC5TVOAH.js} +10 -11
  58. package/dist/{chunk-5NQ3SMVP.js → chunk-AJWGZJFS.js} +122 -35
  59. package/dist/{chunk-JGEPODEP.js → chunk-AVHL2ZTS.js} +85 -3
  60. package/dist/{chunk-3CN2EFKK.js → chunk-AXZ67XVN.js} +1 -1
  61. package/dist/{chunk-MZ6GL46A.js → chunk-BLX6KRK5.js} +1 -1
  62. package/dist/{chunk-ZBPBX656.js → chunk-CGWUVEOB.js} +3 -3
  63. package/dist/{chunk-YG3NFC5N.js → chunk-CMYFV3VC.js} +5 -5
  64. package/dist/{chunk-C3YAMEO2.js → chunk-DT6VCMXS.js} +2 -2
  65. package/dist/{chunk-7OYWCOVC.js → chunk-EITOMPFB.js} +4 -4
  66. package/dist/{chunk-QWCOMSDH.js → chunk-EUEGCEA7.js} +3 -3
  67. package/dist/{chunk-5WHSFMV2.js → chunk-EUHTC7K5.js} +1 -1
  68. package/dist/{chunk-HR6M44CX.js → chunk-F3WI32HG.js} +7 -7
  69. package/dist/{chunk-6W2ASRZO.js → chunk-FAKEKFCI.js} +3 -3
  70. package/dist/{chunk-FTOKESSK.js → chunk-FB262EOM.js} +1 -1
  71. package/dist/{chunk-4XFFGFHN.js → chunk-FK3JSDH5.js} +13 -13
  72. package/dist/{chunk-3FY5ZPIZ.js → chunk-GBR6E7AY.js} +1 -1
  73. package/dist/{chunk-NHC5YUH6.js → chunk-IN3MPEFA.js} +5 -5
  74. package/dist/{chunk-LA3MMHLO.js → chunk-JC7XUZST.js} +6 -3
  75. package/dist/{chunk-2XD4TTUM.js → chunk-JNTYPMVU.js} +10 -10
  76. package/dist/{chunk-B3KPRSCM.js → chunk-JWBZUTYF.js} +5 -5
  77. package/dist/{chunk-LKAO35SY.js → chunk-JYOR57ZW.js} +3 -3
  78. package/dist/{chunk-MGF7D2ZL.js → chunk-KDQLKND5.js} +36 -35
  79. package/dist/{chunk-DOMDCMGC.js → chunk-KSLBS3II.js} +5 -5
  80. package/dist/chunk-L2FQBHIR.js +74 -0
  81. package/dist/{chunk-JEZGDP55.js → chunk-N4VRSFRZ.js} +5 -5
  82. package/dist/chunk-N5P2FLOJ.js +82 -0
  83. package/dist/{chunk-D536PHXF.js → chunk-NGNBX3YH.js} +4 -4
  84. package/dist/{chunk-EZHM6I2V.js → chunk-NJSW2REO.js} +4 -4
  85. package/dist/{chunk-XSUWXBHF.js → chunk-O4EAMVSC.js} +3 -3
  86. package/dist/{chunk-YRWGCYHH.js → chunk-OJ3ZD5VX.js} +1 -1
  87. package/dist/{chunk-25W6AD3W.js → chunk-PGJMSREY.js} +6 -6
  88. package/dist/{chunk-OJFOIKRM.js → chunk-PM2MHBMF.js} +3 -3
  89. package/dist/{chunk-ITKAMUHG.js → chunk-PNXNWZ5P.js} +3 -3
  90. package/dist/{chunk-AZ56FPRG.js → chunk-PPKIL4EL.js} +1 -1
  91. package/dist/{chunk-WOZLV3LN.js → chunk-QAWWTRDD.js} +4 -4
  92. package/dist/{chunk-NPO3ZTR4.js → chunk-QMPM3CAN.js} +1 -1
  93. package/dist/{chunk-X2KK42JA.js → chunk-R5PZPROX.js} +1 -1
  94. package/dist/{chunk-T7FPZN5V.js → chunk-RZVWEDIS.js} +3 -3
  95. package/dist/{chunk-TATE22GH.js → chunk-S63VIM7E.js} +1 -1
  96. package/dist/{chunk-ZZ7VB6QC.js → chunk-S7VMHSJW.js} +3 -3
  97. package/dist/{chunk-X6CSZSSQ.js → chunk-SQ5XCMPB.js} +4 -4
  98. package/dist/{chunk-BJRLJJS2.js → chunk-TFDMJHAB.js} +1 -1
  99. package/dist/{chunk-VMQEB56V.js → chunk-TQ2L3ECX.js} +2 -2
  100. package/dist/{chunk-IPSO7DUB.js → chunk-TTGBS5JB.js} +3 -3
  101. package/dist/{chunk-YX6VQGLG.js → chunk-U3EBM73X.js} +2 -2
  102. package/dist/{chunk-LRS4TJU2.js → chunk-UIF7HWHX.js} +4 -4
  103. package/dist/{chunk-LWAYXE7G.js → chunk-UUCRWEEU.js} +2 -2
  104. package/dist/{chunk-LMP2FAS6.js → chunk-VBSPP4QY.js} +3 -3
  105. package/dist/{chunk-XZTFYFC7.js → chunk-VJUDBZGC.js} +2 -2
  106. package/dist/{chunk-ONZ3UGNY.js → chunk-W47XZJSH.js} +2 -2
  107. package/dist/{chunk-G3J5JKEJ.js → chunk-WJJVCDEU.js} +6 -6
  108. package/dist/{chunk-FA6INGK7.js → chunk-WXV7J3DG.js} +3 -3
  109. package/dist/{chunk-EBOGB7JR.js → chunk-XKQ7CQGG.js} +1 -1
  110. package/dist/{chunk-KEPESIIR.js → chunk-XMSDA5WA.js} +1 -70
  111. package/dist/{chunk-LKKIFFUC.js → chunk-YZ6DMGZZ.js} +1 -1
  112. package/dist/{chunk-YMBJU4CT.js → chunk-Z7AIYWZ2.js} +2 -2
  113. package/dist/{chunk-QSZQCKPB.js → chunk-ZBZBYUNZ.js} +5 -5
  114. package/dist/{codex-app-cards-switch-A77HUVKT.js → codex-app-cards-switch-M7VH7AZH.js} +11 -11
  115. package/dist/{codex-desktop-launch-FSHKRCRL.js → codex-desktop-launch-6DASTBBA.js} +1 -1
  116. package/dist/{codex-session-launch-VAXYRXK6.js → codex-session-launch-2XSMFA4K.js} +20 -18
  117. package/dist/{config-SMJ2QWQF.js → config-7QQOIFTW.js} +3 -3
  118. package/dist/{global-upgrade-63X4UFFP.js → global-upgrade-XAKX26AN.js} +10 -10
  119. package/dist/{identity-3L2YHAZP.js → identity-7AS5MBNM.js} +4 -4
  120. package/dist/{live-capabilities-QBQX7RFM.js → live-capabilities-DV4F7MIB.js} +5 -5
  121. package/dist/{paths-5VPGINSP.js → paths-GSMXYKT5.js} +3 -3
  122. package/dist/{reapply-MSIZJULX.js → reapply-MR7UWBXR.js} +27 -27
  123. package/dist/src/index.d.ts +2 -6
  124. package/dist/src/index.js +28 -28
  125. package/dist/{upgrade-lock-KKW6XZ7B.js → upgrade-lock-6OTHLCBZ.js} +4 -4
  126. package/dist/{usage-ICF3NG4F.js → usage-H53PO3VQ.js} +3 -3
  127. package/package.json +3 -1
@@ -2,43 +2,50 @@
2
2
  import {
3
3
  WRAPPER_ACTIVE_ENV,
4
4
  findClaudeBinary
5
- } from "../chunk-YX6VQGLG.js";
5
+ } from "../chunk-U3EBM73X.js";
6
+ import {
7
+ CommandReceipts,
8
+ controllerCommandSchema
9
+ } from "../chunk-N5P2FLOJ.js";
10
+ import {
11
+ external_exports
12
+ } from "../chunk-XMSDA5WA.js";
6
13
  import {
7
14
  SESSION_IDLE_TIMEOUT_MS,
8
15
  describeIdleWindow,
9
16
  startProcessOwnership,
10
17
  withSpawnProcessOwnership
11
- } from "../chunk-ITKAMUHG.js";
12
- import "../chunk-BJRLJJS2.js";
18
+ } from "../chunk-PNXNWZ5P.js";
19
+ import "../chunk-TFDMJHAB.js";
13
20
  import {
14
21
  announceSubscriptionRequired,
15
22
  isDeferAnswer
16
- } from "../chunk-3CN2EFKK.js";
23
+ } from "../chunk-AXZ67XVN.js";
17
24
  import {
18
25
  claudeTranscriptPath,
19
26
  loadSessionKey,
20
27
  registerTranscript,
21
28
  registeredTranscriptEnabled
22
- } from "../chunk-IYMYGKFB.js";
23
- import "../chunk-FTOKESSK.js";
29
+ } from "../chunk-AC5TVOAH.js";
30
+ import "../chunk-FB262EOM.js";
24
31
  import {
25
32
  ensureDaemonRunning
26
- } from "../chunk-G3J5JKEJ.js";
27
- import "../chunk-WOZLV3LN.js";
28
- import "../chunk-EVJRITSI.js";
29
- import "../chunk-MZ6GL46A.js";
33
+ } from "../chunk-WJJVCDEU.js";
34
+ import "../chunk-QAWWTRDD.js";
35
+ import "../chunk-2HFC4W6J.js";
36
+ import "../chunk-BLX6KRK5.js";
30
37
  import "../chunk-NMUI3ZWG.js";
31
38
  import {
32
39
  encryptRecord
33
- } from "../chunk-LA3MMHLO.js";
40
+ } from "../chunk-JC7XUZST.js";
34
41
  import {
35
42
  transcriptsEnabled
36
- } from "../chunk-TATE22GH.js";
43
+ } from "../chunk-S63VIM7E.js";
37
44
  import {
38
45
  needsShell,
39
46
  spawnClaude
40
47
  } from "../chunk-XHKBHWLX.js";
41
- import "../chunk-D536PHXF.js";
48
+ import "../chunk-NGNBX3YH.js";
42
49
  import {
43
50
  DEFAULT_SESSION,
44
51
  UNNAMED_ACTION,
@@ -55,10 +62,10 @@ import {
55
62
  reportEvent,
56
63
  savePendingCommand,
57
64
  scopePathFor
58
- } from "../chunk-5NQ3SMVP.js";
65
+ } from "../chunk-AJWGZJFS.js";
59
66
  import {
60
67
  repoKeyFor
61
- } from "../chunk-NPO3ZTR4.js";
68
+ } from "../chunk-QMPM3CAN.js";
62
69
  import {
63
70
  askUser,
64
71
  cancelQuestion,
@@ -70,30 +77,30 @@ import {
70
77
  waitForAnswer,
71
78
  waitsForThePhone,
72
79
  withdrawQuestion
73
- } from "../chunk-YG3NFC5N.js";
80
+ } from "../chunk-CMYFV3VC.js";
74
81
  import "../chunk-POFBE6ZY.js";
75
82
  import {
76
83
  SubscriptionRequiredError
77
- } from "../chunk-GIE7PLZD.js";
78
- import "../chunk-ZZ7VB6QC.js";
79
- import "../chunk-XSUWXBHF.js";
84
+ } from "../chunk-7M4AA7QF.js";
85
+ import "../chunk-S7VMHSJW.js";
86
+ import "../chunk-O4EAMVSC.js";
80
87
  import {
81
88
  getMachineId
82
- } from "../chunk-LWAYXE7G.js";
89
+ } from "../chunk-UUCRWEEU.js";
83
90
  import "../chunk-PPY5N2E6.js";
84
91
  import "../chunk-KZERVKTD.js";
85
- import "../chunk-AERRQEWZ.js";
92
+ import "../chunk-7FROPPIH.js";
86
93
  import {
87
94
  claudeConfigDir
88
- } from "../chunk-SMIU5URV.js";
89
- import "../chunk-T7FPZN5V.js";
95
+ } from "../chunk-7S46LCKD.js";
96
+ import "../chunk-RZVWEDIS.js";
90
97
  import {
91
98
  getApiKey,
92
99
  getBaseUrl
93
- } from "../chunk-X2KK42JA.js";
100
+ } from "../chunk-R5PZPROX.js";
94
101
  import {
95
102
  NPX_LATEST_COMMAND
96
- } from "../chunk-AZ56FPRG.js";
103
+ } from "../chunk-PPKIL4EL.js";
97
104
  import "../chunk-QTOLNRET.js";
98
105
  import {
99
106
  DECISION_STOP_REASON,
@@ -101,12 +108,13 @@ import {
101
108
  KILL_REASON,
102
109
  SESSION_PAUSED_REASON,
103
110
  UNVERIFIABLE_APPROVAL_REASON,
111
+ extractMessageText,
104
112
  questionLimitHandBack,
105
113
  redactSecretsFromContent,
106
114
  refusesWhenUnanswered,
107
115
  resolveGate,
108
116
  timeoutActionWithoutLocalPrompt
109
- } from "../chunk-JGEPODEP.js";
117
+ } from "../chunk-AVHL2ZTS.js";
110
118
  import "../chunk-MLKGABMK.js";
111
119
 
112
120
  // src/wrapper/localPassthrough.ts
@@ -153,6 +161,36 @@ var runLocalPassthrough = (binary, args2) => {
153
161
  });
154
162
  };
155
163
 
164
+ // src/wrapper/command-history.ts
165
+ import { open } from "fs/promises";
166
+ var userRecord = external_exports.object({ type: external_exports.literal("user"), uuid: external_exports.string(), message: external_exports.object({ role: external_exports.literal("user") }) });
167
+ var MAX_HISTORY_BYTES = 16 * 1024 * 1024;
168
+ var historyAcceptedCommand = async (file, commandId) => {
169
+ try {
170
+ const handle = await open(file, "r");
171
+ try {
172
+ const stat = await handle.stat();
173
+ const start = Math.max(0, stat.size - MAX_HISTORY_BYTES);
174
+ const buffer = Buffer.alloc(Math.min(stat.size, MAX_HISTORY_BYTES));
175
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, start);
176
+ const lines = buffer.subarray(0, bytesRead).toString("utf8").split("\n");
177
+ for (const line of start ? lines.slice(1) : lines) {
178
+ try {
179
+ const record = userRecord.safeParse(JSON.parse(line));
180
+ if (record.success && record.data.uuid === commandId) return true;
181
+ } catch {
182
+ continue;
183
+ }
184
+ }
185
+ } finally {
186
+ await handle.close();
187
+ }
188
+ } catch {
189
+ return false;
190
+ }
191
+ return false;
192
+ };
193
+
156
194
  // src/wrapper/dualMode.ts
157
195
  import { existsSync as existsSync2 } from "fs";
158
196
 
@@ -170,6 +208,7 @@ var redactDraft = (text, complete = false) => {
170
208
  }
171
209
  return redactSecretsFromContent(text.slice(0, end));
172
210
  };
211
+ var isDraftObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
173
212
  var DraftText = class {
174
213
  messageId = "";
175
214
  text = "";
@@ -177,18 +216,28 @@ var DraftText = class {
177
216
  interrupted = false;
178
217
  add(message) {
179
218
  if (message.parent_tool_use_id) return;
180
- const event = message.event;
219
+ const event = isDraftObject(message.event) ? message.event : null;
181
220
  if (message.type === "stream_event" && event?.type === "message_start") {
182
- this.messageId = event.message?.id ?? "";
221
+ const start = isDraftObject(event.message) ? event.message : null;
222
+ this.messageId = typeof start?.id === "string" ? start.id : "";
183
223
  this.text = "";
184
224
  this.complete = false;
185
225
  this.interrupted = false;
186
- } else if (message.type === "stream_event" && event?.type === "content_block_delta" && event.delta?.type === "text_delta") {
187
- this.text = (this.text + (event.delta.text ?? "")).slice(0, 32 * 1024);
188
- } else if (message.type === "assistant") {
226
+ } else if (message.type === "stream_event" && event?.type === "content_block_start") {
227
+ const block = isDraftObject(event.content_block) ? event.content_block : null;
228
+ if (block?.type === "text") {
229
+ const separator = this.text.length > 0 ? "\n" : "";
230
+ this.text = (this.text + separator + (typeof block.text === "string" ? block.text : "")).slice(0, 32 * 1024);
231
+ }
232
+ } else if (message.type === "stream_event" && event?.type === "content_block_delta") {
233
+ const delta = isDraftObject(event.delta) ? event.delta : null;
234
+ if (delta?.type === "text_delta" && typeof delta.text === "string") {
235
+ this.text = (this.text + delta.text).slice(0, 32 * 1024);
236
+ }
237
+ } else if (message.type === "assistant" && isDraftObject(message.message)) {
189
238
  const body = message.message;
190
- if (body?.id === this.messageId && Array.isArray(body.content)) {
191
- this.text = body.content.filter((block) => block.type === "text").map((block) => block.text).join("").slice(0, 32 * 1024);
239
+ if (body.id === this.messageId && (Array.isArray(body.content) || typeof body.content === "string")) {
240
+ this.text = extractMessageText(body.content).slice(0, 32 * 1024);
192
241
  this.complete = true;
193
242
  }
194
243
  }
@@ -201,7 +250,7 @@ var DraftText = class {
201
250
  return this.messageId && text ? { messageId: this.messageId, text, status: this.interrupted ? "interrupted" : this.complete ? "complete" : "streaming" } : null;
202
251
  }
203
252
  };
204
- var startTranscriptDraft = (apiKey, getSessionId) => {
253
+ var startTranscriptDraft = (apiKey, getSessionId, runtime = {}) => {
205
254
  const text = new DraftText();
206
255
  const startedAt = Date.now();
207
256
  let revision = 0;
@@ -210,17 +259,17 @@ var startTranscriptDraft = (apiKey, getSessionId) => {
210
259
  let ending = false;
211
260
  let sent = "";
212
261
  const flush = async () => {
213
- if (stopped || pending || !transcriptsEnabled()) return;
262
+ if (stopped || pending || !(runtime.enabled ?? transcriptsEnabled)()) return;
214
263
  const sessionId = getSessionId();
215
264
  const snapshot = text.snapshot();
216
- if (!sessionId || !snapshot || !registeredTranscriptEnabled(apiKey, sessionId)) return;
265
+ if (!sessionId || !snapshot || !(runtime.registered ? runtime.registered(sessionId) : registeredTranscriptEnabled(apiKey, sessionId))) return;
217
266
  const raw = JSON.stringify(snapshot);
218
267
  if (raw === sent) return;
219
- const key = loadSessionKey(sessionId);
268
+ const key = loadSessionKey(sessionId, runtime.keyDirectory);
220
269
  if (!key?.keyUploaded) return;
221
270
  pending = (async () => {
222
271
  try {
223
- const response = await fetch(`${getBaseUrl()}/api/agent/transcript`, {
272
+ const response = await fetch(`${runtime.baseUrl ?? getBaseUrl()}/api/agent/transcript`, {
224
273
  method: "POST",
225
274
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
226
275
  body: JSON.stringify({ sessionId, draft: { startedAt, revision: ++revision, content: encryptRecord(raw, key.sessionKey) } }),
@@ -236,7 +285,7 @@ var startTranscriptDraft = (apiKey, getSessionId) => {
236
285
  pending = void 0;
237
286
  }
238
287
  };
239
- const timer = setInterval(() => void flush(), 800);
288
+ const timer = setInterval(() => void flush(), 500);
240
289
  timer.unref();
241
290
  return {
242
291
  add: (message) => {
@@ -284,6 +333,7 @@ var importSdk = async (specifierOrUrl) => {
284
333
  }
285
334
  };
286
335
  var loadClaudeSdk = async () => {
336
+ if (process.env.PUSHARY_REMOTE_SDK_DIR?.trim()) return importSdk(pathToFileURL(cachedSdkEntry()).href);
287
337
  const fromResolution = await importSdk(SDK_PACKAGE);
288
338
  if (fromResolution) return fromResolution;
289
339
  const entry = cachedSdkEntry();
@@ -713,7 +763,10 @@ var startCommandPoller = (opts) => {
713
763
  command = drained.command;
714
764
  }
715
765
  errorStreak = 0;
716
- if (command) {
766
+ if (command && typeof command !== "string" && command.receipt && opts.onClaimedCommand) {
767
+ idleTicks = 0;
768
+ await opts.onClaimedCommand(command.receipt.command, command.receipt.acknowledge);
769
+ } else if (command) {
717
770
  idleTicks = 0;
718
771
  const text = typeof command === "string" ? command : command.text;
719
772
  let accepted = false;
@@ -743,6 +796,11 @@ var startCommandPoller = (opts) => {
743
796
  schedule(fast);
744
797
  opts.log?.("[pushary] command poller active");
745
798
  return {
799
+ wake() {
800
+ if (stopped || inflight) return;
801
+ if (timer) clearTimeout(timer);
802
+ schedule(1);
803
+ },
746
804
  stop() {
747
805
  stopped = true;
748
806
  if (timer) clearTimeout(timer);
@@ -754,11 +812,11 @@ var startCommandPoller = (opts) => {
754
812
 
755
813
  // src/wrapper/pollClient.ts
756
814
  var APPROVAL_MODES = /* @__PURE__ */ new Set(["push_only", "terminal_only", "push_first", "notify_only"]);
757
- var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBaseUrl(), machineId, controlMode, takeoverDeclined) => {
815
+ var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBaseUrl(), machineId, controlMode, takeoverDeclined, durableReceipts = false) => {
758
816
  try {
759
817
  const sessionKey = sessionId || DEFAULT_SESSION;
760
818
  const receiptNamespace = `${apiKey}:${machineId}:claude_code`;
761
- let storedCommand = machineId ? readPendingCommand(sessionKey, receiptNamespace) : null;
819
+ let storedCommand = machineId && !durableReceipts ? readPendingCommand(sessionKey, receiptNamespace) : null;
762
820
  let acknowledgedCommandId = null;
763
821
  const acknowledge = async (id, state) => {
764
822
  const persisted = state === "delivered" ? markPendingCommandDelivered(sessionKey, id, receiptNamespace) : markPendingCommandFailed(sessionKey, id, receiptNamespace);
@@ -797,6 +855,7 @@ var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBase
797
855
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
798
856
  body: JSON.stringify({
799
857
  ...sessionId ? { sessionId } : {},
858
+ ...durableReceipts ? { receiptVersion: 1 } : {},
800
859
  ...controlMode ? { controlMode } : {},
801
860
  ...takeoverDeclined ? { takeoverDeclined } : {},
802
861
  ...machineId ? { machineId } : {},
@@ -815,6 +874,24 @@ var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBase
815
874
  let command = typeof data.command === "string" && data.command.length > 0 ? data.command : null;
816
875
  if (storedCommand) {
817
876
  command = storedCommand.delivered || storedCommand.failed ? null : claimedCommand(storedCommand.id, storedCommand.text);
877
+ } else if (durableReceipts) {
878
+ const parsed = controllerCommandSchema.safeParse(data.command);
879
+ if (parsed.success && machineId) {
880
+ const acknowledge2 = async (state, dispatchId) => {
881
+ try {
882
+ const result = await fetch(`${baseUrl}/api/agent/command/ack`, {
883
+ method: "POST",
884
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
885
+ body: JSON.stringify({ commandId: parsed.data.id, sessionId, machineId, state, dispatchId }),
886
+ signal: AbortSignal.timeout(5e3)
887
+ });
888
+ return result.ok;
889
+ } catch {
890
+ return false;
891
+ }
892
+ };
893
+ command = { ...parsed.data, receipt: { command: parsed.data, acknowledge: acknowledge2 } };
894
+ } else command = null;
818
895
  } else if (data.command && typeof data.command === "object") {
819
896
  const claimed = data.command;
820
897
  if (typeof claimed.id === "string" && typeof claimed.text === "string" && claimed.id !== acknowledgedCommandId) {
@@ -944,7 +1021,7 @@ var createRelayClient = (options) => {
944
1021
  return true;
945
1022
  };
946
1023
  const sendFrame = (frame) => {
947
- if (!socket || !isConnected) return;
1024
+ if (!socket) return;
948
1025
  try {
949
1026
  socket.send(encodeFrame(frame));
950
1027
  } catch {
@@ -978,9 +1055,15 @@ var createRelayClient = (options) => {
978
1055
  switch (frame.t) {
979
1056
  case "welcome":
980
1057
  attempt = 0;
1058
+ isConnected = true;
1059
+ options.onCommandAvailable?.();
981
1060
  log("[pushary] relay connected");
982
1061
  break;
983
1062
  case "command":
1063
+ if (options.onCommandAvailable) {
1064
+ options.onCommandAvailable();
1065
+ break;
1066
+ }
984
1067
  if (pendingCommands.has(frame.id)) {
985
1068
  await pendingCommands.get(frame.id);
986
1069
  acknowledgeStored(frame.id);
@@ -1050,9 +1133,11 @@ var createRelayClient = (options) => {
1050
1133
  if (pongTimer) clearTimeout(pongTimer);
1051
1134
  heartbeatTimer = void 0;
1052
1135
  pongTimer = void 0;
1053
- if (socket) {
1136
+ const previous = socket;
1137
+ socket = void 0;
1138
+ if (previous) {
1054
1139
  try {
1055
- socket.close();
1140
+ previous.close();
1056
1141
  } catch {
1057
1142
  }
1058
1143
  }
@@ -1090,13 +1175,15 @@ var createRelayClient = (options) => {
1090
1175
  }
1091
1176
  socket = created;
1092
1177
  created.onOpen(() => {
1093
- isConnected = true;
1178
+ if (socket !== created || stopped) return;
1094
1179
  sendHello();
1095
1180
  startHeartbeat();
1096
1181
  });
1097
- created.onMessage((data) => onServerFrame(data));
1182
+ created.onMessage((data) => {
1183
+ if (socket === created && !stopped) void onServerFrame(data);
1184
+ });
1098
1185
  created.onClose(() => {
1099
- if (stopped) return;
1186
+ if (stopped || socket !== created) return;
1100
1187
  dropAndReconnect();
1101
1188
  });
1102
1189
  created.onError(() => {
@@ -1375,6 +1462,7 @@ var runRemoteLoop = async (deps) => {
1375
1462
  options: {
1376
1463
  resume: resumeId,
1377
1464
  includePartialMessages: true,
1465
+ extraArgs: { "replay-user-messages": null },
1378
1466
  permissionMode: deps.permissionMode,
1379
1467
  model: deps.model,
1380
1468
  settings: deps.settings,
@@ -1423,7 +1511,7 @@ var runRemoteLoop = async (deps) => {
1423
1511
  return ended(1, "failed");
1424
1512
  }
1425
1513
  resumeId = message.session_id;
1426
- deps.onSessionId?.(resumeId);
1514
+ await deps.onSessionId?.(resumeId);
1427
1515
  }
1428
1516
  if (message.type === "result" && typeof message.total_cost_usd === "number") {
1429
1517
  totalCost = message.total_cost_usd;
@@ -1479,7 +1567,7 @@ var runRemoteLeg = async (session, sdk, deps = {}) => {
1479
1567
  };
1480
1568
  session.setActiveLeg(control);
1481
1569
  session.stdin.captureForWrapper((data) => {
1482
- if (data.includes(CTRL_RIGHT_BRACKET)) {
1570
+ if (deps.allowLocalHandoff !== false && data.includes(CTRL_RIGHT_BRACKET)) {
1483
1571
  switchBack = true;
1484
1572
  if (!legAbort.signal.aborted) legAbort.abort();
1485
1573
  } else if (data.includes(CTRL_C)) {
@@ -1487,7 +1575,7 @@ var runRemoteLeg = async (session, sdk, deps = {}) => {
1487
1575
  }
1488
1576
  });
1489
1577
  process.stderr.write(
1490
- "[pushary] remote \u2014 driving from your phone \xB7 press Ctrl-] for the local terminal\n"
1578
+ deps.allowLocalHandoff === false ? "[pushary] ready for phone control \xB7 Ctrl-C stops this session\n" : "[pushary] remote \u2014 driving from your phone \xB7 press Ctrl-] for the local terminal\n"
1491
1579
  );
1492
1580
  let exitCode = 0;
1493
1581
  try {
@@ -1522,7 +1610,7 @@ var runRemoteLeg = async (session, sdk, deps = {}) => {
1522
1610
  session.stdin.releaseToChild();
1523
1611
  session.setActiveLeg(null);
1524
1612
  }
1525
- if (idleEnded && attended) {
1613
+ if (idleEnded && attended && deps.allowLocalHandoff !== false) {
1526
1614
  process.stderr.write("[pushary] handing the terminal back to local mode\n");
1527
1615
  return "switch";
1528
1616
  }
@@ -1563,7 +1651,7 @@ var extractAssistantText = (message) => {
1563
1651
  }
1564
1652
  return void 0;
1565
1653
  };
1566
- var runDualMode = async (binary, args2, startingMode) => {
1654
+ var runDualMode = async (binary, args2, startingMode, runtime = {}) => {
1567
1655
  const compatibilityIssue = remoteCompatibilityIssue(args2);
1568
1656
  if (startingMode === "remote" && compatibilityIssue) {
1569
1657
  process.stderr.write(`[pushary] ${compatibilityIssue}
@@ -1586,7 +1674,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1586
1674
  let noticedUnavailable = false;
1587
1675
  const ensureSdk = async () => {
1588
1676
  if (sdk === void 0) {
1589
- sdkLoading ??= ensureClaudeSdk((message) => process.stderr.write(`${message}
1677
+ sdkLoading ??= runtime.sdk ? Promise.resolve(runtime.sdk) : ensureClaudeSdk((message) => process.stderr.write(`${message}
1590
1678
  `));
1591
1679
  sdk = await sdkLoading;
1592
1680
  if (!sdk) remoteAvailable = false;
@@ -1599,7 +1687,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1599
1687
  }
1600
1688
  const projectName = basename(process.cwd());
1601
1689
  const agentName = `Claude Code - ${projectName}`;
1602
- const machineId = getMachineId();
1690
+ const machineId = runtime.machineId ?? getMachineId();
1603
1691
  const stdin = createStdinHandoff();
1604
1692
  const verbose = process.env.PUSHARY_VERBOSE === "1";
1605
1693
  const verboseLog = (message) => {
@@ -1625,8 +1713,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1625
1713
  const input = new BoundedInputQueue({
1626
1714
  cap: INPUT_QUEUE_CAP,
1627
1715
  onDrop: (_dropped, total) => process.stderr.write(`[pushary] input queue full; refused ${total} instruction(s)
1628
- `),
1629
- coalesce: (items) => userMessage(items.map((m) => m.message.content).join("\n\n"))
1716
+ `)
1630
1717
  });
1631
1718
  const approver = createRemoteApprover({
1632
1719
  apiKey,
@@ -1646,6 +1733,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1646
1733
  agentType: "claude_code",
1647
1734
  getSessionId: () => sessionId,
1648
1735
  onCommand: (command) => onPhoneCommand(command),
1736
+ onCommandAvailable: () => poller.wake?.(),
1649
1737
  onFatal: (message) => verboseLog(`[pushary] relay unavailable (${message}); using polling`),
1650
1738
  log: verboseLog
1651
1739
  });
@@ -1673,13 +1761,19 @@ var runDualMode = async (binary, args2, startingMode) => {
1673
1761
  { event, agentType: "claude_code", agentName, action, sessionId, machineId, pid: process.pid },
1674
1762
  { maxAttempts: event === "session_start" ? 4 : 2, timeoutMs: 4e3, requireOk: event === "session_start" }
1675
1763
  );
1676
- } catch {
1764
+ } catch (error) {
1765
+ if (runtime.onReady && event === "session_start") throw error;
1677
1766
  }
1678
1767
  };
1679
1768
  let presenceHeartbeat;
1680
1769
  let transcriptStarted;
1681
1770
  const ensureTranscriptSync = () => {
1682
- if (transcriptStarted === sessionId || !transcriptsEnabled() || !sessionId) return;
1771
+ if (!sessionId) return;
1772
+ if (runtime.registerTranscript) {
1773
+ runtime.registerTranscript(sessionId, false);
1774
+ return;
1775
+ }
1776
+ if (transcriptStarted === sessionId || !transcriptsEnabled()) return;
1683
1777
  transcriptStarted = sessionId;
1684
1778
  registerTranscript("claude", { session_id: sessionId, cwd: process.cwd() });
1685
1779
  };
@@ -1691,14 +1785,25 @@ var runDualMode = async (binary, args2, startingMode) => {
1691
1785
  );
1692
1786
  presenceHeartbeat.unref();
1693
1787
  };
1694
- const setSessionId = (id) => {
1788
+ let readySessionId;
1789
+ const setSessionId = async (id) => {
1695
1790
  const isNew = sessionId !== id;
1696
1791
  sessionId = id;
1697
1792
  sessionCreated = true;
1698
1793
  relay?.reannounce();
1699
- if (isNew) void announcePresence("session_start", "Remote session ready \u2014 reachable from your phone");
1794
+ if (runtime.onReady && readySessionId !== id) {
1795
+ const control = await pollAgent(apiKey, id, false, AbortSignal.any([AbortSignal.timeout(5e3), ...runtime.signal ? [runtime.signal] : []]), void 0, machineId, "remote");
1796
+ if (!control.mode || control.rejected || control.mode.kill) throw new Error("Phone control could not register or was stopped");
1797
+ }
1798
+ if (isNew || readySessionId !== id) {
1799
+ const registered = announcePresence("session_start", "Remote session ready \u2014 reachable from your phone");
1800
+ if (runtime.onReady) await registered;
1801
+ else void registered;
1802
+ }
1700
1803
  ensurePresenceHeartbeat();
1701
1804
  ensureTranscriptSync();
1805
+ if (readySessionId !== id) await runtime.onReady?.(id);
1806
+ readySessionId = id;
1702
1807
  };
1703
1808
  const onPhoneCommand = async (command) => {
1704
1809
  if (controlMode !== "remote") return false;
@@ -1711,10 +1816,26 @@ var runDualMode = async (binary, args2, startingMode) => {
1711
1816
  }
1712
1817
  if (finished || !input.push(userMessage(command))) return false;
1713
1818
  activeLeg?.onPhoneCommand();
1819
+ void announcePresence("user_prompt", "Working on your message");
1714
1820
  return true;
1715
1821
  };
1822
+ const receipts = new CommandReceipts({
1823
+ wasAccepted: (commandId) => sessionId ? historyAcceptedCommand(claudeTranscriptPath(process.cwd(), sessionId), commandId) : Promise.resolve(false),
1824
+ submit: async (command) => {
1825
+ if (controlMode !== "remote" || finished || !remoteAvailable || !sessionId) return "failed";
1826
+ if (!input.push({ ...userMessage(command.text), uuid: command.id })) return "failed";
1827
+ activeLeg?.onPhoneCommand();
1828
+ void announcePresence("user_prompt", "Working on your message");
1829
+ },
1830
+ log: (message) => process.stderr.write(`${message}
1831
+ `)
1832
+ });
1716
1833
  let draft;
1717
1834
  const streamMessage = (message) => {
1835
+ if (message.type === "user" && typeof message.uuid === "string" && !message.parent_tool_use_id) {
1836
+ receipts.accepted(message.uuid);
1837
+ void receipts.flush();
1838
+ }
1718
1839
  draft?.add(message);
1719
1840
  if (message.type === "assistant") {
1720
1841
  const text = extractAssistantText(message);
@@ -1756,13 +1877,15 @@ var runDualMode = async (binary, args2, startingMode) => {
1756
1877
  apiKey,
1757
1878
  getSessionId: () => sessionId,
1758
1879
  onCommand: (command) => onPhoneCommand(command),
1759
- shouldDrainCommands: () => controlMode === "remote" && Boolean(sessionId) && !(relay?.connected() ?? false),
1880
+ onClaimedCommand: (command, acknowledge) => receipts.receive(command, acknowledge),
1881
+ shouldDrainCommands: () => controlMode === "remote" && Boolean(sessionId),
1760
1882
  // Unified poll: one request per tick for both command + mode/kill (half the
1761
1883
  // requests and the per-poll DB write of the old drain + mode pair).
1762
1884
  poll: async (key, id, wantCommand, signal) => {
1885
+ await receipts.flush();
1763
1886
  const declined = takeoverDecline;
1764
1887
  takeoverDecline = void 0;
1765
- const result = await pollAgent(key, id, wantCommand, signal, void 0, machineId, controlMode, declined);
1888
+ const result = await pollAgent(key, id, wantCommand, signal, void 0, machineId, controlMode, declined, true);
1766
1889
  if (result.takeoverRequested) void takeOver();
1767
1890
  return result;
1768
1891
  },
@@ -1773,8 +1896,12 @@ var runDualMode = async (binary, args2, startingMode) => {
1773
1896
  // of Redis round trips. Takeover already sits behind a confirm and a biometric.
1774
1897
  slowPollMs: 15e3,
1775
1898
  onAuthRejected: () => {
1899
+ if (runtime.remoteOnly) {
1900
+ finished = true;
1901
+ activeLeg?.stop("kill");
1902
+ }
1776
1903
  process.stderr.write(
1777
- "[pushary] this key was rejected \u2014 remote stop and phone messages are NOT active; run `pushary login` to reconnect\n"
1904
+ runtime.remoteOnly ? "[pushary] Mac account access was rejected. Sign in to Pushary on this Mac and retry.\n" : "[pushary] this key was rejected \u2014 remote stop and phone messages are NOT active; run `pushary login` to reconnect\n"
1778
1905
  );
1779
1906
  },
1780
1907
  onModeState: (state) => {
@@ -1793,6 +1920,8 @@ var runDualMode = async (binary, args2, startingMode) => {
1793
1920
  activeLeg?.stop("signal");
1794
1921
  };
1795
1922
  for (const signal of signals) process.on(signal, onSignal);
1923
+ runtime.signal?.addEventListener("abort", onSignal, { once: true });
1924
+ if (runtime.signal?.aborted) onSignal();
1796
1925
  const restoreStdin = () => stdin.dispose();
1797
1926
  process.once("exit", restoreStdin);
1798
1927
  const session = {
@@ -1833,6 +1962,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1833
1962
  const exitCode = await driveModeLoop({
1834
1963
  startingMode,
1835
1964
  runLocal: async () => {
1965
+ if (runtime.remoteOnly) return { type: "exit", code: 1 };
1836
1966
  controlMode = "local";
1837
1967
  await draft?.stop();
1838
1968
  draft = void 0;
@@ -1841,6 +1971,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1841
1971
  return runLocalLeg(session);
1842
1972
  },
1843
1973
  runRemote: async () => {
1974
+ if (finished) return { type: "exit", code: 130 };
1844
1975
  controlMode = "remote";
1845
1976
  startRelay();
1846
1977
  const ready = await ensureSdk();
@@ -1861,8 +1992,8 @@ var runDualMode = async (binary, args2, startingMode) => {
1861
1992
  }
1862
1993
  return "switch";
1863
1994
  }
1864
- draft = startTranscriptDraft(apiKey, () => sessionId);
1865
- return runRemoteLeg(session, ready);
1995
+ draft = (runtime.startDraft ?? startTranscriptDraft)(apiKey, () => sessionId);
1996
+ return runRemoteLeg(session, ready, { allowLocalHandoff: !runtime.remoteOnly, ...runtime.remoteOnly ? { attended: false } : {} });
1866
1997
  },
1867
1998
  isFinished: () => finished,
1868
1999
  minDwellMs: MIN_MODE_DWELL_MS
@@ -1870,6 +2001,7 @@ var runDualMode = async (binary, args2, startingMode) => {
1870
2001
  return { implemented: true, exitCode };
1871
2002
  } finally {
1872
2003
  for (const signal of signals) process.off(signal, onSignal);
2004
+ runtime.signal?.removeEventListener("abort", onSignal);
1873
2005
  process.removeListener("exit", restoreStdin);
1874
2006
  await draft?.stop();
1875
2007
  relay?.stop();
@@ -1878,7 +2010,10 @@ var runDualMode = async (binary, args2, startingMode) => {
1878
2010
  approver.teardown();
1879
2011
  input.close();
1880
2012
  stdin.dispose();
1881
- if (sessionId) registerTranscript("claude", { session_id: sessionId, cwd: process.cwd() }, void 0, { ended: true });
2013
+ if (sessionId) {
2014
+ if (runtime.registerTranscript) runtime.registerTranscript(sessionId, true);
2015
+ else registerTranscript("claude", { session_id: sessionId, cwd: process.cwd() }, void 0, { ended: true });
2016
+ }
1882
2017
  await announcePresence("session_closed", "Session ended");
1883
2018
  ownership?.stop();
1884
2019
  if (totalCostUsd > 0) {