sessionpipe-core 0.13.3 → 0.13.4
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 +424 -227
- 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 +30 -3
- 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 +22 -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.4 · 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,68 @@ 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 HOOK_FEEDBACK = /^[A-Za-z]+ hook feedback:\n/;
|
|
391
|
+
var hookDelivery = (text) => {
|
|
392
|
+
const head = HOOK_FEEDBACK.exec(text);
|
|
393
|
+
if (!head) return "";
|
|
394
|
+
const said = deliveredWords(text.slice(head[0].length));
|
|
395
|
+
return said.delivered ? said.text : "";
|
|
396
|
+
};
|
|
397
|
+
var hasBlock = (c, type) => Array.isArray(c) && c.some((b) => b && typeof b === "object" && b.type === type);
|
|
289
398
|
function readClaude(lines, from) {
|
|
290
399
|
const msgs = [];
|
|
400
|
+
let saidLine = -1;
|
|
401
|
+
let turnOver = false;
|
|
291
402
|
for (let i = from; i < lines.length; i++) {
|
|
292
403
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
293
404
|
if (!j || j.isSidechain) continue;
|
|
294
405
|
const origin = j.origin;
|
|
295
406
|
const channel = j.type === "user" && origin?.kind === "channel";
|
|
296
407
|
const peer = origin?.kind === "peer";
|
|
408
|
+
const c = j.message?.content;
|
|
409
|
+
if (!turnOver && (j.type === "assistant" && hasBlock(c, "tool_use") || j.type === "user" && hasBlock(c, "tool_result")))
|
|
410
|
+
saidLine = -1;
|
|
297
411
|
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
412
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
300
|
-
const
|
|
413
|
+
const id = typeof j.promptId === "string" && j.promptId ? j.promptId : void 0;
|
|
414
|
+
const ask = (said, m = {}) => {
|
|
415
|
+
const text = deliveredWords(said).text;
|
|
416
|
+
if (!text || isInjected(text)) return;
|
|
417
|
+
msgs.push({ role: "user", text, at, line: i, ...m });
|
|
418
|
+
turnOver = true;
|
|
419
|
+
};
|
|
420
|
+
if (j.type === "user" && !channel && !peer && (j.isMeta || origin?.kind && origin.kind !== "human")) {
|
|
421
|
+
if (j.isMeta && !origin?.kind)
|
|
422
|
+
ask(hookDelivery(textParts(c, ["text"]).trim()), { injected: true, ...id ? { id } : {} });
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
301
425
|
if (channel) {
|
|
302
|
-
|
|
303
|
-
if (text) msgs.push({ role: "user", text, at, line: i, injected: true });
|
|
426
|
+
ask(channelText(textParts(c, ["text"]).trim()), { injected: true });
|
|
304
427
|
} else if (j.type === "user") {
|
|
305
428
|
const raw = textParts(c, ["text"]).trim();
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
429
|
+
if (!peer && INTERRUPTED.test(raw)) {
|
|
430
|
+
msgs.push({ role: "interrupt", text: "", at, line: i });
|
|
431
|
+
turnOver = true;
|
|
432
|
+
} else if (!peer) ask(raw, id ? { id } : {});
|
|
433
|
+
else if (!fromAgent(origin)) ask(peerAsk(origin, raw), { injected: true, ...id ? { id } : {} });
|
|
309
434
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
310
435
|
const text = textParts(c, ["text"]).trim();
|
|
311
|
-
if (text)
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
436
|
+
if (text) {
|
|
437
|
+
msgs.push({ role: "assistant", text, at, line: i });
|
|
438
|
+
saidLine = i;
|
|
439
|
+
turnOver = false;
|
|
440
|
+
}
|
|
441
|
+
} else if (j.type === "attachment") ask(queuedPrompt(j.attachment));
|
|
316
442
|
}
|
|
317
|
-
|
|
443
|
+
const out = pairTurns(msgs, endOf(lines), true);
|
|
444
|
+
if (out.tail && saidLine !== out.turns[out.turns.length - 1].endLine) delete out.tail.said;
|
|
445
|
+
return out;
|
|
318
446
|
}
|
|
319
447
|
function codexInjected(text) {
|
|
320
448
|
return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
|
|
@@ -766,6 +894,8 @@ var claudeCode = {
|
|
|
766
894
|
}
|
|
767
895
|
const hints = {};
|
|
768
896
|
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
897
|
+
if (event === "Stop" && typeof s.last_assistant_message === "string" && s.last_assistant_message.trim())
|
|
898
|
+
hints.final = s.last_assistant_message;
|
|
769
899
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
770
900
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
771
901
|
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
@@ -1058,7 +1188,9 @@ function askText(j) {
|
|
|
1058
1188
|
const origin = j.origin;
|
|
1059
1189
|
if (origin?.kind && origin.kind !== "human") return null;
|
|
1060
1190
|
const c = j.message?.content;
|
|
1061
|
-
const text = (
|
|
1191
|
+
const text = deliveredWords(
|
|
1192
|
+
(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()
|
|
1193
|
+
).text;
|
|
1062
1194
|
if (!text || isInjected(text)) return null;
|
|
1063
1195
|
return text;
|
|
1064
1196
|
}
|
|
@@ -1703,6 +1835,7 @@ var acp_control_exports = {};
|
|
|
1703
1835
|
__export(acp_control_exports, {
|
|
1704
1836
|
ACP_AGENTS: () => ACP_AGENTS,
|
|
1705
1837
|
ACP_HARNESSES: () => ACP_HARNESSES,
|
|
1838
|
+
CANCELLED_NOTE: () => CANCELLED_NOTE,
|
|
1706
1839
|
acpAgent: () => acpAgent,
|
|
1707
1840
|
acpHarnesses: () => acpHarnesses,
|
|
1708
1841
|
acpOverride: () => acpOverride,
|
|
@@ -1810,8 +1943,13 @@ function runHeadless(bin, args, o) {
|
|
|
1810
1943
|
let tooBig = false;
|
|
1811
1944
|
let errTail = "";
|
|
1812
1945
|
let timedOut = false;
|
|
1946
|
+
let stopped = false;
|
|
1813
1947
|
let settled = false;
|
|
1814
1948
|
let child;
|
|
1949
|
+
if (o.signal?.aborted) {
|
|
1950
|
+
resolve({ stopped: true });
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1815
1953
|
const c = spawnable(bin, args);
|
|
1816
1954
|
if (!c) {
|
|
1817
1955
|
resolve({ error: shimNote(bin) });
|
|
@@ -1874,27 +2012,53 @@ function runHeadless(bin, args, o) {
|
|
|
1874
2012
|
child.stderr?.on("data", (b) => {
|
|
1875
2013
|
errTail = (errTail + b.toString("utf8")).slice(-4e3);
|
|
1876
2014
|
});
|
|
2015
|
+
let ending = null;
|
|
2016
|
+
let gone = false;
|
|
2017
|
+
const end = () => {
|
|
2018
|
+
kill("SIGTERM");
|
|
2019
|
+
ending ??= setTimeout(() => {
|
|
2020
|
+
ending = null;
|
|
2021
|
+
kill("SIGKILL");
|
|
2022
|
+
}, 1e4);
|
|
2023
|
+
ending.unref();
|
|
2024
|
+
};
|
|
1877
2025
|
const timer = setTimeout(() => {
|
|
2026
|
+
if (gone) return;
|
|
1878
2027
|
timedOut = true;
|
|
1879
|
-
|
|
1880
|
-
setTimeout(() => kill("SIGKILL"), 1e4).unref();
|
|
2028
|
+
end();
|
|
1881
2029
|
}, o.timeoutMs ?? JOB_TIMEOUT_MS);
|
|
2030
|
+
const stop = () => {
|
|
2031
|
+
if (gone) return;
|
|
2032
|
+
stopped = true;
|
|
2033
|
+
end();
|
|
2034
|
+
};
|
|
2035
|
+
o.signal?.addEventListener("abort", stop, { once: true });
|
|
1882
2036
|
const result = (code, signal) => ({
|
|
1883
2037
|
code,
|
|
1884
2038
|
signal,
|
|
1885
2039
|
stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
|
|
1886
2040
|
tooBig,
|
|
1887
2041
|
stderr: errTail,
|
|
1888
|
-
timedOut
|
|
2042
|
+
timedOut,
|
|
2043
|
+
...stopped ? { stopped } : {}
|
|
1889
2044
|
});
|
|
1890
2045
|
const finish = (r) => {
|
|
1891
2046
|
if (settled) return;
|
|
1892
2047
|
settled = true;
|
|
1893
2048
|
clearTimeout(timer);
|
|
2049
|
+
o.signal?.removeEventListener("abort", stop);
|
|
1894
2050
|
resolve(r);
|
|
1895
2051
|
};
|
|
1896
2052
|
child.on("error", (e) => finish({ error: e.message }));
|
|
1897
|
-
child.on("exit", (code, signal) =>
|
|
2053
|
+
child.on("exit", (code, signal) => {
|
|
2054
|
+
if (ending) {
|
|
2055
|
+
clearTimeout(ending);
|
|
2056
|
+
ending = null;
|
|
2057
|
+
kill("SIGKILL");
|
|
2058
|
+
}
|
|
2059
|
+
gone = true;
|
|
2060
|
+
setTimeout(() => finish(result(code, signal)), 2e3).unref();
|
|
2061
|
+
});
|
|
1898
2062
|
child.on("close", (code, signal) => {
|
|
1899
2063
|
if (o.onLine && partial.trim()) {
|
|
1900
2064
|
try {
|
|
@@ -1911,6 +2075,7 @@ function runHeadless(bin, args, o) {
|
|
|
1911
2075
|
function stderrTail(stderr, n = 3) {
|
|
1912
2076
|
return String(stderr || "").trim().split("\n").slice(-n).join(" ").trim();
|
|
1913
2077
|
}
|
|
2078
|
+
var CANCELLED_NOTE = "(The turn was cancelled.)";
|
|
1914
2079
|
function deniedNote(names, count) {
|
|
1915
2080
|
if (!count) return "";
|
|
1916
2081
|
return `
|
|
@@ -2152,7 +2317,9 @@ function startAcp(o) {
|
|
|
2152
2317
|
pending.clear();
|
|
2153
2318
|
};
|
|
2154
2319
|
child.on("error", (e) => left(`couldn't start ${label}: ${e.message}`));
|
|
2320
|
+
let dead = false;
|
|
2155
2321
|
const exited = (code, signal) => {
|
|
2322
|
+
dead = true;
|
|
2156
2323
|
const tail = errTail.trim().split("\n").slice(-3).join(" ").trim();
|
|
2157
2324
|
left(`${label} exited (${signal || `code ${code}`})${tail ? `: ${tail}` : ""}`);
|
|
2158
2325
|
};
|
|
@@ -2166,6 +2333,7 @@ function startAcp(o) {
|
|
|
2166
2333
|
child.stdin?.end();
|
|
2167
2334
|
} catch {
|
|
2168
2335
|
}
|
|
2336
|
+
if (dead) return;
|
|
2169
2337
|
const grace = o.closeGraceMs ?? 2e3;
|
|
2170
2338
|
const t = setTimeout(() => kill("SIGTERM"), grace);
|
|
2171
2339
|
t.unref();
|
|
@@ -2223,7 +2391,7 @@ function startAcp(o) {
|
|
|
2223
2391
|
error: `${label} was still working after ${Math.round(limit / 6e4)} minutes, so it was stopped`
|
|
2224
2392
|
} : { reply: `${words}
|
|
2225
2393
|
|
|
2226
|
-
|
|
2394
|
+
${CANCELLED_NOTE}${refused2}` };
|
|
2227
2395
|
return { reply: "", error: `${label} ended the turn with ${stop || "no stop reason"}` };
|
|
2228
2396
|
} catch (e) {
|
|
2229
2397
|
return { reply: "", error: e.message };
|
|
@@ -2711,8 +2879,10 @@ __export(claude_code_control_exports, {
|
|
|
2711
2879
|
findClaude: () => findClaude,
|
|
2712
2880
|
findTranscript: () => findTranscript,
|
|
2713
2881
|
findTranscripts: () => findTranscripts,
|
|
2882
|
+
helpFailure: () => helpFailure,
|
|
2714
2883
|
homeSettings: () => homeSettings,
|
|
2715
2884
|
hookMode: () => hookMode,
|
|
2885
|
+
initSession: () => initSession,
|
|
2716
2886
|
isUuid: () => isUuid,
|
|
2717
2887
|
liveProcess: () => liveProcess,
|
|
2718
2888
|
liveRecord: () => liveRecord,
|
|
@@ -2727,6 +2897,7 @@ __export(claude_code_control_exports, {
|
|
|
2727
2897
|
parseCaps: () => parseCaps,
|
|
2728
2898
|
parseResult: () => parseResult,
|
|
2729
2899
|
peerClass: () => peerClass,
|
|
2900
|
+
recentWrite: () => recentWrite,
|
|
2730
2901
|
recentlyWritten: () => recentlyWritten,
|
|
2731
2902
|
relaunchMode: () => relaunchMode,
|
|
2732
2903
|
resolveHome: () => resolveHome,
|
|
@@ -2760,6 +2931,201 @@ var POOL_FIELDS = FIELDS.pool;
|
|
|
2760
2931
|
var enc = new TextEncoder();
|
|
2761
2932
|
var dec = new TextDecoder();
|
|
2762
2933
|
|
|
2934
|
+
// src/privacy/pii.ts
|
|
2935
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2936
|
+
var RULESET_PII = "pii@1";
|
|
2937
|
+
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2938
|
+
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;
|
|
2939
|
+
function looksPhone(s) {
|
|
2940
|
+
const digits = s.replace(/\D/g, "");
|
|
2941
|
+
if (digits.length < 7 || digits.length > 15) return false;
|
|
2942
|
+
if (!/[+() .-]/.test(s)) return false;
|
|
2943
|
+
if (!/[+() -]/.test(s)) return false;
|
|
2944
|
+
return true;
|
|
2945
|
+
}
|
|
2946
|
+
function reducePii(text, home) {
|
|
2947
|
+
if (!text) return text;
|
|
2948
|
+
let out = text.replace(EMAIL, "[email]");
|
|
2949
|
+
out = out.replace(PHONE, (m, date) => !date && looksPhone(m) ? "[phone]" : m);
|
|
2950
|
+
if (home) {
|
|
2951
|
+
const h = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2952
|
+
out = out.replace(new RegExp(`(?<![A-Za-z0-9_])${h}(?=/|$|\\s|["'\`])`, "g"), "~");
|
|
2953
|
+
}
|
|
2954
|
+
return out;
|
|
2955
|
+
}
|
|
2956
|
+
function hashMachine(name) {
|
|
2957
|
+
return createHash3("sha256").update(name).digest("hex").slice(0, 8);
|
|
2958
|
+
}
|
|
2959
|
+
function reducePiiDeep(v, home) {
|
|
2960
|
+
if (typeof v === "string") return reducePii(v, home);
|
|
2961
|
+
if (Array.isArray(v)) return v.map((x) => reducePiiDeep(x, home));
|
|
2962
|
+
if (v && typeof v === "object") {
|
|
2963
|
+
const out = {};
|
|
2964
|
+
for (const [k, x] of Object.entries(v)) out[k] = reducePiiDeep(x, home);
|
|
2965
|
+
return out;
|
|
2966
|
+
}
|
|
2967
|
+
return v;
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
// src/privacy/secrets.ts
|
|
2971
|
+
var RULESET_SECRETS = "secrets@1";
|
|
2972
|
+
var REDACTED = "[redacted]";
|
|
2973
|
+
var TOKEN_PATTERNS = [
|
|
2974
|
+
/-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/g,
|
|
2975
|
+
/\b(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16}\b/g,
|
|
2976
|
+
// AWS access key id
|
|
2977
|
+
/\bgh[pousr]_[A-Za-z0-9]{30,}\b/g,
|
|
2978
|
+
// GitHub
|
|
2979
|
+
/\bgithub_pat_[A-Za-z0-9_]{30,}\b/g,
|
|
2980
|
+
/\bglpat-[A-Za-z0-9_-]{20,}\b/g,
|
|
2981
|
+
// GitLab
|
|
2982
|
+
/\bxox[abposre]-[A-Za-z0-9-]{10,}\b/g,
|
|
2983
|
+
// Slack
|
|
2984
|
+
/\bhttps:\/\/hooks\.slack\.com\/services\/[A-Za-z0-9/_-]+/g,
|
|
2985
|
+
/\bhttps:\/\/(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_-]+/g,
|
|
2986
|
+
/\bsk-(?:ant-|proj-|svcacct-|admin-|or-v1-)?[A-Za-z0-9_-]{20,}\b/g,
|
|
2987
|
+
// Anthropic, OpenAI, DeepSeek, OpenRouter
|
|
2988
|
+
/\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}\b/g,
|
|
2989
|
+
// Stripe secret / restricted
|
|
2990
|
+
/\bwhsec_[A-Za-z0-9+/=]{20,}/g,
|
|
2991
|
+
// Stripe / Svix / Standard Webhooks secret
|
|
2992
|
+
/\bAIza[0-9A-Za-z_-]{35}\b/g,
|
|
2993
|
+
// Google API key
|
|
2994
|
+
/\bya29\.[0-9A-Za-z_-]{20,}/g,
|
|
2995
|
+
// Google OAuth access token
|
|
2996
|
+
/\b1\/\/0[0-9A-Za-z_-]{30,}/g,
|
|
2997
|
+
// Google OAuth refresh token
|
|
2998
|
+
/\bGOCSPX-[0-9A-Za-z_-]{20,}/g,
|
|
2999
|
+
// Google OAuth client secret
|
|
3000
|
+
/\bxai-[A-Za-z0-9]{30,}\b/g,
|
|
3001
|
+
/\bgsk_[A-Za-z0-9]{30,}\b/g,
|
|
3002
|
+
// Groq
|
|
3003
|
+
/\bpplx-[A-Za-z0-9]{30,}\b/g,
|
|
3004
|
+
// Perplexity
|
|
3005
|
+
/\bhf_[A-Za-z0-9]{30,}\b/g,
|
|
3006
|
+
// Hugging Face
|
|
3007
|
+
/\bpcsk_[A-Za-z0-9_]{30,}\b/g,
|
|
3008
|
+
// Pinecone
|
|
3009
|
+
/\bnpm_[A-Za-z0-9]{30,}\b/g,
|
|
3010
|
+
/\bpypi-[A-Za-z0-9_-]{50,}/g,
|
|
3011
|
+
/\bSG\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g,
|
|
3012
|
+
// SendGrid
|
|
3013
|
+
/\bshp(?:at|ss|ca|pa)_[a-fA-F0-9]{32}\b/g,
|
|
3014
|
+
// Shopify
|
|
3015
|
+
/\bdop_v1_[a-f0-9]{64}\b/g,
|
|
3016
|
+
// DigitalOcean
|
|
3017
|
+
/\b(?:lin_api|lin_oauth)_[A-Za-z0-9]{30,}\b/g,
|
|
3018
|
+
// Linear
|
|
3019
|
+
/\b(?:ntn|secret)_[A-Za-z0-9]{40,}\b/g,
|
|
3020
|
+
// Notion
|
|
3021
|
+
/\bsbp_[a-f0-9]{40}\b/g,
|
|
3022
|
+
// Supabase
|
|
3023
|
+
/\bfigd_[A-Za-z0-9_-]{30,}/g,
|
|
3024
|
+
// Figma
|
|
3025
|
+
/\bdp\.(?:pt|st|sa|ct)\.[A-Za-z0-9]{30,}/g,
|
|
3026
|
+
// Doppler
|
|
3027
|
+
/\bgrn_[A-Za-z0-9_-]{20,}/g,
|
|
3028
|
+
// Granola
|
|
3029
|
+
/\bss_[a-f0-9]{32,}\b/g,
|
|
3030
|
+
// spacesheep API keys
|
|
3031
|
+
/\b[0-9]{8,10}:AA[A-Za-z0-9_-]{33}\b/g,
|
|
3032
|
+
// Telegram bot token
|
|
3033
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
|
|
3034
|
+
// JWT
|
|
3035
|
+
];
|
|
3036
|
+
var URL_CREDENTIALS = /\b([a-z][a-z0-9+.-]{1,20}:\/\/[^\s:/@'"`<>]{1,100}:)([^\s@/'"`<>]{1,200})(@)/gi;
|
|
3037
|
+
var AUTH_HEADER = /\b((?:Bearer|Basic|Token)\s+)([A-Za-z0-9._~+/=-]{16,})/g;
|
|
3038
|
+
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)";
|
|
3039
|
+
var NAME_SEP = /(?<![A-Za-z0-9_])([A-Za-z][A-Za-z0-9_-]{1,79})(["']?\s{0,3}(?::=|=>|=|:)\s{0,3})/g;
|
|
3040
|
+
var VALUE = /"([^"\n]{1,300})"|'([^'\n]{1,300})'|`([^`\n]{1,300})`|([^\s"'`,;&)}\]]{1,300})/y;
|
|
3041
|
+
function redactAssignments(text) {
|
|
3042
|
+
let out = "";
|
|
3043
|
+
let last = 0;
|
|
3044
|
+
NAME_SEP.lastIndex = 0;
|
|
3045
|
+
for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
|
|
3046
|
+
const name = m[1];
|
|
3047
|
+
if (!isSecretName(name)) continue;
|
|
3048
|
+
VALUE.lastIndex = NAME_SEP.lastIndex;
|
|
3049
|
+
const v = VALUE.exec(text);
|
|
3050
|
+
if (!v) continue;
|
|
3051
|
+
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
3052
|
+
NAME_SEP.lastIndex = VALUE.lastIndex;
|
|
3053
|
+
if (isPlaceholder(value) || value.includes(REDACTED)) continue;
|
|
3054
|
+
if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
|
|
3055
|
+
const q = v[1] != null ? '"' : v[2] != null ? "'" : v[3] != null ? "`" : "";
|
|
3056
|
+
out += text.slice(last, v.index) + q + REDACTED + q;
|
|
3057
|
+
last = VALUE.lastIndex;
|
|
3058
|
+
}
|
|
3059
|
+
return last ? out + text.slice(last) : text;
|
|
3060
|
+
}
|
|
3061
|
+
function isSecretName(name) {
|
|
3062
|
+
if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
|
|
3063
|
+
const flat = name.toLowerCase().replace(/[_-]/g, "");
|
|
3064
|
+
return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
|
|
3065
|
+
flat
|
|
3066
|
+
);
|
|
3067
|
+
}
|
|
3068
|
+
var PROSE = new RegExp(
|
|
3069
|
+
`(\\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`,
|
|
3070
|
+
"gi"
|
|
3071
|
+
);
|
|
3072
|
+
function isPlaceholder(v) {
|
|
3073
|
+
const s = v.replace(/[.,;:!?]+$/, "");
|
|
3074
|
+
if (!s) return true;
|
|
3075
|
+
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(
|
|
3076
|
+
s
|
|
3077
|
+
))
|
|
3078
|
+
return true;
|
|
3079
|
+
if (/^\[?redacted\]?$/i.test(s) || /^\*+$/.test(s) || /^x{3,}$/i.test(s) || /^\.{3}$|^…$/.test(s)) return true;
|
|
3080
|
+
if (/^[<{$%]/.test(s)) return true;
|
|
3081
|
+
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(
|
|
3082
|
+
s
|
|
3083
|
+
))
|
|
3084
|
+
return true;
|
|
3085
|
+
if (/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(s)) return true;
|
|
3086
|
+
if (/[(]/.test(s)) return true;
|
|
3087
|
+
if (/^(?:your|my|the|a|an|some|new|old)[-_ ]/i.test(s)) return true;
|
|
3088
|
+
return false;
|
|
3089
|
+
}
|
|
3090
|
+
function looksSecret(v) {
|
|
3091
|
+
const s = v.replace(/[.,;:!?)]+$/, "");
|
|
3092
|
+
if (s.length < 6) return false;
|
|
3093
|
+
if (isPlaceholder(s)) return false;
|
|
3094
|
+
if (/^https?:\/\//i.test(s)) return false;
|
|
3095
|
+
if (/^[a-z]+(?:[-'][a-z]+)*$/.test(s)) return false;
|
|
3096
|
+
if (/^[A-Z][a-z]+$/.test(s)) return false;
|
|
3097
|
+
if (/^\d+(?:[.,]\d+)*[a-z%]*$/i.test(s)) return false;
|
|
3098
|
+
if (/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/.test(s)) return false;
|
|
3099
|
+
const kinds = [/[a-z]/, /[A-Z]/, /[0-9]/, /[^A-Za-z0-9]/].filter((r) => r.test(s)).length;
|
|
3100
|
+
return kinds >= 3 || /[0-9]/.test(s) && /[A-Za-z]/.test(s);
|
|
3101
|
+
}
|
|
3102
|
+
function redactSecrets(text) {
|
|
3103
|
+
if (!text) return text;
|
|
3104
|
+
let out = text;
|
|
3105
|
+
for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
|
|
3106
|
+
out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
|
|
3107
|
+
out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
|
|
3108
|
+
out = redactAssignments(out);
|
|
3109
|
+
out = out.replace(
|
|
3110
|
+
PROSE,
|
|
3111
|
+
(m, head, q, v) => !looksSecret(v) || v.includes(REDACTED) ? m : `${head}${q}${REDACTED}${q}`
|
|
3112
|
+
);
|
|
3113
|
+
return out;
|
|
3114
|
+
}
|
|
3115
|
+
function hasSecret(text) {
|
|
3116
|
+
return !!text && redactSecrets(text) !== text;
|
|
3117
|
+
}
|
|
3118
|
+
function redactDeep(v) {
|
|
3119
|
+
if (typeof v === "string") return redactSecrets(v);
|
|
3120
|
+
if (Array.isArray(v)) return v.map(redactDeep);
|
|
3121
|
+
if (v && typeof v === "object") {
|
|
3122
|
+
const out = {};
|
|
3123
|
+
for (const [k, x] of Object.entries(v)) out[k] = redactDeep(x);
|
|
3124
|
+
return out;
|
|
3125
|
+
}
|
|
3126
|
+
return v;
|
|
3127
|
+
}
|
|
3128
|
+
|
|
2763
3129
|
// src/adapters/claude-code-control.ts
|
|
2764
3130
|
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
3131
|
var LIVE_WRITE_MS = 9e4;
|
|
@@ -2952,6 +3318,23 @@ function loginHint(configDir, platform = process.platform) {
|
|
|
2952
3318
|
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
3319
|
return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
|
|
2954
3320
|
}
|
|
3321
|
+
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;?]*[ -/]*[@-~]`, "g");
|
|
3322
|
+
function helpFailure(err, home = os5.homedir()) {
|
|
3323
|
+
if (err?.code === "ENOENT") return "failed: no such file";
|
|
3324
|
+
if (err?.code === "EACCES") return "failed: the file can't be run (permission denied)";
|
|
3325
|
+
const plain2 = Array.from(String(err?.stderr ?? "").replace(ANSI, "")).filter((ch) => {
|
|
3326
|
+
const c = ch.charCodeAt(0);
|
|
3327
|
+
return c === 10 || c >= 32 && c !== 127;
|
|
3328
|
+
}).join("");
|
|
3329
|
+
const line = plain2.split("\n").map((l) => l.replace(/\s+/g, " ").trim()).find((l) => l.length > 0);
|
|
3330
|
+
if (line) {
|
|
3331
|
+
const said = redactSecrets(reducePii(line, home));
|
|
3332
|
+
return `failed: ${said.length > 200 ? `${said.slice(0, 199)}\u2026` : said}`;
|
|
3333
|
+
}
|
|
3334
|
+
if (err?.signal) return `failed: killed by ${err.signal}`;
|
|
3335
|
+
if (typeof err?.status === "number") return `failed with exit code ${err.status}`;
|
|
3336
|
+
return "failed";
|
|
3337
|
+
}
|
|
2955
3338
|
function detectCaps(bin, timeoutMs = 3e4) {
|
|
2956
3339
|
const c = probe(bin, ["--help"]);
|
|
2957
3340
|
try {
|
|
@@ -2969,7 +3352,7 @@ function detectCaps(bin, timeoutMs = 3e4) {
|
|
|
2969
3352
|
const err = e;
|
|
2970
3353
|
const caps = parseCaps(String(err?.stdout || ""));
|
|
2971
3354
|
if (caps.hasMode) return caps;
|
|
2972
|
-
const why = err?.code === "ETIMEDOUT" ? `timed out after ${timeoutMs / 1e3} s` :
|
|
3355
|
+
const why = err?.code === "ETIMEDOUT" ? `timed out after ${timeoutMs / 1e3} s` : helpFailure(err);
|
|
2973
3356
|
return { ...caps, unread: `\`${path10.basename(bin)} --help\` ${why}` };
|
|
2974
3357
|
}
|
|
2975
3358
|
}
|
|
@@ -3190,16 +3573,16 @@ function relaunchMode(recorded, home) {
|
|
|
3190
3573
|
return recorded;
|
|
3191
3574
|
return null;
|
|
3192
3575
|
}
|
|
3193
|
-
function recentlyWritten(file,
|
|
3576
|
+
function recentlyWritten(file, knownEnd, now = Date.now()) {
|
|
3194
3577
|
let m;
|
|
3195
3578
|
try {
|
|
3196
3579
|
m = statSync5(file).mtimeMs;
|
|
3197
3580
|
} catch {
|
|
3198
3581
|
return false;
|
|
3199
3582
|
}
|
|
3200
|
-
|
|
3201
|
-
return !(ourLastEnd && m <= ourLastEnd + 2e3);
|
|
3583
|
+
return recentWrite(m, knownEnd, now);
|
|
3202
3584
|
}
|
|
3585
|
+
var recentWrite = (written, knownEnd, now) => now - written <= LIVE_WRITE_MS && !(knownEnd && written <= knownEnd + 2e3);
|
|
3203
3586
|
function runClaude(bin, args, o) {
|
|
3204
3587
|
return runHeadless(bin, args, { ...o, keep: o.keep ?? ((line) => line.includes('"type":"result"')) });
|
|
3205
3588
|
}
|
|
@@ -3228,6 +3611,15 @@ function parseResult(stdout) {
|
|
|
3228
3611
|
return null;
|
|
3229
3612
|
}
|
|
3230
3613
|
var isUuid = (s) => typeof s === "string" && UUID_RE2.test(s);
|
|
3614
|
+
function initSession(line) {
|
|
3615
|
+
if (!line.includes('"init"')) return null;
|
|
3616
|
+
try {
|
|
3617
|
+
const j = JSON.parse(line);
|
|
3618
|
+
return j.type === "system" && j.subtype === "init" && isUuid(j.session_id) ? j.session_id : null;
|
|
3619
|
+
} catch {
|
|
3620
|
+
return null;
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3231
3623
|
|
|
3232
3624
|
// src/adapters/codex.ts
|
|
3233
3625
|
import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
|
|
@@ -3917,7 +4309,7 @@ function codexStderr(stderr) {
|
|
|
3917
4309
|
var reading2 = () => new CodexReading();
|
|
3918
4310
|
|
|
3919
4311
|
// src/adapters/devin.ts
|
|
3920
|
-
import { createHash as
|
|
4312
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3921
4313
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
3922
4314
|
import path13 from "node:path";
|
|
3923
4315
|
|
|
@@ -4158,7 +4550,7 @@ var devin = {
|
|
|
4158
4550
|
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
4159
4551
|
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
4160
4552
|
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
4161
|
-
const permissionId = () => `perm-${
|
|
4553
|
+
const permissionId = () => `perm-${createHash4("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
4162
4554
|
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
4163
4555
|
const events = [];
|
|
4164
4556
|
switch (event) {
|
|
@@ -4979,201 +5371,6 @@ function processAlive(pid) {
|
|
|
4979
5371
|
}
|
|
4980
5372
|
}
|
|
4981
5373
|
|
|
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
5374
|
// src/privacy/filter.ts
|
|
5178
5375
|
var TYPE_TIER = {
|
|
5179
5376
|
"session.started": 0,
|