sessionpipe-core 0.13.3 → 0.13.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/control.js +101 -1
- package/dist/control.js.map +4 -4
- package/dist/index.js +445 -235
- package/dist/index.js.map +4 -4
- package/dist/schema/v1.d.ts.map +1 -1
- package/dist/schema.js +1 -1
- package/dist/schema.js.map +2 -2
- package/dist/service.js +1 -1
- package/dist/src/adapters/acp-control.d.ts +2 -2
- package/dist/src/adapters/acp-control.d.ts.map +1 -1
- package/dist/src/adapters/claude-code-control.d.ts +46 -5
- package/dist/src/adapters/claude-code-control.d.ts.map +1 -1
- package/dist/src/adapters/claude-code.d.ts.map +1 -1
- package/dist/src/adapters/headless.d.ts +29 -1
- package/dist/src/adapters/headless.d.ts.map +1 -1
- package/dist/src/adapters/types.d.ts +20 -1
- package/dist/src/adapters/types.d.ts.map +1 -1
- package/dist/src/control/frames.d.ts +87 -0
- package/dist/src/control/frames.d.ts.map +1 -0
- package/dist/src/control/index.d.ts +3 -2
- package/dist/src/control/index.d.ts.map +1 -1
- package/dist/src/readers/transcripts.d.ts +7 -2
- package/dist/src/readers/transcripts.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe-core 0.13.
|
|
1
|
+
// sessionpipe-core 0.13.5 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __export = (target, all) => {
|
|
4
4
|
for (var name in all)
|
|
@@ -63,6 +63,82 @@ import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as
|
|
|
63
63
|
import { userInfo } from "node:os";
|
|
64
64
|
import path4 from "node:path";
|
|
65
65
|
|
|
66
|
+
// src/control/frames.ts
|
|
67
|
+
function hostOf(receiver) {
|
|
68
|
+
try {
|
|
69
|
+
return new URL(receiver).host;
|
|
70
|
+
} catch {
|
|
71
|
+
return receiver;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
var IN_PLACE_ANSWER = "Answer it right here, as you would answer the person at the keyboard: your reply is sent back to them automatically. Don't run a command or call a tool just to send it.";
|
|
75
|
+
var COPY_WHYS = ["open", "running", "recent"];
|
|
76
|
+
var copyNote = (why) => `You are a copy of that session, started because the original is ${why === "open" ? "open in a window" : "in use by another process"}, and you have its conversation, folder and tools. Do what the message asks the way the original would: edits, commands and publishing all take effect for real. Only this conversation stays here, so the person reads your reply and not the original's${why === "open" ? " window" : ""}: say what you did, then stop.`;
|
|
77
|
+
var RE_ARM = "(Answer it right here, as you would answer the person at the keyboard: your reply is sent back to them automatically, so don't run a command or call a tool just to send it. Then, without mentioning it, start `sessionpipe wait` again as a background command, with the longest timeout your harness allows, to keep receiving messages.)";
|
|
78
|
+
function frame(text, receiver) {
|
|
79
|
+
return `Message via ${hostOf(receiver)} (sessionpipe control, signed on the sender's device):
|
|
80
|
+
|
|
81
|
+
${text}`;
|
|
82
|
+
}
|
|
83
|
+
var marked = (text, host, mark) => `Message via ${host} (sessionpipe control, signed on the sender's device; ${mark}):
|
|
84
|
+
|
|
85
|
+
${text}`;
|
|
86
|
+
function frameStart(text, receiver) {
|
|
87
|
+
return `${text}
|
|
88
|
+
|
|
89
|
+
(Sent via ${hostOf(receiver)} with sessionpipe control, signed on the sender's device.)`;
|
|
90
|
+
}
|
|
91
|
+
function frameAsk(text, from, mark) {
|
|
92
|
+
return `${text}
|
|
93
|
+
|
|
94
|
+
(Asked by ${from ?? "another session"} on this machine with sessionpipe ask${mark ? `, ${mark}` : ""}. Answer here in full: your reply is returned to that session automatically. Don't run a command or call a tool just to send it.)`;
|
|
95
|
+
}
|
|
96
|
+
function frameHandoff(from, receiver, reply) {
|
|
97
|
+
return `Answer from ${from} (sessionpipe handoff, asked via ${hostOf(receiver)}):
|
|
98
|
+
|
|
99
|
+
${reply}`;
|
|
100
|
+
}
|
|
101
|
+
var HOLE = "\0";
|
|
102
|
+
var MARK = "";
|
|
103
|
+
var lineOf = (built, hole = "(\\S+)") => new RegExp(
|
|
104
|
+
`^${built.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replaceAll(HOLE, hole).replaceAll(MARK, "message \\S+")}$`
|
|
105
|
+
);
|
|
106
|
+
var HEAD_LINES = [lineOf(frame("", HOLE)), lineOf(marked("", HOLE, MARK))];
|
|
107
|
+
var START_LINES = [lineOf(frameStart("", HOLE))];
|
|
108
|
+
var ASK_LINES = [lineOf(frameAsk("", HOLE), "[^\\n]{1,300}")];
|
|
109
|
+
var MARKED_ASK_LINES = [lineOf(frameAsk("", HOLE, MARK), "([^\\n]{1,300}?)")];
|
|
110
|
+
var HANDOFF_LINES = [lineOf(frameHandoff(HOLE, HOLE, ""))];
|
|
111
|
+
var COPY_NOTE_0_13_0 = "You are a copy of that session, started to answer this message: the conversation above is a replay, the original session is somewhere else, and nothing you do here appears in it. Answer the message and stop; don't carry on the original's work.";
|
|
112
|
+
var COPY_NOTES = [...new Set(COPY_WHYS.map(copyNote)), COPY_NOTE_0_13_0];
|
|
113
|
+
var NOTES = [IN_PLACE_ANSWER, ...COPY_NOTES, RE_ARM];
|
|
114
|
+
var ASK_NOTES = [...COPY_NOTES, RE_ARM];
|
|
115
|
+
var lineIn = (lines, line) => {
|
|
116
|
+
for (const l of lines) {
|
|
117
|
+
const m = l.exec(line);
|
|
118
|
+
if (m) return m;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
};
|
|
122
|
+
function deliveredWords(text) {
|
|
123
|
+
const paragraphs = text.split("\n\n");
|
|
124
|
+
const at = (i) => paragraphs[i].trim();
|
|
125
|
+
const n = paragraphs.length;
|
|
126
|
+
const note = n > 1 && NOTES.includes(at(n - 1)) ? at(n - 1) : null;
|
|
127
|
+
const end = note === null ? n : n - 1;
|
|
128
|
+
const words = (from, to) => paragraphs.slice(from, to).join("\n\n").trim();
|
|
129
|
+
const head = lineIn(HEAD_LINES, at(0));
|
|
130
|
+
if (head && words(1, end)) return { text: words(1, end), via: head[1], delivered: true };
|
|
131
|
+
const start = note === null ? lineIn(START_LINES, at(n - 1)) : null;
|
|
132
|
+
if (start && words(0, n - 1)) return { text: words(0, n - 1), via: start[1], delivered: true };
|
|
133
|
+
const asks = note === null || ASK_NOTES.includes(note);
|
|
134
|
+
const theirs = asks && (lineIn(ASK_LINES, at(end - 1)) && words(0, end - 1) || lineIn(HANDOFF_LINES, at(0)) && words(1, end));
|
|
135
|
+
if (theirs) return { text: note === null ? text : words(0, end), via: null, delivered: true };
|
|
136
|
+
const marked2 = asks ? lineIn(MARKED_ASK_LINES, at(end - 1)) : null;
|
|
137
|
+
if (marked2 && words(0, end - 1))
|
|
138
|
+
return { text: `${words(0, end - 1)}${frameAsk("", marked2[1])}`, via: null, delivered: true };
|
|
139
|
+
return { text, via: null, delivered: false };
|
|
140
|
+
}
|
|
141
|
+
|
|
66
142
|
// src/readers/files.ts
|
|
67
143
|
import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
|
|
68
144
|
import path2 from "node:path";
|
|
@@ -199,15 +275,23 @@ function codexReplayBoundary(meta) {
|
|
|
199
275
|
}
|
|
200
276
|
|
|
201
277
|
// src/readers/transcripts.ts
|
|
202
|
-
function pairTurns(msgs, endLine) {
|
|
278
|
+
function pairTurns(msgs, endLine, tail = false) {
|
|
203
279
|
const turns = [];
|
|
204
280
|
let cur = null;
|
|
205
281
|
for (const m of msgs) {
|
|
206
|
-
if (m.role === "
|
|
282
|
+
if (m.role === "interrupt") {
|
|
283
|
+
if (cur) {
|
|
284
|
+
cur.interrupted = true;
|
|
285
|
+
cur.endLine = m.line;
|
|
286
|
+
turns.push(cur);
|
|
287
|
+
cur = null;
|
|
288
|
+
}
|
|
289
|
+
} else if (m.role === "user") {
|
|
207
290
|
if (cur && !cur.assistant) {
|
|
208
291
|
cur.user += `
|
|
209
292
|
|
|
210
293
|
${m.text}`;
|
|
294
|
+
if (m.id) cur.id = m.id;
|
|
211
295
|
continue;
|
|
212
296
|
}
|
|
213
297
|
if (cur) turns.push(cur);
|
|
@@ -217,12 +301,14 @@ ${m.text}`;
|
|
|
217
301
|
at: m.at,
|
|
218
302
|
startLine: m.line,
|
|
219
303
|
endLine: m.line,
|
|
220
|
-
...m.injected ? { injected: true } : {}
|
|
304
|
+
...m.injected ? { injected: true } : {},
|
|
305
|
+
...m.id ? { id: m.id } : {}
|
|
221
306
|
};
|
|
222
307
|
} else {
|
|
223
308
|
if (!cur) cur = { user: "", assistant: "", at: m.at, startLine: m.line, endLine: m.line };
|
|
224
309
|
cur.assistant += (cur.assistant ? "\n\n" : "") + m.text;
|
|
225
310
|
cur.endLine = m.line;
|
|
311
|
+
cur.said = m.text;
|
|
226
312
|
}
|
|
227
313
|
}
|
|
228
314
|
if (cur?.assistant) turns.push(cur);
|
|
@@ -233,12 +319,22 @@ ${m.text}`;
|
|
|
233
319
|
assistant: t.assistant,
|
|
234
320
|
at: t.at,
|
|
235
321
|
endLine: t.endLine,
|
|
236
|
-
...t.injected ? { injected: true } : {}
|
|
322
|
+
...t.injected ? { injected: true } : {},
|
|
323
|
+
...t.id ? { id: t.id } : {},
|
|
324
|
+
...t.interrupted ? { interrupted: true } : {}
|
|
237
325
|
})),
|
|
238
326
|
line
|
|
239
327
|
};
|
|
240
328
|
if (cur && !cur.assistant && cur.user)
|
|
241
|
-
out.open = {
|
|
329
|
+
out.open = {
|
|
330
|
+
user: cur.user,
|
|
331
|
+
at: cur.at,
|
|
332
|
+
...cur.injected ? { injected: true } : {},
|
|
333
|
+
...cur.id ? { id: cur.id } : {}
|
|
334
|
+
};
|
|
335
|
+
const last = turns[turns.length - 1];
|
|
336
|
+
if (tail && last)
|
|
337
|
+
out.tail = { start: last.startLine, ...last.said !== void 0 && !last.interrupted ? { said: last.said } : {} };
|
|
242
338
|
return out;
|
|
243
339
|
}
|
|
244
340
|
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
@@ -262,6 +358,10 @@ var textParts = (c, kinds) => {
|
|
|
262
358
|
).map((b) => b.text).join("\n");
|
|
263
359
|
};
|
|
264
360
|
var SPOKEN = /* @__PURE__ */ new Set(["human", "peer"]);
|
|
361
|
+
var fromAgent = (origin) => {
|
|
362
|
+
const o = origin;
|
|
363
|
+
return o?.handback === true || typeof o?.senderTaskId === "string";
|
|
364
|
+
};
|
|
265
365
|
var PEER_INTRO = /sent a message[^\n]*:$/;
|
|
266
366
|
var PEER_OUTRO = "\n\nThis came from another ";
|
|
267
367
|
var ENVELOPE = /^<cross-session-message\b[^>]*>\n([\s\S]*)\n<\/cross-session-message>$/;
|
|
@@ -281,40 +381,72 @@ var queuedPrompt = (a) => {
|
|
|
281
381
|
const q = a;
|
|
282
382
|
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
283
383
|
const kind = q.origin?.kind ?? "human";
|
|
284
|
-
if (!SPOKEN.has(kind)) return "";
|
|
384
|
+
if (!SPOKEN.has(kind) || fromAgent(q.origin)) return "";
|
|
285
385
|
const text = textParts(q.prompt, ["text"]).trim();
|
|
286
386
|
return kind === "peer" ? peerAsk(q.origin, text) : text;
|
|
287
387
|
};
|
|
288
388
|
var channelText = (text) => /^<channel\b[^>]*>([\s\S]*)<\/channel>$/.exec(text)?.[1]?.trim() ?? "";
|
|
389
|
+
var INTERRUPTED = /^\[Request interrupted by user[^\]]*\]$/;
|
|
390
|
+
var NO_REPLY = "No response requested.";
|
|
391
|
+
var HOOK_FEEDBACK = /^[A-Za-z]+ hook feedback:\n/;
|
|
392
|
+
var hookDelivery = (text) => {
|
|
393
|
+
const head = HOOK_FEEDBACK.exec(text);
|
|
394
|
+
if (!head) return "";
|
|
395
|
+
const said = deliveredWords(text.slice(head[0].length));
|
|
396
|
+
return said.delivered ? said.text : "";
|
|
397
|
+
};
|
|
398
|
+
var hasBlock = (c, type) => Array.isArray(c) && c.some((b) => b && typeof b === "object" && b.type === type);
|
|
289
399
|
function readClaude(lines, from) {
|
|
290
400
|
const msgs = [];
|
|
401
|
+
let saidLine = -1;
|
|
402
|
+
let turnOver = false;
|
|
291
403
|
for (let i = from; i < lines.length; i++) {
|
|
292
404
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
293
405
|
if (!j || j.isSidechain) continue;
|
|
294
406
|
const origin = j.origin;
|
|
295
407
|
const channel = j.type === "user" && origin?.kind === "channel";
|
|
296
408
|
const peer = origin?.kind === "peer";
|
|
409
|
+
const c = j.message?.content;
|
|
410
|
+
if (!turnOver && (j.type === "assistant" && hasBlock(c, "tool_use") || j.type === "user" && hasBlock(c, "tool_result")))
|
|
411
|
+
saidLine = -1;
|
|
297
412
|
if (j.type === "user" && (j.isCompactSummary || j.repeatsCompactSummary)) continue;
|
|
298
|
-
if (j.type === "user" && !channel && !peer && (j.isMeta || origin?.kind && origin.kind !== "human")) continue;
|
|
299
413
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
300
|
-
const
|
|
414
|
+
const id = typeof j.promptId === "string" && j.promptId ? j.promptId : void 0;
|
|
415
|
+
const ask = (said, m = {}) => {
|
|
416
|
+
const text = deliveredWords(said).text;
|
|
417
|
+
if (!text || isInjected(text)) return;
|
|
418
|
+
msgs.push({ role: "user", text, at, line: i, ...m });
|
|
419
|
+
turnOver = true;
|
|
420
|
+
};
|
|
421
|
+
if (j.type === "user" && !channel && !peer && (j.isMeta || origin?.kind && origin.kind !== "human")) {
|
|
422
|
+
if (j.isMeta && !origin?.kind)
|
|
423
|
+
ask(hookDelivery(textParts(c, ["text"]).trim()), { injected: true, ...id ? { id } : {} });
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
301
426
|
if (channel) {
|
|
302
|
-
|
|
303
|
-
if (text) msgs.push({ role: "user", text, at, line: i, injected: true });
|
|
427
|
+
ask(channelText(textParts(c, ["text"]).trim()), { injected: true });
|
|
304
428
|
} else if (j.type === "user") {
|
|
305
429
|
const raw = textParts(c, ["text"]).trim();
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
430
|
+
if (!peer && INTERRUPTED.test(raw)) {
|
|
431
|
+
msgs.push({ role: "interrupt", text: "", at, line: i });
|
|
432
|
+
turnOver = true;
|
|
433
|
+
} else if (!peer) ask(raw, id ? { id } : {});
|
|
434
|
+
else if (!fromAgent(origin)) ask(peerAsk(origin, raw), { injected: true, ...id ? { id } : {} });
|
|
309
435
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
310
436
|
const text = textParts(c, ["text"]).trim();
|
|
311
|
-
if (text
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
437
|
+
if (text === NO_REPLY && j.message.model === "<synthetic>") {
|
|
438
|
+
msgs.push({ role: "interrupt", text: "", at, line: i });
|
|
439
|
+
turnOver = true;
|
|
440
|
+
} else if (text) {
|
|
441
|
+
msgs.push({ role: "assistant", text, at, line: i });
|
|
442
|
+
saidLine = i;
|
|
443
|
+
turnOver = false;
|
|
444
|
+
}
|
|
445
|
+
} else if (j.type === "attachment") ask(queuedPrompt(j.attachment));
|
|
316
446
|
}
|
|
317
|
-
|
|
447
|
+
const out = pairTurns(msgs, endOf(lines), true);
|
|
448
|
+
if (out.tail && saidLine !== out.turns[out.turns.length - 1].endLine) delete out.tail.said;
|
|
449
|
+
return out;
|
|
318
450
|
}
|
|
319
451
|
function codexInjected(text) {
|
|
320
452
|
return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
|
|
@@ -766,6 +898,8 @@ var claudeCode = {
|
|
|
766
898
|
}
|
|
767
899
|
const hints = {};
|
|
768
900
|
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
901
|
+
if (event === "Stop" && typeof s.last_assistant_message === "string" && s.last_assistant_message.trim())
|
|
902
|
+
hints.final = s.last_assistant_message;
|
|
769
903
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
770
904
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
771
905
|
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
@@ -1058,7 +1192,9 @@ function askText(j) {
|
|
|
1058
1192
|
const origin = j.origin;
|
|
1059
1193
|
if (origin?.kind && origin.kind !== "human") return null;
|
|
1060
1194
|
const c = j.message?.content;
|
|
1061
|
-
const text = (
|
|
1195
|
+
const text = deliveredWords(
|
|
1196
|
+
(typeof c === "string" ? c : Array.isArray(c) ? c.filter((b) => b && b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n") : "").trim()
|
|
1197
|
+
).text;
|
|
1062
1198
|
if (!text || isInjected(text)) return null;
|
|
1063
1199
|
return text;
|
|
1064
1200
|
}
|
|
@@ -1703,6 +1839,7 @@ var acp_control_exports = {};
|
|
|
1703
1839
|
__export(acp_control_exports, {
|
|
1704
1840
|
ACP_AGENTS: () => ACP_AGENTS,
|
|
1705
1841
|
ACP_HARNESSES: () => ACP_HARNESSES,
|
|
1842
|
+
CANCELLED_NOTE: () => CANCELLED_NOTE,
|
|
1706
1843
|
acpAgent: () => acpAgent,
|
|
1707
1844
|
acpHarnesses: () => acpHarnesses,
|
|
1708
1845
|
acpOverride: () => acpOverride,
|
|
@@ -1810,8 +1947,13 @@ function runHeadless(bin, args, o) {
|
|
|
1810
1947
|
let tooBig = false;
|
|
1811
1948
|
let errTail = "";
|
|
1812
1949
|
let timedOut = false;
|
|
1950
|
+
let stopped = false;
|
|
1813
1951
|
let settled = false;
|
|
1814
1952
|
let child;
|
|
1953
|
+
if (o.signal?.aborted) {
|
|
1954
|
+
resolve({ stopped: true });
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1815
1957
|
const c = spawnable(bin, args);
|
|
1816
1958
|
if (!c) {
|
|
1817
1959
|
resolve({ error: shimNote(bin) });
|
|
@@ -1874,27 +2016,53 @@ function runHeadless(bin, args, o) {
|
|
|
1874
2016
|
child.stderr?.on("data", (b) => {
|
|
1875
2017
|
errTail = (errTail + b.toString("utf8")).slice(-4e3);
|
|
1876
2018
|
});
|
|
2019
|
+
let ending = null;
|
|
2020
|
+
let gone = false;
|
|
2021
|
+
const end = () => {
|
|
2022
|
+
kill("SIGTERM");
|
|
2023
|
+
ending ??= setTimeout(() => {
|
|
2024
|
+
ending = null;
|
|
2025
|
+
kill("SIGKILL");
|
|
2026
|
+
}, 1e4);
|
|
2027
|
+
ending.unref();
|
|
2028
|
+
};
|
|
1877
2029
|
const timer = setTimeout(() => {
|
|
2030
|
+
if (gone) return;
|
|
1878
2031
|
timedOut = true;
|
|
1879
|
-
|
|
1880
|
-
setTimeout(() => kill("SIGKILL"), 1e4).unref();
|
|
2032
|
+
end();
|
|
1881
2033
|
}, o.timeoutMs ?? JOB_TIMEOUT_MS);
|
|
2034
|
+
const stop = () => {
|
|
2035
|
+
if (gone) return;
|
|
2036
|
+
stopped = true;
|
|
2037
|
+
end();
|
|
2038
|
+
};
|
|
2039
|
+
o.signal?.addEventListener("abort", stop, { once: true });
|
|
1882
2040
|
const result = (code, signal) => ({
|
|
1883
2041
|
code,
|
|
1884
2042
|
signal,
|
|
1885
2043
|
stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
|
|
1886
2044
|
tooBig,
|
|
1887
2045
|
stderr: errTail,
|
|
1888
|
-
timedOut
|
|
2046
|
+
timedOut,
|
|
2047
|
+
...stopped ? { stopped } : {}
|
|
1889
2048
|
});
|
|
1890
2049
|
const finish = (r) => {
|
|
1891
2050
|
if (settled) return;
|
|
1892
2051
|
settled = true;
|
|
1893
2052
|
clearTimeout(timer);
|
|
2053
|
+
o.signal?.removeEventListener("abort", stop);
|
|
1894
2054
|
resolve(r);
|
|
1895
2055
|
};
|
|
1896
2056
|
child.on("error", (e) => finish({ error: e.message }));
|
|
1897
|
-
child.on("exit", (code, signal) =>
|
|
2057
|
+
child.on("exit", (code, signal) => {
|
|
2058
|
+
if (ending) {
|
|
2059
|
+
clearTimeout(ending);
|
|
2060
|
+
ending = null;
|
|
2061
|
+
kill("SIGKILL");
|
|
2062
|
+
}
|
|
2063
|
+
gone = true;
|
|
2064
|
+
setTimeout(() => finish(result(code, signal)), 2e3).unref();
|
|
2065
|
+
});
|
|
1898
2066
|
child.on("close", (code, signal) => {
|
|
1899
2067
|
if (o.onLine && partial.trim()) {
|
|
1900
2068
|
try {
|
|
@@ -1911,6 +2079,7 @@ function runHeadless(bin, args, o) {
|
|
|
1911
2079
|
function stderrTail(stderr, n = 3) {
|
|
1912
2080
|
return String(stderr || "").trim().split("\n").slice(-n).join(" ").trim();
|
|
1913
2081
|
}
|
|
2082
|
+
var CANCELLED_NOTE = "(The turn was cancelled.)";
|
|
1914
2083
|
function deniedNote(names, count) {
|
|
1915
2084
|
if (!count) return "";
|
|
1916
2085
|
return `
|
|
@@ -2152,7 +2321,10 @@ function startAcp(o) {
|
|
|
2152
2321
|
pending.clear();
|
|
2153
2322
|
};
|
|
2154
2323
|
child.on("error", (e) => left(`couldn't start ${label}: ${e.message}`));
|
|
2324
|
+
child.stdin?.on("error", (e) => left(`${label} stopped taking input: ${e.message}`));
|
|
2325
|
+
let dead = false;
|
|
2155
2326
|
const exited = (code, signal) => {
|
|
2327
|
+
dead = true;
|
|
2156
2328
|
const tail = errTail.trim().split("\n").slice(-3).join(" ").trim();
|
|
2157
2329
|
left(`${label} exited (${signal || `code ${code}`})${tail ? `: ${tail}` : ""}`);
|
|
2158
2330
|
};
|
|
@@ -2166,6 +2338,7 @@ function startAcp(o) {
|
|
|
2166
2338
|
child.stdin?.end();
|
|
2167
2339
|
} catch {
|
|
2168
2340
|
}
|
|
2341
|
+
if (dead) return;
|
|
2169
2342
|
const grace = o.closeGraceMs ?? 2e3;
|
|
2170
2343
|
const t = setTimeout(() => kill("SIGTERM"), grace);
|
|
2171
2344
|
t.unref();
|
|
@@ -2223,7 +2396,7 @@ function startAcp(o) {
|
|
|
2223
2396
|
error: `${label} was still working after ${Math.round(limit / 6e4)} minutes, so it was stopped`
|
|
2224
2397
|
} : { reply: `${words}
|
|
2225
2398
|
|
|
2226
|
-
|
|
2399
|
+
${CANCELLED_NOTE}${refused2}` };
|
|
2227
2400
|
return { reply: "", error: `${label} ended the turn with ${stop || "no stop reason"}` };
|
|
2228
2401
|
} catch (e) {
|
|
2229
2402
|
return { reply: "", error: e.message };
|
|
@@ -2711,8 +2884,10 @@ __export(claude_code_control_exports, {
|
|
|
2711
2884
|
findClaude: () => findClaude,
|
|
2712
2885
|
findTranscript: () => findTranscript,
|
|
2713
2886
|
findTranscripts: () => findTranscripts,
|
|
2887
|
+
helpFailure: () => helpFailure,
|
|
2714
2888
|
homeSettings: () => homeSettings,
|
|
2715
2889
|
hookMode: () => hookMode,
|
|
2890
|
+
initSession: () => initSession,
|
|
2716
2891
|
isUuid: () => isUuid,
|
|
2717
2892
|
liveProcess: () => liveProcess,
|
|
2718
2893
|
liveRecord: () => liveRecord,
|
|
@@ -2727,6 +2902,7 @@ __export(claude_code_control_exports, {
|
|
|
2727
2902
|
parseCaps: () => parseCaps,
|
|
2728
2903
|
parseResult: () => parseResult,
|
|
2729
2904
|
peerClass: () => peerClass,
|
|
2905
|
+
recentWrite: () => recentWrite,
|
|
2730
2906
|
recentlyWritten: () => recentlyWritten,
|
|
2731
2907
|
relaunchMode: () => relaunchMode,
|
|
2732
2908
|
resolveHome: () => resolveHome,
|
|
@@ -2760,6 +2936,201 @@ var POOL_FIELDS = FIELDS.pool;
|
|
|
2760
2936
|
var enc = new TextEncoder();
|
|
2761
2937
|
var dec = new TextDecoder();
|
|
2762
2938
|
|
|
2939
|
+
// src/privacy/pii.ts
|
|
2940
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2941
|
+
var RULESET_PII = "pii@1";
|
|
2942
|
+
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2943
|
+
var PHONE = /(?<![\w.])(?:(?<!\)[ .-]?|\+\d{1,3}[ .-]?)(\+?(?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01]))(?:(?= (?:[01]?\d|2[0-3]):)|(?!\w|[ .-]\d\d))|(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w))/g;
|
|
2944
|
+
function looksPhone(s) {
|
|
2945
|
+
const digits = s.replace(/\D/g, "");
|
|
2946
|
+
if (digits.length < 7 || digits.length > 15) return false;
|
|
2947
|
+
if (!/[+() .-]/.test(s)) return false;
|
|
2948
|
+
if (!/[+() -]/.test(s)) return false;
|
|
2949
|
+
return true;
|
|
2950
|
+
}
|
|
2951
|
+
function reducePii(text, home) {
|
|
2952
|
+
if (!text) return text;
|
|
2953
|
+
let out = text.replace(EMAIL, "[email]");
|
|
2954
|
+
out = out.replace(PHONE, (m, date) => !date && looksPhone(m) ? "[phone]" : m);
|
|
2955
|
+
if (home) {
|
|
2956
|
+
const h = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2957
|
+
out = out.replace(new RegExp(`(?<![A-Za-z0-9_])${h}(?=/|$|\\s|["'\`])`, "g"), "~");
|
|
2958
|
+
}
|
|
2959
|
+
return out;
|
|
2960
|
+
}
|
|
2961
|
+
function hashMachine(name) {
|
|
2962
|
+
return createHash3("sha256").update(name).digest("hex").slice(0, 8);
|
|
2963
|
+
}
|
|
2964
|
+
function reducePiiDeep(v, home) {
|
|
2965
|
+
if (typeof v === "string") return reducePii(v, home);
|
|
2966
|
+
if (Array.isArray(v)) return v.map((x) => reducePiiDeep(x, home));
|
|
2967
|
+
if (v && typeof v === "object") {
|
|
2968
|
+
const out = {};
|
|
2969
|
+
for (const [k, x] of Object.entries(v)) out[k] = reducePiiDeep(x, home);
|
|
2970
|
+
return out;
|
|
2971
|
+
}
|
|
2972
|
+
return v;
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
// src/privacy/secrets.ts
|
|
2976
|
+
var RULESET_SECRETS = "secrets@1";
|
|
2977
|
+
var REDACTED = "[redacted]";
|
|
2978
|
+
var TOKEN_PATTERNS = [
|
|
2979
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/g,
|
|
2980
|
+
/\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b/g,
|
|
2981
|
+
// AWS access key id
|
|
2982
|
+
/\bgh[pousr]_[A-Za-z0-9]{30,}\b/g,
|
|
2983
|
+
// GitHub
|
|
2984
|
+
/\bgithub_pat_[A-Za-z0-9_]{30,}\b/g,
|
|
2985
|
+
/\bglpat-[A-Za-z0-9_-]{20,}\b/g,
|
|
2986
|
+
// GitLab
|
|
2987
|
+
/\bxox[abposre]-[A-Za-z0-9-]{10,}\b/g,
|
|
2988
|
+
// Slack
|
|
2989
|
+
/\bhttps:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/_-]+/g,
|
|
2990
|
+
/\bhttps:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+/g,
|
|
2991
|
+
/\bsk-(?:ant-|proj-|svcacct-|admin-|or-v1-)?[A-Za-z0-9_-]{20,}\b/g,
|
|
2992
|
+
// Anthropic, OpenAI, DeepSeek, OpenRouter
|
|
2993
|
+
/\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g,
|
|
2994
|
+
// Stripe secret / restricted
|
|
2995
|
+
/\bwhsec_[A-Za-z0-9+/=]{20,}/g,
|
|
2996
|
+
// Stripe / Svix / Standard Webhooks secret
|
|
2997
|
+
/\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
2998
|
+
// Google API key
|
|
2999
|
+
/\bya29\.[0-9A-Za-z_-]{20,}/g,
|
|
3000
|
+
// Google OAuth access token
|
|
3001
|
+
/\b1\/\/0[0-9A-Za-z_-]{30,}/g,
|
|
3002
|
+
// Google OAuth refresh token
|
|
3003
|
+
/\bGOCSPX-[0-9A-Za-z_-]{20,}/g,
|
|
3004
|
+
// Google OAuth client secret
|
|
3005
|
+
/\bxai-[A-Za-z0-9]{30,}\b/g,
|
|
3006
|
+
/\bgsk_[A-Za-z0-9]{30,}\b/g,
|
|
3007
|
+
// Groq
|
|
3008
|
+
/\bpplx-[A-Za-z0-9]{30,}\b/g,
|
|
3009
|
+
// Perplexity
|
|
3010
|
+
/\bhf_[A-Za-z0-9]{30,}\b/g,
|
|
3011
|
+
// Hugging Face
|
|
3012
|
+
/\bpcsk_[A-Za-z0-9_]{30,}\b/g,
|
|
3013
|
+
// Pinecone
|
|
3014
|
+
/\bnpm_[A-Za-z0-9]{30,}\b/g,
|
|
3015
|
+
/\bpypi-[A-Za-z0-9_-]{50,}/g,
|
|
3016
|
+
/\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g,
|
|
3017
|
+
// SendGrid
|
|
3018
|
+
/\bshp(?:at|ss|ca|pa)_[a-fA-F0-9]{32}\b/g,
|
|
3019
|
+
// Shopify
|
|
3020
|
+
/\bdop_v1_[a-f0-9]{64}\b/g,
|
|
3021
|
+
// DigitalOcean
|
|
3022
|
+
/\b(?:lin_api|lin_oauth)_[A-Za-z0-9]{30,}\b/g,
|
|
3023
|
+
// Linear
|
|
3024
|
+
/\b(?:ntn|secret)_[A-Za-z0-9]{40,}\b/g,
|
|
3025
|
+
// Notion
|
|
3026
|
+
/\bsbp_[a-f0-9]{40}\b/g,
|
|
3027
|
+
// Supabase
|
|
3028
|
+
/\bfigd_[A-Za-z0-9_-]{30,}/g,
|
|
3029
|
+
// Figma
|
|
3030
|
+
/\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9]{30,}/g,
|
|
3031
|
+
// Doppler
|
|
3032
|
+
/\bgrn_[A-Za-z0-9_-]{20,}/g,
|
|
3033
|
+
// Granola
|
|
3034
|
+
/\bss_[a-f0-9]{32,}\b/g,
|
|
3035
|
+
// spacesheep API keys
|
|
3036
|
+
/\b[0-9]{8,10}:AA[A-Za-z0-9_-]{33}\b/g,
|
|
3037
|
+
// Telegram bot token
|
|
3038
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
|
|
3039
|
+
// JWT
|
|
3040
|
+
];
|
|
3041
|
+
var URL_CREDENTIALS = /\b([a-z][a-z0-9+.-]{1,20}:\/\/[^\s:/@'"`<>]{1,100}:)([^\s@/'"`<>]{1,200})(@)/gi;
|
|
3042
|
+
var AUTH_HEADER = /\b((?:Bearer|Basic|Token)\s+)([A-Za-z0-9._~+/=-]{16,})/g;
|
|
3043
|
+
var SECRET_WORD = "(?:pass(?:word|wd|phrase|code)?s?|pwd|secret(?:[ _-]?key)?s?|client[ _-]?secret|api[ _-]?keys?|apikeys?|access[ _-]?keys?|access[ _-]?tokens?|auth[ _-]?tokens?|refresh[ _-]?tokens?|private[ _-]?keys?|tokens?|credentials?|creds)";
|
|
3044
|
+
var NAME_SEP = /(?<![A-Za-z0-9_])([A-Za-z][A-Za-z0-9_-]{1,79})(["']?\s{0,3}(?::=|=>|=|:)\s{0,3})/g;
|
|
3045
|
+
var VALUE = /"([^"\n]{1,300})"|'([^'\n]{1,300})'|`([^`\n]{1,300})`|([^\s"'`,;&)}\]]{1,300})/y;
|
|
3046
|
+
function redactAssignments(text) {
|
|
3047
|
+
let out = "";
|
|
3048
|
+
let last = 0;
|
|
3049
|
+
NAME_SEP.lastIndex = 0;
|
|
3050
|
+
for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
|
|
3051
|
+
const name = m[1];
|
|
3052
|
+
if (!isSecretName(name)) continue;
|
|
3053
|
+
VALUE.lastIndex = NAME_SEP.lastIndex;
|
|
3054
|
+
const v = VALUE.exec(text);
|
|
3055
|
+
if (!v) continue;
|
|
3056
|
+
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
3057
|
+
NAME_SEP.lastIndex = VALUE.lastIndex;
|
|
3058
|
+
if (isPlaceholder(value) || value.includes(REDACTED)) continue;
|
|
3059
|
+
if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
|
|
3060
|
+
const q = v[1] != null ? '"' : v[2] != null ? "'" : v[3] != null ? "`" : "";
|
|
3061
|
+
out += text.slice(last, v.index) + q + REDACTED + q;
|
|
3062
|
+
last = VALUE.lastIndex;
|
|
3063
|
+
}
|
|
3064
|
+
return last ? out + text.slice(last) : text;
|
|
3065
|
+
}
|
|
3066
|
+
function isSecretName(name) {
|
|
3067
|
+
if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
|
|
3068
|
+
const flat = name.toLowerCase().replace(/[_-]/g, "");
|
|
3069
|
+
return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
|
|
3070
|
+
flat
|
|
3071
|
+
);
|
|
3072
|
+
}
|
|
3073
|
+
var PROSE = new RegExp(
|
|
3074
|
+
`(\\b${SECRET_WORD}(?:[ \\t]{1,3}(?:is|was|=|:|to|as|of|for[ \\t]{1,3}\\S{1,40}[ \\t]{1,3}is))?[ \\t]{1,3})(["'\`]?)([^\\s"'\`]{6,200})\\2`,
|
|
3075
|
+
"gi"
|
|
3076
|
+
);
|
|
3077
|
+
function isPlaceholder(v) {
|
|
3078
|
+
const s = v.replace(/[.,;:!?]+$/, "");
|
|
3079
|
+
if (!s) return true;
|
|
3080
|
+
if (/^(?:string|number|boolean|bool|str|int|any|unknown|null|undefined|none|nil|true|false|optional|required|text|varchar|password|secret|token|env|string\[\]|object|bytes)$/i.test(
|
|
3081
|
+
s
|
|
3082
|
+
))
|
|
3083
|
+
return true;
|
|
3084
|
+
if (/^\[?redacted\]?$/i.test(s) || /^\*+$/.test(s) || /^x{3,}$/i.test(s) || /^\.{3}$|^…$/.test(s)) return true;
|
|
3085
|
+
if (/^[<{$%]/.test(s)) return true;
|
|
3086
|
+
if (/^(?:process\.env|env|os\.environ|c\.env|this\.env|import\.meta\.env|getenv|config|settings|secrets|args|opts|options|req|request|body|params|input|data|user|self|this)\b/i.test(
|
|
3087
|
+
s
|
|
3088
|
+
))
|
|
3089
|
+
return true;
|
|
3090
|
+
if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(s)) return true;
|
|
3091
|
+
if (/[(]/.test(s)) return true;
|
|
3092
|
+
if (/^(?:your|my|the|a|an|some|new|old)[-_ ]/i.test(s)) return true;
|
|
3093
|
+
return false;
|
|
3094
|
+
}
|
|
3095
|
+
function looksSecret(v) {
|
|
3096
|
+
const s = v.replace(/[.,;:!?)]+$/, "");
|
|
3097
|
+
if (s.length < 6) return false;
|
|
3098
|
+
if (isPlaceholder(s)) return false;
|
|
3099
|
+
if (/^https?:\/\//i.test(s)) return false;
|
|
3100
|
+
if (/^[a-z]+(?:[-'][a-z]+)*$/.test(s)) return false;
|
|
3101
|
+
if (/^[A-Z][a-z]+$/.test(s)) return false;
|
|
3102
|
+
if (/^\d+(?:[.,]\d+)*[a-z%]*$/i.test(s)) return false;
|
|
3103
|
+
if (/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/.test(s)) return false;
|
|
3104
|
+
const kinds = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((r) => r.test(s)).length;
|
|
3105
|
+
return kinds >= 3 || /[0-9]/.test(s) && /[A-Za-z]/.test(s);
|
|
3106
|
+
}
|
|
3107
|
+
function redactSecrets(text) {
|
|
3108
|
+
if (!text) return text;
|
|
3109
|
+
let out = text;
|
|
3110
|
+
for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
|
|
3111
|
+
out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
|
|
3112
|
+
out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
|
|
3113
|
+
out = redactAssignments(out);
|
|
3114
|
+
out = out.replace(
|
|
3115
|
+
PROSE,
|
|
3116
|
+
(m, head, q, v) => !looksSecret(v) || v.includes(REDACTED) ? m : `${head}${q}${REDACTED}${q}`
|
|
3117
|
+
);
|
|
3118
|
+
return out;
|
|
3119
|
+
}
|
|
3120
|
+
function hasSecret(text) {
|
|
3121
|
+
return !!text && redactSecrets(text) !== text;
|
|
3122
|
+
}
|
|
3123
|
+
function redactDeep(v) {
|
|
3124
|
+
if (typeof v === "string") return redactSecrets(v);
|
|
3125
|
+
if (Array.isArray(v)) return v.map(redactDeep);
|
|
3126
|
+
if (v && typeof v === "object") {
|
|
3127
|
+
const out = {};
|
|
3128
|
+
for (const [k, x] of Object.entries(v)) out[k] = redactDeep(x);
|
|
3129
|
+
return out;
|
|
3130
|
+
}
|
|
3131
|
+
return v;
|
|
3132
|
+
}
|
|
3133
|
+
|
|
2763
3134
|
// src/adapters/claude-code-control.ts
|
|
2764
3135
|
var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2765
3136
|
var LIVE_WRITE_MS = 9e4;
|
|
@@ -2952,6 +3323,23 @@ function loginHint(configDir, platform = process.platform) {
|
|
|
2952
3323
|
return `Claude Code (${shown}) isn't signed in as this machine's background service sees it. On a Mac its login lives in the login keychain, which a background service can read only while someone is logged in at the screen: log in at the Mac, or run ${login} there.`;
|
|
2953
3324
|
return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
|
|
2954
3325
|
}
|
|
3326
|
+
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`, "g");
|
|
3327
|
+
function helpFailure(err, home = os5.homedir()) {
|
|
3328
|
+
if (err?.code === "ENOENT") return "failed: no such file";
|
|
3329
|
+
if (err?.code === "EACCES") return "failed: the file can't be run (permission denied)";
|
|
3330
|
+
const plain2 = Array.from(String(err?.stderr ?? "").replace(ANSI, "")).filter((ch) => {
|
|
3331
|
+
const c = ch.charCodeAt(0);
|
|
3332
|
+
return c === 10 || c >= 32 && c !== 127;
|
|
3333
|
+
}).join("");
|
|
3334
|
+
const line = plain2.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).find((l) => l.length > 0);
|
|
3335
|
+
if (line) {
|
|
3336
|
+
const said = redactSecrets(reducePii(line, home));
|
|
3337
|
+
return `failed: ${said.length > 200 ? `${said.slice(0, 199)}\u2026` : said}`;
|
|
3338
|
+
}
|
|
3339
|
+
if (err?.signal) return `failed: killed by ${err.signal}`;
|
|
3340
|
+
if (typeof err?.status === "number") return `failed with exit code ${err.status}`;
|
|
3341
|
+
return "failed";
|
|
3342
|
+
}
|
|
2955
3343
|
function detectCaps(bin, timeoutMs = 3e4) {
|
|
2956
3344
|
const c = probe(bin, ["--help"]);
|
|
2957
3345
|
try {
|
|
@@ -2969,31 +3357,39 @@ function detectCaps(bin, timeoutMs = 3e4) {
|
|
|
2969
3357
|
const err = e;
|
|
2970
3358
|
const caps = parseCaps(String(err?.stdout || ""));
|
|
2971
3359
|
if (caps.hasMode) return caps;
|
|
2972
|
-
const why = err?.code === "ETIMEDOUT" ? `timed out after ${timeoutMs / 1e3} s` :
|
|
3360
|
+
const why = err?.code === "ETIMEDOUT" ? `timed out after ${timeoutMs / 1e3} s` : helpFailure(err);
|
|
2973
3361
|
return { ...caps, unread: `\`${path10.basename(bin)} --help\` ${why}` };
|
|
2974
3362
|
}
|
|
2975
3363
|
}
|
|
2976
|
-
|
|
3364
|
+
var DISK = { statSync: statSync5, readdirSync: readdirSync4 };
|
|
3365
|
+
function transcriptIn(session, dir, anySpelling, disk) {
|
|
2977
3366
|
let projects = [];
|
|
2978
3367
|
try {
|
|
2979
3368
|
projects = readdirSync4(path10.join(dir, "projects"), { withFileTypes: true });
|
|
2980
3369
|
} catch {
|
|
2981
3370
|
return null;
|
|
2982
3371
|
}
|
|
3372
|
+
const spellings = anySpelling ? [.../* @__PURE__ */ new Set([session, session.toLowerCase(), session.toUpperCase()])] : [session];
|
|
3373
|
+
const same = anySpelling ? (n, want) => n.toLowerCase() === want.toLowerCase() : Object.is;
|
|
2983
3374
|
for (const p of projects) {
|
|
2984
3375
|
if (!p.isDirectory()) continue;
|
|
2985
|
-
const
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
3376
|
+
const folder = path10.join(dir, "projects", p.name);
|
|
3377
|
+
for (const spelling of spellings) {
|
|
3378
|
+
const want = `${spelling}.jsonl`;
|
|
3379
|
+
try {
|
|
3380
|
+
if (!disk.statSync(path10.join(folder, want)).isFile()) continue;
|
|
3381
|
+
const name = disk.readdirSync(folder).find((n) => same(n, want));
|
|
3382
|
+
if (name) return { file: path10.join(folder, name), configDir: dir, project: p.name };
|
|
3383
|
+
} catch {
|
|
3384
|
+
}
|
|
2989
3385
|
}
|
|
2990
3386
|
}
|
|
2991
3387
|
return null;
|
|
2992
3388
|
}
|
|
2993
|
-
function findTranscript(session, dirs) {
|
|
3389
|
+
function findTranscript(session, dirs, o = {}) {
|
|
2994
3390
|
if (!UUID_RE2.test(session)) return null;
|
|
2995
3391
|
for (const dir of dirs) {
|
|
2996
|
-
const t = transcriptIn(session, dir);
|
|
3392
|
+
const t = transcriptIn(session, dir, !!o.anySpelling, o.disk ?? DISK);
|
|
2997
3393
|
if (t) return t;
|
|
2998
3394
|
}
|
|
2999
3395
|
return null;
|
|
@@ -3002,7 +3398,7 @@ function findTranscripts(session, dirs) {
|
|
|
3002
3398
|
if (!UUID_RE2.test(session)) return [];
|
|
3003
3399
|
const out = [];
|
|
3004
3400
|
for (const dir of dirs) {
|
|
3005
|
-
const t = transcriptIn(session, dir);
|
|
3401
|
+
const t = transcriptIn(session, dir, false, DISK);
|
|
3006
3402
|
if (t) out.push(t);
|
|
3007
3403
|
}
|
|
3008
3404
|
return out;
|
|
@@ -3190,16 +3586,16 @@ function relaunchMode(recorded, home) {
|
|
|
3190
3586
|
return recorded;
|
|
3191
3587
|
return null;
|
|
3192
3588
|
}
|
|
3193
|
-
function recentlyWritten(file,
|
|
3589
|
+
function recentlyWritten(file, knownEnd, now = Date.now()) {
|
|
3194
3590
|
let m;
|
|
3195
3591
|
try {
|
|
3196
3592
|
m = statSync5(file).mtimeMs;
|
|
3197
3593
|
} catch {
|
|
3198
3594
|
return false;
|
|
3199
3595
|
}
|
|
3200
|
-
|
|
3201
|
-
return !(ourLastEnd && m <= ourLastEnd + 2e3);
|
|
3596
|
+
return recentWrite(m, knownEnd, now);
|
|
3202
3597
|
}
|
|
3598
|
+
var recentWrite = (written, knownEnd, now) => now - written <= LIVE_WRITE_MS && !(knownEnd && written <= knownEnd + 2e3);
|
|
3203
3599
|
function runClaude(bin, args, o) {
|
|
3204
3600
|
return runHeadless(bin, args, { ...o, keep: o.keep ?? ((line) => line.includes('"type":"result"')) });
|
|
3205
3601
|
}
|
|
@@ -3228,6 +3624,15 @@ function parseResult(stdout) {
|
|
|
3228
3624
|
return null;
|
|
3229
3625
|
}
|
|
3230
3626
|
var isUuid = (s) => typeof s === "string" && UUID_RE2.test(s);
|
|
3627
|
+
function initSession(line) {
|
|
3628
|
+
if (!line.includes('"init"')) return null;
|
|
3629
|
+
try {
|
|
3630
|
+
const j = JSON.parse(line);
|
|
3631
|
+
return j.type === "system" && j.subtype === "init" && isUuid(j.session_id) ? j.session_id : null;
|
|
3632
|
+
} catch {
|
|
3633
|
+
return null;
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3231
3636
|
|
|
3232
3637
|
// src/adapters/codex.ts
|
|
3233
3638
|
import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
|
|
@@ -3917,7 +4322,7 @@ function codexStderr(stderr) {
|
|
|
3917
4322
|
var reading2 = () => new CodexReading();
|
|
3918
4323
|
|
|
3919
4324
|
// src/adapters/devin.ts
|
|
3920
|
-
import { createHash as
|
|
4325
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3921
4326
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
3922
4327
|
import path13 from "node:path";
|
|
3923
4328
|
|
|
@@ -4158,7 +4563,7 @@ var devin = {
|
|
|
4158
4563
|
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
4159
4564
|
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
4160
4565
|
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
4161
|
-
const permissionId = () => `perm-${
|
|
4566
|
+
const permissionId = () => `perm-${createHash4("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
4162
4567
|
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
4163
4568
|
const events = [];
|
|
4164
4569
|
switch (event) {
|
|
@@ -4979,201 +5384,6 @@ function processAlive(pid) {
|
|
|
4979
5384
|
}
|
|
4980
5385
|
}
|
|
4981
5386
|
|
|
4982
|
-
// src/privacy/pii.ts
|
|
4983
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
4984
|
-
var RULESET_PII = "pii@1";
|
|
4985
|
-
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
4986
|
-
var PHONE = /(?<![\w.])(?:(?<!\)[ .-]?|\+\d{1,3}[ .-]?)(\+?(?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01]))(?:(?= (?:[01]?\d|2[0-3]):)|(?!\w|[ .-]\d\d))|(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w))/g;
|
|
4987
|
-
function looksPhone(s) {
|
|
4988
|
-
const digits = s.replace(/\D/g, "");
|
|
4989
|
-
if (digits.length < 7 || digits.length > 15) return false;
|
|
4990
|
-
if (!/[+() .-]/.test(s)) return false;
|
|
4991
|
-
if (!/[+() -]/.test(s)) return false;
|
|
4992
|
-
return true;
|
|
4993
|
-
}
|
|
4994
|
-
function reducePii(text, home) {
|
|
4995
|
-
if (!text) return text;
|
|
4996
|
-
let out = text.replace(EMAIL, "[email]");
|
|
4997
|
-
out = out.replace(PHONE, (m, date) => !date && looksPhone(m) ? "[phone]" : m);
|
|
4998
|
-
if (home) {
|
|
4999
|
-
const h = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5000
|
-
out = out.replace(new RegExp(`(?<![A-Za-z0-9_])${h}(?=/|$|\\s|["'\`])`, "g"), "~");
|
|
5001
|
-
}
|
|
5002
|
-
return out;
|
|
5003
|
-
}
|
|
5004
|
-
function hashMachine(name) {
|
|
5005
|
-
return createHash4("sha256").update(name).digest("hex").slice(0, 8);
|
|
5006
|
-
}
|
|
5007
|
-
function reducePiiDeep(v, home) {
|
|
5008
|
-
if (typeof v === "string") return reducePii(v, home);
|
|
5009
|
-
if (Array.isArray(v)) return v.map((x) => reducePiiDeep(x, home));
|
|
5010
|
-
if (v && typeof v === "object") {
|
|
5011
|
-
const out = {};
|
|
5012
|
-
for (const [k, x] of Object.entries(v)) out[k] = reducePiiDeep(x, home);
|
|
5013
|
-
return out;
|
|
5014
|
-
}
|
|
5015
|
-
return v;
|
|
5016
|
-
}
|
|
5017
|
-
|
|
5018
|
-
// src/privacy/secrets.ts
|
|
5019
|
-
var RULESET_SECRETS = "secrets@1";
|
|
5020
|
-
var REDACTED = "[redacted]";
|
|
5021
|
-
var TOKEN_PATTERNS = [
|
|
5022
|
-
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/g,
|
|
5023
|
-
/\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b/g,
|
|
5024
|
-
// AWS access key id
|
|
5025
|
-
/\bgh[pousr]_[A-Za-z0-9]{30,}\b/g,
|
|
5026
|
-
// GitHub
|
|
5027
|
-
/\bgithub_pat_[A-Za-z0-9_]{30,}\b/g,
|
|
5028
|
-
/\bglpat-[A-Za-z0-9_-]{20,}\b/g,
|
|
5029
|
-
// GitLab
|
|
5030
|
-
/\bxox[abposre]-[A-Za-z0-9-]{10,}\b/g,
|
|
5031
|
-
// Slack
|
|
5032
|
-
/\bhttps:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/_-]+/g,
|
|
5033
|
-
/\bhttps:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+/g,
|
|
5034
|
-
/\bsk-(?:ant-|proj-|svcacct-|admin-|or-v1-)?[A-Za-z0-9_-]{20,}\b/g,
|
|
5035
|
-
// Anthropic, OpenAI, DeepSeek, OpenRouter
|
|
5036
|
-
/\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g,
|
|
5037
|
-
// Stripe secret / restricted
|
|
5038
|
-
/\bwhsec_[A-Za-z0-9+/=]{20,}/g,
|
|
5039
|
-
// Stripe / Svix / Standard Webhooks secret
|
|
5040
|
-
/\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
5041
|
-
// Google API key
|
|
5042
|
-
/\bya29\.[0-9A-Za-z_-]{20,}/g,
|
|
5043
|
-
// Google OAuth access token
|
|
5044
|
-
/\b1\/\/0[0-9A-Za-z_-]{30,}/g,
|
|
5045
|
-
// Google OAuth refresh token
|
|
5046
|
-
/\bGOCSPX-[0-9A-Za-z_-]{20,}/g,
|
|
5047
|
-
// Google OAuth client secret
|
|
5048
|
-
/\bxai-[A-Za-z0-9]{30,}\b/g,
|
|
5049
|
-
/\bgsk_[A-Za-z0-9]{30,}\b/g,
|
|
5050
|
-
// Groq
|
|
5051
|
-
/\bpplx-[A-Za-z0-9]{30,}\b/g,
|
|
5052
|
-
// Perplexity
|
|
5053
|
-
/\bhf_[A-Za-z0-9]{30,}\b/g,
|
|
5054
|
-
// Hugging Face
|
|
5055
|
-
/\bpcsk_[A-Za-z0-9_]{30,}\b/g,
|
|
5056
|
-
// Pinecone
|
|
5057
|
-
/\bnpm_[A-Za-z0-9]{30,}\b/g,
|
|
5058
|
-
/\bpypi-[A-Za-z0-9_-]{50,}/g,
|
|
5059
|
-
/\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g,
|
|
5060
|
-
// SendGrid
|
|
5061
|
-
/\bshp(?:at|ss|ca|pa)_[a-fA-F0-9]{32}\b/g,
|
|
5062
|
-
// Shopify
|
|
5063
|
-
/\bdop_v1_[a-f0-9]{64}\b/g,
|
|
5064
|
-
// DigitalOcean
|
|
5065
|
-
/\b(?:lin_api|lin_oauth)_[A-Za-z0-9]{30,}\b/g,
|
|
5066
|
-
// Linear
|
|
5067
|
-
/\b(?:ntn|secret)_[A-Za-z0-9]{40,}\b/g,
|
|
5068
|
-
// Notion
|
|
5069
|
-
/\bsbp_[a-f0-9]{40}\b/g,
|
|
5070
|
-
// Supabase
|
|
5071
|
-
/\bfigd_[A-Za-z0-9_-]{30,}/g,
|
|
5072
|
-
// Figma
|
|
5073
|
-
/\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9]{30,}/g,
|
|
5074
|
-
// Doppler
|
|
5075
|
-
/\bgrn_[A-Za-z0-9_-]{20,}/g,
|
|
5076
|
-
// Granola
|
|
5077
|
-
/\bss_[a-f0-9]{32,}\b/g,
|
|
5078
|
-
// spacesheep API keys
|
|
5079
|
-
/\b[0-9]{8,10}:AA[A-Za-z0-9_-]{33}\b/g,
|
|
5080
|
-
// Telegram bot token
|
|
5081
|
-
/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
|
|
5082
|
-
// JWT
|
|
5083
|
-
];
|
|
5084
|
-
var URL_CREDENTIALS = /\b([a-z][a-z0-9+.-]{1,20}:\/\/[^\s:/@'"`<>]{1,100}:)([^\s@/'"`<>]{1,200})(@)/gi;
|
|
5085
|
-
var AUTH_HEADER = /\b((?:Bearer|Basic|Token)\s+)([A-Za-z0-9._~+/=-]{16,})/g;
|
|
5086
|
-
var SECRET_WORD = "(?:pass(?:word|wd|phrase|code)?s?|pwd|secret(?:[ _-]?key)?s?|client[ _-]?secret|api[ _-]?keys?|apikeys?|access[ _-]?keys?|access[ _-]?tokens?|auth[ _-]?tokens?|refresh[ _-]?tokens?|private[ _-]?keys?|tokens?|credentials?|creds)";
|
|
5087
|
-
var NAME_SEP = /(?<![A-Za-z0-9_])([A-Za-z][A-Za-z0-9_-]{1,79})(["']?\s{0,3}(?::=|=>|=|:)\s{0,3})/g;
|
|
5088
|
-
var VALUE = /"([^"\n]{1,300})"|'([^'\n]{1,300})'|`([^`\n]{1,300})`|([^\s"'`,;&)}\]]{1,300})/y;
|
|
5089
|
-
function redactAssignments(text) {
|
|
5090
|
-
let out = "";
|
|
5091
|
-
let last = 0;
|
|
5092
|
-
NAME_SEP.lastIndex = 0;
|
|
5093
|
-
for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
|
|
5094
|
-
const name = m[1];
|
|
5095
|
-
if (!isSecretName(name)) continue;
|
|
5096
|
-
VALUE.lastIndex = NAME_SEP.lastIndex;
|
|
5097
|
-
const v = VALUE.exec(text);
|
|
5098
|
-
if (!v) continue;
|
|
5099
|
-
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
5100
|
-
NAME_SEP.lastIndex = VALUE.lastIndex;
|
|
5101
|
-
if (isPlaceholder(value) || value.includes(REDACTED)) continue;
|
|
5102
|
-
if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
|
|
5103
|
-
const q = v[1] != null ? '"' : v[2] != null ? "'" : v[3] != null ? "`" : "";
|
|
5104
|
-
out += text.slice(last, v.index) + q + REDACTED + q;
|
|
5105
|
-
last = VALUE.lastIndex;
|
|
5106
|
-
}
|
|
5107
|
-
return last ? out + text.slice(last) : text;
|
|
5108
|
-
}
|
|
5109
|
-
function isSecretName(name) {
|
|
5110
|
-
if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
|
|
5111
|
-
const flat = name.toLowerCase().replace(/[_-]/g, "");
|
|
5112
|
-
return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
|
|
5113
|
-
flat
|
|
5114
|
-
);
|
|
5115
|
-
}
|
|
5116
|
-
var PROSE = new RegExp(
|
|
5117
|
-
`(\\b${SECRET_WORD}(?:[ \\t]{1,3}(?:is|was|=|:|to|as|of|for[ \\t]{1,3}\\S{1,40}[ \\t]{1,3}is))?[ \\t]{1,3})(["'\`]?)([^\\s"'\`]{6,200})\\2`,
|
|
5118
|
-
"gi"
|
|
5119
|
-
);
|
|
5120
|
-
function isPlaceholder(v) {
|
|
5121
|
-
const s = v.replace(/[.,;:!?]+$/, "");
|
|
5122
|
-
if (!s) return true;
|
|
5123
|
-
if (/^(?:string|number|boolean|bool|str|int|any|unknown|null|undefined|none|nil|true|false|optional|required|text|varchar|password|secret|token|env|string\[\]|object|bytes)$/i.test(
|
|
5124
|
-
s
|
|
5125
|
-
))
|
|
5126
|
-
return true;
|
|
5127
|
-
if (/^\[?redacted\]?$/i.test(s) || /^\*+$/.test(s) || /^x{3,}$/i.test(s) || /^\.{3}$|^…$/.test(s)) return true;
|
|
5128
|
-
if (/^[<{$%]/.test(s)) return true;
|
|
5129
|
-
if (/^(?:process\.env|env|os\.environ|c\.env|this\.env|import\.meta\.env|getenv|config|settings|secrets|args|opts|options|req|request|body|params|input|data|user|self|this)\b/i.test(
|
|
5130
|
-
s
|
|
5131
|
-
))
|
|
5132
|
-
return true;
|
|
5133
|
-
if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(s)) return true;
|
|
5134
|
-
if (/[(]/.test(s)) return true;
|
|
5135
|
-
if (/^(?:your|my|the|a|an|some|new|old)[-_ ]/i.test(s)) return true;
|
|
5136
|
-
return false;
|
|
5137
|
-
}
|
|
5138
|
-
function looksSecret(v) {
|
|
5139
|
-
const s = v.replace(/[.,;:!?)]+$/, "");
|
|
5140
|
-
if (s.length < 6) return false;
|
|
5141
|
-
if (isPlaceholder(s)) return false;
|
|
5142
|
-
if (/^https?:\/\//i.test(s)) return false;
|
|
5143
|
-
if (/^[a-z]+(?:[-'][a-z]+)*$/.test(s)) return false;
|
|
5144
|
-
if (/^[A-Z][a-z]+$/.test(s)) return false;
|
|
5145
|
-
if (/^\d+(?:[.,]\d+)*[a-z%]*$/i.test(s)) return false;
|
|
5146
|
-
if (/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/.test(s)) return false;
|
|
5147
|
-
const kinds = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((r) => r.test(s)).length;
|
|
5148
|
-
return kinds >= 3 || /[0-9]/.test(s) && /[A-Za-z]/.test(s);
|
|
5149
|
-
}
|
|
5150
|
-
function redactSecrets(text) {
|
|
5151
|
-
if (!text) return text;
|
|
5152
|
-
let out = text;
|
|
5153
|
-
for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
|
|
5154
|
-
out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
|
|
5155
|
-
out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
|
|
5156
|
-
out = redactAssignments(out);
|
|
5157
|
-
out = out.replace(
|
|
5158
|
-
PROSE,
|
|
5159
|
-
(m, head, q, v) => !looksSecret(v) || v.includes(REDACTED) ? m : `${head}${q}${REDACTED}${q}`
|
|
5160
|
-
);
|
|
5161
|
-
return out;
|
|
5162
|
-
}
|
|
5163
|
-
function hasSecret(text) {
|
|
5164
|
-
return !!text && redactSecrets(text) !== text;
|
|
5165
|
-
}
|
|
5166
|
-
function redactDeep(v) {
|
|
5167
|
-
if (typeof v === "string") return redactSecrets(v);
|
|
5168
|
-
if (Array.isArray(v)) return v.map(redactDeep);
|
|
5169
|
-
if (v && typeof v === "object") {
|
|
5170
|
-
const out = {};
|
|
5171
|
-
for (const [k, x] of Object.entries(v)) out[k] = redactDeep(x);
|
|
5172
|
-
return out;
|
|
5173
|
-
}
|
|
5174
|
-
return v;
|
|
5175
|
-
}
|
|
5176
|
-
|
|
5177
5387
|
// src/privacy/filter.ts
|
|
5178
5388
|
var TYPE_TIER = {
|
|
5179
5389
|
"session.started": 0,
|