sessionpipe-core 0.7.0 → 0.10.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 (73) hide show
  1. package/README.md +1 -1
  2. package/dist/control.js +172 -12
  3. package/dist/control.js.map +2 -2
  4. package/dist/index.js +1508 -431
  5. package/dist/index.js.map +4 -4
  6. package/dist/schema/v1.d.ts +405 -104
  7. package/dist/schema/v1.d.ts.map +1 -1
  8. package/dist/schema.js +174 -28
  9. package/dist/schema.js.map +2 -2
  10. package/dist/service.js +155 -0
  11. package/dist/service.js.map +7 -0
  12. package/dist/src/adapters/acp-control.d.ts +87 -0
  13. package/dist/src/adapters/acp-control.d.ts.map +1 -0
  14. package/dist/src/adapters/antigravity-control.d.ts +15 -5
  15. package/dist/src/adapters/antigravity-control.d.ts.map +1 -1
  16. package/dist/src/adapters/antigravity.d.ts +1 -2
  17. package/dist/src/adapters/antigravity.d.ts.map +1 -1
  18. package/dist/src/adapters/claude-code-control.d.ts +41 -4
  19. package/dist/src/adapters/claude-code-control.d.ts.map +1 -1
  20. package/dist/src/adapters/claude-code.d.ts +10 -0
  21. package/dist/src/adapters/claude-code.d.ts.map +1 -1
  22. package/dist/src/adapters/claude-shaped.d.ts.map +1 -1
  23. package/dist/src/adapters/codex-config.d.ts +2 -2
  24. package/dist/src/adapters/codex-config.d.ts.map +1 -1
  25. package/dist/src/adapters/codex-control.d.ts +23 -7
  26. package/dist/src/adapters/codex-control.d.ts.map +1 -1
  27. package/dist/src/adapters/codex-rollout.d.ts +8 -0
  28. package/dist/src/adapters/codex-rollout.d.ts.map +1 -0
  29. package/dist/src/adapters/codex.d.ts +1 -1
  30. package/dist/src/adapters/codex.d.ts.map +1 -1
  31. package/dist/src/adapters/devin.d.ts +32 -0
  32. package/dist/src/adapters/devin.d.ts.map +1 -0
  33. package/dist/src/adapters/gemini-cli.d.ts.map +1 -1
  34. package/dist/src/adapters/headless.d.ts +4 -1
  35. package/dist/src/adapters/headless.d.ts.map +1 -1
  36. package/dist/src/adapters/index.d.ts +11 -3
  37. package/dist/src/adapters/index.d.ts.map +1 -1
  38. package/dist/src/adapters/types.d.ts +9 -1
  39. package/dist/src/adapters/types.d.ts.map +1 -1
  40. package/dist/src/control/verify.d.ts +96 -13
  41. package/dist/src/control/verify.d.ts.map +1 -1
  42. package/dist/src/index.d.ts +4 -2
  43. package/dist/src/index.d.ts.map +1 -1
  44. package/dist/src/launched-by.d.ts +12 -0
  45. package/dist/src/launched-by.d.ts.map +1 -0
  46. package/dist/src/outbox.d.ts +13 -2
  47. package/dist/src/outbox.d.ts.map +1 -1
  48. package/dist/src/paths.d.ts +7 -1
  49. package/dist/src/paths.d.ts.map +1 -1
  50. package/dist/src/privacy/filter.d.ts +2 -0
  51. package/dist/src/privacy/filter.d.ts.map +1 -1
  52. package/dist/src/privacy/index.d.ts +1 -1
  53. package/dist/src/privacy/index.d.ts.map +1 -1
  54. package/dist/src/readers/files.d.ts.map +1 -1
  55. package/dist/src/readers/git.d.ts.map +1 -1
  56. package/dist/src/readers/sqlite.d.ts +23 -0
  57. package/dist/src/readers/sqlite.d.ts.map +1 -0
  58. package/dist/src/readers/transcripts.d.ts +5 -2
  59. package/dist/src/readers/transcripts.d.ts.map +1 -1
  60. package/dist/src/service/index.d.ts +83 -0
  61. package/dist/src/service/index.d.ts.map +1 -0
  62. package/dist/src/short-id.d.ts +2 -0
  63. package/dist/src/short-id.d.ts.map +1 -0
  64. package/dist/src/sinks/file.d.ts.map +1 -1
  65. package/dist/src/sinks/stdout.d.ts.map +1 -1
  66. package/dist/src/types.d.ts +1 -0
  67. package/dist/src/types.d.ts.map +1 -1
  68. package/dist/src/ulid.d.ts.map +1 -1
  69. package/dist/src/version.d.ts +39 -0
  70. package/dist/src/version.d.ts.map +1 -0
  71. package/dist/version.js +75 -0
  72. package/dist/version.js.map +7 -0
  73. package/package.json +11 -3
package/dist/index.js CHANGED
@@ -1,10 +1,525 @@
1
- // sessionpipe-core 0.7.0 · Apache-2.0 · https://sessionpipe.org
1
+ // sessionpipe-core 0.10.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)
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
+ // src/adapters/acp-control.ts
9
+ var acp_control_exports = {};
10
+ __export(acp_control_exports, {
11
+ ACP_AGENTS: () => ACP_AGENTS,
12
+ ACP_HARNESSES: () => ACP_HARNESSES,
13
+ acpAgent: () => acpAgent,
14
+ acpHarnesses: () => acpHarnesses,
15
+ acpOverride: () => acpOverride,
16
+ errorWords: () => errorWords,
17
+ findAcpAgent: () => findAcpAgent,
18
+ permissionChoice: () => permissionChoice,
19
+ startAcp: () => startAcp
20
+ });
21
+ import { spawn as spawn2 } from "node:child_process";
22
+ import os from "node:os";
23
+ import path2 from "node:path";
24
+
25
+ // src/adapters/headless.ts
26
+ import { spawn } from "node:child_process";
27
+ import { accessSync, constants, statSync } from "node:fs";
28
+ import path from "node:path";
29
+ var JOB_TIMEOUT_MS = 30 * 6e4;
30
+ var OUT_CAP = 1024 * 1024;
31
+ function isExe(p) {
32
+ try {
33
+ if (!statSync(p).isFile()) return false;
34
+ accessSync(p, constants.X_OK);
35
+ return true;
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+ function findBinary(o) {
41
+ if (o.override && isExe(o.override)) return o.override;
42
+ for (const d of String(o.env?.PATH || "").split(path.delimiter)) {
43
+ if (!d || !path.isAbsolute(d)) continue;
44
+ for (const n of o.names) if (isExe(path.join(d, n))) return path.join(d, n);
45
+ }
46
+ for (const d of o.dirs) for (const n of o.names) if (isExe(path.join(d, n))) return path.join(d, n);
47
+ return null;
48
+ }
49
+ function jobEnv(env = process.env) {
50
+ return { ...env, SESSIONPIPE_CONTROL_JOB: "1" };
51
+ }
52
+ function runHeadless(bin, args, o) {
53
+ return new Promise((resolve) => {
54
+ let out = [];
55
+ let outLen = 0;
56
+ let tooBig = false;
57
+ let errTail = "";
58
+ let timedOut = false;
59
+ let settled = false;
60
+ let child;
61
+ try {
62
+ child = spawn(bin, args, {
63
+ cwd: o.cwd,
64
+ env: o.env,
65
+ stdio: ["ignore", "pipe", "pipe"],
66
+ detached: process.platform !== "win32",
67
+ windowsHide: true
68
+ });
69
+ } catch (e) {
70
+ resolve({ error: e.message });
71
+ return;
72
+ }
73
+ const kill = (sig) => {
74
+ try {
75
+ if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
76
+ else child.kill(sig);
77
+ } catch {
78
+ try {
79
+ child.kill(sig);
80
+ } catch {
81
+ }
82
+ }
83
+ };
84
+ const keep = (line) => {
85
+ if (!o.keep?.(line)) return;
86
+ out = [Buffer.from(line)];
87
+ outLen = line.length;
88
+ };
89
+ let partial = "";
90
+ child.stdout?.on("data", (b) => {
91
+ if (o.onLine) {
92
+ const chunk = partial + b.toString("utf8");
93
+ const lines = chunk.split("\n");
94
+ partial = lines.pop();
95
+ for (const line of lines) {
96
+ if (!line.trim()) continue;
97
+ try {
98
+ o.onLine(line);
99
+ } catch {
100
+ }
101
+ keep(line);
102
+ }
103
+ if (partial.length > OUT_CAP) partial = "";
104
+ return;
105
+ }
106
+ if (tooBig) return;
107
+ if (outLen + b.length > OUT_CAP) {
108
+ tooBig = true;
109
+ out = [];
110
+ return;
111
+ }
112
+ out.push(b);
113
+ outLen += b.length;
114
+ });
115
+ child.stderr?.on("data", (b) => {
116
+ errTail = (errTail + b.toString("utf8")).slice(-4e3);
117
+ });
118
+ const timer = setTimeout(() => {
119
+ timedOut = true;
120
+ kill("SIGTERM");
121
+ setTimeout(() => kill("SIGKILL"), 1e4).unref();
122
+ }, o.timeoutMs ?? JOB_TIMEOUT_MS);
123
+ const result = (code, signal) => ({
124
+ code,
125
+ signal,
126
+ stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
127
+ tooBig,
128
+ stderr: errTail,
129
+ timedOut
130
+ });
131
+ const finish = (r) => {
132
+ if (settled) return;
133
+ settled = true;
134
+ clearTimeout(timer);
135
+ resolve(r);
136
+ };
137
+ child.on("error", (e) => finish({ error: e.message }));
138
+ child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
139
+ child.on("close", (code, signal) => {
140
+ if (o.onLine && partial.trim()) {
141
+ try {
142
+ o.onLine(partial);
143
+ } catch {
144
+ }
145
+ keep(partial);
146
+ partial = "";
147
+ }
148
+ finish(result(code, signal));
149
+ });
150
+ });
151
+ }
152
+ function stderrTail(stderr, n = 3) {
153
+ return String(stderr || "").trim().split("\n").slice(-n).join(" ").trim();
154
+ }
155
+ function deniedNote(names, count) {
156
+ if (!count) return "";
157
+ return `
158
+
159
+ (Not allowed on this machine, since nobody was there to approve: ${names.join(", ") || `${count} action(s)`}.)`;
160
+ }
161
+ function runFailure(label, res) {
162
+ if (res.error) return `couldn't start ${label}: ${res.error}`;
163
+ if (res.timedOut) return `${label} was still working after 30 minutes, so it was stopped`;
164
+ return null;
165
+ }
166
+
167
+ // src/adapters/acp-control.ts
168
+ var ACP_AGENTS = [
169
+ { harness: "gemini-cli", registry: "gemini", label: "Gemini CLI", bin: "gemini", args: ["--acp"] },
170
+ { harness: "devin", registry: "devin", label: "Devin", bin: "devin", args: ["acp"] },
171
+ { harness: "opencode", registry: "opencode", label: "OpenCode", bin: "opencode", args: ["acp"] },
172
+ { harness: "cursor", registry: "cursor", label: "Cursor", bin: "cursor-agent", args: ["acp"] },
173
+ { harness: "copilot-cli", registry: "github-copilot-cli", label: "Copilot CLI", bin: "copilot", args: ["--acp"] },
174
+ {
175
+ harness: "droid",
176
+ registry: "factory-droid",
177
+ label: "Droid",
178
+ bin: "droid",
179
+ args: ["exec", "--output-format", "acp-daemon"]
180
+ },
181
+ { harness: "goose", registry: "goose", label: "goose", bin: "goose", args: ["acp"] },
182
+ { harness: "kilo", registry: "kilo", label: "Kilo", bin: "kilo", args: ["acp"] },
183
+ {
184
+ harness: "qwen-code",
185
+ registry: "qwen-code",
186
+ label: "Qwen Code",
187
+ bin: "qwen",
188
+ args: ["--acp", "--experimental-skills"]
189
+ },
190
+ { harness: "cline", registry: "cline", label: "Cline", bin: "cline", args: ["--acp"] },
191
+ { harness: "auggie", registry: "auggie", label: "Auggie", bin: "auggie", args: ["--acp"] },
192
+ { harness: "kimi", registry: "kimi", label: "Kimi CLI", bin: "kimi", args: ["acp"] },
193
+ { harness: "amp", registry: "amp-acp", label: "Amp", bin: "amp-acp", args: [] },
194
+ { harness: "mistral-vibe", registry: "mistral-vibe", label: "Mistral Vibe", bin: "vibe-acp", args: [] },
195
+ { harness: "bob", registry: "bob", label: "Bob", bin: "bob", args: ["acp"] },
196
+ { harness: "grok-build", registry: "grok-build", label: "Grok Build", bin: "grok", args: ["agent", "stdio"] },
197
+ { harness: "qoder", registry: "qoder", label: "Qoder CLI", bin: "qodercli", args: ["--acp"] },
198
+ { harness: "minimax-code", registry: "minimax-code", label: "MiniMax Code", bin: "mcode", args: ["acp"] },
199
+ { harness: "codebuddy-code", registry: "codebuddy-code", label: "Codebuddy Code", bin: "codebuddy", args: ["--acp"] },
200
+ { harness: "nova", registry: "nova", label: "Nova", bin: "nova", args: ["acp"] },
201
+ { harness: "dimcode", registry: "dimcode", label: "DimCode", bin: "dim", args: ["acp"] },
202
+ { harness: "dirac", registry: "dirac", label: "Dirac", bin: "dirac", args: ["--acp"] },
203
+ { harness: "harn", registry: "harn", label: "Harn", bin: "harn", args: ["serve", "acp"] },
204
+ { harness: "vtcode", registry: "vtcode", label: "VT Code", bin: "vtcode", args: ["acp"] },
205
+ { harness: "stakpak", registry: "stakpak", label: "Stakpak", bin: "stakpak", args: ["acp"] },
206
+ { harness: "crow-cli", registry: "crow-cli", label: "crow-cli", bin: "crow-cli", args: ["acp"] },
207
+ { harness: "corust-agent", registry: "corust-agent", label: "Corust Agent", bin: "corust-agent-acp", args: [] },
208
+ { harness: "cortex-code", registry: "cortex-code", label: "Cortex Code", bin: "cortex", args: ["acp", "serve"] },
209
+ { harness: "kimchi", registry: "kimchi", label: "Kimchi", bin: "kimchi", args: ["--mode", "acp"] }
210
+ ];
211
+ var ACP_HARNESSES = ACP_AGENTS.map((a) => a.harness);
212
+ var acpAgent = (harness) => ACP_AGENTS.find((a) => a.harness === harness);
213
+ var acpOverride = (harness) => `SESSIONPIPE_ACP_${harness.toUpperCase().replace(/-/g, "_")}`;
214
+ function findAcpAgent(harness, env = process.env, home = os.homedir(), nodeDir = path2.dirname(process.execPath)) {
215
+ const a = acpAgent(harness);
216
+ if (!a) return null;
217
+ const bin = findBinary({
218
+ override: env[acpOverride(harness)],
219
+ names: process.platform === "win32" ? [`${a.bin}.exe`, `${a.bin}.cmd`] : [a.bin],
220
+ env,
221
+ dirs: [
222
+ nodeDir,
223
+ path2.join(home, ".local", "bin"),
224
+ path2.join(home, ".cargo", "bin"),
225
+ "/opt/homebrew/bin",
226
+ "/usr/local/bin"
227
+ ]
228
+ });
229
+ return bin ? { bin, args: a.args } : null;
230
+ }
231
+ function acpHarnesses(env = process.env) {
232
+ return ACP_HARNESSES.filter((h) => findAcpAgent(h, env));
233
+ }
234
+ var obj = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : null;
235
+ var str = (v) => typeof v === "string" ? v : "";
236
+ function permissionChoice(options, mode) {
237
+ const list = (Array.isArray(options) ? options : []).map(obj).filter((o) => !!o && !!str(o.optionId)).map((o) => ({ optionId: str(o.optionId), kind: str(o.kind) }));
238
+ const want = mode === "auto" ? ["allow_once", "allow_always"] : ["reject_once", "reject_always"];
239
+ for (const k of want) {
240
+ const hit = list.find((o) => o.kind === k);
241
+ if (hit) return hit;
242
+ }
243
+ return null;
244
+ }
245
+ function errorWords(err) {
246
+ const message = str(err.message);
247
+ const data = str(err.data) || str(obj(err.data)?.message);
248
+ return data && data !== message ? message ? `${message}: ${data}` : data : message;
249
+ }
250
+ function toolName(call, titles) {
251
+ if (!call) return null;
252
+ const t = str(call.title) || titles.get(str(call.toolCallId)) || str(call.name) || str(call.toolCallId);
253
+ return t ? t.slice(0, 80) : null;
254
+ }
255
+ var CLIENT_INFO = { name: "sessionpipe", title: "sessionpipe control", version: "1" };
256
+ function startAcp(o) {
257
+ const now = o.now ?? Date.now;
258
+ const log = o.log ?? (() => {
259
+ });
260
+ const label = o.label;
261
+ let child;
262
+ try {
263
+ child = spawn2(o.bin, o.args, {
264
+ cwd: o.cwd,
265
+ env: jobEnv(o.env),
266
+ stdio: ["pipe", "pipe", "pipe"],
267
+ detached: process.platform !== "win32",
268
+ windowsHide: true
269
+ });
270
+ } catch (e) {
271
+ return Promise.resolve({ failed: `couldn't start ${label}: ${e.message}` });
272
+ }
273
+ const kill = (sig) => {
274
+ try {
275
+ if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
276
+ else child.kill(sig);
277
+ } catch {
278
+ }
279
+ };
280
+ let gone = null;
281
+ let errTail = "";
282
+ let nextId = 1;
283
+ const pending = /* @__PURE__ */ new Map();
284
+ const write = (m) => {
285
+ if (gone) return;
286
+ try {
287
+ child.stdin?.write(`${JSON.stringify(m)}
288
+ `);
289
+ } catch {
290
+ }
291
+ };
292
+ const request = (method, params, timeoutMs) => new Promise((resolve, reject) => {
293
+ if (gone) return reject(new Error(gone));
294
+ const id = nextId++;
295
+ const t = setTimeout(() => {
296
+ pending.delete(id);
297
+ reject(new Error(`${label} didn't answer ${method} in ${Math.round(timeoutMs / 1e3)} s`));
298
+ }, timeoutMs);
299
+ t.unref();
300
+ pending.set(id, {
301
+ resolve: (r) => {
302
+ clearTimeout(t);
303
+ resolve(r);
304
+ },
305
+ reject: (e) => {
306
+ clearTimeout(t);
307
+ reject(e);
308
+ }
309
+ });
310
+ write({ jsonrpc: "2.0", id, method, params });
311
+ });
312
+ const notify = (method, params) => write({ jsonrpc: "2.0", method, params });
313
+ let turn = null;
314
+ const titles = /* @__PURE__ */ new Map();
315
+ const onUpdate = (u) => {
316
+ if (!u) return;
317
+ const kind = str(u.sessionUpdate);
318
+ if (kind === "tool_call" && str(u.toolCallId) && str(u.title)) {
319
+ if (titles.size >= 200) titles.clear();
320
+ titles.set(str(u.toolCallId), str(u.title));
321
+ return;
322
+ }
323
+ if (kind !== "agent_message_chunk" || !turn) return;
324
+ const c = obj(u.content);
325
+ if (c?.type !== "text" || !str(c.text)) return;
326
+ const mid = str(u.messageId) || null;
327
+ if (turn.message !== void 0 && mid !== turn.message && turn.text.trim())
328
+ turn.text = `${turn.text.trimEnd()}
329
+
330
+ `;
331
+ turn.message = mid;
332
+ turn.text += str(c.text);
333
+ o.onText?.(turn.text);
334
+ };
335
+ const onRequest = (id, method, params) => {
336
+ if (method !== "session/request_permission") {
337
+ write({ jsonrpc: "2.0", id, error: { code: -32601, message: `Method not found: ${method}` } });
338
+ return;
339
+ }
340
+ const pick = permissionChoice(params?.options, o.mode);
341
+ if (!pick?.kind.startsWith("allow")) {
342
+ const name = toolName(obj(params?.toolCall), titles);
343
+ if (turn) {
344
+ turn.count++;
345
+ if (name && !turn.denied.includes(name) && turn.denied.length < 20) turn.denied.push(name);
346
+ }
347
+ }
348
+ write({
349
+ jsonrpc: "2.0",
350
+ id,
351
+ result: { outcome: pick ? { outcome: "selected", optionId: pick.optionId } : { outcome: "cancelled" } }
352
+ });
353
+ };
354
+ const onLine = (line) => {
355
+ let m;
356
+ try {
357
+ m = obj(JSON.parse(line));
358
+ } catch {
359
+ return;
360
+ }
361
+ if (!m) return;
362
+ const method = str(m.method);
363
+ if (method) {
364
+ if (m.id !== void 0) onRequest(m.id, method, obj(m.params));
365
+ else if (method === "session/update") onUpdate(obj(obj(m.params)?.update));
366
+ return;
367
+ }
368
+ const p = typeof m.id === "number" ? pending.get(m.id) : void 0;
369
+ if (!p) return;
370
+ pending.delete(m.id);
371
+ const err = obj(m.error);
372
+ if (err) p.reject(new Error(errorWords(err) || `${label} returned error ${String(err.code ?? "?")}`));
373
+ else p.resolve(m.result);
374
+ };
375
+ let partial = "";
376
+ child.stdout?.on("data", (b) => {
377
+ const lines = (partial + b.toString("utf8")).split("\n");
378
+ partial = lines.pop();
379
+ for (const l of lines) if (l.trim()) onLine(l);
380
+ if (partial.length > 4 * 1024 * 1024) partial = "";
381
+ });
382
+ child.stderr?.on("data", (b) => {
383
+ errTail = (errTail + b.toString("utf8")).slice(-4e3);
384
+ });
385
+ const left = (why) => {
386
+ if (gone) return;
387
+ gone = why;
388
+ for (const p of pending.values()) p.reject(new Error(why));
389
+ pending.clear();
390
+ };
391
+ child.on("error", (e) => left(`couldn't start ${label}: ${e.message}`));
392
+ const exited = (code, signal) => {
393
+ const tail = errTail.trim().split("\n").slice(-3).join(" ").trim();
394
+ left(`${label} exited (${signal || `code ${code}`})${tail ? `: ${tail}` : ""}`);
395
+ };
396
+ child.on("exit", exited);
397
+ child.on("close", exited);
398
+ let closed = false;
399
+ const close = () => {
400
+ if (closed) return;
401
+ closed = true;
402
+ try {
403
+ child.stdin?.end();
404
+ } catch {
405
+ }
406
+ const grace = o.closeGraceMs ?? 2e3;
407
+ const t = setTimeout(() => kill("SIGTERM"), grace);
408
+ t.unref();
409
+ const k = setTimeout(() => kill("SIGKILL"), grace * 6);
410
+ k.unref();
411
+ child.once("exit", () => {
412
+ clearTimeout(t);
413
+ clearTimeout(k);
414
+ });
415
+ };
416
+ const open = o.openTimeoutMs ?? 6e4;
417
+ function held(session) {
418
+ let busy = false;
419
+ let lastUsed = now();
420
+ return {
421
+ session,
422
+ mode: "api",
423
+ get busy() {
424
+ return busy;
425
+ },
426
+ get lastUsed() {
427
+ return lastUsed;
428
+ },
429
+ get closed() {
430
+ return closed || !!gone;
431
+ },
432
+ async send(text) {
433
+ if (closed) return { reply: "", error: `${label} was closed` };
434
+ busy = true;
435
+ lastUsed = now();
436
+ const t = { text: "", denied: [], count: 0 };
437
+ turn = t;
438
+ const limit = o.timeoutMs ?? JOB_TIMEOUT_MS;
439
+ let timedOut = false;
440
+ const timer = setTimeout(() => {
441
+ timedOut = true;
442
+ notify("session/cancel", { sessionId: session });
443
+ setTimeout(close, 5e3).unref();
444
+ }, limit);
445
+ timer.unref();
446
+ try {
447
+ const res = obj(
448
+ await request("session/prompt", { sessionId: session, prompt: [{ type: "text", text }] }, limit + 1e4)
449
+ );
450
+ const stop = str(res?.stopReason);
451
+ const words = t.text.trim() || `(${label} finished without a written reply.)`;
452
+ const refused = deniedNote(t.denied, t.count);
453
+ if (stop === "end_turn" || stop === "max_tokens") return { reply: words + refused };
454
+ if (stop === "refusal") return { reply: `${words}
455
+
456
+ (${label} refused the request.)${refused}` };
457
+ if (stop === "cancelled")
458
+ return timedOut ? {
459
+ reply: "",
460
+ error: `${label} was still working after ${Math.round(limit / 6e4)} minutes, so it was stopped`
461
+ } : { reply: `${words}
462
+
463
+ (The turn was cancelled.)${refused}` };
464
+ return { reply: "", error: `${label} ended the turn with ${stop || "no stop reason"}` };
465
+ } catch (e) {
466
+ return { reply: "", error: e.message };
467
+ } finally {
468
+ clearTimeout(timer);
469
+ turn = null;
470
+ busy = false;
471
+ lastUsed = now();
472
+ }
473
+ },
474
+ async interrupt() {
475
+ notify("session/cancel", { sessionId: session });
476
+ },
477
+ close
478
+ };
479
+ }
480
+ return (async () => {
481
+ let signIn = "";
482
+ try {
483
+ const init = obj(
484
+ await request(
485
+ "initialize",
486
+ {
487
+ protocolVersion: 1,
488
+ clientCapabilities: { fs: { readTextFile: false, writeTextFile: false }, terminal: false },
489
+ clientInfo: CLIENT_INFO
490
+ },
491
+ open
492
+ )
493
+ );
494
+ const caps = obj(init?.agentCapabilities);
495
+ const info = obj(init?.agentInfo);
496
+ log(
497
+ `${label}: ${str(info?.name) || "agent"} ${str(info?.version)} (ACP ${String(init?.protocolVersion ?? "?")})`
498
+ );
499
+ const methods = Array.isArray(init?.authMethods) ? init.authMethods.map(obj) : [];
500
+ if (methods.length) {
501
+ const how = str(methods[0]?.description) || str(methods[0]?.name);
502
+ signIn = ` (if ${label} wants a sign-in, do it at the machine's terminal${how ? `: ${how}` : ""})`;
503
+ }
504
+ if (o.load) {
505
+ if (!caps?.loadSession) {
506
+ close();
507
+ return { unsupported: `${label} can't reopen a session once it has been closed (no session/load)` };
508
+ }
509
+ await request("session/load", { sessionId: o.load, cwd: o.cwd, mcpServers: [] }, open);
510
+ return { held: held(o.load) };
511
+ }
512
+ const res = obj(await request("session/new", { cwd: o.cwd, mcpServers: [] }, open));
513
+ const session = str(res?.sessionId);
514
+ if (!session) throw new Error(`${label} opened a session without naming it`);
515
+ return { held: held(session) };
516
+ } catch (e) {
517
+ close();
518
+ return { failed: `${e.message}${signIn}` };
519
+ }
520
+ })();
521
+ }
522
+
8
523
  // src/adapters/antigravity.ts
9
524
  import {
10
525
  existsSync as existsSync2,
@@ -12,38 +527,42 @@ import {
12
527
  readdirSync as readdirSync2,
13
528
  readFileSync as readFileSync2,
14
529
  realpathSync,
15
- statSync as statSync2,
530
+ statSync as statSync3,
16
531
  unlinkSync as unlinkSync2,
17
532
  writeFileSync as writeFileSync2
18
533
  } from "node:fs";
19
- import path4 from "node:path";
534
+ import path6 from "node:path";
20
535
 
21
536
  // src/paths.ts
22
- import os from "node:os";
23
- import path from "node:path";
24
- var HOME = os.homedir();
537
+ import os2 from "node:os";
538
+ import path3 from "node:path";
539
+ var homeDir = () => os2.homedir();
25
540
  function configFile(env = process.env) {
26
- if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);
541
+ if (env.SESSIONPIPE_CONFIG) return path3.resolve(env.SESSIONPIPE_CONFIG);
27
542
  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");
543
+ return path3.join(env.APPDATA || path3.join(homeDir(), "AppData", "Roaming"), "sessionpipe", "config.json");
544
+ return path3.join(env.XDG_CONFIG_HOME || path3.join(homeDir(), ".config"), "sessionpipe", "config.json");
30
545
  }
31
546
  function stateDir(env = process.env) {
32
- if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);
547
+ if (env.SESSIONPIPE_STATE) return path3.resolve(env.SESSIONPIPE_STATE);
33
548
  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");
549
+ return path3.join(env.LOCALAPPDATA || path3.join(homeDir(), "AppData", "Local"), "sessionpipe");
550
+ return path3.join(env.XDG_STATE_HOME || path3.join(homeDir(), ".local", "state"), "sessionpipe");
36
551
  }
37
- function tilde(p, home = HOME) {
552
+ function tilde(p, home = homeDir()) {
38
553
  if (!p) return p;
39
554
  if (p === home) return "~";
40
- return p.startsWith(home + path.sep) || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
555
+ return p.startsWith(home + path3.sep) || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
556
+ }
557
+ function untilde(p, home = homeDir()) {
558
+ if (p === "~") return home;
559
+ return /^~[\\/]/.test(p) ? path3.join(home, p.slice(2)) : p;
41
560
  }
42
561
  var safeId = (s) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 160);
43
562
 
44
563
  // src/readers/files.ts
45
- import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync } from "node:fs";
46
- import path2 from "node:path";
564
+ import { closeSync, fstatSync, openSync, readdirSync, readSync, statSync as statSync2 } from "node:fs";
565
+ import path4 from "node:path";
47
566
  function ends(file, headBytes, tailBytes) {
48
567
  let fd;
49
568
  try {
@@ -115,14 +634,14 @@ function recentFiles(root, depth, sinceMs, match) {
115
634
  return;
116
635
  }
117
636
  for (const e of names) {
118
- const p = path2.join(dir, e.name);
637
+ const p = path4.join(dir, e.name);
119
638
  if (e.isDirectory()) {
120
639
  if (d < depth) walk(p, d + 1);
121
640
  continue;
122
641
  }
123
642
  if (!match(e.name)) continue;
124
643
  try {
125
- if (statSync(p).mtimeMs >= sinceMs) out.push(p);
644
+ if (statSync2(p).mtimeMs >= sinceMs) out.push(p);
126
645
  } catch {
127
646
  }
128
647
  }
@@ -131,6 +650,19 @@ function recentFiles(root, depth, sinceMs, match) {
131
650
  return out;
132
651
  }
133
652
 
653
+ // src/adapters/codex-rollout.ts
654
+ function codexSubagent(meta) {
655
+ if (meta?.type !== "session_meta") return false;
656
+ const p = meta.payload;
657
+ return !!p?.source?.subagent;
658
+ }
659
+ function codexReplayBoundary(meta) {
660
+ if (meta?.type !== "session_meta") return void 0;
661
+ const p = meta.payload;
662
+ if (!p || !p.forked_from_id && !p.source?.subagent) return void 0;
663
+ return (record) => typeof p.id === "string" && !!p.id && record?.type === "event_msg" && record.payload?.thread_id === p.id;
664
+ }
665
+
134
666
  // src/readers/transcripts.ts
135
667
  function pairTurns(msgs, endLine) {
136
668
  const turns = [];
@@ -153,10 +685,14 @@ ${m.text}`;
153
685
  }
154
686
  if (cur?.assistant) turns.push(cur);
155
687
  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 };
688
+ const out = {
689
+ turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
690
+ line
691
+ };
692
+ if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
693
+ return out;
157
694
  }
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;
695
+ var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
160
696
  var parse = (raw) => {
161
697
  try {
162
698
  return JSON.parse(raw);
@@ -164,6 +700,11 @@ var parse = (raw) => {
164
700
  return null;
165
701
  }
166
702
  };
703
+ var endOf = (lines) => {
704
+ const last = lines[lines.length - 1];
705
+ if (last === void 0) return 0;
706
+ return last === "" || parse(last) === null ? lines.length - 1 : lines.length;
707
+ };
167
708
  var textParts = (c, kinds) => {
168
709
  if (typeof c === "string") return c;
169
710
  if (!Array.isArray(c)) return "";
@@ -171,13 +712,21 @@ var textParts = (c, kinds) => {
171
712
  (b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
172
713
  ).map((b) => b.text).join("\n");
173
714
  };
715
+ var queuedPrompt = (a) => {
716
+ if (!a || typeof a !== "object") return "";
717
+ const q = a;
718
+ if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
719
+ if (q.origin?.kind && q.origin.kind !== "human") return "";
720
+ return textParts(q.prompt, ["text"]).trim();
721
+ };
174
722
  function readClaude(lines, from) {
175
723
  const msgs = [];
176
724
  for (let i = from; i < lines.length; i++) {
177
725
  const j = lines[i] ? parse(lines[i]) : null;
178
726
  if (!j || j.isSidechain) continue;
179
727
  const origin = j.origin;
180
- if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human")) continue;
728
+ if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
729
+ continue;
181
730
  const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
182
731
  const c = j.message?.content;
183
732
  if (j.type === "user") {
@@ -187,6 +736,9 @@ function readClaude(lines, from) {
187
736
  } else if (j.type === "assistant" && Array.isArray(c)) {
188
737
  const text = textParts(c, ["text"]).trim();
189
738
  if (text) msgs.push({ role: "assistant", text, at, line: i });
739
+ } else if (j.type === "attachment") {
740
+ const text = queuedPrompt(j.attachment);
741
+ if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
190
742
  }
191
743
  }
192
744
  return pairTurns(msgs, endOf(lines));
@@ -196,7 +748,10 @@ function codexInjected(text) {
196
748
  }
197
749
  function readCodex(lines, from) {
198
750
  const msgs = [];
199
- for (let i = from; i < lines.length; i++) {
751
+ const boundary = codexReplayBoundary(parse(lines.find((line) => line.trim()) ?? ""));
752
+ const start = boundary ? lines.findIndex((line) => boundary(parse(line))) : 0;
753
+ if (start < 0) return pairTurns(msgs, endOf(lines));
754
+ for (let i = Math.max(from, start); i < lines.length; i++) {
200
755
  const j = lines[i] ? parse(lines[i]) : null;
201
756
  if (!j || j.type !== "response_item") continue;
202
757
  const p = j.payload;
@@ -234,7 +789,7 @@ function readAntigravity(lines, from) {
234
789
  const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
235
790
  if (j.type === "USER_INPUT") {
236
791
  const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
237
- const text = (m ? m[1] ?? "" : j.content).trim();
792
+ const text = (m ? m[1] : j.content).trim();
238
793
  if (text) msgs.push({ role: "user", text, at, line: i });
239
794
  } else if (j.type === "PLANNER_RESPONSE") {
240
795
  const text = j.content.trim();
@@ -246,7 +801,7 @@ function readAntigravity(lines, from) {
246
801
 
247
802
  // src/adapters/claude-shaped.ts
248
803
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
249
- import path3 from "node:path";
804
+ import path5 from "node:path";
250
805
  var OURS = /(?:sessionpipe[^"\n]*[\\/]hook\.js|[\\/]sessionpipe-hook)"?\s+[a-z][a-z-]*\s+[A-Za-z]+\s*$/;
251
806
  var isOurs = (h) => Array.isArray(h?.hooks) && h.hooks.some((x) => typeof x.command === "string" && OURS.test(x.command));
252
807
  function readJsonFile(file) {
@@ -282,6 +837,10 @@ function formatJson(value, f) {
282
837
  const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
283
838
  return f.finalNewline ? text + f.eol : text;
284
839
  }
840
+ function readJson(file) {
841
+ const f = readJsonFile(file);
842
+ return f.ok ? f.value : {};
843
+ }
285
844
  function entryFor(event, harness, cmd, o) {
286
845
  const matcher = o.matcherFor(event);
287
846
  return {
@@ -323,7 +882,7 @@ function installClaudeShaped(file, harness, events, cmd, o) {
323
882
  }
324
883
  if (changed) {
325
884
  settings2.hooks = hooks;
326
- mkdirSync(path3.dirname(file), { recursive: true });
885
+ mkdirSync(path5.dirname(file), { recursive: true });
327
886
  writeFileSync(file, formatJson(settings2, f));
328
887
  }
329
888
  return { file, changed, created: !f.existed && changed };
@@ -351,8 +910,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
351
910
  unlinkSync(file);
352
911
  return { file, changed: true, note: "removed (sessionpipe created it)" };
353
912
  }
354
- const next = formatJson(settings2, f);
355
- if (next !== f.raw) writeFileSync(file, next);
913
+ writeFileSync(file, formatJson(settings2, f));
356
914
  return { file, changed: true };
357
915
  }
358
916
  function installedClaudeShaped(file, harness, events, cmd, o) {
@@ -379,10 +937,10 @@ var HOOK_NAME = "sessionpipe";
379
937
  var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
380
938
  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
939
  var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
382
- var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(HOME, ".gemini");
383
- var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
384
- var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
385
- var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
940
+ var gemini = (env) => env.GEMINI_CLI_HOME || path6.join(homeDir(), ".gemini");
941
+ var hooksFile = (env) => path6.join(gemini(env), "config", "hooks.json");
942
+ var projectsDir = (env) => path6.join(gemini(env), "config", "projects");
943
+ var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path6.join(gemini(env), d));
386
944
  var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
387
945
  var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
388
946
  function entry(cmd) {
@@ -394,7 +952,7 @@ function entry(cmd) {
394
952
  var antigravity = {
395
953
  name: NAME,
396
954
  events: AG_EVENTS,
397
- detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
955
+ detect: (env = process.env) => existsSync2(path6.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
398
956
  configFiles: (env = process.env) => [hooksFile(env)],
399
957
  install(cmd, env = process.env) {
400
958
  const file = hooksFile(env);
@@ -404,7 +962,7 @@ var antigravity = {
404
962
  const e = entry(cmd);
405
963
  if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
406
964
  hooks[HOOK_NAME] = e;
407
- mkdirSync2(path4.dirname(file), { recursive: true });
965
+ mkdirSync2(path6.dirname(file), { recursive: true });
408
966
  writeFileSync2(file, formatJson(hooks, f));
409
967
  return [
410
968
  {
@@ -445,7 +1003,8 @@ var antigravity = {
445
1003
  const event = input.argv[1] ?? "";
446
1004
  let s = {};
447
1005
  try {
448
- s = input.stdin ? JSON.parse(input.stdin) : {};
1006
+ const j = input.stdin ? JSON.parse(input.stdin) : {};
1007
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
449
1008
  } catch {
450
1009
  }
451
1010
  const id = s.conversationId ?? s.session_id ?? s.sessionId;
@@ -501,39 +1060,36 @@ var antigravity = {
501
1060
  }
502
1061
  return readAntigravity(text.split("\n"), fromLine);
503
1062
  },
504
- facts(session, transcript, hints, state) {
1063
+ facts(session, transcript, hints) {
505
1064
  if (!transcript) return {};
506
1065
  return antigravityFacts(transcript, {
507
1066
  conversation: session.id,
508
1067
  workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
509
- state,
510
1068
  env: process.env
511
1069
  });
512
1070
  },
513
1071
  backfill(sinceMs, env = process.env) {
514
- const rows = [];
515
- const noState = { get: () => ({}), save: () => {
516
- } };
1072
+ const rows2 = [];
517
1073
  for (const data of dataDirs(env)) {
518
1074
  let ids = [];
519
1075
  try {
520
- ids = readdirSync2(path4.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
1076
+ ids = readdirSync2(path6.join(data, "brain")).filter((n) => /^[0-9a-f-]{36}$/i.test(n));
521
1077
  } catch {
522
1078
  }
523
1079
  for (const id of ids) {
524
- const f = path4.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
1080
+ const f = path6.join(data, "brain", id, ".system_generated", "logs", "transcript.jsonl");
525
1081
  try {
526
- if (statSync2(f).mtimeMs < sinceMs) continue;
1082
+ if (statSync3(f).mtimeMs < sinceMs) continue;
527
1083
  } catch {
528
1084
  continue;
529
1085
  }
530
- const facts = antigravityFacts(f, { conversation: id, workspaces: [], state: noState, env });
1086
+ const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
531
1087
  if (!facts.started_at || !facts.last_at) continue;
532
1088
  const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
533
- rows.push({ session: { id, ...rest }, started_at, last_at });
1089
+ rows2.push({ session: { id, ...rest }, started_at, last_at });
534
1090
  }
535
1091
  }
536
- return rows;
1092
+ return rows2;
537
1093
  }
538
1094
  };
539
1095
  function prune(o) {
@@ -551,8 +1107,8 @@ function agPath(p) {
551
1107
  }
552
1108
  if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
553
1109
  } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
554
- if (!path4.isAbsolute(s)) return null;
555
- return path4.resolve(s);
1110
+ if (!path6.isAbsolute(s)) return null;
1111
+ return path6.resolve(s);
556
1112
  }
557
1113
  var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
558
1114
  function agVariants(p) {
@@ -577,13 +1133,13 @@ function agProjects(dir) {
577
1133
  if (!ID_RE.test(id)) continue;
578
1134
  let j;
579
1135
  try {
580
- j = JSON.parse(readFileSync2(path4.join(dir, n), "utf8"));
1136
+ j = JSON.parse(readFileSync2(path6.join(dir, n), "utf8"));
581
1137
  } catch {
582
1138
  continue;
583
1139
  }
584
1140
  const folders = [];
585
1141
  for (const r of j?.projectResources?.resources ?? []) {
586
- const f = agPath(r?.folderUri);
1142
+ const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
587
1143
  if (f) folders.push(...agVariants(f));
588
1144
  }
589
1145
  if (folders.length) out.push({ id, folders });
@@ -600,7 +1156,7 @@ function agMatchProject(projects, workspaces) {
600
1156
  let bestLen = -1;
601
1157
  for (const pr of projects)
602
1158
  for (const f of pr.folders) {
603
- const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path4.sep) ? f : f + path4.sep));
1159
+ const within = ws.some((w) => w === f || w.startsWith(f.endsWith(path6.sep) ? f : f + path6.sep));
604
1160
  if (within && f.length > bestLen) {
605
1161
  best = pr.id;
606
1162
  bestLen = f.length;
@@ -609,12 +1165,12 @@ function agMatchProject(projects, workspaces) {
609
1165
  return best;
610
1166
  }
611
1167
  function antigravityRemoteUrl(transcript, opts) {
612
- const i = transcript.indexOf(`${path4.sep}brain${path4.sep}`);
1168
+ const i = transcript.indexOf(`${path6.sep}brain${path6.sep}`);
613
1169
  const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
614
1170
  for (const d of dirs) {
615
1171
  try {
616
1172
  const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
617
- readFileSync2(path4.join(d, "antigravity_state.pbtxt"), "utf8")
1173
+ readFileSync2(path6.join(d, "antigravity_state.pbtxt"), "utf8")
618
1174
  );
619
1175
  if (!m) continue;
620
1176
  const base = `https://antigravity.google.com/r/${m[1]}-v2`;
@@ -637,7 +1193,7 @@ function antigravityFacts(file, opts) {
637
1193
  for (const j of H) {
638
1194
  if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
639
1195
  const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
640
- const text = (m ? m[1] ?? "" : j.content).trim();
1196
+ const text = (m ? m[1] : j.content).trim();
641
1197
  const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
642
1198
  if (!line || /^<[a-z_-]+>/i.test(line)) continue;
643
1199
  out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
@@ -661,167 +1217,28 @@ __export(antigravity_control_exports, {
661
1217
  agyError: () => agyError,
662
1218
  agyLine: () => agyLine,
663
1219
  agyModeFlags: () => agyModeFlags,
1220
+ defaultLevel: () => defaultLevel,
664
1221
  findAgy: () => findAgy,
1222
+ offeredLevels: () => offeredLevels,
665
1223
  reading: () => reading
666
1224
  });
667
- import os2 from "node:os";
668
- import path6 from "node:path";
669
-
670
- // src/adapters/headless.ts
671
- import { spawn } from "node:child_process";
672
- import { accessSync, constants, statSync as statSync3 } from "node:fs";
673
- import path5 from "node:path";
674
- var JOB_TIMEOUT_MS = 30 * 6e4;
675
- var OUT_CAP = 1024 * 1024;
676
- function isExe(p) {
677
- try {
678
- if (!statSync3(p).isFile()) return false;
679
- accessSync(p, constants.X_OK);
680
- return true;
681
- } catch {
682
- return false;
683
- }
684
- }
685
- function findBinary(o) {
686
- if (o.override && isExe(o.override)) return o.override;
687
- for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
688
- if (!d || !path5.isAbsolute(d)) continue;
689
- for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
690
- }
691
- for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
692
- return null;
693
- }
694
- function jobEnv(env = process.env) {
695
- return { ...env, SESSIONPIPE_CONTROL_JOB: "1" };
696
- }
697
- function runHeadless(bin, args, o) {
698
- return new Promise((resolve) => {
699
- let out = [];
700
- let outLen = 0;
701
- let tooBig = false;
702
- let errTail = "";
703
- let timedOut = false;
704
- let settled = false;
705
- let child;
706
- try {
707
- child = spawn(bin, args, {
708
- cwd: o.cwd,
709
- env: o.env,
710
- stdio: ["ignore", "pipe", "pipe"],
711
- detached: process.platform !== "win32",
712
- windowsHide: true
713
- });
714
- } catch (e) {
715
- resolve({ error: e.message });
716
- return;
717
- }
718
- const kill = (sig) => {
719
- try {
720
- if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
721
- else child.kill(sig);
722
- } catch {
723
- try {
724
- child.kill(sig);
725
- } catch {
726
- }
727
- }
728
- };
729
- const keep = (line) => {
730
- if (!o.keep?.(line)) return;
731
- out = [Buffer.from(line)];
732
- outLen = line.length;
733
- };
734
- let partial = "";
735
- child.stdout?.on("data", (b) => {
736
- if (o.onLine) {
737
- const chunk = partial + b.toString("utf8");
738
- const lines = chunk.split("\n");
739
- partial = lines.pop() ?? "";
740
- for (const line of lines) {
741
- if (!line.trim()) continue;
742
- try {
743
- o.onLine(line);
744
- } catch {
745
- }
746
- keep(line);
747
- }
748
- if (partial.length > OUT_CAP) partial = "";
749
- return;
750
- }
751
- if (tooBig) return;
752
- if (outLen + b.length > OUT_CAP) {
753
- tooBig = true;
754
- out = [];
755
- return;
756
- }
757
- out.push(b);
758
- outLen += b.length;
759
- });
760
- child.stderr?.on("data", (b) => {
761
- errTail = (errTail + b.toString("utf8")).slice(-4e3);
762
- });
763
- const timer = setTimeout(() => {
764
- timedOut = true;
765
- kill("SIGTERM");
766
- setTimeout(() => kill("SIGKILL"), 1e4).unref();
767
- }, o.timeoutMs ?? JOB_TIMEOUT_MS);
768
- const result = (code, signal) => ({
769
- code,
770
- signal,
771
- stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
772
- tooBig,
773
- stderr: errTail,
774
- timedOut
775
- });
776
- const finish = (r) => {
777
- if (settled) return;
778
- settled = true;
779
- clearTimeout(timer);
780
- resolve(r);
781
- };
782
- child.on("error", (e) => finish({ error: e.message }));
783
- child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
784
- child.on("close", (code, signal) => {
785
- if (o.onLine && partial.trim()) {
786
- try {
787
- o.onLine(partial);
788
- } catch {
789
- }
790
- keep(partial);
791
- partial = "";
792
- }
793
- finish(result(code, signal));
794
- });
795
- });
796
- }
797
- function stderrTail(stderr, n = 3) {
798
- return String(stderr || "").trim().split("\n").slice(-n).join(" ").trim();
799
- }
800
- function deniedNote(names, count) {
801
- if (!count) return "";
802
- return `
803
-
804
- (Not allowed on this machine, since nobody was there to approve: ${names.join(", ") || `${count} action(s)`}.)`;
805
- }
806
- function runFailure(label, res) {
807
- if (res.error) return `couldn't start ${label}: ${res.error}`;
808
- if (res.timedOut) return `${label} was still working after 30 minutes, so it was stopped`;
809
- return null;
810
- }
811
-
812
- // src/adapters/antigravity-control.ts
1225
+ import os3 from "node:os";
1226
+ import path7 from "node:path";
813
1227
  var LABEL = "Antigravity";
814
- function findAgy(env = process.env, home = os2.homedir()) {
1228
+ function findAgy(env = process.env, home = os3.homedir()) {
815
1229
  return findBinary({
816
1230
  override: env.SESSIONPIPE_AGY,
817
1231
  names: process.platform === "win32" ? ["agy.exe", "agy.cmd"] : ["agy"],
818
1232
  env,
819
- dirs: [path6.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
1233
+ dirs: [path7.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
820
1234
  });
821
1235
  }
822
- function agyModeFlags(mode) {
823
- return mode === "auto" ? ["--mode", "accept-edits"] : [];
1236
+ function agyModeFlags(level) {
1237
+ if (level === "read") throw new Error("Antigravity has no read-only mode");
1238
+ return level === "safe" || level === "inherit" ? [] : ["--mode", "accept-edits"];
824
1239
  }
1240
+ var offeredLevels = (mode) => mode === "auto" ? ["safe", "edits"] : ["safe"];
1241
+ var defaultLevel = (mode) => mode === "auto" ? "edits" : mode;
825
1242
  function agyArgs(o) {
826
1243
  const prompt = o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt;
827
1244
  return [
@@ -830,7 +1247,7 @@ function agyArgs(o) {
830
1247
  prompt,
831
1248
  "--output-format",
832
1249
  "stream-json",
833
- ...agyModeFlags(o.mode)
1250
+ ...agyModeFlags(o.permission)
834
1251
  ];
835
1252
  }
836
1253
  var agyEnv = (env = process.env) => jobEnv(env);
@@ -842,26 +1259,26 @@ function agyLine(line) {
842
1259
  return null;
843
1260
  }
844
1261
  if (!j || typeof j !== "object") return null;
845
- const str = (v) => typeof v === "string" ? v : "";
1262
+ const str2 = (v) => typeof v === "string" ? v : "";
846
1263
  const out = {};
847
1264
  if (j.event === "init") {
848
- if (str(j.conversation_id)) out.session = str(j.conversation_id);
1265
+ if (str2(j.conversation_id)) out.session = str2(j.conversation_id);
849
1266
  } else if (j.event === "step_update") {
850
1267
  const s = j.step_update ?? {};
851
- if (str(s.conversation_id)) out.session = str(s.conversation_id);
852
- if (s.step_type === "agent_response" && str(s.text_delta))
853
- out.delta = { step: Number(s.step_index ?? 0), text: str(s.text_delta) };
1268
+ if (str2(s.conversation_id)) out.session = str2(s.conversation_id);
1269
+ if (s.step_type === "agent_response" && str2(s.text_delta))
1270
+ out.delta = { step: Number(s.step_index ?? 0), text: str2(s.text_delta) };
854
1271
  if (s.step_type === "error_message") {
855
- const why = str(s.text_delta) || str(s.error) || str(s.message);
1272
+ const why = str2(s.text_delta) || str2(s.error) || str2(s.message);
856
1273
  if (why) out.note = why;
857
1274
  }
858
1275
  } else if (j.event === "result") {
859
1276
  const r = j.result ?? {};
860
- if (str(r.conversation_id)) out.session = str(r.conversation_id);
1277
+ if (str2(r.conversation_id)) out.session = str2(r.conversation_id);
861
1278
  out.result = {
862
- status: str(r.status),
863
- response: str(r.response),
864
- error: str(r.error),
1279
+ status: str2(r.status),
1280
+ response: str2(r.response),
1281
+ error: str2(r.error),
865
1282
  denied: Array.isArray(r.denied_actions) ? r.denied_actions : []
866
1283
  };
867
1284
  } else return null;
@@ -885,7 +1302,7 @@ function agyError(stderr) {
885
1302
  for (const k of ["message", "error", "detail"]) if (typeof j[k] === "string" && j[k]) return j[k];
886
1303
  } catch {
887
1304
  }
888
- return raw.split("\n")[0]?.slice(0, 300) ?? "";
1305
+ return raw.split("\n")[0].slice(0, 300);
889
1306
  }
890
1307
  var AgyReading = class {
891
1308
  session = null;
@@ -933,7 +1350,7 @@ var reading = () => new AgyReading();
933
1350
  import { execFileSync as execFileSync2 } from "node:child_process";
934
1351
  import { createHash } from "node:crypto";
935
1352
  import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
936
- import path7 from "node:path";
1353
+ import path8 from "node:path";
937
1354
 
938
1355
  // src/readers/git.ts
939
1356
  import { execFileSync } from "node:child_process";
@@ -962,10 +1379,9 @@ function gitFacts(cwd) {
962
1379
  return out;
963
1380
  }
964
1381
  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$/, "");
1382
+ const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
1383
+ if (ssh) return `${ssh[1]}/${ssh[2]}`;
1384
+ return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
969
1385
  }
970
1386
 
971
1387
  // src/adapters/claude-code.ts
@@ -996,27 +1412,27 @@ var OPTS = {
996
1412
  timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
997
1413
  };
998
1414
  var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
999
- function claudeDirs(env = process.env, home = env.HOME || HOME) {
1415
+ function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
1000
1416
  const out = [];
1001
1417
  const add = (d) => {
1002
1418
  if (!d) return;
1003
- const r = path7.resolve(d);
1419
+ const r = path8.resolve(d);
1004
1420
  if (!out.includes(r)) out.push(r);
1005
1421
  };
1006
- add(path7.join(home, ".claude"));
1422
+ add(path8.join(home, ".claude"));
1007
1423
  add(env.CLAUDE_CONFIG_DIR);
1008
1424
  try {
1009
1425
  for (const e of readdirSync3(home, { withFileTypes: true })) {
1010
1426
  if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
1011
- const d = path7.join(home, e.name);
1012
- if (existsSync3(path7.join(d, "projects")) || existsSync3(path7.join(d, "settings.json"))) add(d);
1427
+ const d = path8.join(home, e.name);
1428
+ if (existsSync3(path8.join(d, "projects")) || existsSync3(path8.join(d, "settings.json"))) add(d);
1013
1429
  }
1014
1430
  } catch {
1015
1431
  }
1016
1432
  return out.filter((d, i) => i === 0 || existsSync3(d));
1017
1433
  }
1018
- var settingsIn = (dir) => path7.join(dir, "settings.json");
1019
- var configDirOf = (transcript) => path7.dirname(path7.dirname(path7.dirname(transcript)));
1434
+ var settingsIn = (dir) => path8.join(dir, "settings.json");
1435
+ var configDirOf = (transcript) => path8.dirname(path8.dirname(path8.dirname(transcript)));
1020
1436
  var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1021
1437
  var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
1022
1438
  var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
@@ -1037,11 +1453,13 @@ var claudeCode = {
1037
1453
  const event = input.argv[1] ?? "";
1038
1454
  let s = {};
1039
1455
  try {
1040
- s = input.stdin ? JSON.parse(input.stdin) : {};
1456
+ const j = input.stdin ? JSON.parse(input.stdin) : {};
1457
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
1041
1458
  } catch {
1042
1459
  }
1043
1460
  const id = s.session_id ?? s.sessionId;
1044
1461
  if (!id) return null;
1462
+ if (notClaudeCode(String(id), s)) return null;
1045
1463
  const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
1046
1464
  if (typeof s.model === "string") session.model = s.model;
1047
1465
  if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
@@ -1058,14 +1476,28 @@ var claudeCode = {
1058
1476
  case "SessionStart":
1059
1477
  events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
1060
1478
  break;
1061
- case "UserPromptSubmit":
1479
+ case "UserPromptSubmit": {
1480
+ const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
1062
1481
  events.push(
1063
1482
  ev("turn.started", {
1064
1483
  turn_id,
1065
- prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
1484
+ prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
1485
+ // Claude Code's hook schema documents `source`
1486
+ // (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
1487
+ // payload may omit it while the field rolls out; 2.1.304's payload has no such
1488
+ // key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
1489
+ // to extend.
1490
+ submitted_by: harnessWord(s.source),
1491
+ // One of Claude Code's own prompt wrappers: the prompt is a block the harness
1492
+ // wrote, so it is not the person's words. Absent is unknown, never "the person" —
1493
+ // and `true` is never "no person caused this turn", because a Talk or
1494
+ // `sessionpipe wait` delivery hands the person's own message over inside a
1495
+ // <task-notification> (PROTOCOL.md §4).
1496
+ injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
1066
1497
  })
1067
1498
  );
1068
1499
  break;
1500
+ }
1069
1501
  case "PreToolUse":
1070
1502
  if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
1071
1503
  break;
@@ -1120,7 +1552,13 @@ var claudeCode = {
1120
1552
  );
1121
1553
  break;
1122
1554
  case "Stop":
1123
- events.push(ev("turn.ended", { turn_id, reason: "stop" }));
1555
+ events.push(
1556
+ ev("turn.ended", {
1557
+ turn_id,
1558
+ reason: "stop",
1559
+ background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
1560
+ })
1561
+ );
1124
1562
  break;
1125
1563
  case "StopFailure":
1126
1564
  events.push(
@@ -1160,8 +1598,11 @@ var claudeCode = {
1160
1598
  return null;
1161
1599
  }
1162
1600
  const hints = {};
1601
+ if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
1163
1602
  const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
1164
1603
  if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
1604
+ const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
1605
+ if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
1165
1606
  if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
1166
1607
  const r = { session, events, hints };
1167
1608
  if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
@@ -1182,37 +1623,45 @@ var claudeCode = {
1182
1623
  const cwd = out.cwd ?? session.cwd;
1183
1624
  Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
1184
1625
  if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
1626
+ if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
1185
1627
  return out;
1186
1628
  },
1187
1629
  backfill(sinceMs, env = process.env) {
1188
- const rows = [];
1630
+ const rows2 = [];
1189
1631
  const files = [];
1190
1632
  for (const d of claudeDirs(env))
1191
- files.push(...recentFiles(path7.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
1633
+ files.push(...recentFiles(path8.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
1192
1634
  const noState = { get: () => ({}), save: () => {
1193
1635
  } };
1194
1636
  for (const f of files) {
1195
- const id = path7.basename(f, ".jsonl");
1637
+ const id = path8.basename(f, ".jsonl");
1196
1638
  const facts = claudeFacts(f, id, {}, noState);
1197
1639
  if (!facts.started_at || !facts.last_at) continue;
1198
1640
  const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
1199
1641
  const cwd = rest.cwd;
1200
- rows.push({
1642
+ rows2.push({
1201
1643
  session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
1202
1644
  started_at,
1203
1645
  last_at
1204
1646
  });
1205
1647
  }
1206
- return rows;
1648
+ return rows2;
1207
1649
  }
1208
1650
  };
1209
1651
  function prune2(o) {
1210
1652
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
1211
1653
  return o;
1212
1654
  }
1655
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1656
+ function notClaudeCode(id, s) {
1657
+ return typeof s.transcript_path !== "string" && !UUID.test(id);
1658
+ }
1213
1659
  function pickSource(v) {
1214
1660
  return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
1215
1661
  }
1662
+ var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
1663
+ var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
1664
+ var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
1216
1665
  function errText(v) {
1217
1666
  if (typeof v === "string") return v.slice(0, 2e3);
1218
1667
  if (v && typeof v === "object") {
@@ -1252,12 +1701,12 @@ function claudeLive(dir) {
1252
1701
  const map = /* @__PURE__ */ new Map();
1253
1702
  let names = [];
1254
1703
  try {
1255
- names = readdirSync3(path7.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
1704
+ names = readdirSync3(path8.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
1256
1705
  } catch {
1257
1706
  }
1258
1707
  for (const n of names) {
1259
1708
  try {
1260
- const e = registryEntry(JSON.parse(readFileSync3(path7.join(dir, "sessions", n), "utf8")));
1709
+ const e = registryEntry(JSON.parse(readFileSync3(path8.join(dir, "sessions", n), "utf8")));
1261
1710
  if (e?.sessionId) map.set(e.sessionId, e);
1262
1711
  } catch {
1263
1712
  }
@@ -1268,7 +1717,7 @@ function claudeLive(dir) {
1268
1717
  function claudeLiveByPid(dir, pid) {
1269
1718
  if (!pid) return null;
1270
1719
  try {
1271
- return registryEntry(JSON.parse(readFileSync3(path7.join(dir, "sessions", `${pid}.json`), "utf8")));
1720
+ return registryEntry(JSON.parse(readFileSync3(path8.join(dir, "sessions", `${pid}.json`), "utf8")));
1272
1721
  } catch {
1273
1722
  return null;
1274
1723
  }
@@ -1276,7 +1725,7 @@ function claudeLiveByPid(dir, pid) {
1276
1725
  function claudePid(env = process.env) {
1277
1726
  try {
1278
1727
  const dirs = claudeDirs(env);
1279
- const has = (pid2) => dirs.some((d) => existsSync3(path7.join(d, "sessions", `${pid2}.json`)));
1728
+ const has = (pid2) => dirs.some((d) => existsSync3(path8.join(d, "sessions", `${pid2}.json`)));
1280
1729
  let pid = process.ppid;
1281
1730
  if (has(pid)) return pid;
1282
1731
  const parent = parentOf();
@@ -1315,26 +1764,61 @@ function parentOf() {
1315
1764
  }
1316
1765
  return (pid) => table.get(pid) || 0;
1317
1766
  }
1318
- var accountCache = /* @__PURE__ */ new Map();
1319
- function claudeAccount(dir) {
1320
- const hit = accountCache.get(dir);
1321
- if (hit !== void 0) return hit;
1322
- 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;
1767
+ var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
1768
+ function claudeLogin(dir, home = homeDir()) {
1769
+ const files = [path8.join(dir, ".claude.json")];
1770
+ if (path8.resolve(dir) === path8.join(home, ".claude")) files.unshift(path8.join(home, ".claude.json"));
1325
1771
  for (const f of files) {
1326
1772
  try {
1327
1773
  const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
1328
- if (a?.accountUuid) {
1329
- acct = String(a.accountUuid);
1330
- break;
1774
+ if (typeof a?.accountUuid === "string" && a.accountUuid) {
1775
+ const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
1776
+ return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
1331
1777
  }
1332
1778
  } catch {
1333
1779
  }
1334
1780
  }
1781
+ return apiAccount(dir);
1782
+ }
1783
+ var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
1784
+ function apiAccount(dir) {
1785
+ let base;
1786
+ try {
1787
+ base = JSON.parse(readFileSync3(settingsIn(dir), "utf8")).env?.ANTHROPIC_BASE_URL;
1788
+ } catch {
1789
+ return null;
1790
+ }
1791
+ if (typeof base !== "string") return null;
1792
+ let u;
1793
+ try {
1794
+ u = new URL(base.trim());
1795
+ } catch {
1796
+ return null;
1797
+ }
1798
+ if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
1799
+ return null;
1800
+ const origin = `${u.protocol}//${u.host}`;
1801
+ return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
1802
+ }
1803
+ var accountCache = /* @__PURE__ */ new Map();
1804
+ function claudeAccount(dir) {
1805
+ const hit = accountCache.get(dir);
1806
+ if (hit !== void 0) return hit;
1807
+ const acct = claudeLogin(dir)?.id ?? null;
1335
1808
  accountCache.set(dir, acct);
1336
1809
  return acct;
1337
1810
  }
1811
+ function claudeAccounts(dirs, home = homeDir()) {
1812
+ const out = [];
1813
+ for (const d of dirs) {
1814
+ const l = claudeLogin(d, home);
1815
+ if (!l) continue;
1816
+ const seen = out.find((a) => a.id === l.id);
1817
+ if (!seen) out.push({ harness: NAME2, ...l });
1818
+ else if (!seen.email && l.email) seen.email = l.email;
1819
+ }
1820
+ return out;
1821
+ }
1338
1822
  var LINK_RE = /^https:\/\/claude\.ai\/code\/session_[A-Za-z0-9]+$/;
1339
1823
  function transcriptLink(records) {
1340
1824
  for (let i = records.length - 1; i >= 0; i--) {
@@ -1405,7 +1889,7 @@ function claudeFacts(file, sessionId, hints, state) {
1405
1889
  const H = parseLines(head);
1406
1890
  const T = parseLines(tail);
1407
1891
  const out = {};
1408
- const id = sessionId || path7.basename(file, ".jsonl");
1892
+ const id = sessionId || path8.basename(file, ".jsonl");
1409
1893
  const mem = state.get(NAME2, id);
1410
1894
  let custom = null;
1411
1895
  let ai = null;
@@ -1489,8 +1973,12 @@ function claudeFacts(file, sessionId, hints, state) {
1489
1973
  var claude_code_control_exports = {};
1490
1974
  __export(claude_code_control_exports, {
1491
1975
  JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
1976
+ ceilingLevel: () => ceilingLevel,
1977
+ claudeArgs: () => claudeArgs,
1492
1978
  claudeEnv: () => claudeEnv,
1493
- claudeLogin: () => claudeLogin,
1979
+ claudeLogin: () => claudeLogin2,
1980
+ cmpVersion: () => cmpVersion,
1981
+ defaultLevel: () => defaultLevel2,
1494
1982
  detectCaps: () => detectCaps,
1495
1983
  findClaude: () => findClaude,
1496
1984
  findTranscript: () => findTranscript,
@@ -1499,6 +1987,8 @@ __export(claude_code_control_exports, {
1499
1987
  loginCommand: () => loginCommand,
1500
1988
  loginHint: () => loginHint,
1501
1989
  modeFlags: () => modeFlags,
1990
+ offeredLevels: () => offeredLevels2,
1991
+ outputArgs: () => outputArgs,
1502
1992
  parseAuthStatus: () => parseAuthStatus,
1503
1993
  parseCaps: () => parseCaps,
1504
1994
  parseResult: () => parseResult,
@@ -1508,8 +1998,22 @@ __export(claude_code_control_exports, {
1508
1998
  });
1509
1999
  import { execFile, execFileSync as execFileSync3 } from "node:child_process";
1510
2000
  import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync5 } from "node:fs";
1511
- import os3 from "node:os";
1512
- import path8 from "node:path";
2001
+ import os4 from "node:os";
2002
+ import path9 from "node:path";
2003
+
2004
+ // src/control/verify.ts
2005
+ var GRANT_MAX_MS = 24 * 36e5;
2006
+ var SKEW_MS = 5 * 6e4;
2007
+ var MACHINE_MAX_AGE_MS = 24 * 36e5;
2008
+ var RECEIVER_MAX_AGE_MS = 10 * 6e4;
2009
+ var NONCE_TTL_MS = 25 * 36e5;
2010
+ var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
2011
+ var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
2012
+ var levelRank = (l) => START_PERMISSIONS.indexOf(l);
2013
+ var enc = new TextEncoder();
2014
+ var dec = new TextDecoder();
2015
+
2016
+ // src/adapters/claude-code-control.ts
1513
2017
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1514
2018
  var LIVE_WRITE_MS = 9e4;
1515
2019
  var cmpVersion = (a, b) => {
@@ -1519,28 +2023,28 @@ var cmpVersion = (a, b) => {
1519
2023
  if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
1520
2024
  return 0;
1521
2025
  };
1522
- function findClaude(env = process.env, home = os3.homedir()) {
2026
+ function findClaude(env = process.env, home = os4.homedir()) {
1523
2027
  if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
1524
2028
  const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
1525
- for (const d of String(env.PATH || "").split(path8.delimiter)) {
1526
- if (!d || !path8.isAbsolute(d)) continue;
1527
- for (const n of names) if (isExe(path8.join(d, n))) return path8.join(d, n);
2029
+ for (const d of String(env.PATH || "").split(path9.delimiter)) {
2030
+ if (!d || !path9.isAbsolute(d)) continue;
2031
+ for (const n of names) if (isExe(path9.join(d, n))) return path9.join(d, n);
1528
2032
  }
1529
2033
  for (const p of [
1530
- path8.join(home, ".claude", "local", "claude"),
1531
- path8.join(home, ".local", "bin", "claude"),
2034
+ path9.join(home, ".claude", "local", "claude"),
2035
+ path9.join(home, ".local", "bin", "claude"),
1532
2036
  "/opt/homebrew/bin/claude",
1533
2037
  "/usr/local/bin/claude"
1534
2038
  ])
1535
2039
  if (isExe(p)) return p;
1536
- const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
2040
+ const app = path9.join(home, "Library", "Application Support", "Claude", "claude-code");
1537
2041
  let versions = [];
1538
2042
  try {
1539
2043
  versions = readdirSync4(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
1540
2044
  } catch {
1541
2045
  }
1542
2046
  for (const v of versions) {
1543
- const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
2047
+ const p = path9.join(app, v, "claude.app", "Contents", "MacOS", "claude");
1544
2048
  if (isExe(p)) return p;
1545
2049
  }
1546
2050
  return null;
@@ -1566,11 +2070,19 @@ function parseCaps(help) {
1566
2070
  stream: !!optionBlock(help, "--include-partial-messages")
1567
2071
  };
1568
2072
  }
1569
- function modeFlags(mode, caps) {
1570
- if (mode === "auto") {
1571
- if (!caps.modes.includes("auto"))
1572
- return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
1573
- return { args: ["--permission-mode", "auto"] };
2073
+ var LEVEL_MODE = {
2074
+ read: "plan",
2075
+ edits: "acceptEdits",
2076
+ auto: "auto"
2077
+ };
2078
+ function modeFlags(level, caps) {
2079
+ if (level === "inherit") return { args: [] };
2080
+ if (level === "auto" && !caps.modes.includes("auto"))
2081
+ return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
2082
+ if (level !== "safe") {
2083
+ const m = LEVEL_MODE[level];
2084
+ if (!caps.modes.includes(m)) return { error: `this machine's Claude Code has no ${m} permission mode` };
2085
+ return { args: ["--permission-mode", m] };
1574
2086
  }
1575
2087
  if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
1576
2088
  const args = [];
@@ -1582,6 +2094,32 @@ function modeFlags(mode, caps) {
1582
2094
  if (caps.promptsNone) args.push("--permission-prompts", "none");
1583
2095
  return { args };
1584
2096
  }
2097
+ var ceilingLevel = (mode) => mode === "inherit" ? "safe" : mode;
2098
+ function offeredLevels2(mode, caps) {
2099
+ return START_PERMISSIONS.filter(
2100
+ (l) => levelRank(l) <= levelRank(ceilingLevel(mode)) && (l === "safe" || caps.modes.includes(LEVEL_MODE[l]))
2101
+ );
2102
+ }
2103
+ var defaultLevel2 = (mode) => mode;
2104
+ function outputArgs(caps) {
2105
+ return caps.stream ? ["--output-format", "stream-json", "--verbose", "--include-partial-messages"] : ["--output-format", "json"];
2106
+ }
2107
+ function claudeArgs(o) {
2108
+ const mf = modeFlags(o.level, o.caps);
2109
+ if ("error" in mf) return mf;
2110
+ const where = o.resume ? ["--resume", o.resume, ...o.fork ? ["--fork-session"] : []] : ["--session-id", o.session];
2111
+ return {
2112
+ args: [
2113
+ "-p",
2114
+ ...outputArgs(o.caps),
2115
+ ...mf.args,
2116
+ ...o.model ? ["--model", o.model] : [],
2117
+ ...o.allowedTools?.length ? [`--allowedTools=${o.allowedTools.join(",")}`] : [],
2118
+ ...where,
2119
+ o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt
2120
+ ]
2121
+ };
2122
+ }
1585
2123
  function claudeEnv(configDir, env = process.env) {
1586
2124
  const e = { ...env };
1587
2125
  for (const k of [
@@ -1595,7 +2133,7 @@ function claudeEnv(configDir, env = process.env) {
1595
2133
  delete e[k];
1596
2134
  e.SESSIONPIPE_CONTROL_JOB = "1";
1597
2135
  if (configDir !== void 0) {
1598
- if (path8.resolve(configDir) === path8.resolve(os3.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
2136
+ if (path9.resolve(configDir) === path9.resolve(os4.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
1599
2137
  else e.CLAUDE_CONFIG_DIR = configDir;
1600
2138
  }
1601
2139
  return e;
@@ -1616,7 +2154,7 @@ function parseAuthStatus(stdout) {
1616
2154
  return { loggedIn: null };
1617
2155
  }
1618
2156
  }
1619
- function claudeLogin(bin, configDir, env = process.env) {
2157
+ function claudeLogin2(bin, configDir, env = process.env) {
1620
2158
  return new Promise((resolve) => {
1621
2159
  execFile(
1622
2160
  bin,
@@ -1628,12 +2166,12 @@ function claudeLogin(bin, configDir, env = process.env) {
1628
2166
  });
1629
2167
  }
1630
2168
  function loginCommand(configDir) {
1631
- const home = os3.homedir();
1632
- const isDefault = !configDir || path8.resolve(configDir) === path8.resolve(home, ".claude");
2169
+ const home = os4.homedir();
2170
+ const isDefault = !configDir || path9.resolve(configDir) === path9.resolve(home, ".claude");
1633
2171
  return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
1634
2172
  }
1635
2173
  function loginHint(configDir, platform = process.platform) {
1636
- const shown = configDir ? configDir.replace(os3.homedir(), "~") : "~/.claude";
2174
+ const shown = configDir ? configDir.replace(os4.homedir(), "~") : "~/.claude";
1637
2175
  const login = loginCommand(configDir);
1638
2176
  if (platform === "darwin")
1639
2177
  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.`;
@@ -1659,13 +2197,13 @@ function findTranscript(session, dirs) {
1659
2197
  for (const dir of dirs) {
1660
2198
  let projects = [];
1661
2199
  try {
1662
- projects = readdirSync4(path8.join(dir, "projects"), { withFileTypes: true });
2200
+ projects = readdirSync4(path9.join(dir, "projects"), { withFileTypes: true });
1663
2201
  } catch {
1664
2202
  continue;
1665
2203
  }
1666
2204
  for (const p of projects) {
1667
2205
  if (!p.isDirectory()) continue;
1668
- const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
2206
+ const file = path9.join(dir, "projects", p.name, `${session}.jsonl`);
1669
2207
  try {
1670
2208
  if (statSync5(file).isFile()) return { file, configDir: dir, project: p.name };
1671
2209
  } catch {
@@ -1687,7 +2225,7 @@ function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
1687
2225
  } catch {
1688
2226
  return null;
1689
2227
  }
1690
- if (!j || typeof j.cwd !== "string" || !path8.isAbsolute(j.cwd)) return null;
2228
+ if (!j || typeof j.cwd !== "string" || !path9.isAbsolute(j.cwd)) return null;
1691
2229
  if (projectName(j.cwd) === t.project) return j.cwd;
1692
2230
  first ??= j.cwd;
1693
2231
  count++;
@@ -1748,13 +2286,13 @@ function pidAlive(pid) {
1748
2286
  function liveProcess(session, configDir) {
1749
2287
  let names = [];
1750
2288
  try {
1751
- names = readdirSync4(path8.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
2289
+ names = readdirSync4(path9.join(configDir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
1752
2290
  } catch {
1753
2291
  return false;
1754
2292
  }
1755
2293
  for (const n of names) {
1756
2294
  try {
1757
- const j = JSON.parse(readFileSync4(path8.join(configDir, "sessions", n), "utf8"));
2295
+ const j = JSON.parse(readFileSync4(path9.join(configDir, "sessions", n), "utf8"));
1758
2296
  if (String(j.sessionId) !== session) continue;
1759
2297
  if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
1760
2298
  } catch {
@@ -1803,7 +2341,7 @@ var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
1803
2341
 
1804
2342
  // src/adapters/codex.ts
1805
2343
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
1806
- import path9 from "node:path";
2344
+ import path10 from "node:path";
1807
2345
 
1808
2346
  // src/adapters/codex-config.ts
1809
2347
  var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
@@ -1867,18 +2405,13 @@ function parseArgv(raw) {
1867
2405
  function readNotify(text) {
1868
2406
  const { head, tail, at } = split(text);
1869
2407
  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
- };
2408
+ if (m) {
2409
+ const raw = m[1];
2410
+ return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
2411
+ }
1879
2412
  const k = NOTIFY_KEY.exec(head);
1880
2413
  if (k) {
1881
- const rest = head.slice(k.index).split("\n")[0] ?? "";
2414
+ const rest = head.slice(k.index).split("\n")[0];
1882
2415
  return {
1883
2416
  raw: rest.replace(NOTIFY_KEY, "").trim(),
1884
2417
  argv: null,
@@ -1889,15 +2422,11 @@ function readNotify(text) {
1889
2422
  };
1890
2423
  }
1891
2424
  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
- };
2425
+ if (t) {
2426
+ const raw = t[1];
2427
+ if (OURS2.test(raw))
2428
+ return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
2429
+ }
1901
2430
  return null;
1902
2431
  }
1903
2432
  function dropLine(text, index, length) {
@@ -1916,9 +2445,7 @@ function withNotify(text, line) {
1916
2445
  if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
1917
2446
  return insertTopLevel(text, line);
1918
2447
  }
1919
- function replaceNotify(text, line) {
1920
- const n = readNotify(text);
1921
- if (!n || n.misplaced || !n.length) return null;
2448
+ function replaceNotify(text, n, line) {
1922
2449
  return text.slice(0, n.index) + line + text.slice(n.index + n.length);
1923
2450
  }
1924
2451
  function withoutNotify(text) {
@@ -1959,10 +2486,10 @@ var CODEX_EVENTS = [
1959
2486
  ];
1960
2487
  var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
1961
2488
  var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
1962
- var codexHome = (env) => env.CODEX_HOME || path9.join(HOME, ".codex");
1963
- var hooksFile2 = (env) => path9.join(codexHome(env), "hooks.json");
1964
- var configFile2 = (env) => path9.join(codexHome(env), "config.toml");
1965
- var sessionsDir = (env) => path9.join(codexHome(env), "sessions");
2489
+ var codexHome = (env) => env.CODEX_HOME || path10.join(homeDir(), ".codex");
2490
+ var hooksFile2 = (env) => path10.join(codexHome(env), "hooks.json");
2491
+ var configFile2 = (env) => path10.join(codexHome(env), "config.toml");
2492
+ var sessionsDir = (env) => path10.join(codexHome(env), "sessions");
1966
2493
  var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1967
2494
  var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
1968
2495
  function installNotify(cmd, env) {
@@ -1976,7 +2503,7 @@ function installNotify(cmd, env) {
1976
2503
  const line = `notify = ${JSON.stringify(argv)}`;
1977
2504
  const n = readNotify(text);
1978
2505
  const write = (next) => {
1979
- mkdirSync3(path9.dirname(file), { recursive: true });
2506
+ mkdirSync3(path10.dirname(file), { recursive: true });
1980
2507
  writeFileSync3(file, next);
1981
2508
  };
1982
2509
  if (n && !n.other) {
@@ -1997,11 +2524,8 @@ function installNotify(cmd, env) {
1997
2524
  return { file, changed: false, note: "notify current (after Codex Computer Use)" };
1998
2525
  }
1999
2526
  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
- }
2527
+ write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
2528
+ return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
2005
2529
  }
2006
2530
  }
2007
2531
  return {
@@ -2030,8 +2554,7 @@ function uninstallNotify(env) {
2030
2554
  }
2031
2555
  const prev = previousNotify(n.argv);
2032
2556
  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);
2557
+ writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
2035
2558
  return { file, changed: true };
2036
2559
  }
2037
2560
  return { file, changed: false };
@@ -2047,7 +2570,7 @@ function findRollout(threadId, env = process.env) {
2047
2570
  return;
2048
2571
  }
2049
2572
  for (const n of names.sort().reverse()) {
2050
- const p = path9.join(dir, n);
2573
+ const p = path10.join(dir, n);
2051
2574
  if (n.endsWith(`-${threadId}.jsonl`)) {
2052
2575
  best = p;
2053
2576
  return;
@@ -2061,6 +2584,13 @@ function findRollout(threadId, env = process.env) {
2061
2584
  walk(sessionsDir(env), 0);
2062
2585
  return best;
2063
2586
  }
2587
+ function rolloutMeta(file) {
2588
+ return scanLines(file, (line) => parseLines([line])[0] ?? {});
2589
+ }
2590
+ function ofAnotherThread(file, id) {
2591
+ 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);
2592
+ return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
2593
+ }
2064
2594
  var codex = {
2065
2595
  name: NAME3,
2066
2596
  events: CODEX_EVENTS,
@@ -2103,11 +2633,14 @@ var codex = {
2103
2633
  if (event === "notify") {
2104
2634
  const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
2105
2635
  try {
2106
- s = raw ? JSON.parse(raw) : {};
2636
+ const j = raw ? JSON.parse(raw) : {};
2637
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
2107
2638
  } catch {
2108
2639
  }
2109
2640
  const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
2110
2641
  if (!id2) return null;
2642
+ const file = findRollout(String(id2), input.env);
2643
+ if (!file || codexSubagent(rolloutMeta(file))) return null;
2111
2644
  const r2 = {
2112
2645
  session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
2113
2646
  events: [
@@ -2117,18 +2650,19 @@ var codex = {
2117
2650
  harnessEvent: "notify"
2118
2651
  }
2119
2652
  ],
2120
- hints: {}
2653
+ hints: {},
2654
+ transcript: file
2121
2655
  };
2122
- const file = findRollout(String(id2), input.env);
2123
- if (file) r2.transcript = file;
2124
2656
  return r2;
2125
2657
  }
2126
2658
  try {
2127
- s = input.stdin ? JSON.parse(input.stdin) : {};
2659
+ const j = input.stdin ? JSON.parse(input.stdin) : {};
2660
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
2128
2661
  } catch {
2129
2662
  }
2130
- const id = s.session_id;
2663
+ const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
2131
2664
  if (!id) return null;
2665
+ if (s.transcript_path === null) return null;
2132
2666
  const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
2133
2667
  if (typeof s.model === "string") session.model = s.model;
2134
2668
  const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
@@ -2200,7 +2734,8 @@ var codex = {
2200
2734
  return null;
2201
2735
  }
2202
2736
  const r = { session, events, hints: {} };
2203
- if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
2737
+ if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
2738
+ r.transcript = s.transcript_path;
2204
2739
  return r;
2205
2740
  },
2206
2741
  readTranscript(file, fromLine) {
@@ -2218,16 +2753,17 @@ var codex = {
2218
2753
  return out;
2219
2754
  },
2220
2755
  backfill(sinceMs, env = process.env) {
2221
- const rows = [];
2756
+ const rows2 = [];
2222
2757
  for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
2223
- const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path9.basename(f));
2758
+ const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path10.basename(f));
2224
2759
  if (!m?.[1]) continue;
2760
+ if (codexSubagent(rolloutMeta(f))) continue;
2225
2761
  const facts = codexFacts(f);
2226
2762
  if (!facts.started_at || !facts.last_at) continue;
2227
2763
  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 });
2764
+ rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
2229
2765
  }
2230
- return rows;
2766
+ return rows2;
2231
2767
  }
2232
2768
  };
2233
2769
  function prune3(o) {
@@ -2236,10 +2772,11 @@ function prune3(o) {
2236
2772
  }
2237
2773
  function codexFacts(file) {
2238
2774
  const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
2239
- const H = parseLines(head);
2240
- const T = parseLines(tail);
2775
+ let H = parseLines(head);
2776
+ let T = parseLines(tail);
2241
2777
  const out = {};
2242
2778
  const meta = H.find((j) => j.type === "session_meta" && j.payload);
2779
+ const boundary = codexReplayBoundary(meta);
2243
2780
  if (meta?.payload) {
2244
2781
  const p = meta.payload;
2245
2782
  if (p.cwd) out.cwd = p.cwd;
@@ -2247,6 +2784,13 @@ function codexFacts(file) {
2247
2784
  if (p.git?.branch) out.branch = p.git.branch;
2248
2785
  if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
2249
2786
  }
2787
+ if (boundary) {
2788
+ const h = H.findIndex(boundary);
2789
+ const t = T.findIndex(boundary);
2790
+ H = h < 0 ? [] : H.slice(h);
2791
+ const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
2792
+ T = t >= 0 ? T.slice(t) : found ? T : [];
2793
+ }
2250
2794
  const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
2251
2795
  if (ctx?.payload?.model) out.model = ctx.payload.model;
2252
2796
  for (const j of H) {
@@ -2254,12 +2798,12 @@ function codexFacts(file) {
2254
2798
  if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
2255
2799
  const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
2256
2800
  if (!text || codexInjected(text)) continue;
2257
- const line = text.split("\n").find((l) => l.trim()) ?? "";
2801
+ const line = text.split("\n")[0];
2258
2802
  out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
2259
2803
  out.title_source = "first-ask";
2260
2804
  break;
2261
2805
  }
2262
- const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
2806
+ const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
2263
2807
  if (times.length) {
2264
2808
  out.started_at = Math.min(...times);
2265
2809
  out.last_at = Math.max(...times);
@@ -2278,38 +2822,71 @@ __export(codex_control_exports, {
2278
2822
  codexLine: () => codexLine,
2279
2823
  codexLogin: () => codexLogin,
2280
2824
  codexModeFlags: () => codexModeFlags,
2825
+ codexModels: () => codexModels,
2826
+ defaultLevel: () => defaultLevel3,
2281
2827
  findCodex: () => findCodex,
2282
2828
  findRollout: () => findRollout,
2829
+ offeredLevels: () => offeredLevels3,
2283
2830
  reading: () => reading2
2284
2831
  });
2285
2832
  import { execFile as execFile2 } from "node:child_process";
2286
- import os4 from "node:os";
2287
- import path10 from "node:path";
2833
+ import { readFileSync as readFileSync6 } from "node:fs";
2834
+ import os5 from "node:os";
2835
+ import path11 from "node:path";
2288
2836
  var LABEL2 = "Codex";
2289
- function findCodex(env = process.env, home = os4.homedir(), nodeDir = path10.dirname(process.execPath)) {
2837
+ function findCodex(env = process.env, home = os5.homedir(), nodeDir = path11.dirname(process.execPath)) {
2290
2838
  return findBinary({
2291
2839
  override: env.SESSIONPIPE_CODEX,
2292
2840
  names: process.platform === "win32" ? ["codex.exe", "codex.cmd"] : ["codex"],
2293
2841
  env,
2294
- dirs: [nodeDir, path10.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
2842
+ dirs: [nodeDir, path11.join(home, ".local", "bin"), "/opt/homebrew/bin", "/usr/local/bin"]
2295
2843
  });
2296
2844
  }
2297
- function codexModeFlags(mode, resume = false) {
2845
+ function codexModeFlags(level, resume = false) {
2846
+ if (level === "inherit") return [];
2847
+ const sandbox = level === "read" ? "read-only" : "workspace-write";
2298
2848
  if (resume)
2299
- return mode === "auto" ? [
2849
+ return level === "auto" ? [
2300
2850
  "-c",
2301
2851
  'sandbox_mode="workspace-write"',
2302
2852
  "-c",
2303
2853
  'approval_policy="on-request"',
2304
2854
  "-c",
2305
2855
  'approvals_reviewer="auto_review"'
2306
- ] : ["-c", 'sandbox_mode="workspace-write"'];
2307
- return mode === "auto" ? ["--approve-for-me"] : ["-s", "workspace-write"];
2856
+ ] : ["-c", `sandbox_mode="${sandbox}"`];
2857
+ return level === "auto" ? ["--approve-for-me"] : ["-s", sandbox];
2308
2858
  }
2859
+ var offeredLevels3 = (mode) => mode === "auto" ? ["read", "safe", "auto"] : ["read", "safe"];
2860
+ var defaultLevel3 = (mode) => mode;
2309
2861
  function codexArgs(o) {
2310
2862
  const common = ["--json", "--skip-git-repo-check"];
2311
- if (o.resume) return ["exec", "resume", ...common, ...codexModeFlags(o.mode, true), "--", o.resume, o.prompt];
2312
- return ["exec", ...common, ...o.cwd ? ["-C", o.cwd] : [], ...codexModeFlags(o.mode), "--", o.prompt];
2863
+ const model = o.model ? ["-m", o.model] : [];
2864
+ if (o.resume)
2865
+ return ["exec", "resume", ...common, ...codexModeFlags(o.permission, true), ...model, "--", o.resume, o.prompt];
2866
+ return [
2867
+ "exec",
2868
+ ...common,
2869
+ ...o.cwd ? ["-C", o.cwd] : [],
2870
+ ...codexModeFlags(o.permission),
2871
+ ...model,
2872
+ "--",
2873
+ o.prompt
2874
+ ];
2875
+ }
2876
+ function codexModels(env = process.env, home = os5.homedir()) {
2877
+ try {
2878
+ const file = path11.join(env.CODEX_HOME || path11.join(home, ".codex"), "models_cache.json");
2879
+ const j = JSON.parse(readFileSync6(file, "utf8"));
2880
+ if (!Array.isArray(j.models)) return [];
2881
+ const out = [];
2882
+ for (const m of j.models)
2883
+ if (m && m.visibility === "list" && typeof m.slug === "string" && MODEL_RE.test(m.slug) && m.slug.length <= 100) {
2884
+ if (!out.includes(m.slug)) out.push(m.slug);
2885
+ }
2886
+ return out.slice(0, 50);
2887
+ } catch {
2888
+ return [];
2889
+ }
2313
2890
  }
2314
2891
  var codexEnv = (env = process.env) => jobEnv(env);
2315
2892
  var LOGIN_HINT = "Codex isn't signed in on this machine: run `codex login --device-auth` there.";
@@ -2336,22 +2913,22 @@ function codexLine(line) {
2336
2913
  return null;
2337
2914
  }
2338
2915
  if (!j || typeof j !== "object") return null;
2339
- const str = (v) => typeof v === "string" ? v : "";
2916
+ const str2 = (v) => typeof v === "string" ? v : "";
2340
2917
  switch (j.type) {
2341
2918
  case "thread.started":
2342
- return str(j.thread_id) ? { session: str(j.thread_id) } : null;
2919
+ return str2(j.thread_id) ? { session: str2(j.thread_id) } : null;
2343
2920
  case "item.completed": {
2344
2921
  const it = j.item;
2345
- if (it?.type === "agent_message" && str(it.text)) return { message: str(it.text) };
2346
- if (it?.type === "error" && str(it.message)) return { warning: str(it.message) };
2922
+ if (it?.type === "agent_message" && str2(it.text)) return { message: str2(it.text) };
2923
+ if (it?.type === "error" && str2(it.message)) return { warning: str2(it.message) };
2347
2924
  return null;
2348
2925
  }
2349
2926
  case "turn.completed":
2350
2927
  return { done: true };
2351
2928
  case "turn.failed":
2352
- return { done: true, failed: str(j.error?.message) || "Codex reported an error" };
2929
+ return { done: true, failed: str2(j.error?.message) || "Codex reported an error" };
2353
2930
  case "error":
2354
- return str(j.message) ? { note: str(j.message) } : null;
2931
+ return str2(j.message) ? { note: str2(j.message) } : null;
2355
2932
  default:
2356
2933
  return null;
2357
2934
  }
@@ -2401,11 +2978,432 @@ function codexStderr(stderr) {
2401
2978
  }
2402
2979
  var reading2 = () => new CodexReading();
2403
2980
 
2404
- // src/adapters/gemini-cli.ts
2981
+ // src/adapters/devin.ts
2982
+ import { createHash as createHash2 } from "node:crypto";
2983
+ import { existsSync as existsSync6, readlinkSync } from "node:fs";
2984
+ import path12 from "node:path";
2985
+
2986
+ // src/readers/sqlite.ts
2405
2987
  import { execFileSync as execFileSync4 } from "node:child_process";
2406
- import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
2407
- import path11 from "node:path";
2408
- var NAME4 = "gemini-cli";
2988
+ import { existsSync as existsSync5 } from "node:fs";
2989
+ import { createRequire } from "node:module";
2990
+ function quiet(fn) {
2991
+ const original = process.emitWarning;
2992
+ try {
2993
+ process.emitWarning = ((warning, ...rest) => {
2994
+ const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
2995
+ if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
2996
+ original.call(process, warning, ...rest);
2997
+ });
2998
+ return fn();
2999
+ } finally {
3000
+ process.emitWarning = original;
3001
+ }
3002
+ }
3003
+ var ctorCache;
3004
+ function dbCtor() {
3005
+ if (ctorCache !== void 0) return ctorCache;
3006
+ ctorCache = null;
3007
+ try {
3008
+ const req = createRequire(import.meta.url);
3009
+ const m = quiet(() => req("node:sqlite"));
3010
+ if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
3011
+ } catch {
3012
+ }
3013
+ return ctorCache;
3014
+ }
3015
+ function plain(row) {
3016
+ if (!row || typeof row !== "object") return null;
3017
+ const out = {};
3018
+ for (const [k, v] of Object.entries(row)) {
3019
+ if (typeof v === "bigint") out[k] = Number(v);
3020
+ else if (v instanceof Uint8Array) out[k] = null;
3021
+ else out[k] = v;
3022
+ }
3023
+ return out;
3024
+ }
3025
+ var rows = (v) => {
3026
+ if (!Array.isArray(v)) return null;
3027
+ const out = [];
3028
+ for (const r of v) {
3029
+ const p = plain(r);
3030
+ if (p) out.push(p);
3031
+ }
3032
+ return out;
3033
+ };
3034
+ function viaNode(file, sql, params, o) {
3035
+ const C = dbCtor();
3036
+ if (!C) return null;
3037
+ let db;
3038
+ try {
3039
+ db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
3040
+ } catch {
3041
+ return null;
3042
+ }
3043
+ try {
3044
+ try {
3045
+ db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
3046
+ } catch {
3047
+ }
3048
+ return quiet(() => rows(db.prepare(sql).all(...params)));
3049
+ } catch {
3050
+ return null;
3051
+ } finally {
3052
+ try {
3053
+ db.close();
3054
+ } catch {
3055
+ }
3056
+ }
3057
+ }
3058
+ var CHILD = `const { DatabaseSync } = require("node:sqlite");
3059
+ const [, file, sql, params, ms] = process.argv;
3060
+ const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
3061
+ try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
3062
+ const out = db.prepare(sql).all(...JSON.parse(params));
3063
+ db.close();
3064
+ process.stdout.write(JSON.stringify(out));`;
3065
+ function viaChild(file, sql, params, o) {
3066
+ try {
3067
+ const out = execFileSync4(
3068
+ process.execPath,
3069
+ ["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
3070
+ {
3071
+ encoding: "utf8",
3072
+ timeout: o.timeoutMs + 3e3,
3073
+ stdio: ["ignore", "pipe", "ignore"],
3074
+ maxBuffer: o.maxBuffer,
3075
+ windowsHide: true
3076
+ }
3077
+ );
3078
+ return rows(JSON.parse(out || "null"));
3079
+ } catch {
3080
+ return null;
3081
+ }
3082
+ }
3083
+ var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
3084
+ function inlineParams(sql, params) {
3085
+ if (sql.includes("'")) return null;
3086
+ const holes = sql.split("?").length - 1;
3087
+ if (holes !== params.length) return null;
3088
+ let i = 0;
3089
+ let bad = false;
3090
+ const out = sql.replace(/\?/g, () => {
3091
+ const p = params[i++];
3092
+ if (typeof p === "number") {
3093
+ if (!Number.isFinite(p)) {
3094
+ bad = true;
3095
+ return "NULL";
3096
+ }
3097
+ return String(p);
3098
+ }
3099
+ if (typeof p !== "string" || !SAFE.test(p)) {
3100
+ bad = true;
3101
+ return "NULL";
3102
+ }
3103
+ return `'${p}'`;
3104
+ });
3105
+ return bad ? null : out;
3106
+ }
3107
+ function viaCli(file, sql, params, o) {
3108
+ const text = inlineParams(sql, params);
3109
+ if (!text) return null;
3110
+ try {
3111
+ const out = execFileSync4("sqlite3", ["-readonly", "-json", file, text], {
3112
+ encoding: "utf8",
3113
+ timeout: o.timeoutMs + 3e3,
3114
+ stdio: ["ignore", "pipe", "ignore"],
3115
+ maxBuffer: o.maxBuffer,
3116
+ windowsHide: true
3117
+ }).trim();
3118
+ if (!out) return [];
3119
+ return rows(JSON.parse(out));
3120
+ } catch {
3121
+ return null;
3122
+ }
3123
+ }
3124
+ function querySqlite(file, sql, params = [], opts = {}) {
3125
+ const o = {
3126
+ timeoutMs: opts.timeoutMs ?? 2e3,
3127
+ engine: opts.engine ?? "auto",
3128
+ maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
3129
+ };
3130
+ if (!file) return null;
3131
+ try {
3132
+ if (!existsSync5(file)) return null;
3133
+ } catch {
3134
+ return null;
3135
+ }
3136
+ const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
3137
+ for (const way of ways) {
3138
+ const r = way(file, sql, params, o);
3139
+ if (r) return r;
3140
+ }
3141
+ return null;
3142
+ }
3143
+
3144
+ // src/adapters/devin.ts
3145
+ var NAME4 = "devin";
3146
+ var DEVIN_EVENTS = [
3147
+ "SessionStart",
3148
+ "UserPromptSubmit",
3149
+ "PreToolUse",
3150
+ "PostToolUse",
3151
+ "PermissionRequest",
3152
+ "Stop",
3153
+ "PostCompaction",
3154
+ "SessionEnd"
3155
+ ];
3156
+ var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
3157
+ var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
3158
+ var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
3159
+ 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";
3160
+ 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)`;
3161
+ function foreignEvent(file) {
3162
+ const hooks = readJson(file).hooks;
3163
+ if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
3164
+ const ok = DEVIN_EVENTS;
3165
+ return Object.keys(hooks).find((k) => !ok.includes(k));
3166
+ }
3167
+ var homeOf = (env) => env.HOME || homeDir();
3168
+ var appData = (env) => env.APPDATA || path12.join(homeOf(env), "AppData", "Roaming");
3169
+ var devinConfigDir = (env = process.env) => process.platform === "win32" ? path12.join(appData(env), "devin") : path12.join(env.XDG_CONFIG_HOME || path12.join(homeOf(env), ".config"), "devin");
3170
+ var configFile3 = (env) => path12.join(devinConfigDir(env), "config.json");
3171
+ var devinDataDir = (env = process.env) => process.platform === "win32" ? path12.join(appData(env), "devin", "cli") : path12.join(env.XDG_DATA_HOME || path12.join(homeOf(env), ".local", "share"), "devin", "cli");
3172
+ var sessionsDb = (env) => path12.join(devinDataDir(env), "sessions.db");
3173
+ var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
3174
+ var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
3175
+ var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
3176
+ var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
3177
+ function errText2(v) {
3178
+ if (typeof v === "string" && v) return v.slice(0, 2e3);
3179
+ if (v && typeof v === "object") {
3180
+ const e = v;
3181
+ if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
3182
+ if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
3183
+ }
3184
+ return void 0;
3185
+ }
3186
+ var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
3187
+ var devin = {
3188
+ name: NAME4,
3189
+ events: DEVIN_EVENTS,
3190
+ detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
3191
+ configFiles: (env = process.env) => [configFile3(env)],
3192
+ install: (cmd, env = process.env, opts = {}) => {
3193
+ const file = configFile3(env);
3194
+ const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
3195
+ if (r.skipped) return [r];
3196
+ const bad = foreignEvent(file);
3197
+ return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
3198
+ },
3199
+ uninstall: (env = process.env, opts = {}) => [
3200
+ uninstallClaudeShaped(configFile3(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile3(env)) })
3201
+ ],
3202
+ installed: (cmd, env = process.env, opts = {}) => {
3203
+ const file = configFile3(env);
3204
+ const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
3205
+ const bad = state === "missing" ? void 0 : foreignEvent(file);
3206
+ return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
3207
+ },
3208
+ fromHook(input) {
3209
+ const event = input.argv[1] ?? "";
3210
+ let s = {};
3211
+ try {
3212
+ const j = input.stdin ? JSON.parse(input.stdin) : {};
3213
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
3214
+ } catch {
3215
+ }
3216
+ const id = typeof s.session_id === "string" ? s.session_id : "";
3217
+ if (!id) return null;
3218
+ const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
3219
+ const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
3220
+ const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
3221
+ const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
3222
+ const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
3223
+ const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
3224
+ const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
3225
+ const events = [];
3226
+ switch (event) {
3227
+ case "SessionStart":
3228
+ events.push(ev("session.started", { source: pickSource2(s.source) }));
3229
+ break;
3230
+ case "UserPromptSubmit":
3231
+ events.push(
3232
+ ev("turn.started", {
3233
+ turn_id,
3234
+ prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
3235
+ })
3236
+ );
3237
+ break;
3238
+ case "PreToolUse":
3239
+ if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
3240
+ break;
3241
+ case "PostToolUse":
3242
+ if (tool)
3243
+ events.push(
3244
+ ev("tool.ended", {
3245
+ tool,
3246
+ call_id,
3247
+ turn_id,
3248
+ // `ok` is required on tool.ended (PROTOCOL.md §events), so a response
3249
+ // that carries no success flag at all reads as "nothing said it failed".
3250
+ ok: response?.success !== false,
3251
+ error: errText2(response?.error),
3252
+ input: s.tool_input,
3253
+ output: response ? response.output : s.tool_response
3254
+ })
3255
+ );
3256
+ break;
3257
+ case "PermissionRequest":
3258
+ events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
3259
+ break;
3260
+ case "Stop":
3261
+ events.push(ev("turn.ended", { turn_id, reason: "stop" }));
3262
+ break;
3263
+ case "PostCompaction":
3264
+ events.push(ev("context.compacted", { trigger: "auto" }));
3265
+ break;
3266
+ case "SessionEnd":
3267
+ events.push(ev("session.ended", { reason: endReason3(s.reason) }));
3268
+ break;
3269
+ default:
3270
+ return null;
3271
+ }
3272
+ return {
3273
+ session,
3274
+ events,
3275
+ hints: {},
3276
+ transcript: transcriptRef(sessionsDb(input.env), id)
3277
+ };
3278
+ },
3279
+ readTranscript(file, fromLine) {
3280
+ const ref = splitRef(file);
3281
+ if (!ref) return { turns: [], line: fromLine };
3282
+ return readDevinChain(ref.db, ref.session, fromLine);
3283
+ },
3284
+ facts(session, transcript, _hints, _state, env = process.env) {
3285
+ const out = {};
3286
+ const version = devinVersion(env);
3287
+ if (version) out.harness_version = version;
3288
+ const account = devinAccount(env);
3289
+ if (account) out.account_id = account;
3290
+ const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
3291
+ if (!SESSION_ID.test(ref.session)) return out;
3292
+ const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
3293
+ const row = rows2?.[0];
3294
+ if (!row) return out;
3295
+ Object.assign(out, sessionFacts(row));
3296
+ return out;
3297
+ },
3298
+ backfill(sinceMs, env = process.env) {
3299
+ const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
3300
+ if (!rows2) return [];
3301
+ const out = [];
3302
+ for (const row of rows2) {
3303
+ const id = String(row.id);
3304
+ if (!SESSION_ID.test(id)) continue;
3305
+ const { started_at, last_at, ...rest } = sessionFacts(row);
3306
+ if (!started_at || !last_at) continue;
3307
+ out.push({ session: { id, ...rest }, started_at, last_at });
3308
+ }
3309
+ return out;
3310
+ }
3311
+ };
3312
+ function prune4(o) {
3313
+ for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
3314
+ return o;
3315
+ }
3316
+ var transcriptRef = (db, session) => `${db}#${session}`;
3317
+ function splitRef(ref) {
3318
+ if (!ref) return null;
3319
+ const at = ref.lastIndexOf("#");
3320
+ if (at <= 0) return null;
3321
+ const db = ref.slice(0, at);
3322
+ const session = ref.slice(at + 1);
3323
+ return db && SESSION_ID.test(session) ? { db, session } : null;
3324
+ }
3325
+ var CHAIN_LIMIT = 4e3;
3326
+ var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
3327
+ SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
3328
+ WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
3329
+ UNION ALL
3330
+ SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
3331
+ JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
3332
+ )
3333
+ SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
3334
+ var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
3335
+ (SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
3336
+ ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
3337
+ var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
3338
+ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
3339
+ WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
3340
+ ORDER BY s.last_activity_at DESC LIMIT 500`;
3341
+ function sessionFacts(row) {
3342
+ const out = {};
3343
+ if (typeof row.model === "string" && row.model) out.model = row.model;
3344
+ if (typeof row.working_directory === "string" && path12.isAbsolute(row.working_directory))
3345
+ out.cwd = row.working_directory;
3346
+ const started = Number(row.created_at);
3347
+ const last = Number(row.last_activity_at);
3348
+ if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
3349
+ if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
3350
+ if (typeof row.first_ask === "string") {
3351
+ const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
3352
+ if (line.trim()) {
3353
+ out.title = cut(line.trim(), 80);
3354
+ out.title_source = "first-ask";
3355
+ }
3356
+ }
3357
+ return out;
3358
+ }
3359
+ function readDevinChain(db, session, fromLine) {
3360
+ if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
3361
+ const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
3362
+ const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
3363
+ if (!rows2?.length) return { turns: [], line: fromLine };
3364
+ const msgs = [];
3365
+ let end = fromLine;
3366
+ for (const row of rows2) {
3367
+ const node = Number(row.node_id);
3368
+ if (!Number.isFinite(node)) continue;
3369
+ if (node + 1 > end) end = node + 1;
3370
+ if (node < fromLine) continue;
3371
+ let j;
3372
+ try {
3373
+ j = JSON.parse(String(row.chat_message ?? ""));
3374
+ } catch {
3375
+ continue;
3376
+ }
3377
+ const text = typeof j.content === "string" ? j.content.trim() : "";
3378
+ if (!text) continue;
3379
+ const secs = Number(row.created_at);
3380
+ const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
3381
+ if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
3382
+ else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
3383
+ }
3384
+ msgs.sort((a, b) => a.line - b.line);
3385
+ return pairTurns(msgs, end);
3386
+ }
3387
+ function devinVersion(env = process.env) {
3388
+ try {
3389
+ const target = readlinkSync(path12.join(devinDataDir(env), "_versions", "current"));
3390
+ const name = path12.basename(target);
3391
+ return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
3392
+ } catch {
3393
+ return void 0;
3394
+ }
3395
+ }
3396
+ function devinAccount(env = process.env) {
3397
+ const d = readJson(configFile3(env)).devin;
3398
+ const id = d && typeof d === "object" ? d.org_id : void 0;
3399
+ return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
3400
+ }
3401
+
3402
+ // src/adapters/gemini-cli.ts
3403
+ import { execFileSync as execFileSync5 } from "node:child_process";
3404
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
3405
+ import path13 from "node:path";
3406
+ var NAME5 = "gemini-cli";
2409
3407
  var GEMINI_EVENTS = [
2410
3408
  "SessionStart",
2411
3409
  "BeforeAgent",
@@ -2416,15 +3414,15 @@ var GEMINI_EVENTS = [
2416
3414
  "PreCompress",
2417
3415
  "SessionEnd"
2418
3416
  ];
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];
3417
+ var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
3418
+ var geminiDir = (env) => env.GEMINI_CLI_HOME || path13.join(homeDir(), ".gemini");
3419
+ var settings = (env) => path13.join(geminiDir(env), "settings.json");
3420
+ var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
3421
+ var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
2424
3422
  function geminiHooksActive(settingsFile) {
2425
3423
  let tools;
2426
3424
  try {
2427
- tools = JSON.parse(readFileSync6(settingsFile, "utf8")).tools;
3425
+ tools = JSON.parse(readFileSync7(settingsFile, "utf8")).tools;
2428
3426
  } catch {
2429
3427
  }
2430
3428
  if (tools?.enableHooks === true) return { active: true };
@@ -2432,7 +3430,7 @@ function geminiHooksActive(settingsFile) {
2432
3430
  return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
2433
3431
  let version = "";
2434
3432
  try {
2435
- version = execFileSync4("gemini", ["--version"], {
3433
+ version = execFileSync5("gemini", ["--version"], {
2436
3434
  encoding: "utf8",
2437
3435
  timeout: 4e3,
2438
3436
  stdio: ["ignore", "pipe", "ignore"]
@@ -2451,13 +3449,13 @@ function geminiHooksActive(settingsFile) {
2451
3449
  };
2452
3450
  }
2453
3451
  var geminiCli = {
2454
- name: NAME4,
3452
+ name: NAME5,
2455
3453
  events: GEMINI_EVENTS,
2456
3454
  // ~/.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")),
3455
+ detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path13.join(geminiDir(env), "tmp")),
2458
3456
  configFiles: (env = process.env) => [settings(env)],
2459
3457
  install: (cmd, env = process.env, opts = {}) => {
2460
- const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
3458
+ const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
2461
3459
  const a = geminiHooksActive(settings(env));
2462
3460
  return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
2463
3461
  },
@@ -2465,7 +3463,7 @@ var geminiCli = {
2465
3463
  uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
2466
3464
  ],
2467
3465
  installed: (cmd, env = process.env, opts = {}) => {
2468
- const state = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
3466
+ const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
2469
3467
  const a = geminiHooksActive(settings(env));
2470
3468
  return [
2471
3469
  {
@@ -2479,14 +3477,15 @@ var geminiCli = {
2479
3477
  const event = input.argv[1] ?? "";
2480
3478
  let s = {};
2481
3479
  try {
2482
- s = input.stdin ? JSON.parse(input.stdin) : {};
3480
+ const j = input.stdin ? JSON.parse(input.stdin) : {};
3481
+ if (j && typeof j === "object" && !Array.isArray(j)) s = j;
2483
3482
  } catch {
2484
3483
  }
2485
3484
  const id = s.session_id;
2486
3485
  if (!id) return null;
2487
3486
  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 });
3487
+ const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
3488
+ const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
2490
3489
  const events = [];
2491
3490
  switch (event) {
2492
3491
  case "SessionStart":
@@ -2551,7 +3550,7 @@ var geminiCli = {
2551
3550
  readTranscript(file, fromLine) {
2552
3551
  let text = "";
2553
3552
  try {
2554
- text = readFileSync6(file, "utf8");
3553
+ text = readFileSync7(file, "utf8");
2555
3554
  } catch {
2556
3555
  return { turns: [], line: fromLine };
2557
3556
  }
@@ -2567,7 +3566,7 @@ var geminiCli = {
2567
3566
  const raw = first ? first.content ?? first.message ?? first.text : void 0;
2568
3567
  const text = typeof raw === "string" ? raw.trim() : "";
2569
3568
  if (text) {
2570
- const line = text.split("\n").find((l) => l.trim()) ?? "";
3569
+ const line = text.split("\n")[0];
2571
3570
  out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
2572
3571
  out.title_source = "first-ask";
2573
3572
  }
@@ -2581,29 +3580,31 @@ var geminiCli = {
2581
3580
  backfill: () => []
2582
3581
  // 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
3582
  };
2584
- function prune4(o) {
3583
+ function prune5(o) {
2585
3584
  for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
2586
3585
  return o;
2587
3586
  }
2588
3587
 
2589
3588
  // src/adapters/index.ts
2590
- var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
3589
+ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
2591
3590
  var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
3591
+ var HELLO_HARNESSES_MAX = 20;
2592
3592
  function drivableHarnesses(env = process.env) {
2593
3593
  return [
2594
3594
  ...findClaude(env) ? ["claude-code"] : [],
2595
3595
  ...findCodex(env) ? ["codex"] : [],
2596
- ...findAgy(env) ? ["antigravity"] : []
2597
- ];
3596
+ ...findAgy(env) ? ["antigravity"] : [],
3597
+ ...acpHarnesses(env)
3598
+ ].slice(0, HELLO_HARNESSES_MAX);
2598
3599
  }
2599
3600
 
2600
3601
  // 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";
3602
+ import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "node:fs";
3603
+ import path14 from "node:path";
2603
3604
  var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
2604
3605
  function readConfig(file = configFile()) {
2605
3606
  try {
2606
- const j = JSON.parse(readFileSync7(file, "utf8"));
3607
+ const j = JSON.parse(readFileSync8(file, "utf8"));
2607
3608
  return {
2608
3609
  ...DEFAULT_CONFIG,
2609
3610
  ...j,
@@ -2615,10 +3616,10 @@ function readConfig(file = configFile()) {
2615
3616
  }
2616
3617
  }
2617
3618
  function writeConfig(c, file = configFile()) {
2618
- mkdirSync4(path12.dirname(file), { recursive: true, mode: 448 });
3619
+ mkdirSync4(path14.dirname(file), { recursive: true, mode: 448 });
2619
3620
  writeFileSync4(file, `${JSON.stringify(c, null, 2)}
2620
3621
  `, { mode: 384 });
2621
- if (process.platform !== "win32" && existsSync6(file)) {
3622
+ if (process.platform !== "win32" && existsSync8(file)) {
2622
3623
  try {
2623
3624
  chmodSync(file, 384);
2624
3625
  } catch {
@@ -2647,7 +3648,7 @@ function encodeTime(t) {
2647
3648
  function ulid(now = Date.now()) {
2648
3649
  if (now === lastTime) {
2649
3650
  for (let i = lastRand.length - 1; i >= 0; i--) {
2650
- const v = (lastRand[i] ?? 0) + 1;
3651
+ const v = lastRand[i] + 1;
2651
3652
  if (v < 32) {
2652
3653
  lastRand[i] = v;
2653
3654
  break;
@@ -2656,8 +3657,7 @@ function ulid(now = Date.now()) {
2656
3657
  }
2657
3658
  } else {
2658
3659
  lastTime = now;
2659
- const b = randomBytes(16);
2660
- lastRand = Array.from({ length: 16 }, (_, i) => (b[i] ?? 0) % 32);
3660
+ lastRand = Array.from(randomBytes(16), (b) => b % 32);
2661
3661
  }
2662
3662
  return encodeTime(now) + lastRand.map((v) => ALPHABET[v]).join("");
2663
3663
  }
@@ -2681,57 +3681,76 @@ function makeEvent(a, ctx) {
2681
3681
  };
2682
3682
  }
2683
3683
 
3684
+ // src/launched-by.ts
3685
+ var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
3686
+ var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
3687
+ function launchedBy(env) {
3688
+ const v = env[LAUNCHED_BY_ENV]?.trim();
3689
+ return v && LAUNCHED_BY.test(v) ? v : void 0;
3690
+ }
3691
+ function readHook(adapter, input) {
3692
+ const r = adapter.fromHook(input);
3693
+ if (r && !r.session.parent_id) {
3694
+ const launcher = launchedBy(input.env);
3695
+ if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
3696
+ }
3697
+ return r;
3698
+ }
3699
+
2684
3700
  // src/outbox.ts
2685
3701
  import {
2686
3702
  appendFileSync,
2687
3703
  closeSync as closeSync3,
2688
- existsSync as existsSync7,
3704
+ existsSync as existsSync9,
2689
3705
  mkdirSync as mkdirSync5,
2690
3706
  openSync as openSync3,
2691
3707
  readdirSync as readdirSync6,
2692
- readFileSync as readFileSync8,
3708
+ readFileSync as readFileSync9,
2693
3709
  readSync as readSync3,
2694
3710
  renameSync,
2695
3711
  statSync as statSync6,
2696
3712
  unlinkSync as unlinkSync3,
2697
3713
  writeFileSync as writeFileSync5
2698
3714
  } from "node:fs";
2699
- import path13 from "node:path";
3715
+ import path15 from "node:path";
3716
+ var READ_CHUNK = 1024 * 1024;
3717
+ var READ_MAX = 8 * 1024 * 1024;
2700
3718
  var Outbox = class {
2701
3719
  constructor(root) {
2702
3720
  this.root = root;
2703
3721
  }
2704
3722
  root;
2705
3723
  dir(harness) {
2706
- return path13.join(this.root, "outbox", safeId(harness));
3724
+ return path15.join(this.root, "outbox", safeId(harness));
2707
3725
  }
2708
3726
  file(ref) {
2709
- return path13.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
3727
+ return path15.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
2710
3728
  }
2711
3729
  seqFile(ref) {
2712
- return path13.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
3730
+ return path15.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
2713
3731
  }
2714
3732
  /** The next seq for a session, persisted (survives a hibernating machine and a
2715
3733
  * restart; an in-memory counter would restart at 0 and collide). */
2716
3734
  nextSeq(ref) {
2717
3735
  const f = this.seqFile(ref);
2718
- let n = 0;
3736
+ let n = -1;
2719
3737
  try {
2720
- n = Number(readFileSync8(f, "utf8")) || 0;
3738
+ const t = readFileSync9(f, "utf8").trim();
3739
+ if (/^\d+$/.test(t)) n = Number(t);
2721
3740
  } catch {
2722
- n = this.lastSeqInFile(ref) + 1;
2723
3741
  }
2724
- mkdirSync5(path13.dirname(f), { recursive: true, mode: 448 });
3742
+ if (n < 0) n = this.lastSeqInFile(ref) + 1;
3743
+ mkdirSync5(path15.dirname(f), { recursive: true, mode: 448 });
2725
3744
  writeFileSync5(f, String(n + 1), { mode: 384 });
2726
3745
  return n;
2727
3746
  }
2728
3747
  lastSeqInFile(ref) {
2729
3748
  const f = this.file(ref);
2730
- if (!existsSync7(f)) return -1;
2731
- const lines = readFileSync8(f, "utf8").trim().split("\n");
3749
+ if (!existsSync9(f)) return -1;
3750
+ const lines = readFileSync9(f, "utf8").trim().split("\n");
2732
3751
  for (let i = lines.length - 1; i >= 0; i--) {
2733
3752
  try {
2734
- const j = JSON.parse(lines[i] ?? "");
3753
+ const j = JSON.parse(lines[i]);
2735
3754
  if (typeof j.session?.seq === "number") return j.session.seq;
2736
3755
  } catch {
2737
3756
  }
@@ -2747,10 +3766,10 @@ var Outbox = class {
2747
3766
  /** Every session file, newest first. */
2748
3767
  sessions() {
2749
3768
  const out = [];
2750
- const base = path13.join(this.root, "outbox");
2751
- if (!existsSync7(base)) return [];
3769
+ const base = path15.join(this.root, "outbox");
3770
+ if (!existsSync9(base)) return [];
2752
3771
  for (const h of readdirSync6(base)) {
2753
- const d = path13.join(base, h);
3772
+ const d = path15.join(base, h);
2754
3773
  let names = [];
2755
3774
  try {
2756
3775
  names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
@@ -2759,7 +3778,7 @@ var Outbox = class {
2759
3778
  }
2760
3779
  for (const n of names) {
2761
3780
  try {
2762
- out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path13.join(d, n)).mtimeMs });
3781
+ out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path15.join(d, n)).mtimeMs });
2763
3782
  } catch {
2764
3783
  }
2765
3784
  }
@@ -2767,31 +3786,43 @@ var Outbox = class {
2767
3786
  return out.sort((a, b) => b.mtime - a.mtime).map((x) => x.ref);
2768
3787
  }
2769
3788
  /** 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. */
3789
+ * are read whole; a partial trailing line (a write in progress) is left. A read
3790
+ * takes READ_CHUNK bytes at a time, not the rest of the file: a session file grows
3791
+ * to tens of MB and a drain takes it in batches of 50. It reads on until the batch
3792
+ * is full or the file ends, so lines that parse to nothing never stop a drain; one
3793
+ * line longer than the chunk gets a larger one, up to READ_MAX. */
2771
3794
  read(ref, from, maxEvents = 500) {
2772
3795
  const f = this.file(ref);
2773
- if (!existsSync7(f)) return { events: [], offset: from };
3796
+ if (!existsSync9(f)) return { events: [], offset: from };
2774
3797
  const size = statSync6(f).size;
2775
3798
  if (from >= size) return { events: [], offset: from };
2776
3799
  const fd = openSync3(f, "r");
2777
3800
  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
3801
  const events = [];
2782
3802
  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 {
3803
+ let chunk = READ_CHUNK;
3804
+ while (events.length < maxEvents && offset < size) {
3805
+ const buf = Buffer.alloc(Math.min(size - offset, chunk));
3806
+ const n = readSync3(fd, buf, 0, buf.length, offset);
3807
+ const text = buf.subarray(0, n).toString("utf8");
3808
+ let pos = 0;
3809
+ let used = 0;
3810
+ while (events.length < maxEvents) {
3811
+ const nl = text.indexOf("\n", pos);
3812
+ if (nl < 0) break;
3813
+ const line = text.slice(pos, nl);
3814
+ pos = nl + 1;
3815
+ used += Buffer.byteLength(line, "utf8") + 1;
3816
+ if (!line.trim()) continue;
3817
+ try {
3818
+ events.push(JSON.parse(line));
3819
+ } catch {
3820
+ }
2794
3821
  }
3822
+ offset += used;
3823
+ if (used) continue;
3824
+ if (offset + n >= size || chunk >= READ_MAX) break;
3825
+ chunk *= 2;
2795
3826
  }
2796
3827
  return { events, offset };
2797
3828
  } finally {
@@ -2835,18 +3866,18 @@ var Cursors = class {
2835
3866
  root;
2836
3867
  sink;
2837
3868
  file() {
2838
- return path13.join(this.root, "cursors", `${safeId(this.sink)}.json`);
3869
+ return path15.join(this.root, "cursors", `${safeId(this.sink)}.json`);
2839
3870
  }
2840
3871
  read() {
2841
3872
  try {
2842
- return JSON.parse(readFileSync8(this.file(), "utf8"));
3873
+ return JSON.parse(readFileSync9(this.file(), "utf8"));
2843
3874
  } catch {
2844
3875
  return {};
2845
3876
  }
2846
3877
  }
2847
3878
  write(c) {
2848
3879
  const f = this.file();
2849
- mkdirSync5(path13.dirname(f), { recursive: true, mode: 448 });
3880
+ mkdirSync5(path15.dirname(f), { recursive: true, mode: 448 });
2850
3881
  const tmp = `${f}.${process.pid}.tmp`;
2851
3882
  writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
2852
3883
  renameSync(tmp, f);
@@ -2856,11 +3887,14 @@ var Cursors = class {
2856
3887
  }
2857
3888
  };
2858
3889
  function takeLock(root, ref, staleMs = 5 * 6e4) {
2859
- const dir = path13.join(root, "locks");
3890
+ return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
3891
+ }
3892
+ function takeNamedLock(root, name, staleMs = 5 * 6e4) {
3893
+ const dir = path15.join(root, "locks");
2860
3894
  mkdirSync5(dir, { recursive: true, mode: 448 });
2861
- const f = path13.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
3895
+ const f = path15.join(dir, `${name}.lock`);
2862
3896
  try {
2863
- if (Date.now() - statSync6(f).mtimeMs > staleMs) unlinkSync3(f);
3897
+ if (Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(readFileSync9(f, "utf8")))) unlinkSync3(f);
2864
3898
  else return null;
2865
3899
  } catch {
2866
3900
  }
@@ -2876,9 +3910,18 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
2876
3910
  }
2877
3911
  };
2878
3912
  }
3913
+ function processAlive(pid) {
3914
+ if (!Number.isInteger(pid) || pid <= 0) return true;
3915
+ try {
3916
+ process.kill(pid, 0);
3917
+ return true;
3918
+ } catch (e) {
3919
+ return e.code === "EPERM";
3920
+ }
3921
+ }
2879
3922
 
2880
3923
  // src/privacy/pii.ts
2881
- import { createHash as createHash2 } from "node:crypto";
3924
+ import { createHash as createHash3 } from "node:crypto";
2882
3925
  var RULESET_PII = "pii@1";
2883
3926
  var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
2884
3927
  var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
@@ -2900,7 +3943,7 @@ function reducePii(text, home) {
2900
3943
  return out;
2901
3944
  }
2902
3945
  function hashMachine(name) {
2903
- return createHash2("sha256").update(name).digest("hex").slice(0, 8);
3946
+ return createHash3("sha256").update(name).digest("hex").slice(0, 8);
2904
3947
  }
2905
3948
  function reducePiiDeep(v, home) {
2906
3949
  if (typeof v === "string") return reducePii(v, home);
@@ -2989,12 +4032,12 @@ function redactAssignments(text) {
2989
4032
  let last = 0;
2990
4033
  NAME_SEP.lastIndex = 0;
2991
4034
  for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
2992
- const name = m[1] ?? "";
4035
+ const name = m[1];
2993
4036
  if (!isSecretName(name)) continue;
2994
4037
  VALUE.lastIndex = NAME_SEP.lastIndex;
2995
4038
  const v = VALUE.exec(text);
2996
4039
  if (!v) continue;
2997
- const value = v[1] ?? v[2] ?? v[3] ?? v[4] ?? "";
4040
+ const value = v[1] ?? v[2] ?? v[3] ?? v[4];
2998
4041
  NAME_SEP.lastIndex = VALUE.lastIndex;
2999
4042
  if (isPlaceholder(value) || value.includes(REDACTED)) continue;
3000
4043
  if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
@@ -3050,7 +4093,7 @@ function redactSecrets(text) {
3050
4093
  let out = text;
3051
4094
  for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
3052
4095
  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}`);
4096
+ out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
3054
4097
  out = redactAssignments(out);
3055
4098
  out = out.replace(
3056
4099
  PROSE,
@@ -3082,6 +4125,7 @@ var TYPE_TIER = {
3082
4125
  "turn.started": 0,
3083
4126
  "turn.ended": 0,
3084
4127
  "turn.transcript": 2,
4128
+ "turn.progress": 2,
3085
4129
  "tool.started": 1,
3086
4130
  "tool.ended": 1,
3087
4131
  "files.changed": 1,
@@ -3098,9 +4142,10 @@ var DATA_ALLOW = {
3098
4142
  "session.ended": { 0: ["reason"] },
3099
4143
  "session.backfill": { 0: ["started_at", "last_at", "turns"] },
3100
4144
  "session.forgotten": { 0: [] },
3101
- "turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
3102
- "turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
4145
+ "turn.started": { 0: ["turn_id", "submitted_by", "injected"], 1: ["prompt_chars"] },
4146
+ "turn.ended": { 0: ["turn_id", "reason", "ms", "background"], 1: ["error"] },
3103
4147
  "turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
4148
+ "turn.progress": { 2: ["turn", "user", "assistant", "at"] },
3104
4149
  "tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
3105
4150
  "tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
3106
4151
  "files.changed": { 1: ["paths"] },
@@ -3122,7 +4167,9 @@ var SESSION_ALLOW = [
3122
4167
  "title_source",
3123
4168
  "url",
3124
4169
  "account_id",
3125
- "parent_id"
4170
+ "attended",
4171
+ "parent_id",
4172
+ "launched_by"
3126
4173
  ];
3127
4174
  var MAX_SIDE = 2e4;
3128
4175
  var MAX_IO = 64 * 1024;
@@ -3141,10 +4188,12 @@ function cap(v, max) {
3141
4188
  }
3142
4189
  return v;
3143
4190
  }
4191
+ function capTail(v, max) {
4192
+ return v.length > max ? `\u2026${v.slice(v.length - max + 1)}` : v;
4193
+ }
3144
4194
  function filterEvent(event, tier, pii, opts = {}) {
3145
4195
  const minTier = TYPE_TIER[event.type];
3146
4196
  if (minTier !== void 0 && minTier > tier) return null;
3147
- if (event.type === "native" && tier < 3) return null;
3148
4197
  const session = {};
3149
4198
  for (const k of SESSION_ALLOW)
3150
4199
  if (event.session[k] !== void 0)
@@ -3154,7 +4203,8 @@ function filterEvent(event, tier, pii, opts = {}) {
3154
4203
  if (keys === null) data = { ...event.data };
3155
4204
  else for (const k of keys) if (event.data[k] !== void 0) data[k] = event.data[k];
3156
4205
  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);
4206
+ if (typeof data.assistant === "string")
4207
+ data.assistant = event.type === "turn.progress" ? capTail(data.assistant, MAX_SIDE) : cap(data.assistant, MAX_SIDE);
3158
4208
  if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
3159
4209
  if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
3160
4210
  if (typeof data.message === "string") data.message = cap(data.message, 200);
@@ -3175,9 +4225,15 @@ function filterEvent(event, tier, pii, opts = {}) {
3175
4225
  return out;
3176
4226
  }
3177
4227
 
4228
+ // src/short-id.ts
4229
+ import { createHash as createHash4 } from "node:crypto";
4230
+ function shortId(harness, id) {
4231
+ return createHash4("sha256").update(`sessionpipe.session:${harness}:${id}`).digest("hex").slice(0, 8);
4232
+ }
4233
+
3178
4234
  // src/sinks/file.ts
3179
4235
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
3180
- import path14 from "node:path";
4236
+ import path16 from "node:path";
3181
4237
  var FileSink = class {
3182
4238
  constructor(name, file, tier, pii) {
3183
4239
  this.name = name;
@@ -3191,7 +4247,7 @@ var FileSink = class {
3191
4247
  pii;
3192
4248
  async send(events) {
3193
4249
  try {
3194
- mkdirSync6(path14.dirname(this.file), { recursive: true });
4250
+ mkdirSync6(path16.dirname(this.file), { recursive: true });
3195
4251
  appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
3196
4252
  `);
3197
4253
  return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
@@ -3215,8 +4271,12 @@ var StdoutSink = class {
3215
4271
  pii;
3216
4272
  write;
3217
4273
  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: [] };
4274
+ try {
4275
+ for (const e of events) this.write(JSON.stringify(e));
4276
+ return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
4277
+ } catch (e) {
4278
+ return { ok: false, acknowledged: 0, error: String(e.message ?? e), action: "retry" };
4279
+ }
3220
4280
  }
3221
4281
  };
3222
4282
 
@@ -3227,9 +4287,11 @@ export {
3227
4287
  AG_STALE_PRETOOL,
3228
4288
  Cursors,
3229
4289
  DEFAULT_CONFIG,
4290
+ DEVIN_EVENTS,
3230
4291
  FileSink,
3231
- HOME,
4292
+ HELLO_HARNESSES_MAX,
3232
4293
  JOB_TIMEOUT_MS,
4294
+ LAUNCHED_BY_ENV,
3233
4295
  Outbox,
3234
4296
  PROTOCOL_VERSION,
3235
4297
  REDACTED,
@@ -3237,10 +4299,13 @@ export {
3237
4299
  RULESET_SECRETS,
3238
4300
  StdoutSink,
3239
4301
  TYPE_TIER,
4302
+ acp_control_exports as acpControl,
3240
4303
  adapterByName,
3241
4304
  antigravity,
3242
4305
  antigravityAnswer,
3243
4306
  antigravity_control_exports as antigravityControl,
4307
+ capTail,
4308
+ claudeAccounts,
3244
4309
  claudeCode,
3245
4310
  claude_code_control_exports as claudeControl,
3246
4311
  claudeDirs,
@@ -3248,35 +4313,47 @@ export {
3248
4313
  codex_control_exports as codexControl,
3249
4314
  configFile,
3250
4315
  deniedNote,
4316
+ devin,
4317
+ devinConfigDir,
4318
+ devinDataDir,
3251
4319
  drivableHarnesses,
3252
4320
  filterEvent,
3253
4321
  geminiCli,
3254
4322
  gitFacts,
3255
4323
  hasSecret,
3256
4324
  hashMachine,
4325
+ homeDir,
3257
4326
  isInjected,
3258
4327
  isSecretName,
3259
4328
  isUlid,
3260
4329
  jobEnv,
4330
+ launchedBy,
3261
4331
  machineName,
3262
4332
  makeEvent,
4333
+ notClaudeCode,
3263
4334
  pairTurns,
4335
+ processAlive,
3264
4336
  readAntigravity,
3265
4337
  readClaude,
3266
4338
  readCodex,
3267
4339
  readConfig,
4340
+ readDevinChain,
3268
4341
  readGemini,
4342
+ readHook,
3269
4343
  redactDeep,
3270
4344
  redactSecrets,
3271
4345
  reducePii,
3272
4346
  reducePiiDeep,
3273
4347
  runHeadless,
3274
4348
  safeId,
4349
+ shortId,
3275
4350
  stateDir,
3276
4351
  stripCredentials,
3277
4352
  takeLock,
4353
+ takeNamedLock,
3278
4354
  tilde,
3279
4355
  ulid,
4356
+ untilde,
3280
4357
  writeConfig
3281
4358
  };
3282
4359
  //# sourceMappingURL=index.js.map