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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // sessionpipe-core 0.13.3 · Apache-2.0 · https://sessionpipe.org
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 === "user") {
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 = { user: cur.user, at: cur.at, ...cur.injected ? { injected: true } : {} };
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 c = j.message?.content;
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
- const text = channelText(textParts(c, ["text"]).trim());
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
- const text = peer ? peerAsk(origin, raw) : raw;
307
- if (!text || isInjected(text)) continue;
308
- msgs.push({ role: "user", text, at, line: i, ...peer ? { injected: true } : {} });
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) msgs.push({ role: "assistant", text, at, line: i });
312
- } else if (j.type === "attachment") {
313
- const text = queuedPrompt(j.attachment);
314
- if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
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
- return pairTurns(msgs, endOf(lines));
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 = (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();
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
- kill("SIGTERM");
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) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
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
- (The turn was cancelled.)${refused2}` };
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` : "failed";
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, ourLastEnd, now = Date.now()) {
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
- if (now - m > LIVE_WRITE_MS) return false;
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 createHash3 } from "node:crypto";
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-${createHash3("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
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,