sessionpipe-core 0.7.0 → 0.9.0

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.
Files changed (57) hide show
  1. package/README.md +1 -1
  2. package/dist/control.js +54 -3
  3. package/dist/control.js.map +2 -2
  4. package/dist/index.js +706 -167
  5. package/dist/index.js.map +4 -4
  6. package/dist/schema/v1.d.ts +139 -71
  7. package/dist/schema/v1.d.ts.map +1 -1
  8. package/dist/schema.js +56 -5
  9. package/dist/schema.js.map +2 -2
  10. package/dist/src/adapters/antigravity-control.d.ts.map +1 -1
  11. package/dist/src/adapters/antigravity.d.ts +1 -2
  12. package/dist/src/adapters/antigravity.d.ts.map +1 -1
  13. package/dist/src/adapters/claude-code-control.d.ts +2 -0
  14. package/dist/src/adapters/claude-code-control.d.ts.map +1 -1
  15. package/dist/src/adapters/claude-code.d.ts +10 -0
  16. package/dist/src/adapters/claude-code.d.ts.map +1 -1
  17. package/dist/src/adapters/claude-shaped.d.ts.map +1 -1
  18. package/dist/src/adapters/codex-config.d.ts +2 -2
  19. package/dist/src/adapters/codex-config.d.ts.map +1 -1
  20. package/dist/src/adapters/codex-control.d.ts.map +1 -1
  21. package/dist/src/adapters/codex-rollout.d.ts +8 -0
  22. package/dist/src/adapters/codex-rollout.d.ts.map +1 -0
  23. package/dist/src/adapters/codex.d.ts +1 -1
  24. package/dist/src/adapters/codex.d.ts.map +1 -1
  25. package/dist/src/adapters/devin.d.ts +32 -0
  26. package/dist/src/adapters/devin.d.ts.map +1 -0
  27. package/dist/src/adapters/gemini-cli.d.ts.map +1 -1
  28. package/dist/src/adapters/index.d.ts +4 -2
  29. package/dist/src/adapters/index.d.ts.map +1 -1
  30. package/dist/src/adapters/types.d.ts +9 -1
  31. package/dist/src/adapters/types.d.ts.map +1 -1
  32. package/dist/src/control/verify.d.ts +27 -0
  33. package/dist/src/control/verify.d.ts.map +1 -1
  34. package/dist/src/index.d.ts +3 -2
  35. package/dist/src/index.d.ts.map +1 -1
  36. package/dist/src/outbox.d.ts +13 -2
  37. package/dist/src/outbox.d.ts.map +1 -1
  38. package/dist/src/paths.d.ts +3 -1
  39. package/dist/src/paths.d.ts.map +1 -1
  40. package/dist/src/privacy/filter.d.ts +2 -0
  41. package/dist/src/privacy/filter.d.ts.map +1 -1
  42. package/dist/src/privacy/index.d.ts +1 -1
  43. package/dist/src/privacy/index.d.ts.map +1 -1
  44. package/dist/src/readers/files.d.ts.map +1 -1
  45. package/dist/src/readers/git.d.ts.map +1 -1
  46. package/dist/src/readers/sqlite.d.ts +23 -0
  47. package/dist/src/readers/sqlite.d.ts.map +1 -0
  48. package/dist/src/readers/transcripts.d.ts +5 -2
  49. package/dist/src/readers/transcripts.d.ts.map +1 -1
  50. package/dist/src/short-id.d.ts +2 -0
  51. package/dist/src/short-id.d.ts.map +1 -0
  52. package/dist/src/sinks/file.d.ts.map +1 -1
  53. package/dist/src/sinks/stdout.d.ts.map +1 -1
  54. package/dist/src/types.d.ts +1 -0
  55. package/dist/src/types.d.ts.map +1 -1
  56. package/dist/src/ulid.d.ts.map +1 -1
  57. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- // sessionpipe-core 0.7.0 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe-core 0.9.0 · 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)
@@ -21,20 +21,20 @@ import path4 from "node:path";
21
21
  // src/paths.ts
22
22
  import os from "node:os";
23
23
  import path from "node:path";
24
- var HOME = os.homedir();
24
+ var homeDir = () => os.homedir();
25
25
  function configFile(env = process.env) {
26
26
  if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);
27
27
  if (process.platform === "win32")
28
- return path.join(env.APPDATA || path.join(HOME, "AppData", "Roaming"), "sessionpipe", "config.json");
29
- return path.join(env.XDG_CONFIG_HOME || path.join(HOME, ".config"), "sessionpipe", "config.json");
28
+ return path.join(env.APPDATA || path.join(homeDir(), "AppData", "Roaming"), "sessionpipe", "config.json");
29
+ return path.join(env.XDG_CONFIG_HOME || path.join(homeDir(), ".config"), "sessionpipe", "config.json");
30
30
  }
31
31
  function stateDir(env = process.env) {
32
32
  if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);
33
33
  if (process.platform === "win32")
34
- return path.join(env.LOCALAPPDATA || path.join(HOME, "AppData", "Local"), "sessionpipe");
35
- return path.join(env.XDG_STATE_HOME || path.join(HOME, ".local", "state"), "sessionpipe");
34
+ return path.join(env.LOCALAPPDATA || path.join(homeDir(), "AppData", "Local"), "sessionpipe");
35
+ return path.join(env.XDG_STATE_HOME || path.join(homeDir(), ".local", "state"), "sessionpipe");
36
36
  }
37
- function tilde(p, home = HOME) {
37
+ function tilde(p, home = homeDir()) {
38
38
  if (!p) return p;
39
39
  if (p === home) return "~";
40
40
  return p.startsWith(home + path.sep) || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
@@ -131,6 +131,19 @@ function recentFiles(root, depth, sinceMs, match) {
131
131
  return out;
132
132
  }
133
133
 
134
+ // src/adapters/codex-rollout.ts
135
+ function codexSubagent(meta) {
136
+ if (meta?.type !== "session_meta") return false;
137
+ const p = meta.payload;
138
+ return !!p?.source?.subagent;
139
+ }
140
+ function codexReplayBoundary(meta) {
141
+ if (meta?.type !== "session_meta") return void 0;
142
+ const p = meta.payload;
143
+ if (!p || !p.forked_from_id && !p.source?.subagent) return void 0;
144
+ return (record) => typeof p.id === "string" && !!p.id && record?.type === "event_msg" && record.payload?.thread_id === p.id;
145
+ }
146
+
134
147
  // src/readers/transcripts.ts
135
148
  function pairTurns(msgs, endLine) {
136
149
  const turns = [];
@@ -153,10 +166,14 @@ ${m.text}`;
153
166
  }
154
167
  if (cur?.assistant) turns.push(cur);
155
168
  const line = cur && !cur.assistant ? cur.startLine : endLine;
156
- return { turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })), line };
169
+ const out = {
170
+ turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
171
+ line
172
+ };
173
+ if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
174
+ return out;
157
175
  }
158
- var isInjected = (text) => /^<[a-z_-]+>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
159
- var endOf = (lines) => lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
176
+ var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
160
177
  var parse = (raw) => {
161
178
  try {
162
179
  return JSON.parse(raw);
@@ -164,6 +181,11 @@ var parse = (raw) => {
164
181
  return null;
165
182
  }
166
183
  };
184
+ var endOf = (lines) => {
185
+ const last = lines[lines.length - 1];
186
+ if (last === void 0) return 0;
187
+ return last === "" || parse(last) === null ? lines.length - 1 : lines.length;
188
+ };
167
189
  var textParts = (c, kinds) => {
168
190
  if (typeof c === "string") return c;
169
191
  if (!Array.isArray(c)) return "";
@@ -171,13 +193,21 @@ var textParts = (c, kinds) => {
171
193
  (b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
172
194
  ).map((b) => b.text).join("\n");
173
195
  };
196
+ var queuedPrompt = (a) => {
197
+ if (!a || typeof a !== "object") return "";
198
+ const q = a;
199
+ if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
200
+ if (q.origin?.kind && q.origin.kind !== "human") return "";
201
+ return textParts(q.prompt, ["text"]).trim();
202
+ };
174
203
  function readClaude(lines, from) {
175
204
  const msgs = [];
176
205
  for (let i = from; i < lines.length; i++) {
177
206
  const j = lines[i] ? parse(lines[i]) : null;
178
207
  if (!j || j.isSidechain) continue;
179
208
  const origin = j.origin;
180
- if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human")) continue;
209
+ if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
210
+ continue;
181
211
  const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
182
212
  const c = j.message?.content;
183
213
  if (j.type === "user") {
@@ -187,6 +217,9 @@ function readClaude(lines, from) {
187
217
  } else if (j.type === "assistant" && Array.isArray(c)) {
188
218
  const text = textParts(c, ["text"]).trim();
189
219
  if (text) msgs.push({ role: "assistant", text, at, line: i });
220
+ } else if (j.type === "attachment") {
221
+ const text = queuedPrompt(j.attachment);
222
+ if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
190
223
  }
191
224
  }
192
225
  return pairTurns(msgs, endOf(lines));
@@ -196,7 +229,10 @@ function codexInjected(text) {
196
229
  }
197
230
  function readCodex(lines, from) {
198
231
  const msgs = [];
199
- for (let i = from; i < lines.length; i++) {
232
+ const boundary = codexReplayBoundary(parse(lines.find((line) => line.trim()) ?? ""));
233
+ const start = boundary ? lines.findIndex((line) => boundary(parse(line))) : 0;
234
+ if (start < 0) return pairTurns(msgs, endOf(lines));
235
+ for (let i = Math.max(from, start); i < lines.length; i++) {
200
236
  const j = lines[i] ? parse(lines[i]) : null;
201
237
  if (!j || j.type !== "response_item") continue;
202
238
  const p = j.payload;
@@ -234,7 +270,7 @@ function readAntigravity(lines, from) {
234
270
  const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
235
271
  if (j.type === "USER_INPUT") {
236
272
  const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
237
- const text = (m ? m[1] ?? "" : j.content).trim();
273
+ const text = (m ? m[1] : j.content).trim();
238
274
  if (text) msgs.push({ role: "user", text, at, line: i });
239
275
  } else if (j.type === "PLANNER_RESPONSE") {
240
276
  const text = j.content.trim();
@@ -282,6 +318,10 @@ function formatJson(value, f) {
282
318
  const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
283
319
  return f.finalNewline ? text + f.eol : text;
284
320
  }
321
+ function readJson(file) {
322
+ const f = readJsonFile(file);
323
+ return f.ok ? f.value : {};
324
+ }
285
325
  function entryFor(event, harness, cmd, o) {
286
326
  const matcher = o.matcherFor(event);
287
327
  return {
@@ -351,8 +391,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
351
391
  unlinkSync(file);
352
392
  return { file, changed: true, note: "removed (sessionpipe created it)" };
353
393
  }
354
- const next = formatJson(settings2, f);
355
- if (next !== f.raw) writeFileSync(file, next);
394
+ writeFileSync(file, formatJson(settings2, f));
356
395
  return { file, changed: true };
357
396
  }
358
397
  function installedClaudeShaped(file, harness, events, cmd, o) {
@@ -379,7 +418,7 @@ var HOOK_NAME = "sessionpipe";
379
418
  var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
380
419
  var AG_STALE_PRETOOL = '{"decision":"ask","reason":"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it."}\n';
381
420
  var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
382
- var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(HOME, ".gemini");
421
+ var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
383
422
  var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
384
423
  var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
385
424
  var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
@@ -445,7 +484,8 @@ var antigravity = {
445
484
  const event = input.argv[1] ?? "";
446
485
  let s = {};
447
486
  try {
448
- s = input.stdin ? JSON.parse(input.stdin) : {};
487
+ const j = input.stdin ? JSON.parse(input.stdin) : {};
488
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
449
489
  } catch {
450
490
  }
451
491
  const id = s.conversationId ?? s.session_id ?? s.sessionId;
@@ -501,19 +541,16 @@ var antigravity = {
501
541
  }
502
542
  return readAntigravity(text.split("\n"), fromLine);
503
543
  },
504
- facts(session, transcript, hints, state) {
544
+ facts(session, transcript, hints) {
505
545
  if (!transcript) return {};
506
546
  return antigravityFacts(transcript, {
507
547
  conversation: session.id,
508
548
  workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
509
- state,
510
549
  env: process.env
511
550
  });
512
551
  },
513
552
  backfill(sinceMs, env = process.env) {
514
- const rows = [];
515
- const noState = { get: () => ({}), save: () => {
516
- } };
553
+ const rows2 = [];
517
554
  for (const data of dataDirs(env)) {
518
555
  let ids = [];
519
556
  try {
@@ -527,13 +564,13 @@ var antigravity = {
527
564
  } catch {
528
565
  continue;
529
566
  }
530
- const facts = antigravityFacts(f, { conversation: id, workspaces: [], state: noState, env });
567
+ const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
531
568
  if (!facts.started_at || !facts.last_at) continue;
532
569
  const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
533
- rows.push({ session: { id, ...rest }, started_at, last_at });
570
+ rows2.push({ session: { id, ...rest }, started_at, last_at });
534
571
  }
535
572
  }
536
- return rows;
573
+ return rows2;
537
574
  }
538
575
  };
539
576
  function prune(o) {
@@ -583,7 +620,7 @@ function agProjects(dir) {
583
620
  }
584
621
  const folders = [];
585
622
  for (const r of j?.projectResources?.resources ?? []) {
586
- const f = agPath(r?.folderUri);
623
+ const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
587
624
  if (f) folders.push(...agVariants(f));
588
625
  }
589
626
  if (folders.length) out.push({ id, folders });
@@ -637,7 +674,7 @@ function antigravityFacts(file, opts) {
637
674
  for (const j of H) {
638
675
  if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
639
676
  const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
640
- const text = (m ? m[1] ?? "" : j.content).trim();
677
+ const text = (m ? m[1] : j.content).trim();
641
678
  const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
642
679
  if (!line || /^<[a-z_-]+>/i.test(line)) continue;
643
680
  out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
@@ -736,7 +773,7 @@ function runHeadless(bin, args, o) {
736
773
  if (o.onLine) {
737
774
  const chunk = partial + b.toString("utf8");
738
775
  const lines = chunk.split("\n");
739
- partial = lines.pop() ?? "";
776
+ partial = lines.pop();
740
777
  for (const line of lines) {
741
778
  if (!line.trim()) continue;
742
779
  try {
@@ -885,7 +922,7 @@ function agyError(stderr) {
885
922
  for (const k of ["message", "error", "detail"]) if (typeof j[k] === "string" && j[k]) return j[k];
886
923
  } catch {
887
924
  }
888
- return raw.split("\n")[0]?.slice(0, 300) ?? "";
925
+ return raw.split("\n")[0].slice(0, 300);
889
926
  }
890
927
  var AgyReading = class {
891
928
  session = null;
@@ -962,10 +999,9 @@ function gitFacts(cwd) {
962
999
  return out;
963
1000
  }
964
1001
  function stripCredentials(url) {
965
- let u = url.replace(/\/\/[^/@]*@/, "//");
966
- const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(u);
967
- if (ssh) u = `${ssh[1]}/${ssh[2]}`;
968
- return u.replace(/^https?:\/\//, "").replace(/\.git$/, "");
1002
+ const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
1003
+ if (ssh) return `${ssh[1]}/${ssh[2]}`;
1004
+ return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
969
1005
  }
970
1006
 
971
1007
  // src/adapters/claude-code.ts
@@ -996,7 +1032,7 @@ var OPTS = {
996
1032
  timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
997
1033
  };
998
1034
  var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
999
- function claudeDirs(env = process.env, home = env.HOME || HOME) {
1035
+ function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
1000
1036
  const out = [];
1001
1037
  const add = (d) => {
1002
1038
  if (!d) return;
@@ -1037,11 +1073,13 @@ var claudeCode = {
1037
1073
  const event = input.argv[1] ?? "";
1038
1074
  let s = {};
1039
1075
  try {
1040
- s = input.stdin ? JSON.parse(input.stdin) : {};
1076
+ const j = input.stdin ? JSON.parse(input.stdin) : {};
1077
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
1041
1078
  } catch {
1042
1079
  }
1043
1080
  const id = s.session_id ?? s.sessionId;
1044
1081
  if (!id) return null;
1082
+ if (notClaudeCode(String(id), s)) return null;
1045
1083
  const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
1046
1084
  if (typeof s.model === "string") session.model = s.model;
1047
1085
  if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
@@ -1160,6 +1198,7 @@ var claudeCode = {
1160
1198
  return null;
1161
1199
  }
1162
1200
  const hints = {};
1201
+ if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
1163
1202
  const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
1164
1203
  if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
1165
1204
  if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
@@ -1185,7 +1224,7 @@ var claudeCode = {
1185
1224
  return out;
1186
1225
  },
1187
1226
  backfill(sinceMs, env = process.env) {
1188
- const rows = [];
1227
+ const rows2 = [];
1189
1228
  const files = [];
1190
1229
  for (const d of claudeDirs(env))
1191
1230
  files.push(...recentFiles(path7.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
@@ -1197,19 +1236,23 @@ var claudeCode = {
1197
1236
  if (!facts.started_at || !facts.last_at) continue;
1198
1237
  const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
1199
1238
  const cwd = rest.cwd;
1200
- rows.push({
1239
+ rows2.push({
1201
1240
  session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
1202
1241
  started_at,
1203
1242
  last_at
1204
1243
  });
1205
1244
  }
1206
- return rows;
1245
+ return rows2;
1207
1246
  }
1208
1247
  };
1209
1248
  function prune2(o) {
1210
1249
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1211
1250
  return o;
1212
1251
  }
1252
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1253
+ function notClaudeCode(id, s) {
1254
+ return typeof s.transcript_path !== "string" && !UUID.test(id);
1255
+ }
1213
1256
  function pickSource(v) {
1214
1257
  return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
1215
1258
  }
@@ -1315,26 +1358,41 @@ function parentOf() {
1315
1358
  }
1316
1359
  return (pid) => table.get(pid) || 0;
1317
1360
  }
1318
- var accountCache = /* @__PURE__ */ new Map();
1319
- function claudeAccount(dir) {
1320
- const hit = accountCache.get(dir);
1321
- if (hit !== void 0) return hit;
1361
+ var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
1362
+ function claudeLogin(dir, home = homeDir()) {
1322
1363
  const files = [path7.join(dir, ".claude.json")];
1323
- if (path7.resolve(dir) === path7.join(HOME, ".claude")) files.unshift(path7.join(HOME, ".claude.json"));
1324
- let acct = null;
1364
+ if (path7.resolve(dir) === path7.join(home, ".claude")) files.unshift(path7.join(home, ".claude.json"));
1325
1365
  for (const f of files) {
1326
1366
  try {
1327
1367
  const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
1328
- if (a?.accountUuid) {
1329
- acct = String(a.accountUuid);
1330
- break;
1368
+ if (typeof a?.accountUuid === "string" && a.accountUuid) {
1369
+ const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
1370
+ return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
1331
1371
  }
1332
1372
  } catch {
1333
1373
  }
1334
1374
  }
1375
+ return null;
1376
+ }
1377
+ var accountCache = /* @__PURE__ */ new Map();
1378
+ function claudeAccount(dir) {
1379
+ const hit = accountCache.get(dir);
1380
+ if (hit !== void 0) return hit;
1381
+ const acct = claudeLogin(dir)?.id ?? null;
1335
1382
  accountCache.set(dir, acct);
1336
1383
  return acct;
1337
1384
  }
1385
+ function claudeAccounts(dirs, home = homeDir()) {
1386
+ const out = [];
1387
+ for (const d of dirs) {
1388
+ const l = claudeLogin(d, home);
1389
+ if (!l) continue;
1390
+ const seen = out.find((a) => a.id === l.id);
1391
+ if (!seen) out.push({ harness: NAME2, ...l });
1392
+ else if (!seen.email && l.email) seen.email = l.email;
1393
+ }
1394
+ return out;
1395
+ }
1338
1396
  var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
1339
1397
  function transcriptLink(records) {
1340
1398
  for (let i = records.length - 1; i >= 0; i--) {
@@ -1490,7 +1548,8 @@ var claude_code_control_exports = {};
1490
1548
  __export(claude_code_control_exports, {
1491
1549
  JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
1492
1550
  claudeEnv: () => claudeEnv,
1493
- claudeLogin: () => claudeLogin,
1551
+ claudeLogin: () => claudeLogin2,
1552
+ cmpVersion: () => cmpVersion,
1494
1553
  detectCaps: () => detectCaps,
1495
1554
  findClaude: () => findClaude,
1496
1555
  findTranscript: () => findTranscript,
@@ -1616,7 +1675,7 @@ function parseAuthStatus(stdout) {
1616
1675
  return { loggedIn: null };
1617
1676
  }
1618
1677
  }
1619
- function claudeLogin(bin, configDir, env = process.env) {
1678
+ function claudeLogin2(bin, configDir, env = process.env) {
1620
1679
  return new Promise((resolve) => {
1621
1680
  execFile(
1622
1681
  bin,
@@ -1867,18 +1926,13 @@ function parseArgv(raw) {
1867
1926
  function readNotify(text) {
1868
1927
  const { head, tail, at } = split(text);
1869
1928
  const m = NOTIFY_LINE.exec(head);
1870
- if (m)
1871
- return {
1872
- raw: m[1] ?? "",
1873
- argv: parseArgv(m[1] ?? ""),
1874
- index: m.index,
1875
- length: m[0].length,
1876
- misplaced: false,
1877
- other: !OURS2.test(m[1] ?? "")
1878
- };
1929
+ if (m) {
1930
+ const raw = m[1];
1931
+ return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
1932
+ }
1879
1933
  const k = NOTIFY_KEY.exec(head);
1880
1934
  if (k) {
1881
- const rest = head.slice(k.index).split("\n")[0] ?? "";
1935
+ const rest = head.slice(k.index).split("\n")[0];
1882
1936
  return {
1883
1937
  raw: rest.replace(NOTIFY_KEY, "").trim(),
1884
1938
  argv: null,
@@ -1889,15 +1943,11 @@ function readNotify(text) {
1889
1943
  };
1890
1944
  }
1891
1945
  const t = NOTIFY_LINE.exec(tail);
1892
- if (t && OURS2.test(t[1] ?? ""))
1893
- return {
1894
- raw: t[1] ?? "",
1895
- argv: parseArgv(t[1] ?? ""),
1896
- index: at + t.index,
1897
- length: t[0].length,
1898
- misplaced: true,
1899
- other: false
1900
- };
1946
+ if (t) {
1947
+ const raw = t[1];
1948
+ if (OURS2.test(raw))
1949
+ return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
1950
+ }
1901
1951
  return null;
1902
1952
  }
1903
1953
  function dropLine(text, index, length) {
@@ -1916,9 +1966,7 @@ function withNotify(text, line) {
1916
1966
  if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
1917
1967
  return insertTopLevel(text, line);
1918
1968
  }
1919
- function replaceNotify(text, line) {
1920
- const n = readNotify(text);
1921
- if (!n || n.misplaced || !n.length) return null;
1969
+ function replaceNotify(text, n, line) {
1922
1970
  return text.slice(0, n.index) + line + text.slice(n.index + n.length);
1923
1971
  }
1924
1972
  function withoutNotify(text) {
@@ -1959,7 +2007,7 @@ var CODEX_EVENTS = [
1959
2007
  ];
1960
2008
  var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
1961
2009
  var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
1962
- var codexHome = (env) => env.CODEX_HOME || path9.join(HOME, ".codex");
2010
+ var codexHome = (env) => env.CODEX_HOME || path9.join(homeDir(), ".codex");
1963
2011
  var hooksFile2 = (env) => path9.join(codexHome(env), "hooks.json");
1964
2012
  var configFile2 = (env) => path9.join(codexHome(env), "config.toml");
1965
2013
  var sessionsDir = (env) => path9.join(codexHome(env), "sessions");
@@ -1997,11 +2045,8 @@ function installNotify(cmd, env) {
1997
2045
  return { file, changed: false, note: "notify current (after Codex Computer Use)" };
1998
2046
  }
1999
2047
  if (!prev || OURS2.test(JSON.stringify(prev))) {
2000
- const next = replaceNotify(text, `notify = ${withPreviousRaw(n.raw, argv)}`);
2001
- if (next) {
2002
- write(next);
2003
- return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
2004
- }
2048
+ write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
2049
+ return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
2005
2050
  }
2006
2051
  }
2007
2052
  return {
@@ -2030,8 +2075,7 @@ function uninstallNotify(env) {
2030
2075
  }
2031
2076
  const prev = previousNotify(n.argv);
2032
2077
  if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
2033
- const next = replaceNotify(text, `notify = ${stripPreviousRaw(n.raw)}`);
2034
- if (next) writeFileSync3(file, next);
2078
+ writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
2035
2079
  return { file, changed: true };
2036
2080
  }
2037
2081
  return { file, changed: false };
@@ -2061,6 +2105,13 @@ function findRollout(threadId, env = process.env) {
2061
2105
  walk(sessionsDir(env), 0);
2062
2106
  return best;
2063
2107
  }
2108
+ function rolloutMeta(file) {
2109
+ return scanLines(file, (line) => parseLines([line])[0] ?? {});
2110
+ }
2111
+ function ofAnotherThread(file, id) {
2112
+ const m = /-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl(?:\.zst)?$/i.exec(file);
2113
+ return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
2114
+ }
2064
2115
  var codex = {
2065
2116
  name: NAME3,
2066
2117
  events: CODEX_EVENTS,
@@ -2103,11 +2154,14 @@ var codex = {
2103
2154
  if (event === "notify") {
2104
2155
  const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
2105
2156
  try {
2106
- s = raw ? JSON.parse(raw) : {};
2157
+ const j = raw ? JSON.parse(raw) : {};
2158
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
2107
2159
  } catch {
2108
2160
  }
2109
2161
  const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
2110
2162
  if (!id2) return null;
2163
+ const file = findRollout(String(id2), input.env);
2164
+ if (!file || codexSubagent(rolloutMeta(file))) return null;
2111
2165
  const r2 = {
2112
2166
  session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
2113
2167
  events: [
@@ -2117,18 +2171,19 @@ var codex = {
2117
2171
  harnessEvent: "notify"
2118
2172
  }
2119
2173
  ],
2120
- hints: {}
2174
+ hints: {},
2175
+ transcript: file
2121
2176
  };
2122
- const file = findRollout(String(id2), input.env);
2123
- if (file) r2.transcript = file;
2124
2177
  return r2;
2125
2178
  }
2126
2179
  try {
2127
- s = input.stdin ? JSON.parse(input.stdin) : {};
2180
+ const j = input.stdin ? JSON.parse(input.stdin) : {};
2181
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
2128
2182
  } catch {
2129
2183
  }
2130
2184
  const id = s.session_id;
2131
2185
  if (!id) return null;
2186
+ if (s.transcript_path === null) return null;
2132
2187
  const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
2133
2188
  if (typeof s.model === "string") session.model = s.model;
2134
2189
  const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
@@ -2200,7 +2255,8 @@ var codex = {
2200
2255
  return null;
2201
2256
  }
2202
2257
  const r = { session, events, hints: {} };
2203
- if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
2258
+ if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
2259
+ r.transcript = s.transcript_path;
2204
2260
  return r;
2205
2261
  },
2206
2262
  readTranscript(file, fromLine) {
@@ -2218,16 +2274,17 @@ var codex = {
2218
2274
  return out;
2219
2275
  },
2220
2276
  backfill(sinceMs, env = process.env) {
2221
- const rows = [];
2277
+ const rows2 = [];
2222
2278
  for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
2223
2279
  const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path9.basename(f));
2224
2280
  if (!m?.[1]) continue;
2281
+ if (codexSubagent(rolloutMeta(f))) continue;
2225
2282
  const facts = codexFacts(f);
2226
2283
  if (!facts.started_at || !facts.last_at) continue;
2227
2284
  const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
2228
- rows.push({ session: { id: m[1], ...rest }, started_at, last_at });
2285
+ rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
2229
2286
  }
2230
- return rows;
2287
+ return rows2;
2231
2288
  }
2232
2289
  };
2233
2290
  function prune3(o) {
@@ -2236,10 +2293,11 @@ function prune3(o) {
2236
2293
  }
2237
2294
  function codexFacts(file) {
2238
2295
  const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
2239
- const H = parseLines(head);
2240
- const T = parseLines(tail);
2296
+ let H = parseLines(head);
2297
+ let T = parseLines(tail);
2241
2298
  const out = {};
2242
2299
  const meta = H.find((j) => j.type === "session_meta" && j.payload);
2300
+ const boundary = codexReplayBoundary(meta);
2243
2301
  if (meta?.payload) {
2244
2302
  const p = meta.payload;
2245
2303
  if (p.cwd) out.cwd = p.cwd;
@@ -2247,6 +2305,13 @@ function codexFacts(file) {
2247
2305
  if (p.git?.branch) out.branch = p.git.branch;
2248
2306
  if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
2249
2307
  }
2308
+ if (boundary) {
2309
+ const h = H.findIndex(boundary);
2310
+ const t = T.findIndex(boundary);
2311
+ H = h < 0 ? [] : H.slice(h);
2312
+ const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
2313
+ T = t >= 0 ? T.slice(t) : found ? T : [];
2314
+ }
2250
2315
  const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
2251
2316
  if (ctx?.payload?.model) out.model = ctx.payload.model;
2252
2317
  for (const j of H) {
@@ -2254,12 +2319,12 @@ function codexFacts(file) {
2254
2319
  if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
2255
2320
  const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
2256
2321
  if (!text || codexInjected(text)) continue;
2257
- const line = text.split("\n").find((l) => l.trim()) ?? "";
2322
+ const line = text.split("\n")[0];
2258
2323
  out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
2259
2324
  out.title_source = "first-ask";
2260
2325
  break;
2261
2326
  }
2262
- const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
2327
+ const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
2263
2328
  if (times.length) {
2264
2329
  out.started_at = Math.min(...times);
2265
2330
  out.last_at = Math.max(...times);
@@ -2401,11 +2466,432 @@ function codexStderr(stderr) {
2401
2466
  }
2402
2467
  var reading2 = () => new CodexReading();
2403
2468
 
2404
- // src/adapters/gemini-cli.ts
2405
- import { execFileSync as execFileSync4 } from "node:child_process";
2406
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
2469
+ // src/adapters/devin.ts
2470
+ import { createHash as createHash2 } from "node:crypto";
2471
+ import { existsSync as existsSync6, readlinkSync } from "node:fs";
2407
2472
  import path11 from "node:path";
2408
- var NAME4 = "gemini-cli";
2473
+
2474
+ // src/readers/sqlite.ts
2475
+ import { execFileSync as execFileSync4 } from "node:child_process";
2476
+ import { existsSync as existsSync5 } from "node:fs";
2477
+ import { createRequire } from "node:module";
2478
+ function quiet(fn) {
2479
+ const original = process.emitWarning;
2480
+ try {
2481
+ process.emitWarning = ((warning, ...rest) => {
2482
+ const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
2483
+ if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
2484
+ original.call(process, warning, ...rest);
2485
+ });
2486
+ return fn();
2487
+ } finally {
2488
+ process.emitWarning = original;
2489
+ }
2490
+ }
2491
+ var ctorCache;
2492
+ function dbCtor() {
2493
+ if (ctorCache !== void 0) return ctorCache;
2494
+ ctorCache = null;
2495
+ try {
2496
+ const req = createRequire(import.meta.url);
2497
+ const m = quiet(() => req("node:sqlite"));
2498
+ if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
2499
+ } catch {
2500
+ }
2501
+ return ctorCache;
2502
+ }
2503
+ function plain(row) {
2504
+ if (!row || typeof row !== "object") return null;
2505
+ const out = {};
2506
+ for (const [k, v] of Object.entries(row)) {
2507
+ if (typeof v === "bigint") out[k] = Number(v);
2508
+ else if (v instanceof Uint8Array) out[k] = null;
2509
+ else out[k] = v;
2510
+ }
2511
+ return out;
2512
+ }
2513
+ var rows = (v) => {
2514
+ if (!Array.isArray(v)) return null;
2515
+ const out = [];
2516
+ for (const r of v) {
2517
+ const p = plain(r);
2518
+ if (p) out.push(p);
2519
+ }
2520
+ return out;
2521
+ };
2522
+ function viaNode(file, sql, params, o) {
2523
+ const C = dbCtor();
2524
+ if (!C) return null;
2525
+ let db;
2526
+ try {
2527
+ db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
2528
+ } catch {
2529
+ return null;
2530
+ }
2531
+ try {
2532
+ try {
2533
+ db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
2534
+ } catch {
2535
+ }
2536
+ return quiet(() => rows(db.prepare(sql).all(...params)));
2537
+ } catch {
2538
+ return null;
2539
+ } finally {
2540
+ try {
2541
+ db.close();
2542
+ } catch {
2543
+ }
2544
+ }
2545
+ }
2546
+ var CHILD = `const { DatabaseSync } = require("node:sqlite");
2547
+ const [, file, sql, params, ms] = process.argv;
2548
+ const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
2549
+ try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
2550
+ const out = db.prepare(sql).all(...JSON.parse(params));
2551
+ db.close();
2552
+ process.stdout.write(JSON.stringify(out));`;
2553
+ function viaChild(file, sql, params, o) {
2554
+ try {
2555
+ const out = execFileSync4(
2556
+ process.execPath,
2557
+ ["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
2558
+ {
2559
+ encoding: "utf8",
2560
+ timeout: o.timeoutMs + 3e3,
2561
+ stdio: ["ignore", "pipe", "ignore"],
2562
+ maxBuffer: o.maxBuffer,
2563
+ windowsHide: true
2564
+ }
2565
+ );
2566
+ return rows(JSON.parse(out || "null"));
2567
+ } catch {
2568
+ return null;
2569
+ }
2570
+ }
2571
+ var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
2572
+ function inlineParams(sql, params) {
2573
+ if (sql.includes("'")) return null;
2574
+ const holes = sql.split("?").length - 1;
2575
+ if (holes !== params.length) return null;
2576
+ let i = 0;
2577
+ let bad = false;
2578
+ const out = sql.replace(/\?/g, () => {
2579
+ const p = params[i++];
2580
+ if (typeof p === "number") {
2581
+ if (!Number.isFinite(p)) {
2582
+ bad = true;
2583
+ return "NULL";
2584
+ }
2585
+ return String(p);
2586
+ }
2587
+ if (typeof p !== "string" || !SAFE.test(p)) {
2588
+ bad = true;
2589
+ return "NULL";
2590
+ }
2591
+ return `'${p}'`;
2592
+ });
2593
+ return bad ? null : out;
2594
+ }
2595
+ function viaCli(file, sql, params, o) {
2596
+ const text = inlineParams(sql, params);
2597
+ if (!text) return null;
2598
+ try {
2599
+ const out = execFileSync4("sqlite3", ["-readonly", "-json", file, text], {
2600
+ encoding: "utf8",
2601
+ timeout: o.timeoutMs + 3e3,
2602
+ stdio: ["ignore", "pipe", "ignore"],
2603
+ maxBuffer: o.maxBuffer,
2604
+ windowsHide: true
2605
+ }).trim();
2606
+ if (!out) return [];
2607
+ return rows(JSON.parse(out));
2608
+ } catch {
2609
+ return null;
2610
+ }
2611
+ }
2612
+ function querySqlite(file, sql, params = [], opts = {}) {
2613
+ const o = {
2614
+ timeoutMs: opts.timeoutMs ?? 2e3,
2615
+ engine: opts.engine ?? "auto",
2616
+ maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
2617
+ };
2618
+ if (!file) return null;
2619
+ try {
2620
+ if (!existsSync5(file)) return null;
2621
+ } catch {
2622
+ return null;
2623
+ }
2624
+ const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
2625
+ for (const way of ways) {
2626
+ const r = way(file, sql, params, o);
2627
+ if (r) return r;
2628
+ }
2629
+ return null;
2630
+ }
2631
+
2632
+ // src/adapters/devin.ts
2633
+ var NAME4 = "devin";
2634
+ var DEVIN_EVENTS = [
2635
+ "SessionStart",
2636
+ "UserPromptSubmit",
2637
+ "PreToolUse",
2638
+ "PostToolUse",
2639
+ "PermissionRequest",
2640
+ "Stop",
2641
+ "PostCompaction",
2642
+ "SessionEnd"
2643
+ ];
2644
+ var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
2645
+ var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
2646
+ var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
2647
+ var CLAUDE_NOTE = "Devin also reads ~/.claude/settings.json hooks (read_config_from.claude); a Claude-only event name there makes it ignore that file whole";
2648
+ var deadFileNote = (bad) => `Devin loads NO hooks from this file: "${bad}" is not one of its event names and it rejects the file whole \u2014 remove that key (\`/hooks\` in \`devin\` lists what loaded)`;
2649
+ function foreignEvent(file) {
2650
+ const hooks = readJson(file).hooks;
2651
+ if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
2652
+ const ok = DEVIN_EVENTS;
2653
+ return Object.keys(hooks).find((k) => !ok.includes(k));
2654
+ }
2655
+ var homeOf = (env) => env.HOME || homeDir();
2656
+ var appData = (env) => env.APPDATA || path11.join(homeOf(env), "AppData", "Roaming");
2657
+ var devinConfigDir = (env = process.env) => process.platform === "win32" ? path11.join(appData(env), "devin") : path11.join(env.XDG_CONFIG_HOME || path11.join(homeOf(env), ".config"), "devin");
2658
+ var configFile3 = (env) => path11.join(devinConfigDir(env), "config.json");
2659
+ var devinDataDir = (env = process.env) => process.platform === "win32" ? path11.join(appData(env), "devin", "cli") : path11.join(env.XDG_DATA_HOME || path11.join(homeOf(env), ".local", "share"), "devin", "cli");
2660
+ var sessionsDb = (env) => path11.join(devinDataDir(env), "sessions.db");
2661
+ var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
2662
+ var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2663
+ var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
2664
+ var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
2665
+ function errText2(v) {
2666
+ if (typeof v === "string" && v) return v.slice(0, 2e3);
2667
+ if (v && typeof v === "object") {
2668
+ const e = v;
2669
+ if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
2670
+ if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
2671
+ }
2672
+ return void 0;
2673
+ }
2674
+ var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
2675
+ var devin = {
2676
+ name: NAME4,
2677
+ events: DEVIN_EVENTS,
2678
+ detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
2679
+ configFiles: (env = process.env) => [configFile3(env)],
2680
+ install: (cmd, env = process.env, opts = {}) => {
2681
+ const file = configFile3(env);
2682
+ const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
2683
+ if (r.skipped) return [r];
2684
+ const bad = foreignEvent(file);
2685
+ return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
2686
+ },
2687
+ uninstall: (env = process.env, opts = {}) => [
2688
+ uninstallClaudeShaped(configFile3(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile3(env)) })
2689
+ ],
2690
+ installed: (cmd, env = process.env, opts = {}) => {
2691
+ const file = configFile3(env);
2692
+ const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
2693
+ const bad = state === "missing" ? void 0 : foreignEvent(file);
2694
+ return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
2695
+ },
2696
+ fromHook(input) {
2697
+ const event = input.argv[1] ?? "";
2698
+ let s = {};
2699
+ try {
2700
+ const j = input.stdin ? JSON.parse(input.stdin) : {};
2701
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
2702
+ } catch {
2703
+ }
2704
+ const id = typeof s.session_id === "string" ? s.session_id : "";
2705
+ if (!id) return null;
2706
+ const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
2707
+ const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
2708
+ const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
2709
+ const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
2710
+ const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
2711
+ const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
2712
+ const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
2713
+ const events = [];
2714
+ switch (event) {
2715
+ case "SessionStart":
2716
+ events.push(ev("session.started", { source: pickSource2(s.source) }));
2717
+ break;
2718
+ case "UserPromptSubmit":
2719
+ events.push(
2720
+ ev("turn.started", {
2721
+ turn_id,
2722
+ prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
2723
+ })
2724
+ );
2725
+ break;
2726
+ case "PreToolUse":
2727
+ if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
2728
+ break;
2729
+ case "PostToolUse":
2730
+ if (tool)
2731
+ events.push(
2732
+ ev("tool.ended", {
2733
+ tool,
2734
+ call_id,
2735
+ turn_id,
2736
+ // `ok` is required on tool.ended (PROTOCOL.md §events), so a response
2737
+ // that carries no success flag at all reads as "nothing said it failed".
2738
+ ok: response?.success !== false,
2739
+ error: errText2(response?.error),
2740
+ input: s.tool_input,
2741
+ output: response ? response.output : s.tool_response
2742
+ })
2743
+ );
2744
+ break;
2745
+ case "PermissionRequest":
2746
+ events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
2747
+ break;
2748
+ case "Stop":
2749
+ events.push(ev("turn.ended", { turn_id, reason: "stop" }));
2750
+ break;
2751
+ case "PostCompaction":
2752
+ events.push(ev("context.compacted", { trigger: "auto" }));
2753
+ break;
2754
+ case "SessionEnd":
2755
+ events.push(ev("session.ended", { reason: endReason3(s.reason) }));
2756
+ break;
2757
+ default:
2758
+ return null;
2759
+ }
2760
+ return {
2761
+ session,
2762
+ events,
2763
+ hints: {},
2764
+ transcript: transcriptRef(sessionsDb(input.env), id)
2765
+ };
2766
+ },
2767
+ readTranscript(file, fromLine) {
2768
+ const ref = splitRef(file);
2769
+ if (!ref) return { turns: [], line: fromLine };
2770
+ return readDevinChain(ref.db, ref.session, fromLine);
2771
+ },
2772
+ facts(session, transcript, _hints, _state, env = process.env) {
2773
+ const out = {};
2774
+ const version = devinVersion(env);
2775
+ if (version) out.harness_version = version;
2776
+ const account = devinAccount(env);
2777
+ if (account) out.account_id = account;
2778
+ const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
2779
+ if (!SESSION_ID.test(ref.session)) return out;
2780
+ const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
2781
+ const row = rows2?.[0];
2782
+ if (!row) return out;
2783
+ Object.assign(out, sessionFacts(row));
2784
+ return out;
2785
+ },
2786
+ backfill(sinceMs, env = process.env) {
2787
+ const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
2788
+ if (!rows2) return [];
2789
+ const out = [];
2790
+ for (const row of rows2) {
2791
+ const id = String(row.id);
2792
+ if (!SESSION_ID.test(id)) continue;
2793
+ const { started_at, last_at, ...rest } = sessionFacts(row);
2794
+ if (!started_at || !last_at) continue;
2795
+ out.push({ session: { id, ...rest }, started_at, last_at });
2796
+ }
2797
+ return out;
2798
+ }
2799
+ };
2800
+ function prune4(o) {
2801
+ for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
2802
+ return o;
2803
+ }
2804
+ var transcriptRef = (db, session) => `${db}#${session}`;
2805
+ function splitRef(ref) {
2806
+ if (!ref) return null;
2807
+ const at = ref.lastIndexOf("#");
2808
+ if (at <= 0) return null;
2809
+ const db = ref.slice(0, at);
2810
+ const session = ref.slice(at + 1);
2811
+ return db && SESSION_ID.test(session) ? { db, session } : null;
2812
+ }
2813
+ var CHAIN_LIMIT = 4e3;
2814
+ var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
2815
+ SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
2816
+ WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
2817
+ UNION ALL
2818
+ SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
2819
+ JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
2820
+ )
2821
+ SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
2822
+ var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
2823
+ (SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
2824
+ ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
2825
+ var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
2826
+ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
2827
+ WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
2828
+ ORDER BY s.last_activity_at DESC LIMIT 500`;
2829
+ function sessionFacts(row) {
2830
+ const out = {};
2831
+ if (typeof row.model === "string" && row.model) out.model = row.model;
2832
+ if (typeof row.working_directory === "string" && path11.isAbsolute(row.working_directory))
2833
+ out.cwd = row.working_directory;
2834
+ const started = Number(row.created_at);
2835
+ const last = Number(row.last_activity_at);
2836
+ if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
2837
+ if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
2838
+ if (typeof row.first_ask === "string") {
2839
+ const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
2840
+ if (line.trim()) {
2841
+ out.title = cut(line.trim(), 80);
2842
+ out.title_source = "first-ask";
2843
+ }
2844
+ }
2845
+ return out;
2846
+ }
2847
+ function readDevinChain(db, session, fromLine) {
2848
+ if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
2849
+ const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
2850
+ const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
2851
+ if (!rows2?.length) return { turns: [], line: fromLine };
2852
+ const msgs = [];
2853
+ let end = fromLine;
2854
+ for (const row of rows2) {
2855
+ const node = Number(row.node_id);
2856
+ if (!Number.isFinite(node)) continue;
2857
+ if (node + 1 > end) end = node + 1;
2858
+ if (node < fromLine) continue;
2859
+ let j;
2860
+ try {
2861
+ j = JSON.parse(String(row.chat_message ?? ""));
2862
+ } catch {
2863
+ continue;
2864
+ }
2865
+ const text = typeof j.content === "string" ? j.content.trim() : "";
2866
+ if (!text) continue;
2867
+ const secs = Number(row.created_at);
2868
+ const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
2869
+ if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
2870
+ else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
2871
+ }
2872
+ msgs.sort((a, b) => a.line - b.line);
2873
+ return pairTurns(msgs, end);
2874
+ }
2875
+ function devinVersion(env = process.env) {
2876
+ try {
2877
+ const target = readlinkSync(path11.join(devinDataDir(env), "_versions", "current"));
2878
+ const name = path11.basename(target);
2879
+ return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
2880
+ } catch {
2881
+ return void 0;
2882
+ }
2883
+ }
2884
+ function devinAccount(env = process.env) {
2885
+ const d = readJson(configFile3(env)).devin;
2886
+ const id = d && typeof d === "object" ? d.org_id : void 0;
2887
+ return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
2888
+ }
2889
+
2890
+ // src/adapters/gemini-cli.ts
2891
+ import { execFileSync as execFileSync5 } from "node:child_process";
2892
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
2893
+ import path12 from "node:path";
2894
+ var NAME5 = "gemini-cli";
2409
2895
  var GEMINI_EVENTS = [
2410
2896
  "SessionStart",
2411
2897
  "BeforeAgent",
@@ -2416,11 +2902,11 @@ var GEMINI_EVENTS = [
2416
2902
  "PreCompress",
2417
2903
  "SessionEnd"
2418
2904
  ];
2419
- var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
2420
- var geminiDir = (env) => env.GEMINI_CLI_HOME || path11.join(HOME, ".gemini");
2421
- var settings = (env) => path11.join(geminiDir(env), "settings.json");
2422
- var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2423
- var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
2905
+ var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
2906
+ var geminiDir = (env) => env.GEMINI_CLI_HOME || path12.join(homeDir(), ".gemini");
2907
+ var settings = (env) => path12.join(geminiDir(env), "settings.json");
2908
+ var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2909
+ var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
2424
2910
  function geminiHooksActive(settingsFile) {
2425
2911
  let tools;
2426
2912
  try {
@@ -2432,7 +2918,7 @@ function geminiHooksActive(settingsFile) {
2432
2918
  return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
2433
2919
  let version = "";
2434
2920
  try {
2435
- version = execFileSync4("gemini", ["--version"], {
2921
+ version = execFileSync5("gemini", ["--version"], {
2436
2922
  encoding: "utf8",
2437
2923
  timeout: 4e3,
2438
2924
  stdio: ["ignore", "pipe", "ignore"]
@@ -2451,13 +2937,13 @@ function geminiHooksActive(settingsFile) {
2451
2937
  };
2452
2938
  }
2453
2939
  var geminiCli = {
2454
- name: NAME4,
2940
+ name: NAME5,
2455
2941
  events: GEMINI_EVENTS,
2456
2942
  // ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
2457
- detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path11.join(geminiDir(env), "tmp")),
2943
+ detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path12.join(geminiDir(env), "tmp")),
2458
2944
  configFiles: (env = process.env) => [settings(env)],
2459
2945
  install: (cmd, env = process.env, opts = {}) => {
2460
- const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
2946
+ const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
2461
2947
  const a = geminiHooksActive(settings(env));
2462
2948
  return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
2463
2949
  },
@@ -2465,7 +2951,7 @@ var geminiCli = {
2465
2951
  uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
2466
2952
  ],
2467
2953
  installed: (cmd, env = process.env, opts = {}) => {
2468
- const state = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
2954
+ const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
2469
2955
  const a = geminiHooksActive(settings(env));
2470
2956
  return [
2471
2957
  {
@@ -2479,14 +2965,15 @@ var geminiCli = {
2479
2965
  const event = input.argv[1] ?? "";
2480
2966
  let s = {};
2481
2967
  try {
2482
- s = input.stdin ? JSON.parse(input.stdin) : {};
2968
+ const j = input.stdin ? JSON.parse(input.stdin) : {};
2969
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
2483
2970
  } catch {
2484
2971
  }
2485
2972
  const id = s.session_id;
2486
2973
  if (!id) return null;
2487
2974
  const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
2488
- const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
2489
- const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
2975
+ const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
2976
+ const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
2490
2977
  const events = [];
2491
2978
  switch (event) {
2492
2979
  case "SessionStart":
@@ -2567,7 +3054,7 @@ var geminiCli = {
2567
3054
  const raw = first ? first.content ?? first.message ?? first.text : void 0;
2568
3055
  const text = typeof raw === "string" ? raw.trim() : "";
2569
3056
  if (text) {
2570
- const line = text.split("\n").find((l) => l.trim()) ?? "";
3057
+ const line = text.split("\n")[0];
2571
3058
  out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
2572
3059
  out.title_source = "first-ask";
2573
3060
  }
@@ -2581,13 +3068,13 @@ var geminiCli = {
2581
3068
  backfill: () => []
2582
3069
  // chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
2583
3070
  };
2584
- function prune4(o) {
3071
+ function prune5(o) {
2585
3072
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
2586
3073
  return o;
2587
3074
  }
2588
3075
 
2589
3076
  // src/adapters/index.ts
2590
- var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
3077
+ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
2591
3078
  var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
2592
3079
  function drivableHarnesses(env = process.env) {
2593
3080
  return [
@@ -2598,8 +3085,8 @@ function drivableHarnesses(env = process.env) {
2598
3085
  }
2599
3086
 
2600
3087
  // src/config.ts
2601
- import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
2602
- import path12 from "node:path";
3088
+ import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
3089
+ import path13 from "node:path";
2603
3090
  var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
2604
3091
  function readConfig(file = configFile()) {
2605
3092
  try {
@@ -2615,10 +3102,10 @@ function readConfig(file = configFile()) {
2615
3102
  }
2616
3103
  }
2617
3104
  function writeConfig(c, file = configFile()) {
2618
- mkdirSync4(path12.dirname(file), { recursive: true, mode: 448 });
3105
+ mkdirSync4(path13.dirname(file), { recursive: true, mode: 448 });
2619
3106
  writeFileSync4(file, `${JSON.stringify(c, null, 2)}
2620
3107
  `, { mode: 384 });
2621
- if (process.platform !== "win32" && existsSync6(file)) {
3108
+ if (process.platform !== "win32" && existsSync8(file)) {
2622
3109
  try {
2623
3110
  chmodSync(file, 384);
2624
3111
  } catch {
@@ -2647,7 +3134,7 @@ function encodeTime(t) {
2647
3134
  function ulid(now = Date.now()) {
2648
3135
  if (now === lastTime) {
2649
3136
  for (let i = lastRand.length - 1; i >= 0; i--) {
2650
- const v = (lastRand[i] ?? 0) + 1;
3137
+ const v = lastRand[i] + 1;
2651
3138
  if (v < 32) {
2652
3139
  lastRand[i] = v;
2653
3140
  break;
@@ -2656,8 +3143,7 @@ function ulid(now = Date.now()) {
2656
3143
  }
2657
3144
  } else {
2658
3145
  lastTime = now;
2659
- const b = randomBytes(16);
2660
- lastRand = Array.from({ length: 16 }, (_, i) => (b[i] ?? 0) % 32);
3146
+ lastRand = Array.from(randomBytes(16), (b) => b % 32);
2661
3147
  }
2662
3148
  return encodeTime(now) + lastRand.map((v) => ALPHABET[v]).join("");
2663
3149
  }
@@ -2685,7 +3171,7 @@ function makeEvent(a, ctx) {
2685
3171
  import {
2686
3172
  appendFileSync,
2687
3173
  closeSync as closeSync3,
2688
- existsSync as existsSync7,
3174
+ existsSync as existsSync9,
2689
3175
  mkdirSync as mkdirSync5,
2690
3176
  openSync as openSync3,
2691
3177
  readdirSync as readdirSync6,
@@ -2696,42 +3182,45 @@ import {
2696
3182
  unlinkSync as unlinkSync3,
2697
3183
  writeFileSync as writeFileSync5
2698
3184
  } from "node:fs";
2699
- import path13 from "node:path";
3185
+ import path14 from "node:path";
3186
+ var READ_CHUNK = 1024 * 1024;
3187
+ var READ_MAX = 8 * 1024 * 1024;
2700
3188
  var Outbox = class {
2701
3189
  constructor(root) {
2702
3190
  this.root = root;
2703
3191
  }
2704
3192
  root;
2705
3193
  dir(harness) {
2706
- return path13.join(this.root, "outbox", safeId(harness));
3194
+ return path14.join(this.root, "outbox", safeId(harness));
2707
3195
  }
2708
3196
  file(ref) {
2709
- return path13.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
3197
+ return path14.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
2710
3198
  }
2711
3199
  seqFile(ref) {
2712
- return path13.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
3200
+ return path14.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
2713
3201
  }
2714
3202
  /** The next seq for a session, persisted (survives a hibernating machine and a
2715
3203
  * restart; an in-memory counter would restart at 0 and collide). */
2716
3204
  nextSeq(ref) {
2717
3205
  const f = this.seqFile(ref);
2718
- let n = 0;
3206
+ let n = -1;
2719
3207
  try {
2720
- n = Number(readFileSync8(f, "utf8")) || 0;
3208
+ const t = readFileSync8(f, "utf8").trim();
3209
+ if (/^\d+$/.test(t)) n = Number(t);
2721
3210
  } catch {
2722
- n = this.lastSeqInFile(ref) + 1;
2723
3211
  }
2724
- mkdirSync5(path13.dirname(f), { recursive: true, mode: 448 });
3212
+ if (n < 0) n = this.lastSeqInFile(ref) + 1;
3213
+ mkdirSync5(path14.dirname(f), { recursive: true, mode: 448 });
2725
3214
  writeFileSync5(f, String(n + 1), { mode: 384 });
2726
3215
  return n;
2727
3216
  }
2728
3217
  lastSeqInFile(ref) {
2729
3218
  const f = this.file(ref);
2730
- if (!existsSync7(f)) return -1;
3219
+ if (!existsSync9(f)) return -1;
2731
3220
  const lines = readFileSync8(f, "utf8").trim().split("\n");
2732
3221
  for (let i = lines.length - 1; i >= 0; i--) {
2733
3222
  try {
2734
- const j = JSON.parse(lines[i] ?? "");
3223
+ const j = JSON.parse(lines[i]);
2735
3224
  if (typeof j.session?.seq === "number") return j.session.seq;
2736
3225
  } catch {
2737
3226
  }
@@ -2747,10 +3236,10 @@ var Outbox = class {
2747
3236
  /** Every session file, newest first. */
2748
3237
  sessions() {
2749
3238
  const out = [];
2750
- const base = path13.join(this.root, "outbox");
2751
- if (!existsSync7(base)) return [];
3239
+ const base = path14.join(this.root, "outbox");
3240
+ if (!existsSync9(base)) return [];
2752
3241
  for (const h of readdirSync6(base)) {
2753
- const d = path13.join(base, h);
3242
+ const d = path14.join(base, h);
2754
3243
  let names = [];
2755
3244
  try {
2756
3245
  names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
@@ -2759,7 +3248,7 @@ var Outbox = class {
2759
3248
  }
2760
3249
  for (const n of names) {
2761
3250
  try {
2762
- out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path13.join(d, n)).mtimeMs });
3251
+ out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path14.join(d, n)).mtimeMs });
2763
3252
  } catch {
2764
3253
  }
2765
3254
  }
@@ -2767,31 +3256,43 @@ var Outbox = class {
2767
3256
  return out.sort((a, b) => b.mtime - a.mtime).map((x) => x.ref);
2768
3257
  }
2769
3258
  /** Read events after byte offset `from`; returns them and the new offset. Lines
2770
- * are read whole; a partial trailing line (a write in progress) is left. */
3259
+ * are read whole; a partial trailing line (a write in progress) is left. A read
3260
+ * takes READ_CHUNK bytes at a time, not the rest of the file: a session file grows
3261
+ * to tens of MB and a drain takes it in batches of 50. It reads on until the batch
3262
+ * is full or the file ends, so lines that parse to nothing never stop a drain; one
3263
+ * line longer than the chunk gets a larger one, up to READ_MAX. */
2771
3264
  read(ref, from, maxEvents = 500) {
2772
3265
  const f = this.file(ref);
2773
- if (!existsSync7(f)) return { events: [], offset: from };
3266
+ if (!existsSync9(f)) return { events: [], offset: from };
2774
3267
  const size = statSync6(f).size;
2775
3268
  if (from >= size) return { events: [], offset: from };
2776
3269
  const fd = openSync3(f, "r");
2777
3270
  try {
2778
- const buf = Buffer.alloc(Math.min(size - from, 8 * 1024 * 1024));
2779
- const n = readSync3(fd, buf, 0, buf.length, from);
2780
- const text = buf.subarray(0, n).toString("utf8");
2781
3271
  const events = [];
2782
3272
  let offset = from;
2783
- let pos = 0;
2784
- while (events.length < maxEvents) {
2785
- const nl = text.indexOf("\n", pos);
2786
- if (nl < 0) break;
2787
- const line = text.slice(pos, nl);
2788
- pos = nl + 1;
2789
- offset = from + Buffer.byteLength(text.slice(0, pos), "utf8");
2790
- if (!line.trim()) continue;
2791
- try {
2792
- events.push(JSON.parse(line));
2793
- } catch {
3273
+ let chunk = READ_CHUNK;
3274
+ while (events.length < maxEvents && offset < size) {
3275
+ const buf = Buffer.alloc(Math.min(size - offset, chunk));
3276
+ const n = readSync3(fd, buf, 0, buf.length, offset);
3277
+ const text = buf.subarray(0, n).toString("utf8");
3278
+ let pos = 0;
3279
+ let used = 0;
3280
+ while (events.length < maxEvents) {
3281
+ const nl = text.indexOf("\n", pos);
3282
+ if (nl < 0) break;
3283
+ const line = text.slice(pos, nl);
3284
+ pos = nl + 1;
3285
+ used += Buffer.byteLength(line, "utf8") + 1;
3286
+ if (!line.trim()) continue;
3287
+ try {
3288
+ events.push(JSON.parse(line));
3289
+ } catch {
3290
+ }
2794
3291
  }
3292
+ offset += used;
3293
+ if (used) continue;
3294
+ if (offset + n >= size || chunk >= READ_MAX) break;
3295
+ chunk *= 2;
2795
3296
  }
2796
3297
  return { events, offset };
2797
3298
  } finally {
@@ -2835,7 +3336,7 @@ var Cursors = class {
2835
3336
  root;
2836
3337
  sink;
2837
3338
  file() {
2838
- return path13.join(this.root, "cursors", `${safeId(this.sink)}.json`);
3339
+ return path14.join(this.root, "cursors", `${safeId(this.sink)}.json`);
2839
3340
  }
2840
3341
  read() {
2841
3342
  try {
@@ -2846,7 +3347,7 @@ var Cursors = class {
2846
3347
  }
2847
3348
  write(c) {
2848
3349
  const f = this.file();
2849
- mkdirSync5(path13.dirname(f), { recursive: true, mode: 448 });
3350
+ mkdirSync5(path14.dirname(f), { recursive: true, mode: 448 });
2850
3351
  const tmp = `${f}.${process.pid}.tmp`;
2851
3352
  writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
2852
3353
  renameSync(tmp, f);
@@ -2856,11 +3357,14 @@ var Cursors = class {
2856
3357
  }
2857
3358
  };
2858
3359
  function takeLock(root, ref, staleMs = 5 * 6e4) {
2859
- const dir = path13.join(root, "locks");
3360
+ return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
3361
+ }
3362
+ function takeNamedLock(root, name, staleMs = 5 * 6e4) {
3363
+ const dir = path14.join(root, "locks");
2860
3364
  mkdirSync5(dir, { recursive: true, mode: 448 });
2861
- const f = path13.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
3365
+ const f = path14.join(dir, `${name}.lock`);
2862
3366
  try {
2863
- if (Date.now() - statSync6(f).mtimeMs > staleMs) unlinkSync3(f);
3367
+ if (Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(readFileSync8(f, "utf8")))) unlinkSync3(f);
2864
3368
  else return null;
2865
3369
  } catch {
2866
3370
  }
@@ -2876,9 +3380,18 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
2876
3380
  }
2877
3381
  };
2878
3382
  }
3383
+ function processAlive(pid) {
3384
+ if (!Number.isInteger(pid) || pid <= 0) return true;
3385
+ try {
3386
+ process.kill(pid, 0);
3387
+ return true;
3388
+ } catch (e) {
3389
+ return e.code === "EPERM";
3390
+ }
3391
+ }
2879
3392
 
2880
3393
  // src/privacy/pii.ts
2881
- import { createHash as createHash2 } from "node:crypto";
3394
+ import { createHash as createHash3 } from "node:crypto";
2882
3395
  var RULESET_PII = "pii@1";
2883
3396
  var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
2884
3397
  var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
@@ -2900,7 +3413,7 @@ function reducePii(text, home) {
2900
3413
  return out;
2901
3414
  }
2902
3415
  function hashMachine(name) {
2903
- return createHash2("sha256").update(name).digest("hex").slice(0, 8);
3416
+ return createHash3("sha256").update(name).digest("hex").slice(0, 8);
2904
3417
  }
2905
3418
  function reducePiiDeep(v, home) {
2906
3419
  if (typeof v === "string") return reducePii(v, home);
@@ -2989,12 +3502,12 @@ function redactAssignments(text) {
2989
3502
  let last = 0;
2990
3503
  NAME_SEP.lastIndex = 0;
2991
3504
  for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
2992
- const name = m[1] ?? "";
3505
+ const name = m[1];
2993
3506
  if (!isSecretName(name)) continue;
2994
3507
  VALUE.lastIndex = NAME_SEP.lastIndex;
2995
3508
  const v = VALUE.exec(text);
2996
3509
  if (!v) continue;
2997
- const value = v[1] ?? v[2] ?? v[3] ?? v[4] ?? "";
3510
+ const value = v[1] ?? v[2] ?? v[3] ?? v[4];
2998
3511
  NAME_SEP.lastIndex = VALUE.lastIndex;
2999
3512
  if (isPlaceholder(value) || value.includes(REDACTED)) continue;
3000
3513
  if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
@@ -3050,7 +3563,7 @@ function redactSecrets(text) {
3050
3563
  let out = text;
3051
3564
  for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
3052
3565
  out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
3053
- out = out.replace(AUTH_HEADER, (m, head, v) => v.includes(REDACTED) ? m : `${head}${REDACTED}`);
3566
+ out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
3054
3567
  out = redactAssignments(out);
3055
3568
  out = out.replace(
3056
3569
  PROSE,
@@ -3082,6 +3595,7 @@ var TYPE_TIER = {
3082
3595
  "turn.started": 0,
3083
3596
  "turn.ended": 0,
3084
3597
  "turn.transcript": 2,
3598
+ "turn.progress": 2,
3085
3599
  "tool.started": 1,
3086
3600
  "tool.ended": 1,
3087
3601
  "files.changed": 1,
@@ -3101,6 +3615,7 @@ var DATA_ALLOW = {
3101
3615
  "turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
3102
3616
  "turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
3103
3617
  "turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
3618
+ "turn.progress": { 2: ["turn", "user", "assistant", "at"] },
3104
3619
  "tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
3105
3620
  "tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
3106
3621
  "files.changed": { 1: ["paths"] },
@@ -3141,10 +3656,12 @@ function cap(v, max) {
3141
3656
  }
3142
3657
  return v;
3143
3658
  }
3659
+ function capTail(v, max) {
3660
+ return v.length > max ? `\u2026${v.slice(v.length - max + 1)}` : v;
3661
+ }
3144
3662
  function filterEvent(event, tier, pii, opts = {}) {
3145
3663
  const minTier = TYPE_TIER[event.type];
3146
3664
  if (minTier !== void 0 && minTier > tier) return null;
3147
- if (event.type === "native" && tier < 3) return null;
3148
3665
  const session = {};
3149
3666
  for (const k of SESSION_ALLOW)
3150
3667
  if (event.session[k] !== void 0)
@@ -3154,7 +3671,8 @@ function filterEvent(event, tier, pii, opts = {}) {
3154
3671
  if (keys === null) data = { ...event.data };
3155
3672
  else for (const k of keys) if (event.data[k] !== void 0) data[k] = event.data[k];
3156
3673
  if (typeof data.user === "string") data.user = cap(data.user, MAX_SIDE);
3157
- if (typeof data.assistant === "string") data.assistant = cap(data.assistant, MAX_SIDE);
3674
+ if (typeof data.assistant === "string")
3675
+ data.assistant = event.type === "turn.progress" ? capTail(data.assistant, MAX_SIDE) : cap(data.assistant, MAX_SIDE);
3158
3676
  if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
3159
3677
  if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
3160
3678
  if (typeof data.message === "string") data.message = cap(data.message, 200);
@@ -3175,9 +3693,15 @@ function filterEvent(event, tier, pii, opts = {}) {
3175
3693
  return out;
3176
3694
  }
3177
3695
 
3696
+ // src/short-id.ts
3697
+ import { createHash as createHash4 } from "node:crypto";
3698
+ function shortId(harness, id) {
3699
+ return createHash4("sha256").update(`sessionpipe.session:${harness}:${id}`).digest("hex").slice(0, 8);
3700
+ }
3701
+
3178
3702
  // src/sinks/file.ts
3179
3703
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
3180
- import path14 from "node:path";
3704
+ import path15 from "node:path";
3181
3705
  var FileSink = class {
3182
3706
  constructor(name, file, tier, pii) {
3183
3707
  this.name = name;
@@ -3191,7 +3715,7 @@ var FileSink = class {
3191
3715
  pii;
3192
3716
  async send(events) {
3193
3717
  try {
3194
- mkdirSync6(path14.dirname(this.file), { recursive: true });
3718
+ mkdirSync6(path15.dirname(this.file), { recursive: true });
3195
3719
  appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
3196
3720
  `);
3197
3721
  return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
@@ -3215,8 +3739,12 @@ var StdoutSink = class {
3215
3739
  pii;
3216
3740
  write;
3217
3741
  async send(events) {
3218
- for (const e of events) this.write(JSON.stringify(e));
3219
- return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
3742
+ try {
3743
+ for (const e of events) this.write(JSON.stringify(e));
3744
+ return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
3745
+ } catch (e) {
3746
+ return { ok: false, acknowledged: 0, error: String(e.message ?? e), action: "retry" };
3747
+ }
3220
3748
  }
3221
3749
  };
3222
3750
 
@@ -3227,8 +3755,8 @@ export {
3227
3755
  AG_STALE_PRETOOL,
3228
3756
  Cursors,
3229
3757
  DEFAULT_CONFIG,
3758
+ DEVIN_EVENTS,
3230
3759
  FileSink,
3231
- HOME,
3232
3760
  JOB_TIMEOUT_MS,
3233
3761
  Outbox,
3234
3762
  PROTOCOL_VERSION,
@@ -3241,6 +3769,8 @@ export {
3241
3769
  antigravity,
3242
3770
  antigravityAnswer,
3243
3771
  antigravity_control_exports as antigravityControl,
3772
+ capTail,
3773
+ claudeAccounts,
3244
3774
  claudeCode,
3245
3775
  claude_code_control_exports as claudeControl,
3246
3776
  claudeDirs,
@@ -3248,23 +3778,30 @@ export {
3248
3778
  codex_control_exports as codexControl,
3249
3779
  configFile,
3250
3780
  deniedNote,
3781
+ devin,
3782
+ devinConfigDir,
3783
+ devinDataDir,
3251
3784
  drivableHarnesses,
3252
3785
  filterEvent,
3253
3786
  geminiCli,
3254
3787
  gitFacts,
3255
3788
  hasSecret,
3256
3789
  hashMachine,
3790
+ homeDir,
3257
3791
  isInjected,
3258
3792
  isSecretName,
3259
3793
  isUlid,
3260
3794
  jobEnv,
3261
3795
  machineName,
3262
3796
  makeEvent,
3797
+ notClaudeCode,
3263
3798
  pairTurns,
3799
+ processAlive,
3264
3800
  readAntigravity,
3265
3801
  readClaude,
3266
3802
  readCodex,
3267
3803
  readConfig,
3804
+ readDevinChain,
3268
3805
  readGemini,
3269
3806
  redactDeep,
3270
3807
  redactSecrets,
@@ -3272,9 +3809,11 @@ export {
3272
3809
  reducePiiDeep,
3273
3810
  runHeadless,
3274
3811
  safeId,
3812
+ shortId,
3275
3813
  stateDir,
3276
3814
  stripCredentials,
3277
3815
  takeLock,
3816
+ takeNamedLock,
3278
3817
  tilde,
3279
3818
  ulid,
3280
3819
  writeConfig