sessionpipe-core 0.9.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 (40) hide show
  1. package/dist/control.js +121 -12
  2. package/dist/control.js.map +2 -2
  3. package/dist/index.js +846 -308
  4. package/dist/index.js.map +4 -4
  5. package/dist/schema/v1.d.ts +266 -33
  6. package/dist/schema/v1.d.ts.map +1 -1
  7. package/dist/schema.js +128 -33
  8. package/dist/schema.js.map +2 -2
  9. package/dist/service.js +155 -0
  10. package/dist/service.js.map +7 -0
  11. package/dist/src/adapters/acp-control.d.ts +87 -0
  12. package/dist/src/adapters/acp-control.d.ts.map +1 -0
  13. package/dist/src/adapters/antigravity-control.d.ts +15 -5
  14. package/dist/src/adapters/antigravity-control.d.ts.map +1 -1
  15. package/dist/src/adapters/claude-code-control.d.ts +39 -4
  16. package/dist/src/adapters/claude-code-control.d.ts.map +1 -1
  17. package/dist/src/adapters/claude-code.d.ts.map +1 -1
  18. package/dist/src/adapters/codex-control.d.ts +23 -7
  19. package/dist/src/adapters/codex-control.d.ts.map +1 -1
  20. package/dist/src/adapters/codex.d.ts.map +1 -1
  21. package/dist/src/adapters/headless.d.ts +4 -1
  22. package/dist/src/adapters/headless.d.ts.map +1 -1
  23. package/dist/src/adapters/index.d.ts +7 -1
  24. package/dist/src/adapters/index.d.ts.map +1 -1
  25. package/dist/src/control/verify.d.ts +72 -16
  26. package/dist/src/control/verify.d.ts.map +1 -1
  27. package/dist/src/index.d.ts +2 -1
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/launched-by.d.ts +12 -0
  30. package/dist/src/launched-by.d.ts.map +1 -0
  31. package/dist/src/paths.d.ts +4 -0
  32. package/dist/src/paths.d.ts.map +1 -1
  33. package/dist/src/privacy/filter.d.ts.map +1 -1
  34. package/dist/src/service/index.d.ts +83 -0
  35. package/dist/src/service/index.d.ts.map +1 -0
  36. package/dist/src/version.d.ts +39 -0
  37. package/dist/src/version.d.ts.map +1 -0
  38. package/dist/version.js +75 -0
  39. package/dist/version.js.map +7 -0
  40. package/package.json +9 -1
package/dist/index.js CHANGED
@@ -1,10 +1,525 @@
1
- // sessionpipe-core 0.9.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 homeDir = () => 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(homeDir(), "AppData", "Roaming"), "sessionpipe", "config.json");
29
- return path.join(env.XDG_CONFIG_HOME || path.join(homeDir(), ".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(homeDir(), "AppData", "Local"), "sessionpipe");
35
- return path.join(env.XDG_STATE_HOME || path.join(homeDir(), ".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
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
  }
@@ -282,7 +801,7 @@ function readAntigravity(lines, from) {
282
801
 
283
802
  // src/adapters/claude-shaped.ts
284
803
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
285
- import path3 from "node:path";
804
+ import path5 from "node:path";
286
805
  var OURS = /(?:sessionpipe[^"\n]*[\\/]hook\.js|[\\/]sessionpipe-hook)"?\s+[a-z][a-z-]*\s+[A-Za-z]+\s*$/;
287
806
  var isOurs = (h) => Array.isArray(h?.hooks) && h.hooks.some((x) => typeof x.command === "string" && OURS.test(x.command));
288
807
  function readJsonFile(file) {
@@ -363,7 +882,7 @@ function installClaudeShaped(file, harness, events, cmd, o) {
363
882
  }
364
883
  if (changed) {
365
884
  settings2.hooks = hooks;
366
- mkdirSync(path3.dirname(file), { recursive: true });
885
+ mkdirSync(path5.dirname(file), { recursive: true });
367
886
  writeFileSync(file, formatJson(settings2, f));
368
887
  }
369
888
  return { file, changed, created: !f.existed && changed };
@@ -418,10 +937,10 @@ var HOOK_NAME = "sessionpipe";
418
937
  var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
419
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';
420
939
  var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
421
- var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
422
- var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
423
- var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
424
- 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));
425
944
  var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
426
945
  var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
427
946
  function entry(cmd) {
@@ -433,7 +952,7 @@ function entry(cmd) {
433
952
  var antigravity = {
434
953
  name: NAME,
435
954
  events: AG_EVENTS,
436
- 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)),
437
956
  configFiles: (env = process.env) => [hooksFile(env)],
438
957
  install(cmd, env = process.env) {
439
958
  const file = hooksFile(env);
@@ -443,7 +962,7 @@ var antigravity = {
443
962
  const e = entry(cmd);
444
963
  if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
445
964
  hooks[HOOK_NAME] = e;
446
- mkdirSync2(path4.dirname(file), { recursive: true });
965
+ mkdirSync2(path6.dirname(file), { recursive: true });
447
966
  writeFileSync2(file, formatJson(hooks, f));
448
967
  return [
449
968
  {
@@ -554,13 +1073,13 @@ var antigravity = {
554
1073
  for (const data of dataDirs(env)) {
555
1074
  let ids = [];
556
1075
  try {
557
- 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));
558
1077
  } catch {
559
1078
  }
560
1079
  for (const id of ids) {
561
- 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");
562
1081
  try {
563
- if (statSync2(f).mtimeMs < sinceMs) continue;
1082
+ if (statSync3(f).mtimeMs < sinceMs) continue;
564
1083
  } catch {
565
1084
  continue;
566
1085
  }
@@ -588,8 +1107,8 @@ function agPath(p) {
588
1107
  }
589
1108
  if (process.platform === "win32" && /^\/[A-Za-z]:/.test(s)) s = s.slice(1);
590
1109
  } else if (/^[a-z][a-z0-9+.-]*:\/\//i.test(s)) return null;
591
- if (!path4.isAbsolute(s)) return null;
592
- return path4.resolve(s);
1110
+ if (!path6.isAbsolute(s)) return null;
1111
+ return path6.resolve(s);
593
1112
  }
594
1113
  var agWorkspaces = (paths) => (Array.isArray(paths) ? paths : []).map(agPath).filter((p) => !!p).filter((p, i, a) => a.indexOf(p) === i);
595
1114
  function agVariants(p) {
@@ -614,7 +1133,7 @@ function agProjects(dir) {
614
1133
  if (!ID_RE.test(id)) continue;
615
1134
  let j;
616
1135
  try {
617
- j = JSON.parse(readFileSync2(path4.join(dir, n), "utf8"));
1136
+ j = JSON.parse(readFileSync2(path6.join(dir, n), "utf8"));
618
1137
  } catch {
619
1138
  continue;
620
1139
  }
@@ -637,7 +1156,7 @@ function agMatchProject(projects, workspaces) {
637
1156
  let bestLen = -1;
638
1157
  for (const pr of projects)
639
1158
  for (const f of pr.folders) {
640
- 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));
641
1160
  if (within && f.length > bestLen) {
642
1161
  best = pr.id;
643
1162
  bestLen = f.length;
@@ -646,12 +1165,12 @@ function agMatchProject(projects, workspaces) {
646
1165
  return best;
647
1166
  }
648
1167
  function antigravityRemoteUrl(transcript, opts) {
649
- const i = transcript.indexOf(`${path4.sep}brain${path4.sep}`);
1168
+ const i = transcript.indexOf(`${path6.sep}brain${path6.sep}`);
650
1169
  const dirs = (i > 0 ? [transcript.slice(0, i)] : []).concat(dataDirs(opts.env));
651
1170
  for (const d of dirs) {
652
1171
  try {
653
1172
  const m = /installation_uuid:\s*"([0-9a-f-]{36})"/i.exec(
654
- readFileSync2(path4.join(d, "antigravity_state.pbtxt"), "utf8")
1173
+ readFileSync2(path6.join(d, "antigravity_state.pbtxt"), "utf8")
655
1174
  );
656
1175
  if (!m) continue;
657
1176
  const base = `https://antigravity.google.com/r/${m[1]}-v2`;
@@ -698,167 +1217,28 @@ __export(antigravity_control_exports, {
698
1217
  agyError: () => agyError,
699
1218
  agyLine: () => agyLine,
700
1219
  agyModeFlags: () => agyModeFlags,
1220
+ defaultLevel: () => defaultLevel,
701
1221
  findAgy: () => findAgy,
1222
+ offeredLevels: () => offeredLevels,
702
1223
  reading: () => reading
703
1224
  });
704
- import os2 from "node:os";
705
- import path6 from "node:path";
706
-
707
- // src/adapters/headless.ts
708
- import { spawn } from "node:child_process";
709
- import { accessSync, constants, statSync as statSync3 } from "node:fs";
710
- import path5 from "node:path";
711
- var JOB_TIMEOUT_MS = 30 * 6e4;
712
- var OUT_CAP = 1024 * 1024;
713
- function isExe(p) {
714
- try {
715
- if (!statSync3(p).isFile()) return false;
716
- accessSync(p, constants.X_OK);
717
- return true;
718
- } catch {
719
- return false;
720
- }
721
- }
722
- function findBinary(o) {
723
- if (o.override && isExe(o.override)) return o.override;
724
- for (const d of String(o.env?.PATH || "").split(path5.delimiter)) {
725
- if (!d || !path5.isAbsolute(d)) continue;
726
- for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
727
- }
728
- for (const d of o.dirs) for (const n of o.names) if (isExe(path5.join(d, n))) return path5.join(d, n);
729
- return null;
730
- }
731
- function jobEnv(env = process.env) {
732
- return { ...env, SESSIONPIPE_CONTROL_JOB: "1" };
733
- }
734
- function runHeadless(bin, args, o) {
735
- return new Promise((resolve) => {
736
- let out = [];
737
- let outLen = 0;
738
- let tooBig = false;
739
- let errTail = "";
740
- let timedOut = false;
741
- let settled = false;
742
- let child;
743
- try {
744
- child = spawn(bin, args, {
745
- cwd: o.cwd,
746
- env: o.env,
747
- stdio: ["ignore", "pipe", "pipe"],
748
- detached: process.platform !== "win32",
749
- windowsHide: true
750
- });
751
- } catch (e) {
752
- resolve({ error: e.message });
753
- return;
754
- }
755
- const kill = (sig) => {
756
- try {
757
- if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
758
- else child.kill(sig);
759
- } catch {
760
- try {
761
- child.kill(sig);
762
- } catch {
763
- }
764
- }
765
- };
766
- const keep = (line) => {
767
- if (!o.keep?.(line)) return;
768
- out = [Buffer.from(line)];
769
- outLen = line.length;
770
- };
771
- let partial = "";
772
- child.stdout?.on("data", (b) => {
773
- if (o.onLine) {
774
- const chunk = partial + b.toString("utf8");
775
- const lines = chunk.split("\n");
776
- partial = lines.pop();
777
- for (const line of lines) {
778
- if (!line.trim()) continue;
779
- try {
780
- o.onLine(line);
781
- } catch {
782
- }
783
- keep(line);
784
- }
785
- if (partial.length > OUT_CAP) partial = "";
786
- return;
787
- }
788
- if (tooBig) return;
789
- if (outLen + b.length > OUT_CAP) {
790
- tooBig = true;
791
- out = [];
792
- return;
793
- }
794
- out.push(b);
795
- outLen += b.length;
796
- });
797
- child.stderr?.on("data", (b) => {
798
- errTail = (errTail + b.toString("utf8")).slice(-4e3);
799
- });
800
- const timer = setTimeout(() => {
801
- timedOut = true;
802
- kill("SIGTERM");
803
- setTimeout(() => kill("SIGKILL"), 1e4).unref();
804
- }, o.timeoutMs ?? JOB_TIMEOUT_MS);
805
- const result = (code, signal) => ({
806
- code,
807
- signal,
808
- stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
809
- tooBig,
810
- stderr: errTail,
811
- timedOut
812
- });
813
- const finish = (r) => {
814
- if (settled) return;
815
- settled = true;
816
- clearTimeout(timer);
817
- resolve(r);
818
- };
819
- child.on("error", (e) => finish({ error: e.message }));
820
- child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
821
- child.on("close", (code, signal) => {
822
- if (o.onLine && partial.trim()) {
823
- try {
824
- o.onLine(partial);
825
- } catch {
826
- }
827
- keep(partial);
828
- partial = "";
829
- }
830
- finish(result(code, signal));
831
- });
832
- });
833
- }
834
- function stderrTail(stderr, n = 3) {
835
- return String(stderr || "").trim().split("\n").slice(-n).join(" ").trim();
836
- }
837
- function deniedNote(names, count) {
838
- if (!count) return "";
839
- return `
840
-
841
- (Not allowed on this machine, since nobody was there to approve: ${names.join(", ") || `${count} action(s)`}.)`;
842
- }
843
- function runFailure(label, res) {
844
- if (res.error) return `couldn't start ${label}: ${res.error}`;
845
- if (res.timedOut) return `${label} was still working after 30 minutes, so it was stopped`;
846
- return null;
847
- }
848
-
849
- // src/adapters/antigravity-control.ts
1225
+ import os3 from "node:os";
1226
+ import path7 from "node:path";
850
1227
  var LABEL = "Antigravity";
851
- function findAgy(env = process.env, home = os2.homedir()) {
1228
+ function findAgy(env = process.env, home = os3.homedir()) {
852
1229
  return findBinary({
853
1230
  override: env.SESSIONPIPE_AGY,
854
1231
  names: process.platform === "win32" ? ["agy.exe", "agy.cmd"] : ["agy"],
855
1232
  env,
856
- 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"]
857
1234
  });
858
1235
  }
859
- function agyModeFlags(mode) {
860
- 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"];
861
1239
  }
1240
+ var offeredLevels = (mode) => mode === "auto" ? ["safe", "edits"] : ["safe"];
1241
+ var defaultLevel = (mode) => mode === "auto" ? "edits" : mode;
862
1242
  function agyArgs(o) {
863
1243
  const prompt = o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt;
864
1244
  return [
@@ -867,7 +1247,7 @@ function agyArgs(o) {
867
1247
  prompt,
868
1248
  "--output-format",
869
1249
  "stream-json",
870
- ...agyModeFlags(o.mode)
1250
+ ...agyModeFlags(o.permission)
871
1251
  ];
872
1252
  }
873
1253
  var agyEnv = (env = process.env) => jobEnv(env);
@@ -879,26 +1259,26 @@ function agyLine(line) {
879
1259
  return null;
880
1260
  }
881
1261
  if (!j || typeof j !== "object") return null;
882
- const str = (v) => typeof v === "string" ? v : "";
1262
+ const str2 = (v) => typeof v === "string" ? v : "";
883
1263
  const out = {};
884
1264
  if (j.event === "init") {
885
- if (str(j.conversation_id)) out.session = str(j.conversation_id);
1265
+ if (str2(j.conversation_id)) out.session = str2(j.conversation_id);
886
1266
  } else if (j.event === "step_update") {
887
1267
  const s = j.step_update ?? {};
888
- if (str(s.conversation_id)) out.session = str(s.conversation_id);
889
- if (s.step_type === "agent_response" && str(s.text_delta))
890
- 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) };
891
1271
  if (s.step_type === "error_message") {
892
- 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);
893
1273
  if (why) out.note = why;
894
1274
  }
895
1275
  } else if (j.event === "result") {
896
1276
  const r = j.result ?? {};
897
- if (str(r.conversation_id)) out.session = str(r.conversation_id);
1277
+ if (str2(r.conversation_id)) out.session = str2(r.conversation_id);
898
1278
  out.result = {
899
- status: str(r.status),
900
- response: str(r.response),
901
- error: str(r.error),
1279
+ status: str2(r.status),
1280
+ response: str2(r.response),
1281
+ error: str2(r.error),
902
1282
  denied: Array.isArray(r.denied_actions) ? r.denied_actions : []
903
1283
  };
904
1284
  } else return null;
@@ -970,7 +1350,7 @@ var reading = () => new AgyReading();
970
1350
  import { execFileSync as execFileSync2 } from "node:child_process";
971
1351
  import { createHash } from "node:crypto";
972
1352
  import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
973
- import path7 from "node:path";
1353
+ import path8 from "node:path";
974
1354
 
975
1355
  // src/readers/git.ts
976
1356
  import { execFileSync } from "node:child_process";
@@ -1036,23 +1416,23 @@ function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
1036
1416
  const out = [];
1037
1417
  const add = (d) => {
1038
1418
  if (!d) return;
1039
- const r = path7.resolve(d);
1419
+ const r = path8.resolve(d);
1040
1420
  if (!out.includes(r)) out.push(r);
1041
1421
  };
1042
- add(path7.join(home, ".claude"));
1422
+ add(path8.join(home, ".claude"));
1043
1423
  add(env.CLAUDE_CONFIG_DIR);
1044
1424
  try {
1045
1425
  for (const e of readdirSync3(home, { withFileTypes: true })) {
1046
1426
  if (!e.isDirectory() || !/^\.claude[-_.]/.test(e.name)) continue;
1047
- const d = path7.join(home, e.name);
1048
- 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);
1049
1429
  }
1050
1430
  } catch {
1051
1431
  }
1052
1432
  return out.filter((d, i) => i === 0 || existsSync3(d));
1053
1433
  }
1054
- var settingsIn = (dir) => path7.join(dir, "settings.json");
1055
- 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)));
1056
1436
  var validTool2 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
1057
1437
  var endReason = (r) => r === "clear" || r === "logout" || r === "exit" ? r : r === "prompt_input_exit" ? "exit" : "other";
1058
1438
  var notifKind = (t) => t === "permission_prompt" ? "permission" : t === "idle_prompt" ? "idle" : t === "elicitation_dialog" ? "elicitation" : "question";
@@ -1096,14 +1476,28 @@ var claudeCode = {
1096
1476
  case "SessionStart":
1097
1477
  events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
1098
1478
  break;
1099
- case "UserPromptSubmit":
1479
+ case "UserPromptSubmit": {
1480
+ const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
1100
1481
  events.push(
1101
1482
  ev("turn.started", {
1102
1483
  turn_id,
1103
- 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
1104
1497
  })
1105
1498
  );
1106
1499
  break;
1500
+ }
1107
1501
  case "PreToolUse":
1108
1502
  if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
1109
1503
  break;
@@ -1158,7 +1552,13 @@ var claudeCode = {
1158
1552
  );
1159
1553
  break;
1160
1554
  case "Stop":
1161
- 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
+ );
1162
1562
  break;
1163
1563
  case "StopFailure":
1164
1564
  events.push(
@@ -1201,6 +1601,8 @@ var claudeCode = {
1201
1601
  if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
1202
1602
  const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
1203
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;
1204
1606
  if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
1205
1607
  const r = { session, events, hints };
1206
1608
  if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
@@ -1221,17 +1623,18 @@ var claudeCode = {
1221
1623
  const cwd = out.cwd ?? session.cwd;
1222
1624
  Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
1223
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;
1224
1627
  return out;
1225
1628
  },
1226
1629
  backfill(sinceMs, env = process.env) {
1227
1630
  const rows2 = [];
1228
1631
  const files = [];
1229
1632
  for (const d of claudeDirs(env))
1230
- 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)));
1231
1634
  const noState = { get: () => ({}), save: () => {
1232
1635
  } };
1233
1636
  for (const f of files) {
1234
- const id = path7.basename(f, ".jsonl");
1637
+ const id = path8.basename(f, ".jsonl");
1235
1638
  const facts = claudeFacts(f, id, {}, noState);
1236
1639
  if (!facts.started_at || !facts.last_at) continue;
1237
1640
  const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
@@ -1256,6 +1659,9 @@ function notClaudeCode(id, s) {
1256
1659
  function pickSource(v) {
1257
1660
  return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
1258
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());
1259
1665
  function errText(v) {
1260
1666
  if (typeof v === "string") return v.slice(0, 2e3);
1261
1667
  if (v && typeof v === "object") {
@@ -1295,12 +1701,12 @@ function claudeLive(dir) {
1295
1701
  const map = /* @__PURE__ */ new Map();
1296
1702
  let names = [];
1297
1703
  try {
1298
- names = readdirSync3(path7.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
1704
+ names = readdirSync3(path8.join(dir, "sessions")).filter((n) => n.endsWith(".json"));
1299
1705
  } catch {
1300
1706
  }
1301
1707
  for (const n of names) {
1302
1708
  try {
1303
- 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")));
1304
1710
  if (e?.sessionId) map.set(e.sessionId, e);
1305
1711
  } catch {
1306
1712
  }
@@ -1311,7 +1717,7 @@ function claudeLive(dir) {
1311
1717
  function claudeLiveByPid(dir, pid) {
1312
1718
  if (!pid) return null;
1313
1719
  try {
1314
- 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")));
1315
1721
  } catch {
1316
1722
  return null;
1317
1723
  }
@@ -1319,7 +1725,7 @@ function claudeLiveByPid(dir, pid) {
1319
1725
  function claudePid(env = process.env) {
1320
1726
  try {
1321
1727
  const dirs = claudeDirs(env);
1322
- 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`)));
1323
1729
  let pid = process.ppid;
1324
1730
  if (has(pid)) return pid;
1325
1731
  const parent = parentOf();
@@ -1360,8 +1766,8 @@ function parentOf() {
1360
1766
  }
1361
1767
  var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
1362
1768
  function claudeLogin(dir, home = homeDir()) {
1363
- const files = [path7.join(dir, ".claude.json")];
1364
- if (path7.resolve(dir) === path7.join(home, ".claude")) files.unshift(path7.join(home, ".claude.json"));
1769
+ const files = [path8.join(dir, ".claude.json")];
1770
+ if (path8.resolve(dir) === path8.join(home, ".claude")) files.unshift(path8.join(home, ".claude.json"));
1365
1771
  for (const f of files) {
1366
1772
  try {
1367
1773
  const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
@@ -1372,7 +1778,27 @@ function claudeLogin(dir, home = homeDir()) {
1372
1778
  } catch {
1373
1779
  }
1374
1780
  }
1375
- return null;
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 };
1376
1802
  }
1377
1803
  var accountCache = /* @__PURE__ */ new Map();
1378
1804
  function claudeAccount(dir) {
@@ -1463,7 +1889,7 @@ function claudeFacts(file, sessionId, hints, state) {
1463
1889
  const H = parseLines(head);
1464
1890
  const T = parseLines(tail);
1465
1891
  const out = {};
1466
- const id = sessionId || path7.basename(file, ".jsonl");
1892
+ const id = sessionId || path8.basename(file, ".jsonl");
1467
1893
  const mem = state.get(NAME2, id);
1468
1894
  let custom = null;
1469
1895
  let ai = null;
@@ -1547,9 +1973,12 @@ function claudeFacts(file, sessionId, hints, state) {
1547
1973
  var claude_code_control_exports = {};
1548
1974
  __export(claude_code_control_exports, {
1549
1975
  JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
1976
+ ceilingLevel: () => ceilingLevel,
1977
+ claudeArgs: () => claudeArgs,
1550
1978
  claudeEnv: () => claudeEnv,
1551
1979
  claudeLogin: () => claudeLogin2,
1552
1980
  cmpVersion: () => cmpVersion,
1981
+ defaultLevel: () => defaultLevel2,
1553
1982
  detectCaps: () => detectCaps,
1554
1983
  findClaude: () => findClaude,
1555
1984
  findTranscript: () => findTranscript,
@@ -1558,6 +1987,8 @@ __export(claude_code_control_exports, {
1558
1987
  loginCommand: () => loginCommand,
1559
1988
  loginHint: () => loginHint,
1560
1989
  modeFlags: () => modeFlags,
1990
+ offeredLevels: () => offeredLevels2,
1991
+ outputArgs: () => outputArgs,
1561
1992
  parseAuthStatus: () => parseAuthStatus,
1562
1993
  parseCaps: () => parseCaps,
1563
1994
  parseResult: () => parseResult,
@@ -1567,8 +1998,22 @@ __export(claude_code_control_exports, {
1567
1998
  });
1568
1999
  import { execFile, execFileSync as execFileSync3 } from "node:child_process";
1569
2000
  import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync4, readSync as readSync2, statSync as statSync5 } from "node:fs";
1570
- import os3 from "node:os";
1571
- 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
1572
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;
1573
2018
  var LIVE_WRITE_MS = 9e4;
1574
2019
  var cmpVersion = (a, b) => {
@@ -1578,28 +2023,28 @@ var cmpVersion = (a, b) => {
1578
2023
  if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
1579
2024
  return 0;
1580
2025
  };
1581
- function findClaude(env = process.env, home = os3.homedir()) {
2026
+ function findClaude(env = process.env, home = os4.homedir()) {
1582
2027
  if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
1583
2028
  const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
1584
- for (const d of String(env.PATH || "").split(path8.delimiter)) {
1585
- if (!d || !path8.isAbsolute(d)) continue;
1586
- 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);
1587
2032
  }
1588
2033
  for (const p of [
1589
- path8.join(home, ".claude", "local", "claude"),
1590
- path8.join(home, ".local", "bin", "claude"),
2034
+ path9.join(home, ".claude", "local", "claude"),
2035
+ path9.join(home, ".local", "bin", "claude"),
1591
2036
  "/opt/homebrew/bin/claude",
1592
2037
  "/usr/local/bin/claude"
1593
2038
  ])
1594
2039
  if (isExe(p)) return p;
1595
- const app = path8.join(home, "Library", "Application Support", "Claude", "claude-code");
2040
+ const app = path9.join(home, "Library", "Application Support", "Claude", "claude-code");
1596
2041
  let versions = [];
1597
2042
  try {
1598
2043
  versions = readdirSync4(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
1599
2044
  } catch {
1600
2045
  }
1601
2046
  for (const v of versions) {
1602
- const p = path8.join(app, v, "claude.app", "Contents", "MacOS", "claude");
2047
+ const p = path9.join(app, v, "claude.app", "Contents", "MacOS", "claude");
1603
2048
  if (isExe(p)) return p;
1604
2049
  }
1605
2050
  return null;
@@ -1625,11 +2070,19 @@ function parseCaps(help) {
1625
2070
  stream: !!optionBlock(help, "--include-partial-messages")
1626
2071
  };
1627
2072
  }
1628
- function modeFlags(mode, caps) {
1629
- if (mode === "auto") {
1630
- if (!caps.modes.includes("auto"))
1631
- return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
1632
- 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] };
1633
2086
  }
1634
2087
  if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
1635
2088
  const args = [];
@@ -1641,6 +2094,32 @@ function modeFlags(mode, caps) {
1641
2094
  if (caps.promptsNone) args.push("--permission-prompts", "none");
1642
2095
  return { args };
1643
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
+ }
1644
2123
  function claudeEnv(configDir, env = process.env) {
1645
2124
  const e = { ...env };
1646
2125
  for (const k of [
@@ -1654,7 +2133,7 @@ function claudeEnv(configDir, env = process.env) {
1654
2133
  delete e[k];
1655
2134
  e.SESSIONPIPE_CONTROL_JOB = "1";
1656
2135
  if (configDir !== void 0) {
1657
- 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;
1658
2137
  else e.CLAUDE_CONFIG_DIR = configDir;
1659
2138
  }
1660
2139
  return e;
@@ -1687,12 +2166,12 @@ function claudeLogin2(bin, configDir, env = process.env) {
1687
2166
  });
1688
2167
  }
1689
2168
  function loginCommand(configDir) {
1690
- const home = os3.homedir();
1691
- 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");
1692
2171
  return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
1693
2172
  }
1694
2173
  function loginHint(configDir, platform = process.platform) {
1695
- const shown = configDir ? configDir.replace(os3.homedir(), "~") : "~/.claude";
2174
+ const shown = configDir ? configDir.replace(os4.homedir(), "~") : "~/.claude";
1696
2175
  const login = loginCommand(configDir);
1697
2176
  if (platform === "darwin")
1698
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.`;
@@ -1718,13 +2197,13 @@ function findTranscript(session, dirs) {
1718
2197
  for (const dir of dirs) {
1719
2198
  let projects = [];
1720
2199
  try {
1721
- projects = readdirSync4(path8.join(dir, "projects"), { withFileTypes: true });
2200
+ projects = readdirSync4(path9.join(dir, "projects"), { withFileTypes: true });
1722
2201
  } catch {
1723
2202
  continue;
1724
2203
  }
1725
2204
  for (const p of projects) {
1726
2205
  if (!p.isDirectory()) continue;
1727
- const file = path8.join(dir, "projects", p.name, `${session}.jsonl`);
2206
+ const file = path9.join(dir, "projects", p.name, `${session}.jsonl`);
1728
2207
  try {
1729
2208
  if (statSync5(file).isFile()) return { file, configDir: dir, project: p.name };
1730
2209
  } catch {
@@ -1746,7 +2225,7 @@ function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
1746
2225
  } catch {
1747
2226
  return null;
1748
2227
  }
1749
- 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;
1750
2229
  if (projectName(j.cwd) === t.project) return j.cwd;
1751
2230
  first ??= j.cwd;
1752
2231
  count++;
@@ -1807,13 +2286,13 @@ function pidAlive(pid) {
1807
2286
  function liveProcess(session, configDir) {
1808
2287
  let names = [];
1809
2288
  try {
1810
- 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));
1811
2290
  } catch {
1812
2291
  return false;
1813
2292
  }
1814
2293
  for (const n of names) {
1815
2294
  try {
1816
- const j = JSON.parse(readFileSync4(path8.join(configDir, "sessions", n), "utf8"));
2295
+ const j = JSON.parse(readFileSync4(path9.join(configDir, "sessions", n), "utf8"));
1817
2296
  if (String(j.sessionId) !== session) continue;
1818
2297
  if (pidAlive(Number(j.pid ?? n.slice(0, -5)))) return true;
1819
2298
  } catch {
@@ -1862,7 +2341,7 @@ var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
1862
2341
 
1863
2342
  // src/adapters/codex.ts
1864
2343
  import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
1865
- import path9 from "node:path";
2344
+ import path10 from "node:path";
1866
2345
 
1867
2346
  // src/adapters/codex-config.ts
1868
2347
  var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?$/m;
@@ -2007,10 +2486,10 @@ var CODEX_EVENTS = [
2007
2486
  ];
2008
2487
  var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
2009
2488
  var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
2010
- var codexHome = (env) => env.CODEX_HOME || path9.join(homeDir(), ".codex");
2011
- var hooksFile2 = (env) => path9.join(codexHome(env), "hooks.json");
2012
- var configFile2 = (env) => path9.join(codexHome(env), "config.toml");
2013
- 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");
2014
2493
  var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2015
2494
  var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
2016
2495
  function installNotify(cmd, env) {
@@ -2024,7 +2503,7 @@ function installNotify(cmd, env) {
2024
2503
  const line = `notify = ${JSON.stringify(argv)}`;
2025
2504
  const n = readNotify(text);
2026
2505
  const write = (next) => {
2027
- mkdirSync3(path9.dirname(file), { recursive: true });
2506
+ mkdirSync3(path10.dirname(file), { recursive: true });
2028
2507
  writeFileSync3(file, next);
2029
2508
  };
2030
2509
  if (n && !n.other) {
@@ -2091,7 +2570,7 @@ function findRollout(threadId, env = process.env) {
2091
2570
  return;
2092
2571
  }
2093
2572
  for (const n of names.sort().reverse()) {
2094
- const p = path9.join(dir, n);
2573
+ const p = path10.join(dir, n);
2095
2574
  if (n.endsWith(`-${threadId}.jsonl`)) {
2096
2575
  best = p;
2097
2576
  return;
@@ -2181,7 +2660,7 @@ var codex = {
2181
2660
  if (j && typeof j === "object" && !Array.isArray(j)) s = j;
2182
2661
  } catch {
2183
2662
  }
2184
- const id = s.session_id;
2663
+ const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
2185
2664
  if (!id) return null;
2186
2665
  if (s.transcript_path === null) return null;
2187
2666
  const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
@@ -2276,7 +2755,7 @@ var codex = {
2276
2755
  backfill(sinceMs, env = process.env) {
2277
2756
  const rows2 = [];
2278
2757
  for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
2279
- 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));
2280
2759
  if (!m?.[1]) continue;
2281
2760
  if (codexSubagent(rolloutMeta(f))) continue;
2282
2761
  const facts = codexFacts(f);
@@ -2343,38 +2822,71 @@ __export(codex_control_exports, {
2343
2822
  codexLine: () => codexLine,
2344
2823
  codexLogin: () => codexLogin,
2345
2824
  codexModeFlags: () => codexModeFlags,
2825
+ codexModels: () => codexModels,
2826
+ defaultLevel: () => defaultLevel3,
2346
2827
  findCodex: () => findCodex,
2347
2828
  findRollout: () => findRollout,
2829
+ offeredLevels: () => offeredLevels3,
2348
2830
  reading: () => reading2
2349
2831
  });
2350
2832
  import { execFile as execFile2 } from "node:child_process";
2351
- import os4 from "node:os";
2352
- 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";
2353
2836
  var LABEL2 = "Codex";
2354
- 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)) {
2355
2838
  return findBinary({
2356
2839
  override: env.SESSIONPIPE_CODEX,
2357
2840
  names: process.platform === "win32" ? ["codex.exe", "codex.cmd"] : ["codex"],
2358
2841
  env,
2359
- 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"]
2360
2843
  });
2361
2844
  }
2362
- 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";
2363
2848
  if (resume)
2364
- return mode === "auto" ? [
2849
+ return level === "auto" ? [
2365
2850
  "-c",
2366
2851
  'sandbox_mode="workspace-write"',
2367
2852
  "-c",
2368
2853
  'approval_policy="on-request"',
2369
2854
  "-c",
2370
2855
  'approvals_reviewer="auto_review"'
2371
- ] : ["-c", 'sandbox_mode="workspace-write"'];
2372
- return mode === "auto" ? ["--approve-for-me"] : ["-s", "workspace-write"];
2856
+ ] : ["-c", `sandbox_mode="${sandbox}"`];
2857
+ return level === "auto" ? ["--approve-for-me"] : ["-s", sandbox];
2373
2858
  }
2859
+ var offeredLevels3 = (mode) => mode === "auto" ? ["read", "safe", "auto"] : ["read", "safe"];
2860
+ var defaultLevel3 = (mode) => mode;
2374
2861
  function codexArgs(o) {
2375
2862
  const common = ["--json", "--skip-git-repo-check"];
2376
- if (o.resume) return ["exec", "resume", ...common, ...codexModeFlags(o.mode, true), "--", o.resume, o.prompt];
2377
- 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
+ }
2378
2890
  }
2379
2891
  var codexEnv = (env = process.env) => jobEnv(env);
2380
2892
  var LOGIN_HINT = "Codex isn't signed in on this machine: run `codex login --device-auth` there.";
@@ -2401,22 +2913,22 @@ function codexLine(line) {
2401
2913
  return null;
2402
2914
  }
2403
2915
  if (!j || typeof j !== "object") return null;
2404
- const str = (v) => typeof v === "string" ? v : "";
2916
+ const str2 = (v) => typeof v === "string" ? v : "";
2405
2917
  switch (j.type) {
2406
2918
  case "thread.started":
2407
- return str(j.thread_id) ? { session: str(j.thread_id) } : null;
2919
+ return str2(j.thread_id) ? { session: str2(j.thread_id) } : null;
2408
2920
  case "item.completed": {
2409
2921
  const it = j.item;
2410
- if (it?.type === "agent_message" && str(it.text)) return { message: str(it.text) };
2411
- 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) };
2412
2924
  return null;
2413
2925
  }
2414
2926
  case "turn.completed":
2415
2927
  return { done: true };
2416
2928
  case "turn.failed":
2417
- 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" };
2418
2930
  case "error":
2419
- return str(j.message) ? { note: str(j.message) } : null;
2931
+ return str2(j.message) ? { note: str2(j.message) } : null;
2420
2932
  default:
2421
2933
  return null;
2422
2934
  }
@@ -2469,7 +2981,7 @@ var reading2 = () => new CodexReading();
2469
2981
  // src/adapters/devin.ts
2470
2982
  import { createHash as createHash2 } from "node:crypto";
2471
2983
  import { existsSync as existsSync6, readlinkSync } from "node:fs";
2472
- import path11 from "node:path";
2984
+ import path12 from "node:path";
2473
2985
 
2474
2986
  // src/readers/sqlite.ts
2475
2987
  import { execFileSync as execFileSync4 } from "node:child_process";
@@ -2653,11 +3165,11 @@ function foreignEvent(file) {
2653
3165
  return Object.keys(hooks).find((k) => !ok.includes(k));
2654
3166
  }
2655
3167
  var homeOf = (env) => env.HOME || homeDir();
2656
- var appData = (env) => env.APPDATA || path11.join(homeOf(env), "AppData", "Roaming");
2657
- var devinConfigDir = (env = process.env) => process.platform === "win32" ? path11.join(appData(env), "devin") : path11.join(env.XDG_CONFIG_HOME || path11.join(homeOf(env), ".config"), "devin");
2658
- var configFile3 = (env) => path11.join(devinConfigDir(env), "config.json");
2659
- var devinDataDir = (env = process.env) => process.platform === "win32" ? path11.join(appData(env), "devin", "cli") : path11.join(env.XDG_DATA_HOME || path11.join(homeOf(env), ".local", "share"), "devin", "cli");
2660
- var sessionsDb = (env) => path11.join(devinDataDir(env), "sessions.db");
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");
2661
3173
  var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
2662
3174
  var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2663
3175
  var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
@@ -2829,7 +3341,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
2829
3341
  function sessionFacts(row) {
2830
3342
  const out = {};
2831
3343
  if (typeof row.model === "string" && row.model) out.model = row.model;
2832
- if (typeof row.working_directory === "string" && path11.isAbsolute(row.working_directory))
3344
+ if (typeof row.working_directory === "string" && path12.isAbsolute(row.working_directory))
2833
3345
  out.cwd = row.working_directory;
2834
3346
  const started = Number(row.created_at);
2835
3347
  const last = Number(row.last_activity_at);
@@ -2874,8 +3386,8 @@ function readDevinChain(db, session, fromLine) {
2874
3386
  }
2875
3387
  function devinVersion(env = process.env) {
2876
3388
  try {
2877
- const target = readlinkSync(path11.join(devinDataDir(env), "_versions", "current"));
2878
- const name = path11.basename(target);
3389
+ const target = readlinkSync(path12.join(devinDataDir(env), "_versions", "current"));
3390
+ const name = path12.basename(target);
2879
3391
  return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
2880
3392
  } catch {
2881
3393
  return void 0;
@@ -2889,8 +3401,8 @@ function devinAccount(env = process.env) {
2889
3401
 
2890
3402
  // src/adapters/gemini-cli.ts
2891
3403
  import { execFileSync as execFileSync5 } from "node:child_process";
2892
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
2893
- import path12 from "node:path";
3404
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
3405
+ import path13 from "node:path";
2894
3406
  var NAME5 = "gemini-cli";
2895
3407
  var GEMINI_EVENTS = [
2896
3408
  "SessionStart",
@@ -2903,14 +3415,14 @@ var GEMINI_EVENTS = [
2903
3415
  "SessionEnd"
2904
3416
  ];
2905
3417
  var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
2906
- var geminiDir = (env) => env.GEMINI_CLI_HOME || path12.join(homeDir(), ".gemini");
2907
- var settings = (env) => path12.join(geminiDir(env), "settings.json");
3418
+ var geminiDir = (env) => env.GEMINI_CLI_HOME || path13.join(homeDir(), ".gemini");
3419
+ var settings = (env) => path13.join(geminiDir(env), "settings.json");
2908
3420
  var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
2909
3421
  var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
2910
3422
  function geminiHooksActive(settingsFile) {
2911
3423
  let tools;
2912
3424
  try {
2913
- tools = JSON.parse(readFileSync6(settingsFile, "utf8")).tools;
3425
+ tools = JSON.parse(readFileSync7(settingsFile, "utf8")).tools;
2914
3426
  } catch {
2915
3427
  }
2916
3428
  if (tools?.enableHooks === true) return { active: true };
@@ -2940,7 +3452,7 @@ var geminiCli = {
2940
3452
  name: NAME5,
2941
3453
  events: GEMINI_EVENTS,
2942
3454
  // ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
2943
- detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path12.join(geminiDir(env), "tmp")),
3455
+ detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path13.join(geminiDir(env), "tmp")),
2944
3456
  configFiles: (env = process.env) => [settings(env)],
2945
3457
  install: (cmd, env = process.env, opts = {}) => {
2946
3458
  const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
@@ -3038,7 +3550,7 @@ var geminiCli = {
3038
3550
  readTranscript(file, fromLine) {
3039
3551
  let text = "";
3040
3552
  try {
3041
- text = readFileSync6(file, "utf8");
3553
+ text = readFileSync7(file, "utf8");
3042
3554
  } catch {
3043
3555
  return { turns: [], line: fromLine };
3044
3556
  }
@@ -3076,21 +3588,23 @@ function prune5(o) {
3076
3588
  // src/adapters/index.ts
3077
3589
  var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
3078
3590
  var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
3591
+ var HELLO_HARNESSES_MAX = 20;
3079
3592
  function drivableHarnesses(env = process.env) {
3080
3593
  return [
3081
3594
  ...findClaude(env) ? ["claude-code"] : [],
3082
3595
  ...findCodex(env) ? ["codex"] : [],
3083
- ...findAgy(env) ? ["antigravity"] : []
3084
- ];
3596
+ ...findAgy(env) ? ["antigravity"] : [],
3597
+ ...acpHarnesses(env)
3598
+ ].slice(0, HELLO_HARNESSES_MAX);
3085
3599
  }
3086
3600
 
3087
3601
  // src/config.ts
3088
- import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "node:fs";
3089
- import path13 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";
3090
3604
  var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
3091
3605
  function readConfig(file = configFile()) {
3092
3606
  try {
3093
- const j = JSON.parse(readFileSync7(file, "utf8"));
3607
+ const j = JSON.parse(readFileSync8(file, "utf8"));
3094
3608
  return {
3095
3609
  ...DEFAULT_CONFIG,
3096
3610
  ...j,
@@ -3102,7 +3616,7 @@ function readConfig(file = configFile()) {
3102
3616
  }
3103
3617
  }
3104
3618
  function writeConfig(c, file = configFile()) {
3105
- mkdirSync4(path13.dirname(file), { recursive: true, mode: 448 });
3619
+ mkdirSync4(path14.dirname(file), { recursive: true, mode: 448 });
3106
3620
  writeFileSync4(file, `${JSON.stringify(c, null, 2)}
3107
3621
  `, { mode: 384 });
3108
3622
  if (process.platform !== "win32" && existsSync8(file)) {
@@ -3167,6 +3681,22 @@ function makeEvent(a, ctx) {
3167
3681
  };
3168
3682
  }
3169
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
+
3170
3700
  // src/outbox.ts
3171
3701
  import {
3172
3702
  appendFileSync,
@@ -3175,14 +3705,14 @@ import {
3175
3705
  mkdirSync as mkdirSync5,
3176
3706
  openSync as openSync3,
3177
3707
  readdirSync as readdirSync6,
3178
- readFileSync as readFileSync8,
3708
+ readFileSync as readFileSync9,
3179
3709
  readSync as readSync3,
3180
3710
  renameSync,
3181
3711
  statSync as statSync6,
3182
3712
  unlinkSync as unlinkSync3,
3183
3713
  writeFileSync as writeFileSync5
3184
3714
  } from "node:fs";
3185
- import path14 from "node:path";
3715
+ import path15 from "node:path";
3186
3716
  var READ_CHUNK = 1024 * 1024;
3187
3717
  var READ_MAX = 8 * 1024 * 1024;
3188
3718
  var Outbox = class {
@@ -3191,13 +3721,13 @@ var Outbox = class {
3191
3721
  }
3192
3722
  root;
3193
3723
  dir(harness) {
3194
- return path14.join(this.root, "outbox", safeId(harness));
3724
+ return path15.join(this.root, "outbox", safeId(harness));
3195
3725
  }
3196
3726
  file(ref) {
3197
- return path14.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
3727
+ return path15.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
3198
3728
  }
3199
3729
  seqFile(ref) {
3200
- return path14.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
3730
+ return path15.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
3201
3731
  }
3202
3732
  /** The next seq for a session, persisted (survives a hibernating machine and a
3203
3733
  * restart; an in-memory counter would restart at 0 and collide). */
@@ -3205,19 +3735,19 @@ var Outbox = class {
3205
3735
  const f = this.seqFile(ref);
3206
3736
  let n = -1;
3207
3737
  try {
3208
- const t = readFileSync8(f, "utf8").trim();
3738
+ const t = readFileSync9(f, "utf8").trim();
3209
3739
  if (/^\d+$/.test(t)) n = Number(t);
3210
3740
  } catch {
3211
3741
  }
3212
3742
  if (n < 0) n = this.lastSeqInFile(ref) + 1;
3213
- mkdirSync5(path14.dirname(f), { recursive: true, mode: 448 });
3743
+ mkdirSync5(path15.dirname(f), { recursive: true, mode: 448 });
3214
3744
  writeFileSync5(f, String(n + 1), { mode: 384 });
3215
3745
  return n;
3216
3746
  }
3217
3747
  lastSeqInFile(ref) {
3218
3748
  const f = this.file(ref);
3219
3749
  if (!existsSync9(f)) return -1;
3220
- const lines = readFileSync8(f, "utf8").trim().split("\n");
3750
+ const lines = readFileSync9(f, "utf8").trim().split("\n");
3221
3751
  for (let i = lines.length - 1; i >= 0; i--) {
3222
3752
  try {
3223
3753
  const j = JSON.parse(lines[i]);
@@ -3236,10 +3766,10 @@ var Outbox = class {
3236
3766
  /** Every session file, newest first. */
3237
3767
  sessions() {
3238
3768
  const out = [];
3239
- const base = path14.join(this.root, "outbox");
3769
+ const base = path15.join(this.root, "outbox");
3240
3770
  if (!existsSync9(base)) return [];
3241
3771
  for (const h of readdirSync6(base)) {
3242
- const d = path14.join(base, h);
3772
+ const d = path15.join(base, h);
3243
3773
  let names = [];
3244
3774
  try {
3245
3775
  names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
@@ -3248,7 +3778,7 @@ var Outbox = class {
3248
3778
  }
3249
3779
  for (const n of names) {
3250
3780
  try {
3251
- out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path14.join(d, n)).mtimeMs });
3781
+ out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path15.join(d, n)).mtimeMs });
3252
3782
  } catch {
3253
3783
  }
3254
3784
  }
@@ -3336,18 +3866,18 @@ var Cursors = class {
3336
3866
  root;
3337
3867
  sink;
3338
3868
  file() {
3339
- return path14.join(this.root, "cursors", `${safeId(this.sink)}.json`);
3869
+ return path15.join(this.root, "cursors", `${safeId(this.sink)}.json`);
3340
3870
  }
3341
3871
  read() {
3342
3872
  try {
3343
- return JSON.parse(readFileSync8(this.file(), "utf8"));
3873
+ return JSON.parse(readFileSync9(this.file(), "utf8"));
3344
3874
  } catch {
3345
3875
  return {};
3346
3876
  }
3347
3877
  }
3348
3878
  write(c) {
3349
3879
  const f = this.file();
3350
- mkdirSync5(path14.dirname(f), { recursive: true, mode: 448 });
3880
+ mkdirSync5(path15.dirname(f), { recursive: true, mode: 448 });
3351
3881
  const tmp = `${f}.${process.pid}.tmp`;
3352
3882
  writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
3353
3883
  renameSync(tmp, f);
@@ -3360,11 +3890,11 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
3360
3890
  return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
3361
3891
  }
3362
3892
  function takeNamedLock(root, name, staleMs = 5 * 6e4) {
3363
- const dir = path14.join(root, "locks");
3893
+ const dir = path15.join(root, "locks");
3364
3894
  mkdirSync5(dir, { recursive: true, mode: 448 });
3365
- const f = path14.join(dir, `${name}.lock`);
3895
+ const f = path15.join(dir, `${name}.lock`);
3366
3896
  try {
3367
- if (Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(readFileSync8(f, "utf8")))) unlinkSync3(f);
3897
+ if (Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(readFileSync9(f, "utf8")))) unlinkSync3(f);
3368
3898
  else return null;
3369
3899
  } catch {
3370
3900
  }
@@ -3612,8 +4142,8 @@ var DATA_ALLOW = {
3612
4142
  "session.ended": { 0: ["reason"] },
3613
4143
  "session.backfill": { 0: ["started_at", "last_at", "turns"] },
3614
4144
  "session.forgotten": { 0: [] },
3615
- "turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
3616
- "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"] },
3617
4147
  "turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
3618
4148
  "turn.progress": { 2: ["turn", "user", "assistant", "at"] },
3619
4149
  "tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
@@ -3637,7 +4167,9 @@ var SESSION_ALLOW = [
3637
4167
  "title_source",
3638
4168
  "url",
3639
4169
  "account_id",
3640
- "parent_id"
4170
+ "attended",
4171
+ "parent_id",
4172
+ "launched_by"
3641
4173
  ];
3642
4174
  var MAX_SIDE = 2e4;
3643
4175
  var MAX_IO = 64 * 1024;
@@ -3701,7 +4233,7 @@ function shortId(harness, id) {
3701
4233
 
3702
4234
  // src/sinks/file.ts
3703
4235
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
3704
- import path15 from "node:path";
4236
+ import path16 from "node:path";
3705
4237
  var FileSink = class {
3706
4238
  constructor(name, file, tier, pii) {
3707
4239
  this.name = name;
@@ -3715,7 +4247,7 @@ var FileSink = class {
3715
4247
  pii;
3716
4248
  async send(events) {
3717
4249
  try {
3718
- mkdirSync6(path15.dirname(this.file), { recursive: true });
4250
+ mkdirSync6(path16.dirname(this.file), { recursive: true });
3719
4251
  appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
3720
4252
  `);
3721
4253
  return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
@@ -3757,7 +4289,9 @@ export {
3757
4289
  DEFAULT_CONFIG,
3758
4290
  DEVIN_EVENTS,
3759
4291
  FileSink,
4292
+ HELLO_HARNESSES_MAX,
3760
4293
  JOB_TIMEOUT_MS,
4294
+ LAUNCHED_BY_ENV,
3761
4295
  Outbox,
3762
4296
  PROTOCOL_VERSION,
3763
4297
  REDACTED,
@@ -3765,6 +4299,7 @@ export {
3765
4299
  RULESET_SECRETS,
3766
4300
  StdoutSink,
3767
4301
  TYPE_TIER,
4302
+ acp_control_exports as acpControl,
3768
4303
  adapterByName,
3769
4304
  antigravity,
3770
4305
  antigravityAnswer,
@@ -3792,6 +4327,7 @@ export {
3792
4327
  isSecretName,
3793
4328
  isUlid,
3794
4329
  jobEnv,
4330
+ launchedBy,
3795
4331
  machineName,
3796
4332
  makeEvent,
3797
4333
  notClaudeCode,
@@ -3803,6 +4339,7 @@ export {
3803
4339
  readConfig,
3804
4340
  readDevinChain,
3805
4341
  readGemini,
4342
+ readHook,
3806
4343
  redactDeep,
3807
4344
  redactSecrets,
3808
4345
  reducePii,
@@ -3816,6 +4353,7 @@ export {
3816
4353
  takeNamedLock,
3817
4354
  tilde,
3818
4355
  ulid,
4356
+ untilde,
3819
4357
  writeConfig
3820
4358
  };
3821
4359
  //# sourceMappingURL=index.js.map