@pushary/agent-hooks 0.94.0 → 0.95.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/data/SKILL.md +31 -24
- package/data/cursor-plugin/.cursor-plugin/plugin.json +1 -1
- package/data/cursor-plugin/CONTRIBUTING.md +31 -19
- package/data/cursor-plugin/skills/pushary/SKILL.md +30 -23
- package/data/vscode-plugin/.claude-plugin/plugin.json +1 -1
- package/data/vscode-plugin/CONTRIBUTING.md +31 -19
- package/data/vscode-plugin/skills/pushary/SKILL.md +30 -23
- package/dist/bin/pushary-bell-hook.js +26 -5
- package/dist/bin/pushary-bell.js +8 -8
- package/dist/bin/pushary-claude.js +343 -365
- package/dist/bin/pushary-clean.js +28 -23
- package/dist/bin/pushary-codex-bridge.js +12 -12
- package/dist/bin/pushary-codex-hook.js +28 -10
- package/dist/bin/pushary-codex.js +7 -7
- package/dist/bin/pushary-connect.js +9 -9
- package/dist/bin/pushary-daemon.js +41 -26
- package/dist/bin/pushary-disconnect.js +8 -6
- package/dist/bin/pushary-doctor.js +42 -39
- package/dist/bin/pushary-elicitation-hook.js +24 -4
- package/dist/bin/pushary-gemini-bridge.js +12 -12
- package/dist/bin/pushary-gemini-hook.js +27 -9
- package/dist/bin/pushary-hook.js +28 -11
- package/dist/bin/pushary-login.js +10 -10
- package/dist/bin/pushary-logout.js +7 -7
- package/dist/bin/pushary-mode.js +6 -6
- package/dist/bin/pushary-notification-hook.js +26 -8
- package/dist/bin/pushary-opencode-hook.js +27 -9
- package/dist/bin/pushary-permission-denied-hook.js +29 -11
- package/dist/bin/pushary-permission-hook.js +29 -11
- package/dist/bin/pushary-post-hook.js +26 -8
- package/dist/bin/pushary-prompt-hook.js +26 -8
- package/dist/bin/pushary-session-end-hook.js +30 -9
- package/dist/bin/pushary-session-start-hook.js +29 -14
- package/dist/bin/pushary-setup.js +59 -53
- package/dist/bin/pushary-stats.js +2 -2
- package/dist/bin/pushary-status.js +11 -11
- package/dist/bin/pushary-stop-hook.js +26 -8
- package/dist/bin/pushary-stopfailure-hook.js +26 -8
- package/dist/bin/pushary-suggestions.js +3 -3
- package/dist/bin/pushary-transcript-register.d.ts +1 -0
- package/dist/bin/pushary-transcript-register.js +42 -0
- package/dist/bin/pushary-transcripts.js +4 -4
- package/dist/bin/pushary-upgrade.js +19 -18
- package/dist/bin/pushary-wait.js +6 -6
- package/dist/{chunk-Y6XXFXVB.js → chunk-3F4VV5TS.js} +1 -1
- package/dist/chunk-77U3ZVV6.js +86 -0
- package/dist/{chunk-RH3TM7TO.js → chunk-7MGF5Q7G.js} +1 -1
- package/dist/{chunk-WY4ALDYN.js → chunk-AQ5OAYOH.js} +1 -1
- package/dist/chunk-BWNU4MX4.js +185 -0
- package/dist/{chunk-OYNPLEXP.js → chunk-C2MV4G4A.js} +1 -1
- package/dist/chunk-CC625NRG.js +503 -0
- package/dist/{chunk-ZTJF6ANY.js → chunk-CU5KI425.js} +2 -2
- package/dist/{chunk-HSAVPZ46.js → chunk-IFCS6LXB.js} +3 -3
- package/dist/{chunk-36LR2LOT.js → chunk-IP63LMZM.js} +10 -8
- package/dist/{chunk-BSEAOCCQ.js → chunk-IZRI4M3C.js} +1 -1
- package/dist/{chunk-5HGLZGI4.js → chunk-J55K6YS4.js} +6 -5
- package/dist/{chunk-6BIMJIRG.js → chunk-JSASMV3H.js} +1 -1
- package/dist/{chunk-6CUUA7HO.js → chunk-K7ZZGI2Y.js} +7 -1
- package/dist/{chunk-TDUTNYAQ.js → chunk-M64I4T7J.js} +1 -1
- package/dist/{chunk-ZO2XG3CX.js → chunk-MF64S7WO.js} +1 -1
- package/dist/chunk-N6FAXQDD.js +276 -0
- package/dist/{chunk-UZFVNEEU.js → chunk-PDFVIRPB.js} +1 -1
- package/dist/chunk-PQ4K74WL.js +30 -0
- package/dist/{chunk-ROHFVGXM.js → chunk-R7NPGCSD.js} +1 -1
- package/dist/{chunk-QGA2ZB4I.js → chunk-RNH2AE52.js} +1 -1
- package/dist/{chunk-NVPYRDCT.js → chunk-SSQ5X6SA.js} +1 -1
- package/dist/{chunk-OB4RF2TF.js → chunk-T23EGPRI.js} +1 -1
- package/dist/{chunk-ODL52O5V.js → chunk-T5B3LRYF.js} +1 -1
- package/dist/{chunk-Y3UGCQQC.js → chunk-TTO76NZJ.js} +1 -1
- package/dist/{chunk-46P3VES5.js → chunk-UFGDCMUD.js} +2 -1
- package/dist/{chunk-2ORRI66J.js → chunk-UQSKCQBO.js} +1 -1
- package/dist/{chunk-4PE2SQVM.js → chunk-VLKEAYJ4.js} +1 -1
- package/dist/chunk-WJZUTMMU.js +194 -0
- package/dist/{chunk-QKNLW7H6.js → chunk-Y24D5ZWS.js} +7 -7
- package/dist/{chunk-YANV5TGK.js → chunk-YPYPI6BG.js} +8 -90
- package/dist/{chunk-6WYL3XIP.js → chunk-ZOR5OJZJ.js} +4 -3
- package/dist/chunk-ZW3KAWTM.js +266 -0
- package/dist/src/index.js +11 -11
- package/package.json +3 -2
- package/dist/chunk-FYOIW5RV.js +0 -539
- package/dist/chunk-RX75MRGC.js +0 -368
- /package/dist/{chunk-Z6CP5ZZD.js → chunk-VOWZ5VOO.js} +0 -0
|
@@ -1,47 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
verifyRecipients
|
|
7
|
-
} from "../chunk-YKLCWWEI.js";
|
|
8
|
-
import {
|
|
9
|
-
ensureDaemonRunning
|
|
10
|
-
} from "../chunk-ZTJF6ANY.js";
|
|
11
|
-
import {
|
|
12
|
-
buildTranscriptRecords,
|
|
13
|
-
decodePublicKey,
|
|
14
|
-
generateSessionKey,
|
|
15
|
-
wrapSessionKey
|
|
16
|
-
} from "../chunk-46P3VES5.js";
|
|
17
|
-
import {
|
|
18
|
-
fetchTranscriptRecipients,
|
|
19
|
-
uploadTranscriptRecords
|
|
20
|
-
} from "../chunk-EQRS3SGM.js";
|
|
21
|
-
import {
|
|
22
|
-
transcriptsEnabled
|
|
23
|
-
} from "../chunk-6WYL3XIP.js";
|
|
3
|
+
WRAPPER_ACTIVE_ENV,
|
|
4
|
+
findClaudeBinary
|
|
5
|
+
} from "../chunk-PQ4K74WL.js";
|
|
24
6
|
import {
|
|
25
7
|
SESSION_IDLE_TIMEOUT_MS,
|
|
26
8
|
describeIdleWindow,
|
|
27
9
|
startProcessOwnership,
|
|
28
10
|
withSpawnProcessOwnership
|
|
29
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-M64I4T7J.js";
|
|
12
|
+
import {
|
|
13
|
+
isDeferAnswer
|
|
14
|
+
} from "../chunk-YHG74UFF.js";
|
|
15
|
+
import {
|
|
16
|
+
claudeTranscriptPath,
|
|
17
|
+
loadSessionKey,
|
|
18
|
+
registerTranscript,
|
|
19
|
+
registeredTranscriptEnabled
|
|
20
|
+
} from "../chunk-CC625NRG.js";
|
|
21
|
+
import "../chunk-YKLCWWEI.js";
|
|
22
|
+
import "../chunk-ZW3KAWTM.js";
|
|
23
|
+
import "../chunk-77U3ZVV6.js";
|
|
24
|
+
import "../chunk-IFCS6LXB.js";
|
|
25
|
+
import {
|
|
26
|
+
ensureDaemonRunning
|
|
27
|
+
} from "../chunk-CU5KI425.js";
|
|
28
|
+
import "../chunk-BWNU4MX4.js";
|
|
29
|
+
import "../chunk-DSTLA2TC.js";
|
|
30
|
+
import "../chunk-EZIEXHYM.js";
|
|
31
|
+
import {
|
|
32
|
+
encryptRecord
|
|
33
|
+
} from "../chunk-UFGDCMUD.js";
|
|
34
|
+
import "../chunk-EQRS3SGM.js";
|
|
35
|
+
import {
|
|
36
|
+
transcriptsEnabled
|
|
37
|
+
} from "../chunk-ZOR5OJZJ.js";
|
|
30
38
|
import {
|
|
31
39
|
needsShell,
|
|
32
40
|
spawnClaude
|
|
33
41
|
} from "../chunk-XHKBHWLX.js";
|
|
34
42
|
import "../chunk-L4ZCFMWW.js";
|
|
35
|
-
import {
|
|
36
|
-
isDeferAnswer
|
|
37
|
-
} from "../chunk-YHG74UFF.js";
|
|
38
43
|
import {
|
|
39
44
|
fetchModeState,
|
|
40
45
|
getPolicy,
|
|
41
46
|
phoneDrivenEnv,
|
|
42
47
|
repoKeyFor,
|
|
43
48
|
reportEvent
|
|
44
|
-
} from "../chunk-
|
|
49
|
+
} from "../chunk-J55K6YS4.js";
|
|
45
50
|
import {
|
|
46
51
|
DEFAULT_SESSION,
|
|
47
52
|
askUser,
|
|
@@ -59,20 +64,21 @@ import {
|
|
|
59
64
|
deriveToolTarget,
|
|
60
65
|
describeToolCall,
|
|
61
66
|
scopePathFor
|
|
62
|
-
} from "../chunk-
|
|
63
|
-
import "../chunk-
|
|
67
|
+
} from "../chunk-VLKEAYJ4.js";
|
|
68
|
+
import "../chunk-TTO76NZJ.js";
|
|
64
69
|
import {
|
|
65
70
|
getMachineId
|
|
66
|
-
} from "../chunk-
|
|
67
|
-
import "../chunk-
|
|
71
|
+
} from "../chunk-SSQ5X6SA.js";
|
|
72
|
+
import "../chunk-3F4VV5TS.js";
|
|
73
|
+
import "../chunk-GMXKITVA.js";
|
|
68
74
|
import "../chunk-DINDL2CF.js";
|
|
69
|
-
import "../chunk-
|
|
75
|
+
import "../chunk-JSASMV3H.js";
|
|
70
76
|
import "../chunk-BC3VCZ3E.js";
|
|
71
77
|
import "../chunk-EYRGBYZG.js";
|
|
72
78
|
import "../chunk-SAF6HGAA.js";
|
|
73
79
|
import "../chunk-7QLSKOSU.js";
|
|
74
80
|
import "../chunk-KZERVKTD.js";
|
|
75
|
-
import "../chunk-
|
|
81
|
+
import "../chunk-T5B3LRYF.js";
|
|
76
82
|
import "../chunk-4LL6LG5X.js";
|
|
77
83
|
import {
|
|
78
84
|
getApiKey,
|
|
@@ -80,34 +86,9 @@ import {
|
|
|
80
86
|
} from "../chunk-2UMNXADU.js";
|
|
81
87
|
import {
|
|
82
88
|
KILL_REASON,
|
|
89
|
+
redactSecrets,
|
|
83
90
|
resolveGate
|
|
84
|
-
} from "../chunk-
|
|
85
|
-
|
|
86
|
-
// src/wrapper/claudeBinary.ts
|
|
87
|
-
import { statSync } from "fs";
|
|
88
|
-
import { join, delimiter } from "path";
|
|
89
|
-
var WRAPPER_ACTIVE_ENV = "PUSHARY_WRAPPER_ACTIVE";
|
|
90
|
-
var isFile = (path) => {
|
|
91
|
-
try {
|
|
92
|
-
return statSync(path).isFile();
|
|
93
|
-
} catch {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
var findClaudeBinary = () => {
|
|
98
|
-
const override = process.env.PUSHARY_CLAUDE_BIN?.trim();
|
|
99
|
-
if (override) return isFile(override) ? override : null;
|
|
100
|
-
const pathVar = process.env.PATH ?? "";
|
|
101
|
-
const exts = process.platform === "win32" ? [".cmd", ".exe", ".bat", ""] : [""];
|
|
102
|
-
for (const dir of pathVar.split(delimiter)) {
|
|
103
|
-
if (!dir) continue;
|
|
104
|
-
for (const ext of exts) {
|
|
105
|
-
const candidate = join(dir, `claude${ext}`);
|
|
106
|
-
if (isFile(candidate)) return candidate;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
};
|
|
91
|
+
} from "../chunk-VOWZ5VOO.js";
|
|
111
92
|
|
|
112
93
|
// src/wrapper/localPassthrough.ts
|
|
113
94
|
var SIGNAL_NUMBERS = {
|
|
@@ -119,7 +100,7 @@ var SIGNAL_NUMBERS = {
|
|
|
119
100
|
};
|
|
120
101
|
var FORWARDED_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
121
102
|
var runLocalPassthrough = (binary, args2) => {
|
|
122
|
-
return new Promise((
|
|
103
|
+
return new Promise((resolve) => {
|
|
123
104
|
let child;
|
|
124
105
|
try {
|
|
125
106
|
child = spawnClaude(binary, args2, {
|
|
@@ -127,7 +108,7 @@ var runLocalPassthrough = (binary, args2) => {
|
|
|
127
108
|
env: { ...process.env, [WRAPPER_ACTIVE_ENV]: "1" }
|
|
128
109
|
});
|
|
129
110
|
} catch {
|
|
130
|
-
|
|
111
|
+
resolve(127);
|
|
131
112
|
return;
|
|
132
113
|
}
|
|
133
114
|
const forward = (signal) => {
|
|
@@ -142,275 +123,126 @@ var runLocalPassthrough = (binary, args2) => {
|
|
|
142
123
|
};
|
|
143
124
|
child.on("error", () => {
|
|
144
125
|
cleanup();
|
|
145
|
-
|
|
126
|
+
resolve(127);
|
|
146
127
|
});
|
|
147
128
|
child.on("exit", (code2, signal) => {
|
|
148
129
|
cleanup();
|
|
149
|
-
if (typeof code2 === "number")
|
|
150
|
-
else if (signal)
|
|
151
|
-
else
|
|
130
|
+
if (typeof code2 === "number") resolve(code2);
|
|
131
|
+
else if (signal) resolve(128 + (SIGNAL_NUMBERS[signal] ?? 0));
|
|
132
|
+
else resolve(0);
|
|
152
133
|
});
|
|
153
134
|
});
|
|
154
135
|
};
|
|
155
136
|
|
|
156
137
|
// src/wrapper/dualMode.ts
|
|
157
|
-
import {
|
|
158
|
-
|
|
159
|
-
// src/transcript-sync.ts
|
|
160
|
-
import { homedir as homedir2 } from "os";
|
|
161
|
-
import { join as join3, resolve } from "path";
|
|
162
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
138
|
+
import { existsSync as existsSync2 } from "fs";
|
|
163
139
|
|
|
164
|
-
// src/transcript-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
var SAFE_SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/;
|
|
171
|
-
var fileFor = (sessionId) => SAFE_SESSION_ID.test(sessionId) ? join2(sessionDir(), `${sessionId}.json`) : null;
|
|
172
|
-
var loadSessionKey = (sessionId) => {
|
|
173
|
-
const path = fileFor(sessionId);
|
|
174
|
-
if (!path || !existsSync(path)) return null;
|
|
175
|
-
try {
|
|
176
|
-
if (Date.now() - statSync2(path).mtimeMs > SESSION_KEY_TTL_MS) return null;
|
|
177
|
-
const raw = JSON.parse(readFileSync(path, "utf-8"));
|
|
178
|
-
const key = typeof raw.sessionKey === "string" ? Buffer.from(raw.sessionKey, "base64") : null;
|
|
179
|
-
if (!key || key.length !== 32) return null;
|
|
180
|
-
return {
|
|
181
|
-
sessionKey: new Uint8Array(key),
|
|
182
|
-
wrappedKey: typeof raw.wrappedKey === "string" ? raw.wrappedKey : null,
|
|
183
|
-
providerWrappedKey: typeof raw.providerWrappedKey === "string" ? raw.providerWrappedKey : null,
|
|
184
|
-
providerKeyId: typeof raw.providerKeyId === "string" ? raw.providerKeyId : null,
|
|
185
|
-
keyUploaded: raw.keyUploaded === true
|
|
186
|
-
};
|
|
187
|
-
} catch {
|
|
188
|
-
return null;
|
|
140
|
+
// src/transcript-draft.ts
|
|
141
|
+
var redactDraft = (text, complete = false) => {
|
|
142
|
+
let end = complete ? text.length : text.lastIndexOf("\n") + 1;
|
|
143
|
+
const pem = /-----BEGIN [^\n]*PRIVATE KEY-----/g;
|
|
144
|
+
for (const match of text.matchAll(pem)) {
|
|
145
|
+
if (!/-----END [^\n]*PRIVATE KEY-----/.test(text.slice(match.index, end))) end = Math.min(end, match.index);
|
|
189
146
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
try {
|
|
195
|
-
mkdirSync(sessionDir(), { recursive: true, mode: 448 });
|
|
196
|
-
writeFileSync(
|
|
197
|
-
path,
|
|
198
|
-
JSON.stringify({
|
|
199
|
-
sessionKey: Buffer.from(value.sessionKey).toString("base64"),
|
|
200
|
-
wrappedKey: value.wrappedKey,
|
|
201
|
-
providerWrappedKey: value.providerWrappedKey,
|
|
202
|
-
providerKeyId: value.providerKeyId,
|
|
203
|
-
keyUploaded: value.keyUploaded
|
|
204
|
-
}),
|
|
205
|
-
{ mode: 384 }
|
|
206
|
-
);
|
|
207
|
-
} catch {
|
|
147
|
+
const quoted = /(?:secret|token|password|passwd|api[_-]?key|access[_-]?key|client[_-]?secret|private[_-]?key)\s*[=:]\s*(["'])/gi;
|
|
148
|
+
for (const match of text.matchAll(quoted)) {
|
|
149
|
+
const close = text.indexOf(match[1], match.index + match[0].length);
|
|
150
|
+
if (close < 0 || close >= end) end = Math.min(end, match.index);
|
|
208
151
|
}
|
|
152
|
+
return redactSecrets(text.slice(0, end));
|
|
209
153
|
};
|
|
210
|
-
var
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
154
|
+
var DraftText = class {
|
|
155
|
+
messageId = "";
|
|
156
|
+
text = "";
|
|
157
|
+
complete = false;
|
|
158
|
+
interrupted = false;
|
|
159
|
+
add(message) {
|
|
160
|
+
if (message.parent_tool_use_id) return;
|
|
161
|
+
const event = message.event;
|
|
162
|
+
if (message.type === "stream_event" && event?.type === "message_start") {
|
|
163
|
+
this.messageId = event.message?.id ?? "";
|
|
164
|
+
this.text = "";
|
|
165
|
+
this.complete = false;
|
|
166
|
+
this.interrupted = false;
|
|
167
|
+
} else if (message.type === "stream_event" && event?.type === "content_block_delta" && event.delta?.type === "text_delta") {
|
|
168
|
+
this.text = (this.text + (event.delta.text ?? "")).slice(0, 32 * 1024);
|
|
169
|
+
} else if (message.type === "assistant") {
|
|
170
|
+
const body = message.message;
|
|
171
|
+
if (body?.id === this.messageId && Array.isArray(body.content)) {
|
|
172
|
+
this.text = body.content.filter((block) => block.type === "text").map((block) => block.text).join("").slice(0, 32 * 1024);
|
|
173
|
+
this.complete = true;
|
|
223
174
|
}
|
|
224
175
|
}
|
|
225
|
-
} catch {
|
|
226
|
-
return removed;
|
|
227
176
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
// src/transcript-sync.ts
|
|
232
|
-
var claudeTranscriptPath = (cwd, sessionId) => {
|
|
233
|
-
const projectId = resolve(cwd).replace(/[^a-zA-Z0-9-]/g, "-");
|
|
234
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR || join3(homedir2(), ".claude");
|
|
235
|
-
return join3(configDir, "projects", projectId, `${sessionId}.jsonl`);
|
|
236
|
-
};
|
|
237
|
-
var createSyncState = () => ({
|
|
238
|
-
sessionKey: null,
|
|
239
|
-
wrappedKey: null,
|
|
240
|
-
providerWrappedKey: null,
|
|
241
|
-
providerKeyId: null,
|
|
242
|
-
seen: /* @__PURE__ */ new Set(),
|
|
243
|
-
keyUploaded: false,
|
|
244
|
-
keyRejected: false,
|
|
245
|
-
keyUnavailable: false
|
|
246
|
-
});
|
|
247
|
-
var syncTick = async (lines, state, uploader, sessionId, machineId) => {
|
|
248
|
-
if (state.keyRejected) return state;
|
|
249
|
-
let sessionKey = state.sessionKey;
|
|
250
|
-
let wrappedKey = state.wrappedKey;
|
|
251
|
-
let providerWrappedKey = state.providerWrappedKey;
|
|
252
|
-
let providerKeyId = state.providerKeyId;
|
|
253
|
-
if (!sessionKey) {
|
|
254
|
-
const recipients = await uploader.fetchTranscriptRecipients();
|
|
255
|
-
if (!recipients) return state;
|
|
256
|
-
sessionKey = generateSessionKey();
|
|
257
|
-
wrappedKey = wrapSessionKey(sessionKey, decodePublicKey(recipients.phonePublicKey));
|
|
258
|
-
if (recipients.provider) {
|
|
259
|
-
providerWrappedKey = wrapSessionKey(sessionKey, decodePublicKey(recipients.provider.publicKey));
|
|
260
|
-
providerKeyId = recipients.provider.keyId;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
const built = buildTranscriptRecords(lines, sessionKey, state.seen);
|
|
264
|
-
if (built.records.length === 0) {
|
|
265
|
-
return { ...state, sessionKey, wrappedKey, providerWrappedKey, providerKeyId, seen: built.seen };
|
|
266
|
-
}
|
|
267
|
-
const seen = new Set(state.seen);
|
|
268
|
-
let keyUploaded = state.keyUploaded;
|
|
269
|
-
let keyRejected = false;
|
|
270
|
-
let keyUnavailable = false;
|
|
271
|
-
let rejected = 0;
|
|
272
|
-
for (const chunk of chunkRecords(built.records)) {
|
|
273
|
-
const result = await uploader.uploadRecords({
|
|
274
|
-
sessionId,
|
|
275
|
-
machineId,
|
|
276
|
-
dataEncryptionKey: keyUploaded ? void 0 : wrappedKey ?? void 0,
|
|
277
|
-
providerWrappedSessionKey: keyUploaded ? void 0 : providerWrappedKey ?? void 0,
|
|
278
|
-
providerKeyId: keyUploaded ? void 0 : providerKeyId ?? void 0,
|
|
279
|
-
records: chunk
|
|
280
|
-
});
|
|
281
|
-
keyUploaded = keyUploaded || result.keyRegistered;
|
|
282
|
-
if (result.keyConflict) {
|
|
283
|
-
keyRejected = true;
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
if (result.keyUnavailable) {
|
|
287
|
-
keyUnavailable = true;
|
|
288
|
-
break;
|
|
289
|
-
}
|
|
290
|
-
if (!result.ok) break;
|
|
291
|
-
for (const id of result.settledIds ?? chunk.map((r) => r.localId)) seen.add(id);
|
|
292
|
-
rejected += result.rejectedIds.length;
|
|
177
|
+
interrupt() {
|
|
178
|
+
if (!this.complete) this.interrupted = true;
|
|
293
179
|
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
return {
|
|
299
|
-
sessionKey,
|
|
300
|
-
wrappedKey,
|
|
301
|
-
providerWrappedKey,
|
|
302
|
-
providerKeyId,
|
|
303
|
-
seen,
|
|
304
|
-
keyUploaded,
|
|
305
|
-
keyRejected,
|
|
306
|
-
keyUnavailable
|
|
307
|
-
};
|
|
308
|
-
};
|
|
309
|
-
var MAX_RECORDS_PER_BATCH = 200;
|
|
310
|
-
var MAX_BATCH_BYTES = 3e6;
|
|
311
|
-
var chunkRecords = (records) => {
|
|
312
|
-
const chunks = [];
|
|
313
|
-
let current = [];
|
|
314
|
-
let bytes = 0;
|
|
315
|
-
for (const record of records) {
|
|
316
|
-
const size = record.content.length;
|
|
317
|
-
if (current.length > 0 && (current.length >= MAX_RECORDS_PER_BATCH || bytes + size > MAX_BATCH_BYTES)) {
|
|
318
|
-
chunks.push(current);
|
|
319
|
-
current = [];
|
|
320
|
-
bytes = 0;
|
|
321
|
-
}
|
|
322
|
-
current.push(record);
|
|
323
|
-
bytes += size;
|
|
180
|
+
snapshot() {
|
|
181
|
+
const text = redactDraft(this.text, this.complete);
|
|
182
|
+
return this.messageId && text ? { messageId: this.messageId, text, status: this.interrupted ? "interrupted" : this.complete ? "complete" : "streaming" } : null;
|
|
324
183
|
}
|
|
325
|
-
if (current.length > 0) chunks.push(current);
|
|
326
|
-
return chunks;
|
|
327
184
|
};
|
|
328
|
-
var
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return offer;
|
|
353
|
-
};
|
|
354
|
-
const uploader = {
|
|
355
|
-
fetchTranscriptRecipients: fetchPinnedRecipients,
|
|
356
|
-
uploadRecords: (params) => uploadTranscriptRecords(opts.apiKey, params)
|
|
357
|
-
};
|
|
358
|
-
const path = claudeTranscriptPath(opts.cwd, opts.sessionId);
|
|
359
|
-
const persisted = loadSessionKey(opts.sessionId);
|
|
360
|
-
let state = persisted ? { ...createSyncState(), ...persisted } : createSyncState();
|
|
361
|
-
sweepStaleSessionKeys();
|
|
362
|
-
let persistedUploaded = persisted?.keyUploaded ?? false;
|
|
363
|
-
let running = true;
|
|
364
|
-
let inFlight = false;
|
|
365
|
-
const tick = async () => {
|
|
366
|
-
if (!running || inFlight) return;
|
|
367
|
-
inFlight = true;
|
|
368
|
-
try {
|
|
369
|
-
const before = state.sessionKey;
|
|
370
|
-
state = await syncTick(readLines(path), state, uploader, opts.sessionId, opts.machineId);
|
|
371
|
-
if (state.sessionKey && (before !== state.sessionKey || state.keyUploaded !== persistedUploaded)) {
|
|
372
|
-
persistedUploaded = state.keyUploaded;
|
|
373
|
-
saveSessionKey(opts.sessionId, {
|
|
374
|
-
sessionKey: state.sessionKey,
|
|
375
|
-
wrappedKey: state.wrappedKey,
|
|
376
|
-
providerWrappedKey: state.providerWrappedKey,
|
|
377
|
-
providerKeyId: state.providerKeyId,
|
|
378
|
-
keyUploaded: state.keyUploaded
|
|
185
|
+
var startTranscriptDraft = (apiKey, getSessionId) => {
|
|
186
|
+
const text = new DraftText();
|
|
187
|
+
const startedAt = Date.now();
|
|
188
|
+
let revision = 0;
|
|
189
|
+
let stopped = false;
|
|
190
|
+
let pending;
|
|
191
|
+
let ending = false;
|
|
192
|
+
let sent = "";
|
|
193
|
+
const flush = async () => {
|
|
194
|
+
if (stopped || pending || !transcriptsEnabled()) return;
|
|
195
|
+
const sessionId = getSessionId();
|
|
196
|
+
const snapshot = text.snapshot();
|
|
197
|
+
if (!sessionId || !snapshot || !registeredTranscriptEnabled(apiKey, sessionId)) return;
|
|
198
|
+
const raw = JSON.stringify(snapshot);
|
|
199
|
+
if (raw === sent) return;
|
|
200
|
+
const key = loadSessionKey(sessionId);
|
|
201
|
+
if (!key?.keyUploaded) return;
|
|
202
|
+
pending = (async () => {
|
|
203
|
+
try {
|
|
204
|
+
const response = await fetch(`${getBaseUrl()}/api/agent/transcript`, {
|
|
205
|
+
method: "POST",
|
|
206
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
207
|
+
body: JSON.stringify({ sessionId, draft: { startedAt, revision: ++revision, content: encryptRecord(raw, key.sessionKey) } }),
|
|
208
|
+
signal: AbortSignal.timeout(5e3)
|
|
379
209
|
});
|
|
210
|
+
if (response.ok) sent = raw;
|
|
211
|
+
} catch {
|
|
380
212
|
}
|
|
381
|
-
}
|
|
213
|
+
})();
|
|
214
|
+
try {
|
|
215
|
+
await pending;
|
|
382
216
|
} finally {
|
|
383
|
-
|
|
217
|
+
pending = void 0;
|
|
384
218
|
}
|
|
385
219
|
};
|
|
386
|
-
const
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
void tick();
|
|
402
|
-
schedule(baseIntervalMs);
|
|
403
|
-
return () => {
|
|
404
|
-
running = false;
|
|
405
|
-
if (timer) clearTimeout(timer);
|
|
220
|
+
const timer = setInterval(() => void flush(), 800);
|
|
221
|
+
timer.unref();
|
|
222
|
+
return {
|
|
223
|
+
add: (message) => {
|
|
224
|
+
if (!ending) text.add(message);
|
|
225
|
+
},
|
|
226
|
+
stop: async () => {
|
|
227
|
+
if (ending) return;
|
|
228
|
+
ending = true;
|
|
229
|
+
clearInterval(timer);
|
|
230
|
+
text.interrupt();
|
|
231
|
+
await pending;
|
|
232
|
+
await flush();
|
|
233
|
+
stopped = true;
|
|
234
|
+
}
|
|
406
235
|
};
|
|
407
236
|
};
|
|
408
237
|
|
|
238
|
+
// src/wrapper/dualMode.ts
|
|
239
|
+
import { basename } from "path";
|
|
240
|
+
|
|
409
241
|
// src/wrapper/sdkLoader.ts
|
|
410
242
|
import { spawn } from "child_process";
|
|
411
|
-
import { existsSync
|
|
412
|
-
import { homedir
|
|
413
|
-
import { join
|
|
243
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
244
|
+
import { homedir } from "os";
|
|
245
|
+
import { join } from "path";
|
|
414
246
|
import { pathToFileURL } from "url";
|
|
415
247
|
var userMessage = (text) => ({
|
|
416
248
|
type: "user",
|
|
@@ -420,8 +252,8 @@ var userMessage = (text) => ({
|
|
|
420
252
|
var SDK_VERSION = "0.3.207";
|
|
421
253
|
var SDK_PACKAGE = "@anthropic-ai/claude-agent-sdk";
|
|
422
254
|
var SDK_SPEC = `${SDK_PACKAGE}@${SDK_VERSION}`;
|
|
423
|
-
var sdkCacheDir = () => process.env.PUSHARY_REMOTE_SDK_DIR?.trim() ||
|
|
424
|
-
var cachedSdkEntry = () =>
|
|
255
|
+
var sdkCacheDir = () => process.env.PUSHARY_REMOTE_SDK_DIR?.trim() || join(homedir(), ".pushary", "remote-sdk");
|
|
256
|
+
var cachedSdkEntry = () => join(sdkCacheDir(), "node_modules", "@anthropic-ai", "claude-agent-sdk", "sdk.mjs");
|
|
425
257
|
var importSdk = async (specifierOrUrl) => {
|
|
426
258
|
try {
|
|
427
259
|
const mod = await import(specifierOrUrl);
|
|
@@ -436,7 +268,7 @@ var loadClaudeSdk = async () => {
|
|
|
436
268
|
const fromResolution = await importSdk(SDK_PACKAGE);
|
|
437
269
|
if (fromResolution) return fromResolution;
|
|
438
270
|
const entry = cachedSdkEntry();
|
|
439
|
-
if (
|
|
271
|
+
if (existsSync(entry)) return importSdk(pathToFileURL(entry).href);
|
|
440
272
|
return null;
|
|
441
273
|
};
|
|
442
274
|
var ensureClaudeSdk = async (log) => {
|
|
@@ -444,10 +276,10 @@ var ensureClaudeSdk = async (log) => {
|
|
|
444
276
|
if (present) return present;
|
|
445
277
|
const dir = sdkCacheDir();
|
|
446
278
|
try {
|
|
447
|
-
|
|
448
|
-
const pkgJson =
|
|
449
|
-
if (!
|
|
450
|
-
|
|
279
|
+
mkdirSync(dir, { recursive: true });
|
|
280
|
+
const pkgJson = join(dir, "package.json");
|
|
281
|
+
if (!existsSync(pkgJson)) {
|
|
282
|
+
writeFileSync(pkgJson, `${JSON.stringify({ name: "pushary-remote-sdk", private: true })}
|
|
451
283
|
`);
|
|
452
284
|
}
|
|
453
285
|
} catch {
|
|
@@ -455,14 +287,14 @@ var ensureClaudeSdk = async (log) => {
|
|
|
455
287
|
}
|
|
456
288
|
log?.("[pushary] setting up phone control (one-time, fetching the Claude Agent SDK)...");
|
|
457
289
|
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
458
|
-
const installed = await new Promise((
|
|
290
|
+
const installed = await new Promise((resolve) => {
|
|
459
291
|
const child = spawn(
|
|
460
292
|
npmCmd,
|
|
461
293
|
["install", "--no-optional", "--no-audit", "--no-fund", "--loglevel=error", SDK_SPEC],
|
|
462
294
|
{ cwd: dir, stdio: "ignore", shell: process.platform === "win32" }
|
|
463
295
|
);
|
|
464
|
-
child.on("error", () =>
|
|
465
|
-
child.on("close", (code2) =>
|
|
296
|
+
child.on("error", () => resolve(false));
|
|
297
|
+
child.on("close", (code2) => resolve(code2 === 0));
|
|
466
298
|
});
|
|
467
299
|
if (!installed) {
|
|
468
300
|
log?.(`[pushary] could not auto-install the SDK. Install it manually: npm i -g ${SDK_SPEC}`);
|
|
@@ -476,6 +308,7 @@ var BoundedInputQueue = class {
|
|
|
476
308
|
buffer = [];
|
|
477
309
|
waiter;
|
|
478
310
|
closed = false;
|
|
311
|
+
generation = 0;
|
|
479
312
|
cap;
|
|
480
313
|
onDrop;
|
|
481
314
|
coalesce;
|
|
@@ -491,18 +324,18 @@ var BoundedInputQueue = class {
|
|
|
491
324
|
if (this.closed) return false;
|
|
492
325
|
this.pushedCount++;
|
|
493
326
|
if (this.waiter) {
|
|
494
|
-
const
|
|
327
|
+
const resolve = this.waiter;
|
|
495
328
|
this.waiter = void 0;
|
|
496
329
|
this.deliveredCount++;
|
|
497
|
-
|
|
330
|
+
resolve({ value: item, done: false });
|
|
498
331
|
return true;
|
|
499
332
|
}
|
|
500
|
-
this.buffer.
|
|
501
|
-
while (this.buffer.length > this.cap) {
|
|
502
|
-
const dropped = this.buffer.shift();
|
|
333
|
+
if (this.buffer.length >= this.cap) {
|
|
503
334
|
this.droppedCount++;
|
|
504
|
-
this.onDrop?.(
|
|
335
|
+
this.onDrop?.(item, this.droppedCount);
|
|
336
|
+
return false;
|
|
505
337
|
}
|
|
338
|
+
this.buffer.push(item);
|
|
506
339
|
return true;
|
|
507
340
|
}
|
|
508
341
|
// Mark done. Any parked consumer completes; further push() is ignored. The
|
|
@@ -512,9 +345,9 @@ var BoundedInputQueue = class {
|
|
|
512
345
|
this.closed = true;
|
|
513
346
|
this.buffer.length = 0;
|
|
514
347
|
if (this.waiter) {
|
|
515
|
-
const
|
|
348
|
+
const resolve = this.waiter;
|
|
516
349
|
this.waiter = void 0;
|
|
517
|
-
|
|
350
|
+
resolve({ value: void 0, done: true });
|
|
518
351
|
}
|
|
519
352
|
}
|
|
520
353
|
// End the CURRENT consumer without closing the queue, so a fresh query can
|
|
@@ -522,10 +355,11 @@ var BoundedInputQueue = class {
|
|
|
522
355
|
// the parked waiter as done lets the abandoned query's for-await unwind cleanly
|
|
523
356
|
// instead of hanging on a promise that would otherwise never settle.
|
|
524
357
|
detachConsumer() {
|
|
358
|
+
this.generation++;
|
|
525
359
|
if (this.waiter) {
|
|
526
|
-
const
|
|
360
|
+
const resolve = this.waiter;
|
|
527
361
|
this.waiter = void 0;
|
|
528
|
-
|
|
362
|
+
resolve({ value: void 0, done: true });
|
|
529
363
|
}
|
|
530
364
|
}
|
|
531
365
|
// Drop every buffered item without closing the queue. Used when phone control
|
|
@@ -548,8 +382,12 @@ var BoundedInputQueue = class {
|
|
|
548
382
|
return { pushed: this.pushedCount, delivered: this.deliveredCount, dropped: this.droppedCount };
|
|
549
383
|
}
|
|
550
384
|
[Symbol.asyncIterator]() {
|
|
385
|
+
const generation = this.generation;
|
|
551
386
|
return {
|
|
552
387
|
next: () => {
|
|
388
|
+
if (generation !== this.generation || this.closed) {
|
|
389
|
+
return Promise.resolve({ value: void 0, done: true });
|
|
390
|
+
}
|
|
553
391
|
if (this.buffer.length > 0) {
|
|
554
392
|
if (this.coalesce && this.buffer.length > 1) {
|
|
555
393
|
const items = this.buffer.splice(0, this.buffer.length);
|
|
@@ -568,12 +406,12 @@ var BoundedInputQueue = class {
|
|
|
568
406
|
new Error("BoundedInputQueue: concurrent consumers are not supported")
|
|
569
407
|
);
|
|
570
408
|
}
|
|
571
|
-
return new Promise((
|
|
572
|
-
this.waiter =
|
|
409
|
+
return new Promise((resolve) => {
|
|
410
|
+
this.waiter = resolve;
|
|
573
411
|
});
|
|
574
412
|
},
|
|
575
413
|
return: () => {
|
|
576
|
-
this.
|
|
414
|
+
if (generation === this.generation) this.detachConsumer();
|
|
577
415
|
return Promise.resolve({ value: void 0, done: true });
|
|
578
416
|
}
|
|
579
417
|
};
|
|
@@ -610,8 +448,8 @@ var createRemoteApprover = (deps) => {
|
|
|
610
448
|
const deadline = now() + windowMs;
|
|
611
449
|
while (now() < deadline && !entry.controller.signal.aborted) {
|
|
612
450
|
const remaining = Math.min(Math.max(deadline - now(), 1e3), 3e4);
|
|
613
|
-
const abortPromise = new Promise((
|
|
614
|
-
entry.controller.signal.addEventListener("abort", () =>
|
|
451
|
+
const abortPromise = new Promise((resolve) => {
|
|
452
|
+
entry.controller.signal.addEventListener("abort", () => resolve({ answered: false, aborted: true }), {
|
|
615
453
|
once: true
|
|
616
454
|
});
|
|
617
455
|
});
|
|
@@ -842,7 +680,7 @@ var startCommandPoller = (opts) => {
|
|
|
842
680
|
const text = typeof command === "string" ? command : command.text;
|
|
843
681
|
let accepted = false;
|
|
844
682
|
try {
|
|
845
|
-
accepted = opts.onCommand(text) !== false;
|
|
683
|
+
accepted = await opts.onCommand(text) !== false;
|
|
846
684
|
} catch {
|
|
847
685
|
}
|
|
848
686
|
if (typeof command !== "string") {
|
|
@@ -878,7 +716,7 @@ var startCommandPoller = (opts) => {
|
|
|
878
716
|
|
|
879
717
|
// src/wrapper/pollClient.ts
|
|
880
718
|
var APPROVAL_MODES = /* @__PURE__ */ new Set(["push_only", "terminal_only", "push_first", "notify_only"]);
|
|
881
|
-
var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBaseUrl(), machineId) => {
|
|
719
|
+
var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBaseUrl(), machineId, controlMode, takeoverDeclined) => {
|
|
882
720
|
try {
|
|
883
721
|
const sessionKey = sessionId || DEFAULT_SESSION;
|
|
884
722
|
const receiptNamespace = `${apiKey}:${machineId}:claude_code`;
|
|
@@ -921,6 +759,8 @@ var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBase
|
|
|
921
759
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}` },
|
|
922
760
|
body: JSON.stringify({
|
|
923
761
|
...sessionId ? { sessionId } : {},
|
|
762
|
+
...controlMode ? { controlMode } : {},
|
|
763
|
+
...takeoverDeclined ? { takeoverDeclined } : {},
|
|
924
764
|
...machineId ? { machineId } : {},
|
|
925
765
|
...wantCommand && !storedCommand ? {} : { drain: false },
|
|
926
766
|
...wantCommand && !storedCommand && machineId ? { ackMode: true } : {}
|
|
@@ -956,7 +796,7 @@ var pollAgent = async (apiKey, sessionId, wantCommand, signal, baseUrl = getBase
|
|
|
956
796
|
policyVersion: typeof data.policyVersion === "string" && data.policyVersion.length > 0 ? data.policyVersion : null,
|
|
957
797
|
relayUrl: typeof data.relayUrl === "string" && data.relayUrl.length > 0 ? data.relayUrl : null
|
|
958
798
|
};
|
|
959
|
-
return { command, mode, rejected: false };
|
|
799
|
+
return { command, mode, rejected: false, ...data.takeoverRequested === true ? { takeoverRequested: true } : {} };
|
|
960
800
|
} catch {
|
|
961
801
|
return { command: null, mode: null, rejected: false };
|
|
962
802
|
}
|
|
@@ -1034,6 +874,7 @@ var createRelayClient = (options) => {
|
|
|
1034
874
|
let pongTimer;
|
|
1035
875
|
const seenCommands = [];
|
|
1036
876
|
const seenSet = /* @__PURE__ */ new Set();
|
|
877
|
+
const pendingCommands = /* @__PURE__ */ new Map();
|
|
1037
878
|
const log = (message) => options.log?.(message);
|
|
1038
879
|
const receiptNamespace = `${options.apiKey}:${options.machineId}:${options.agentType}`;
|
|
1039
880
|
const acknowledgeStored = (id) => {
|
|
@@ -1093,7 +934,7 @@ var createRelayClient = (options) => {
|
|
|
1093
934
|
armPong();
|
|
1094
935
|
}, heartbeatMs);
|
|
1095
936
|
};
|
|
1096
|
-
const onServerFrame = (raw) => {
|
|
937
|
+
const onServerFrame = async (raw) => {
|
|
1097
938
|
const frame = decodeServerFrame(raw);
|
|
1098
939
|
if (!frame) return;
|
|
1099
940
|
switch (frame.t) {
|
|
@@ -1102,6 +943,11 @@ var createRelayClient = (options) => {
|
|
|
1102
943
|
log("[pushary] relay connected");
|
|
1103
944
|
break;
|
|
1104
945
|
case "command":
|
|
946
|
+
if (pendingCommands.has(frame.id)) {
|
|
947
|
+
await pendingCommands.get(frame.id);
|
|
948
|
+
acknowledgeStored(frame.id);
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
1105
951
|
if (seenSet.has(frame.id)) {
|
|
1106
952
|
sendFrame({ v: PROTOCOL_VERSION, t: "ack", id: frame.id, state: "delivered" });
|
|
1107
953
|
break;
|
|
@@ -1118,9 +964,14 @@ var createRelayClient = (options) => {
|
|
|
1118
964
|
acknowledgeStored(frame.id);
|
|
1119
965
|
break;
|
|
1120
966
|
}
|
|
967
|
+
let completePending;
|
|
968
|
+
pendingCommands.set(frame.id, new Promise((resolve) => {
|
|
969
|
+
completePending = resolve;
|
|
970
|
+
}));
|
|
1121
971
|
let accepted = false;
|
|
1122
972
|
try {
|
|
1123
|
-
|
|
973
|
+
const result = options.onCommand(frame.text);
|
|
974
|
+
accepted = (result instanceof Promise ? await result : result) !== false;
|
|
1124
975
|
} catch {
|
|
1125
976
|
}
|
|
1126
977
|
if (accepted && markPendingCommandDelivered(sessionKey, frame.id, receiptNamespace)) {
|
|
@@ -1135,6 +986,8 @@ var createRelayClient = (options) => {
|
|
|
1135
986
|
failureCode: "consumer_rejected"
|
|
1136
987
|
});
|
|
1137
988
|
}
|
|
989
|
+
completePending();
|
|
990
|
+
pendingCommands.delete(frame.id);
|
|
1138
991
|
break;
|
|
1139
992
|
case "ping":
|
|
1140
993
|
sendFrame({ v: PROTOCOL_VERSION, t: "pong" });
|
|
@@ -1299,14 +1152,22 @@ var flagValue = (args2, index) => {
|
|
|
1299
1152
|
const next = args2[index + 1];
|
|
1300
1153
|
return next && !next.startsWith("-") ? next : void 0;
|
|
1301
1154
|
};
|
|
1302
|
-
var PASS_THROUGH_VALUE_FLAGS = /* @__PURE__ */ new Set(["--
|
|
1155
|
+
var PASS_THROUGH_VALUE_FLAGS = /* @__PURE__ */ new Set(["--cwd", "--add-dir"]);
|
|
1303
1156
|
var parseRemoteArgs = (args2) => {
|
|
1304
1157
|
let initialPrompt;
|
|
1305
1158
|
let resume;
|
|
1306
1159
|
let permissionMode;
|
|
1160
|
+
let model;
|
|
1161
|
+
let settings;
|
|
1307
1162
|
for (let i = 0; i < args2.length; i++) {
|
|
1308
1163
|
const arg = args2[i];
|
|
1309
|
-
if (
|
|
1164
|
+
if (arg === "--model" || arg === "-m" || arg?.startsWith("--model=")) {
|
|
1165
|
+
model = flagValue(args2, i);
|
|
1166
|
+
if (!arg.includes("=")) i++;
|
|
1167
|
+
} else if (arg === "--settings" || arg?.startsWith("--settings=")) {
|
|
1168
|
+
settings = flagValue(args2, i);
|
|
1169
|
+
if (!arg.includes("=")) i++;
|
|
1170
|
+
} else if (PASS_THROUGH_VALUE_FLAGS.has(arg ?? "")) {
|
|
1310
1171
|
i++;
|
|
1311
1172
|
} else if (arg === "-p" || arg === "--print" || arg?.startsWith("--print=")) {
|
|
1312
1173
|
initialPrompt = flagValue(args2, i);
|
|
@@ -1316,15 +1177,38 @@ var parseRemoteArgs = (args2) => {
|
|
|
1316
1177
|
permissionMode = flagValue(args2, i);
|
|
1317
1178
|
}
|
|
1318
1179
|
}
|
|
1319
|
-
return { initialPrompt, resume, permissionMode };
|
|
1180
|
+
return { initialPrompt, resume, permissionMode, ...model ? { model } : {}, ...settings ? { settings } : {} };
|
|
1181
|
+
};
|
|
1182
|
+
var remoteCompatibilityIssue = (args2) => {
|
|
1183
|
+
const values = /* @__PURE__ */ new Set(["--model", "-m", "--settings", "--permission-mode", "--resume", "-r", "--session-id", "-p", "--print", "--cwd", "--add-dir"]);
|
|
1184
|
+
const switches = /* @__PURE__ */ new Set(["--continue", "-c", "--verbose", "--debug", "--dangerously-skip-permissions"]);
|
|
1185
|
+
for (let index = 0; index < args2.length; index++) {
|
|
1186
|
+
const arg = args2[index];
|
|
1187
|
+
if (!arg.startsWith("-")) continue;
|
|
1188
|
+
const flag = arg.split("=")[0];
|
|
1189
|
+
if (switches.has(flag)) continue;
|
|
1190
|
+
if (!values.has(flag)) return `Phone takeover does not yet preserve ${flag}; keep using this terminal.`;
|
|
1191
|
+
if (!arg.includes("=") && args2[index + 1] && !args2[index + 1].startsWith("-")) index++;
|
|
1192
|
+
}
|
|
1193
|
+
return null;
|
|
1320
1194
|
};
|
|
1321
1195
|
|
|
1322
1196
|
// src/wrapper/localLeg.ts
|
|
1197
|
+
import { readFileSync } from "fs";
|
|
1198
|
+
import { homedir as homedir2 } from "os";
|
|
1199
|
+
import { join as join2 } from "path";
|
|
1323
1200
|
var SIGNAL_EXIT_BASE = 128;
|
|
1324
1201
|
var SIGNAL_NUMBERS2 = { SIGHUP: 1, SIGINT: 2, SIGTERM: 15 };
|
|
1325
1202
|
var nativeArgs = (session) => {
|
|
1326
|
-
|
|
1203
|
+
let args2 = [...session.claudeArgs];
|
|
1327
1204
|
const id = session.getSessionId();
|
|
1205
|
+
if (id && session.userManagesSession && session.isSessionCreated()) {
|
|
1206
|
+
args2 = args2.filter((arg, index, all) => {
|
|
1207
|
+
if (/^(--resume|-r|--session-id)(=|$)|^(--continue|-c)$/.test(arg)) return false;
|
|
1208
|
+
return !/^(--resume|-r|--session-id)$/.test(all[index - 1] ?? "") || arg.startsWith("-");
|
|
1209
|
+
});
|
|
1210
|
+
return [...args2, "--resume", id];
|
|
1211
|
+
}
|
|
1328
1212
|
if (id && !session.userManagesSession) {
|
|
1329
1213
|
args2.push(session.isSessionCreated() ? "--resume" : "--session-id", id);
|
|
1330
1214
|
}
|
|
@@ -1332,22 +1216,24 @@ var nativeArgs = (session) => {
|
|
|
1332
1216
|
};
|
|
1333
1217
|
var runLocalLeg = (session, deps = {}) => {
|
|
1334
1218
|
const spawn2 = deps.spawn ?? spawnClaude;
|
|
1335
|
-
return new Promise((
|
|
1219
|
+
return new Promise((resolve) => {
|
|
1336
1220
|
const legAbort = new AbortController();
|
|
1337
1221
|
let exitReason = null;
|
|
1338
1222
|
let settled = false;
|
|
1223
|
+
let identityTimer;
|
|
1339
1224
|
const finish = () => {
|
|
1340
1225
|
if (settled) return;
|
|
1341
1226
|
settled = true;
|
|
1227
|
+
if (identityTimer) clearInterval(identityTimer);
|
|
1342
1228
|
session.setActiveLeg(null);
|
|
1343
|
-
|
|
1229
|
+
resolve(exitReason ?? { type: "exit", code: 0 });
|
|
1344
1230
|
};
|
|
1345
1231
|
const control = {
|
|
1346
1232
|
onPhoneCommand: () => {
|
|
1347
1233
|
if (!exitReason) exitReason = "switch";
|
|
1348
1234
|
if (!legAbort.signal.aborted) {
|
|
1349
1235
|
process.stderr.write(
|
|
1350
|
-
"\n[pushary]
|
|
1236
|
+
"\n[pushary] phone takeover requested \u2014 resuming this conversation with phone control (Ctrl-] hands the terminal back)\n"
|
|
1351
1237
|
);
|
|
1352
1238
|
legAbort.abort();
|
|
1353
1239
|
}
|
|
@@ -1376,8 +1262,24 @@ var runLocalLeg = (session, deps = {}) => {
|
|
|
1376
1262
|
finish();
|
|
1377
1263
|
return;
|
|
1378
1264
|
}
|
|
1265
|
+
const spawnedAt = Date.now();
|
|
1266
|
+
const readIdentity = () => {
|
|
1267
|
+
if (!child.pid) return;
|
|
1268
|
+
try {
|
|
1269
|
+
const root = process.env.CLAUDE_CONFIG_DIR || join2(homedir2(), ".claude");
|
|
1270
|
+
const record = JSON.parse(readFileSync(join2(root, "sessions", `${child.pid}.json`), "utf8"));
|
|
1271
|
+
if (record.pid !== child.pid || record.cwd !== process.cwd() || record.startedAt < spawnedAt - 5e3 || typeof record.startedAt !== "number" || !/^[a-f0-9-]{36}$/i.test(record.sessionId)) return;
|
|
1272
|
+
if (record.sessionId !== session.getSessionId()) session.setSessionId(record.sessionId);
|
|
1273
|
+
session.markSessionCreated();
|
|
1274
|
+
} catch {
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1379
1277
|
session.markSessionCreated();
|
|
1380
|
-
|
|
1278
|
+
readIdentity();
|
|
1279
|
+
identityTimer = setInterval(readIdentity, 500);
|
|
1280
|
+
identityTimer.unref();
|
|
1281
|
+
child.on("error", (error) => {
|
|
1282
|
+
if (legAbort.signal.aborted && error.name === "AbortError") return;
|
|
1381
1283
|
if (!legAbort.signal.aborted && !exitReason) {
|
|
1382
1284
|
exitReason = { type: "exit", code: 127 };
|
|
1383
1285
|
}
|
|
@@ -1435,7 +1337,10 @@ var runRemoteLoop = async (deps) => {
|
|
|
1435
1337
|
prompt: deps.input,
|
|
1436
1338
|
options: {
|
|
1437
1339
|
resume: resumeId,
|
|
1340
|
+
includePartialMessages: true,
|
|
1438
1341
|
permissionMode: deps.permissionMode,
|
|
1342
|
+
model: deps.model,
|
|
1343
|
+
settings: deps.settings,
|
|
1439
1344
|
canUseTool: deps.canUseTool,
|
|
1440
1345
|
env: deps.env,
|
|
1441
1346
|
cwd: deps.cwd,
|
|
@@ -1447,8 +1352,9 @@ var runRemoteLoop = async (deps) => {
|
|
|
1447
1352
|
return ended(1, "failed");
|
|
1448
1353
|
}
|
|
1449
1354
|
const onAbort = () => {
|
|
1450
|
-
deps.input.
|
|
1451
|
-
|
|
1355
|
+
deps.input.detachConsumer();
|
|
1356
|
+
if (query.close) query.close();
|
|
1357
|
+
else void query.interrupt?.().catch(() => {
|
|
1452
1358
|
});
|
|
1453
1359
|
};
|
|
1454
1360
|
if (deps.signal.aborted) onAbort();
|
|
@@ -1471,6 +1377,14 @@ var runRemoteLoop = async (deps) => {
|
|
|
1471
1377
|
for await (const message of query) {
|
|
1472
1378
|
deps.onMessage?.(message);
|
|
1473
1379
|
if (isInit(message) && typeof message.session_id === "string") {
|
|
1380
|
+
if (resumeId && message.session_id !== resumeId) {
|
|
1381
|
+
disarmIdle();
|
|
1382
|
+
deps.signal.removeEventListener("abort", onAbort);
|
|
1383
|
+
deps.input.detachConsumer();
|
|
1384
|
+
query.close?.();
|
|
1385
|
+
deps.log?.("[pushary] refused a different session ID during resume");
|
|
1386
|
+
return ended(1, "failed");
|
|
1387
|
+
}
|
|
1474
1388
|
resumeId = message.session_id;
|
|
1475
1389
|
deps.onSessionId?.(resumeId);
|
|
1476
1390
|
}
|
|
@@ -1484,11 +1398,13 @@ var runRemoteLoop = async (deps) => {
|
|
|
1484
1398
|
}
|
|
1485
1399
|
disarmIdle();
|
|
1486
1400
|
deps.signal.removeEventListener("abort", onAbort);
|
|
1401
|
+
query.close?.();
|
|
1487
1402
|
if (idleExpired) return ended(0, "idle");
|
|
1488
1403
|
return deps.signal.aborted ? ended(130, "aborted") : ended(0, "input-closed");
|
|
1489
1404
|
} catch (err) {
|
|
1490
1405
|
disarmIdle();
|
|
1491
1406
|
deps.signal.removeEventListener("abort", onAbort);
|
|
1407
|
+
query.close?.();
|
|
1492
1408
|
if (idleExpired) return ended(0, "idle");
|
|
1493
1409
|
if (deps.signal.aborted) return ended(130, "aborted");
|
|
1494
1410
|
restarts++;
|
|
@@ -1511,6 +1427,7 @@ var CTRL_C = "";
|
|
|
1511
1427
|
var terminalAttended = () => Boolean(process.stdin.isTTY);
|
|
1512
1428
|
var runRemoteLeg = async (session, sdk, deps = {}) => {
|
|
1513
1429
|
const legAbort = new AbortController();
|
|
1430
|
+
const launchArgs = parseRemoteArgs(session.claudeArgs);
|
|
1514
1431
|
const attended = deps.attended ?? terminalAttended();
|
|
1515
1432
|
let switchBack = false;
|
|
1516
1433
|
let idleEnded = false;
|
|
@@ -1546,6 +1463,8 @@ var runRemoteLeg = async (session, sdk, deps = {}) => {
|
|
|
1546
1463
|
// conversation is continuous across the switch. Undefined = fresh session.
|
|
1547
1464
|
initialResume: session.getSessionId(),
|
|
1548
1465
|
permissionMode: session.permissionMode,
|
|
1466
|
+
model: launchArgs.model,
|
|
1467
|
+
settings: launchArgs.settings,
|
|
1549
1468
|
// The SDK REPLACES the subprocess env; spread process.env and mark the
|
|
1550
1469
|
// recursion guard so a wrapper-spawned claude cannot re-enter the wrapper.
|
|
1551
1470
|
env: phoneDrivenEnv({ ...process.env, [WRAPPER_ACTIVE_ENV]: "1" }),
|
|
@@ -1596,7 +1515,6 @@ var driveModeLoop = async (deps) => {
|
|
|
1596
1515
|
// src/wrapper/dualMode.ts
|
|
1597
1516
|
var INPUT_QUEUE_CAP = 32;
|
|
1598
1517
|
var MIN_MODE_DWELL_MS = 300;
|
|
1599
|
-
var RELAY_FALLBACK_POLL_MS = 6e4;
|
|
1600
1518
|
var PRESENCE_HEARTBEAT_MS = 6e4;
|
|
1601
1519
|
var SESSION_FLAG_RE = /^(-r|--resume|-c|--continue|--session-id)(=.*)?$/;
|
|
1602
1520
|
var extractAssistantText = (message) => {
|
|
@@ -1609,6 +1527,12 @@ var extractAssistantText = (message) => {
|
|
|
1609
1527
|
return void 0;
|
|
1610
1528
|
};
|
|
1611
1529
|
var runDualMode = async (binary, args2, startingMode) => {
|
|
1530
|
+
const compatibilityIssue = remoteCompatibilityIssue(args2);
|
|
1531
|
+
if (startingMode === "remote" && compatibilityIssue) {
|
|
1532
|
+
process.stderr.write(`[pushary] ${compatibilityIssue}
|
|
1533
|
+
`);
|
|
1534
|
+
return { implemented: false };
|
|
1535
|
+
}
|
|
1612
1536
|
let apiKey;
|
|
1613
1537
|
try {
|
|
1614
1538
|
apiKey = getApiKey();
|
|
@@ -1619,12 +1543,14 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1619
1543
|
return { implemented: false };
|
|
1620
1544
|
}
|
|
1621
1545
|
let sdk;
|
|
1546
|
+
let sdkLoading;
|
|
1622
1547
|
let remoteAvailable = true;
|
|
1623
1548
|
let noticedUnavailable = false;
|
|
1624
1549
|
const ensureSdk = async () => {
|
|
1625
1550
|
if (sdk === void 0) {
|
|
1626
|
-
|
|
1551
|
+
sdkLoading ??= ensureClaudeSdk((message) => process.stderr.write(`${message}
|
|
1627
1552
|
`));
|
|
1553
|
+
sdk = await sdkLoading;
|
|
1628
1554
|
if (!sdk) remoteAvailable = false;
|
|
1629
1555
|
}
|
|
1630
1556
|
return sdk;
|
|
@@ -1644,7 +1570,9 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1644
1570
|
};
|
|
1645
1571
|
const { initialPrompt, resume, permissionMode } = parseRemoteArgs(args2);
|
|
1646
1572
|
const userManagesSession = args2.some((a) => SESSION_FLAG_RE.test(a));
|
|
1647
|
-
|
|
1573
|
+
const explicitIdIndex = args2.findIndex((arg) => arg === "--session-id" || arg.startsWith("--session-id="));
|
|
1574
|
+
const explicitId = explicitIdIndex < 0 ? void 0 : args2[explicitIdIndex].split("=")[1] ?? args2[explicitIdIndex + 1];
|
|
1575
|
+
let sessionId = resume ?? explicitId;
|
|
1648
1576
|
let sessionCreated = Boolean(resume);
|
|
1649
1577
|
if (!sessionId && startingMode === "local" && !userManagesSession) {
|
|
1650
1578
|
sessionId = crypto.randomUUID();
|
|
@@ -1653,9 +1581,12 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1653
1581
|
let killed = false;
|
|
1654
1582
|
let finished = false;
|
|
1655
1583
|
let activeLeg = null;
|
|
1584
|
+
let controlMode = startingMode;
|
|
1585
|
+
let takeoverPending = false;
|
|
1586
|
+
let takeoverDecline;
|
|
1656
1587
|
const input = new BoundedInputQueue({
|
|
1657
1588
|
cap: INPUT_QUEUE_CAP,
|
|
1658
|
-
onDrop: (_dropped, total) => process.stderr.write(`[pushary] input queue full;
|
|
1589
|
+
onDrop: (_dropped, total) => process.stderr.write(`[pushary] input queue full; refused ${total} instruction(s)
|
|
1659
1590
|
`),
|
|
1660
1591
|
coalesce: (items) => userMessage(items.map((m) => m.message.content).join("\n\n"))
|
|
1661
1592
|
});
|
|
@@ -1667,9 +1598,9 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1667
1598
|
getSessionId: () => sessionId
|
|
1668
1599
|
});
|
|
1669
1600
|
const initialModeState = await fetchModeState(apiKey).catch(() => null);
|
|
1670
|
-
const relayAdvertised = Boolean(initialModeState?.relayUrl);
|
|
1671
1601
|
let relay;
|
|
1672
|
-
|
|
1602
|
+
const startRelay = () => {
|
|
1603
|
+
if (!initialModeState?.relayUrl || relay) return;
|
|
1673
1604
|
relay = createRelayClient({
|
|
1674
1605
|
url: initialModeState.relayUrl,
|
|
1675
1606
|
apiKey,
|
|
@@ -1681,7 +1612,7 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1681
1612
|
log: verboseLog
|
|
1682
1613
|
});
|
|
1683
1614
|
relay.start();
|
|
1684
|
-
}
|
|
1615
|
+
};
|
|
1685
1616
|
let ownership;
|
|
1686
1617
|
let ownershipSessionId;
|
|
1687
1618
|
const ensureOwnership = async () => {
|
|
@@ -1707,13 +1638,12 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1707
1638
|
} catch {
|
|
1708
1639
|
}
|
|
1709
1640
|
};
|
|
1710
|
-
let stopTranscript;
|
|
1711
1641
|
let presenceHeartbeat;
|
|
1712
|
-
let transcriptStarted
|
|
1642
|
+
let transcriptStarted;
|
|
1713
1643
|
const ensureTranscriptSync = () => {
|
|
1714
|
-
if (transcriptStarted || !transcriptsEnabled() || !sessionId) return;
|
|
1715
|
-
transcriptStarted =
|
|
1716
|
-
|
|
1644
|
+
if (transcriptStarted === sessionId || !transcriptsEnabled() || !sessionId) return;
|
|
1645
|
+
transcriptStarted = sessionId;
|
|
1646
|
+
registerTranscript("claude", { session_id: sessionId, cwd: process.cwd() });
|
|
1717
1647
|
};
|
|
1718
1648
|
const ensurePresenceHeartbeat = () => {
|
|
1719
1649
|
if (presenceHeartbeat || !sessionId) return;
|
|
@@ -1732,19 +1662,22 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1732
1662
|
ensurePresenceHeartbeat();
|
|
1733
1663
|
ensureTranscriptSync();
|
|
1734
1664
|
};
|
|
1735
|
-
const onPhoneCommand = (command) => {
|
|
1736
|
-
if (
|
|
1665
|
+
const onPhoneCommand = async (command) => {
|
|
1666
|
+
if (controlMode !== "remote") return false;
|
|
1667
|
+
if (!sessionId || finished || !await ensureSdk() || !remoteAvailable) {
|
|
1737
1668
|
if (!noticedUnavailable) {
|
|
1738
1669
|
noticedUnavailable = true;
|
|
1739
1670
|
process.stderr.write("[pushary] phone control is unavailable this run; the instruction was not delivered.\n");
|
|
1740
1671
|
}
|
|
1741
1672
|
return false;
|
|
1742
1673
|
}
|
|
1743
|
-
if (!input.push(userMessage(command))) return false;
|
|
1674
|
+
if (finished || !input.push(userMessage(command))) return false;
|
|
1744
1675
|
activeLeg?.onPhoneCommand();
|
|
1745
1676
|
return true;
|
|
1746
1677
|
};
|
|
1678
|
+
let draft;
|
|
1747
1679
|
const streamMessage = (message) => {
|
|
1680
|
+
draft?.add(message);
|
|
1748
1681
|
if (message.type === "assistant") {
|
|
1749
1682
|
const text = extractAssistantText(message);
|
|
1750
1683
|
if (text) process.stdout.write(`${text}
|
|
@@ -1755,18 +1688,52 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1755
1688
|
void announcePresence("stop", "Turn complete");
|
|
1756
1689
|
}
|
|
1757
1690
|
};
|
|
1691
|
+
const declineTakeover = (reason, message) => {
|
|
1692
|
+
takeoverDecline = reason;
|
|
1693
|
+
process.stderr.write(`[pushary] ${message}
|
|
1694
|
+
`);
|
|
1695
|
+
};
|
|
1696
|
+
const takeOver = async () => {
|
|
1697
|
+
if (controlMode !== "local" || takeoverPending || finished) return;
|
|
1698
|
+
if (compatibilityIssue) {
|
|
1699
|
+
declineTakeover("unsupported_flags", compatibilityIssue);
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
takeoverPending = true;
|
|
1703
|
+
try {
|
|
1704
|
+
if (!sessionId || !existsSync2(claudeTranscriptPath(process.cwd(), sessionId))) {
|
|
1705
|
+
declineTakeover("no_transcript", "phone takeover needs a turn in this session first; your terminal and queued instructions are unchanged.");
|
|
1706
|
+
return;
|
|
1707
|
+
}
|
|
1708
|
+
if (!await ensureSdk()) {
|
|
1709
|
+
declineTakeover("sdk_unavailable", "phone takeover could not start the Agent SDK; your terminal and queued instructions are unchanged.");
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
if (!finished && controlMode === "local") activeLeg?.onPhoneCommand();
|
|
1713
|
+
} finally {
|
|
1714
|
+
takeoverPending = false;
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1758
1717
|
const poller = startCommandPoller({
|
|
1759
1718
|
apiKey,
|
|
1760
1719
|
getSessionId: () => sessionId,
|
|
1761
1720
|
onCommand: (command) => onPhoneCommand(command),
|
|
1762
|
-
shouldDrainCommands: () => !(relay?.connected() ?? false),
|
|
1721
|
+
shouldDrainCommands: () => controlMode === "remote" && Boolean(sessionId) && !(relay?.connected() ?? false),
|
|
1763
1722
|
// Unified poll: one request per tick for both command + mode/kill (half the
|
|
1764
1723
|
// requests and the per-poll DB write of the old drain + mode pair).
|
|
1765
|
-
poll: (key, id, wantCommand, signal) =>
|
|
1724
|
+
poll: async (key, id, wantCommand, signal) => {
|
|
1725
|
+
const declined = takeoverDecline;
|
|
1726
|
+
takeoverDecline = void 0;
|
|
1727
|
+
const result = await pollAgent(key, id, wantCommand, signal, void 0, machineId, controlMode, declined);
|
|
1728
|
+
if (result.takeoverRequested) void takeOver();
|
|
1729
|
+
return result;
|
|
1730
|
+
},
|
|
1766
1731
|
fetchModeState,
|
|
1767
|
-
//
|
|
1768
|
-
//
|
|
1769
|
-
|
|
1732
|
+
// Observe explicit takeover promptly even when the command queue is idle.
|
|
1733
|
+
// Not faster than this: the poll runs for the life of every wrapper, including
|
|
1734
|
+
// local mode where it cannot return a command, and each tick costs a handful
|
|
1735
|
+
// of Redis round trips. Takeover already sits behind a confirm and a biometric.
|
|
1736
|
+
slowPollMs: 15e3,
|
|
1770
1737
|
onAuthRejected: () => {
|
|
1771
1738
|
process.stderr.write(
|
|
1772
1739
|
"[pushary] this key was rejected \u2014 remote stop and phone messages are NOT active; run `pushary login` to reconnect\n"
|
|
@@ -1817,7 +1784,7 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1817
1784
|
try {
|
|
1818
1785
|
if (sessionId && startingMode === "local") {
|
|
1819
1786
|
process.stderr.write(
|
|
1820
|
-
"[pushary]
|
|
1787
|
+
"[pushary] phone messages queue for the next turn. Choose Continue on phone in the app for idle control (Ctrl-] returns the terminal).\n"
|
|
1821
1788
|
);
|
|
1822
1789
|
void announcePresence("session_start", "Session ready \u2014 reachable from your phone");
|
|
1823
1790
|
ensurePresenceHeartbeat();
|
|
@@ -1827,8 +1794,17 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1827
1794
|
if (startingMode === "remote" && initialPrompt) input.push(userMessage(initialPrompt));
|
|
1828
1795
|
const exitCode = await driveModeLoop({
|
|
1829
1796
|
startingMode,
|
|
1830
|
-
runLocal: () =>
|
|
1797
|
+
runLocal: async () => {
|
|
1798
|
+
controlMode = "local";
|
|
1799
|
+
await draft?.stop();
|
|
1800
|
+
draft = void 0;
|
|
1801
|
+
relay?.stop();
|
|
1802
|
+
relay = void 0;
|
|
1803
|
+
return runLocalLeg(session);
|
|
1804
|
+
},
|
|
1831
1805
|
runRemote: async () => {
|
|
1806
|
+
controlMode = "remote";
|
|
1807
|
+
startRelay();
|
|
1832
1808
|
const ready = await ensureSdk();
|
|
1833
1809
|
if (!ready) {
|
|
1834
1810
|
remoteAvailable = false;
|
|
@@ -1847,6 +1823,7 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1847
1823
|
}
|
|
1848
1824
|
return "switch";
|
|
1849
1825
|
}
|
|
1826
|
+
draft = startTranscriptDraft(apiKey, () => sessionId);
|
|
1850
1827
|
return runRemoteLeg(session, ready);
|
|
1851
1828
|
},
|
|
1852
1829
|
isFinished: () => finished,
|
|
@@ -1856,13 +1833,14 @@ var runDualMode = async (binary, args2, startingMode) => {
|
|
|
1856
1833
|
} finally {
|
|
1857
1834
|
for (const signal of signals) process.off(signal, onSignal);
|
|
1858
1835
|
process.removeListener("exit", restoreStdin);
|
|
1836
|
+
await draft?.stop();
|
|
1859
1837
|
relay?.stop();
|
|
1860
1838
|
poller.stop();
|
|
1861
1839
|
if (presenceHeartbeat) clearInterval(presenceHeartbeat);
|
|
1862
|
-
stopTranscript?.();
|
|
1863
1840
|
approver.teardown();
|
|
1864
1841
|
input.close();
|
|
1865
1842
|
stdin.dispose();
|
|
1843
|
+
if (sessionId) registerTranscript("claude", { session_id: sessionId, cwd: process.cwd() }, void 0, { ended: true });
|
|
1866
1844
|
await announcePresence("session_closed", "Session ended");
|
|
1867
1845
|
ownership?.stop();
|
|
1868
1846
|
if (totalCostUsd > 0) {
|