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/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // sessionpipe-core 0.13.3 · Apache-2.0 · https://sessionpipe.org
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 === "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,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 c = j.message?.content;
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
- const text = channelText(textParts(c, ["text"]).trim());
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
- 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 } : {} });
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) 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
- }
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
- return pairTurns(msgs, endOf(lines));
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 = (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();
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
- kill("SIGTERM");
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) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
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
- (The turn was cancelled.)${refused2}` };
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` : "failed";
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
- function transcriptIn(session, dir) {
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 file = path10.join(dir, "projects", p.name, `${session}.jsonl`);
2986
- try {
2987
- if (statSync5(file).isFile()) return { file, configDir: dir, project: p.name };
2988
- } catch {
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, ourLastEnd, now = Date.now()) {
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
- if (now - m > LIVE_WRITE_MS) return false;
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 createHash3 } from "node:crypto";
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-${createHash3("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
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,