sessionpipe 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -7
- package/dist/ask.d.ts +40 -0
- package/dist/ask.d.ts.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4439 -2047
- package/dist/cli.js.map +4 -4
- package/dist/connect.d.ts +6 -1
- package/dist/connect.d.ts.map +1 -1
- package/dist/control/autoupdate.d.ts +1 -0
- package/dist/control/autoupdate.d.ts.map +1 -1
- package/dist/control/channel.d.ts +43 -0
- package/dist/control/channel.d.ts.map +1 -0
- package/dist/control/cli.d.ts +13 -1
- package/dist/control/cli.d.ts.map +1 -1
- package/dist/control/daemon.d.ts +189 -2
- package/dist/control/daemon.d.ts.map +1 -1
- package/dist/control/inbox.d.ts +119 -0
- package/dist/control/inbox.d.ts.map +1 -0
- package/dist/control/local.d.ts +69 -4
- package/dist/control/local.d.ts.map +1 -1
- package/dist/control/pair.d.ts.map +1 -1
- package/dist/control/reply.d.ts +25 -1
- package/dist/control/reply.d.ts.map +1 -1
- package/dist/control/route.d.ts +50 -2
- package/dist/control/route.d.ts.map +1 -1
- package/dist/control/sdk.d.ts.map +1 -1
- package/dist/control/store.d.ts +49 -4
- package/dist/control/store.d.ts.map +1 -1
- package/dist/fold.d.ts +29 -0
- package/dist/fold.d.ts.map +1 -0
- package/dist/help.d.ts.map +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +15 -5
- package/dist/hook.js.map +2 -2
- package/dist/index.js +1272 -257
- package/dist/index.js.map +4 -4
- package/dist/run.d.ts +41 -5
- package/dist/run.d.ts.map +1 -1
- package/dist/secrets.d.ts +14 -4
- package/dist/secrets.d.ts.map +1 -1
- package/dist/wellknown.d.ts +31 -0
- package/dist/wellknown.d.ts.map +1 -0
- package/dist/worker.d.ts +7 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +1372 -279
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe 0.
|
|
2
|
+
// sessionpipe 0.13.1 · Apache-2.0 · https://sessionpipe.org
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __export = (target, all) => {
|
|
5
|
+
for (var name in all)
|
|
6
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
7
|
+
};
|
|
3
8
|
|
|
4
9
|
// src/worker.ts
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
10
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
6
11
|
import { realpathSync as realpathSync2 } from "node:fs";
|
|
7
12
|
import { pathToFileURL } from "node:url";
|
|
8
13
|
|
|
@@ -32,7 +37,7 @@ var safeId = (s) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 160);
|
|
|
32
37
|
// ../core/src/adapters/claude-code.ts
|
|
33
38
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
34
39
|
import { createHash } from "node:crypto";
|
|
35
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2
|
|
40
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
36
41
|
import { userInfo } from "node:os";
|
|
37
42
|
import path4 from "node:path";
|
|
38
43
|
|
|
@@ -184,7 +189,14 @@ ${m.text}`;
|
|
|
184
189
|
continue;
|
|
185
190
|
}
|
|
186
191
|
if (cur) turns.push(cur);
|
|
187
|
-
cur = {
|
|
192
|
+
cur = {
|
|
193
|
+
user: m.text,
|
|
194
|
+
assistant: "",
|
|
195
|
+
at: m.at,
|
|
196
|
+
startLine: m.line,
|
|
197
|
+
endLine: m.line,
|
|
198
|
+
...m.injected ? { injected: true } : {}
|
|
199
|
+
};
|
|
188
200
|
} else {
|
|
189
201
|
if (!cur) cur = { user: "", assistant: "", at: m.at, startLine: m.line, endLine: m.line };
|
|
190
202
|
cur.assistant += (cur.assistant ? "\n\n" : "") + m.text;
|
|
@@ -194,10 +206,17 @@ ${m.text}`;
|
|
|
194
206
|
if (cur?.assistant) turns.push(cur);
|
|
195
207
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
196
208
|
const out = {
|
|
197
|
-
turns: turns.map((t) => ({
|
|
209
|
+
turns: turns.map((t) => ({
|
|
210
|
+
user: t.user,
|
|
211
|
+
assistant: t.assistant,
|
|
212
|
+
at: t.at,
|
|
213
|
+
endLine: t.endLine,
|
|
214
|
+
...t.injected ? { injected: true } : {}
|
|
215
|
+
})),
|
|
198
216
|
line
|
|
199
217
|
};
|
|
200
|
-
if (cur && !cur.assistant && cur.user)
|
|
218
|
+
if (cur && !cur.assistant && cur.user)
|
|
219
|
+
out.open = { user: cur.user, at: cur.at, ...cur.injected ? { injected: true } : {} };
|
|
201
220
|
return out;
|
|
202
221
|
}
|
|
203
222
|
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
@@ -220,27 +239,51 @@ var textParts = (c, kinds) => {
|
|
|
220
239
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
221
240
|
).map((b) => b.text).join("\n");
|
|
222
241
|
};
|
|
242
|
+
var SPOKEN = /* @__PURE__ */ new Set(["human", "peer"]);
|
|
243
|
+
var PEER_INTRO = /sent a message[^\n]*:$/;
|
|
244
|
+
var PEER_OUTRO = "\n\nThis came from another ";
|
|
245
|
+
var ENVELOPE = /^<cross-session-message\b[^>]*>\n([\s\S]*)\n<\/cross-session-message>$/;
|
|
246
|
+
function peerAsk(origin, text) {
|
|
247
|
+
const body = origin?.body;
|
|
248
|
+
let t = typeof body === "string" && body.trim() ? body : text;
|
|
249
|
+
if (t !== body) {
|
|
250
|
+
const nl = t.indexOf("\n");
|
|
251
|
+
if (nl > 0 && PEER_INTRO.test(t.slice(0, nl))) t = t.slice(nl + 1);
|
|
252
|
+
const outro = t.indexOf(PEER_OUTRO);
|
|
253
|
+
if (outro > 0) t = t.slice(0, outro);
|
|
254
|
+
}
|
|
255
|
+
return (ENVELOPE.exec(t.trim())?.[1] ?? t).trim();
|
|
256
|
+
}
|
|
223
257
|
var queuedPrompt = (a) => {
|
|
224
258
|
if (!a || typeof a !== "object") return "";
|
|
225
259
|
const q = a;
|
|
226
260
|
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
227
|
-
|
|
228
|
-
|
|
261
|
+
const kind = q.origin?.kind ?? "human";
|
|
262
|
+
if (!SPOKEN.has(kind)) return "";
|
|
263
|
+
const text = textParts(q.prompt, ["text"]).trim();
|
|
264
|
+
return kind === "peer" ? peerAsk(q.origin, text) : text;
|
|
229
265
|
};
|
|
266
|
+
var channelText = (text) => /^<channel\b[^>]*>([\s\S]*)<\/channel>$/.exec(text)?.[1]?.trim() ?? "";
|
|
230
267
|
function readClaude(lines, from) {
|
|
231
268
|
const msgs = [];
|
|
232
269
|
for (let i = from; i < lines.length; i++) {
|
|
233
270
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
234
271
|
if (!j || j.isSidechain) continue;
|
|
235
272
|
const origin = j.origin;
|
|
236
|
-
|
|
237
|
-
|
|
273
|
+
const channel = j.type === "user" && origin?.kind === "channel";
|
|
274
|
+
const peer = origin?.kind === "peer";
|
|
275
|
+
if (j.type === "user" && (j.isCompactSummary || j.repeatsCompactSummary)) continue;
|
|
276
|
+
if (j.type === "user" && !channel && !peer && (j.isMeta || origin?.kind && origin.kind !== "human")) continue;
|
|
238
277
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
239
278
|
const c = j.message?.content;
|
|
240
|
-
if (
|
|
241
|
-
const text = textParts(c, ["text"]).trim();
|
|
279
|
+
if (channel) {
|
|
280
|
+
const text = channelText(textParts(c, ["text"]).trim());
|
|
281
|
+
if (text) msgs.push({ role: "user", text, at, line: i, injected: true });
|
|
282
|
+
} else if (j.type === "user") {
|
|
283
|
+
const raw = textParts(c, ["text"]).trim();
|
|
284
|
+
const text = peer ? peerAsk(origin, raw) : raw;
|
|
242
285
|
if (!text || isInjected(text)) continue;
|
|
243
|
-
msgs.push({ role: "user", text, at, line: i });
|
|
286
|
+
msgs.push({ role: "user", text, at, line: i, ...peer ? { injected: true } : {} });
|
|
244
287
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
245
288
|
const text = textParts(c, ["text"]).trim();
|
|
246
289
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
@@ -352,6 +395,41 @@ function readJson(file) {
|
|
|
352
395
|
const f = readJsonFile(file);
|
|
353
396
|
return f.ok ? f.value : {};
|
|
354
397
|
}
|
|
398
|
+
function refused(file, e, what, existed) {
|
|
399
|
+
const { code, syscall } = e;
|
|
400
|
+
const why = `can't ${what} it (${code ?? String(e)})`;
|
|
401
|
+
if (what === "remove" || syscall === "open" || syscall === "mkdir")
|
|
402
|
+
return { file, changed: false, skipped: true, note: `${why}; not touched` };
|
|
403
|
+
return {
|
|
404
|
+
file,
|
|
405
|
+
changed: true,
|
|
406
|
+
skipped: true,
|
|
407
|
+
...existed ? {} : { created: true },
|
|
408
|
+
note: `${why} part way; check the file before the harness reads it`
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
function writeBack(file, text, existed = true) {
|
|
412
|
+
try {
|
|
413
|
+
mkdirSync(path3.dirname(file), { recursive: true });
|
|
414
|
+
writeFileSync(file, text);
|
|
415
|
+
return null;
|
|
416
|
+
} catch (e) {
|
|
417
|
+
return refused(file, e, "write", existed);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function removeEmptied(file, empty) {
|
|
421
|
+
try {
|
|
422
|
+
unlinkSync(file);
|
|
423
|
+
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
424
|
+
} catch (e) {
|
|
425
|
+
const bad = refused(file, e, "remove", true);
|
|
426
|
+
return writeBack(file, empty) ?? {
|
|
427
|
+
file,
|
|
428
|
+
changed: true,
|
|
429
|
+
note: `emptied; ${bad.note.replace(/; not touched$/, "")}, so the empty file stays`
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
355
433
|
function entryFor(event, harness, cmd, o) {
|
|
356
434
|
const matcher = o.matcherFor(event);
|
|
357
435
|
return {
|
|
@@ -393,8 +471,8 @@ function installClaudeShaped(file, harness, events, cmd, o) {
|
|
|
393
471
|
}
|
|
394
472
|
if (changed) {
|
|
395
473
|
settings2.hooks = hooks;
|
|
396
|
-
|
|
397
|
-
|
|
474
|
+
const bad = writeBack(file, formatJson(settings2, f), f.existed);
|
|
475
|
+
if (bad) return bad;
|
|
398
476
|
}
|
|
399
477
|
return { file, changed, created: !f.existed && changed };
|
|
400
478
|
}
|
|
@@ -417,12 +495,8 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
417
495
|
}
|
|
418
496
|
if (!changed) return { file, changed: false };
|
|
419
497
|
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
420
|
-
if (opts.created && !Object.keys(settings2).length)
|
|
421
|
-
|
|
422
|
-
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
423
|
-
}
|
|
424
|
-
writeFileSync(file, formatJson(settings2, f));
|
|
425
|
-
return { file, changed: true };
|
|
498
|
+
if (opts.created && !Object.keys(settings2).length) return removeEmptied(file, formatJson(settings2, f));
|
|
499
|
+
return writeBack(file, formatJson(settings2, f)) ?? { file, changed: true };
|
|
426
500
|
}
|
|
427
501
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
428
502
|
const f = readJsonFile(file);
|
|
@@ -501,7 +575,7 @@ var claudeCode = {
|
|
|
501
575
|
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
502
576
|
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
|
|
503
577
|
const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
|
|
504
|
-
const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
578
|
+
const line = opts.statusLine === void 0 || hooks.skipped ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
505
579
|
return line ? [hooks, line] : [hooks];
|
|
506
580
|
}),
|
|
507
581
|
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
|
|
@@ -509,7 +583,9 @@ var claudeCode = {
|
|
|
509
583
|
const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
|
|
510
584
|
created: opts.created?.includes(settingsIn(d))
|
|
511
585
|
});
|
|
512
|
-
|
|
586
|
+
if (line?.changed && !line.skipped && hooks.skipped)
|
|
587
|
+
return { ...hooks, note: `status line removed, hooks not: ${hooks.note?.replace(/; not touched$/, "")}` };
|
|
588
|
+
return line && !hooks.changed && !hooks.skipped ? line : hooks;
|
|
513
589
|
}),
|
|
514
590
|
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
515
591
|
file: settingsIn(d),
|
|
@@ -604,7 +680,8 @@ var claudeCode = {
|
|
|
604
680
|
attention_id: call_id ?? permissionId(),
|
|
605
681
|
kind: "permission",
|
|
606
682
|
tool,
|
|
607
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
683
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0,
|
|
684
|
+
agent_id
|
|
608
685
|
})
|
|
609
686
|
);
|
|
610
687
|
break;
|
|
@@ -613,7 +690,8 @@ var claudeCode = {
|
|
|
613
690
|
ev("attention.needed", {
|
|
614
691
|
attention_id: `notif-${Date.now()}`,
|
|
615
692
|
kind: notifKind(s.notification_type),
|
|
616
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
693
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0,
|
|
694
|
+
agent_id
|
|
617
695
|
})
|
|
618
696
|
);
|
|
619
697
|
break;
|
|
@@ -738,8 +816,11 @@ function statusLine(file, want, cmd) {
|
|
|
738
816
|
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
739
817
|
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
740
818
|
else delete f.value.statusLine;
|
|
741
|
-
|
|
742
|
-
|
|
819
|
+
return writeBack(file, formatJson(f.value, f), f.existed) ?? {
|
|
820
|
+
file,
|
|
821
|
+
changed: true,
|
|
822
|
+
note: want ? "status line written (account limits)" : "status line removed"
|
|
823
|
+
};
|
|
743
824
|
}
|
|
744
825
|
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
745
826
|
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
@@ -1154,8 +1235,44 @@ async function claudeAiTitle(bridge, token) {
|
|
|
1154
1235
|
}
|
|
1155
1236
|
|
|
1156
1237
|
// ../core/src/adapters/headless.ts
|
|
1238
|
+
import { spawn } from "node:child_process";
|
|
1157
1239
|
import { accessSync, constants, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
1158
1240
|
import path5 from "node:path";
|
|
1241
|
+
|
|
1242
|
+
// ../core/src/launched-by.ts
|
|
1243
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
1244
|
+
var DELEGATED_BY_ENV = "SESSIONPIPE_ASKED_BY";
|
|
1245
|
+
var ASK_CHAIN_ENV = "SESSIONPIPE_ASK_CHAIN";
|
|
1246
|
+
var LINEAGE_ENV = [LAUNCHED_BY_ENV, DELEGATED_BY_ENV, ASK_CHAIN_ENV];
|
|
1247
|
+
var SESSION_REF = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
1248
|
+
function withoutLineage(env) {
|
|
1249
|
+
const e = { ...env };
|
|
1250
|
+
for (const k of LINEAGE_ENV) delete e[k];
|
|
1251
|
+
return e;
|
|
1252
|
+
}
|
|
1253
|
+
function refIn(env, name) {
|
|
1254
|
+
const v = env[name]?.trim();
|
|
1255
|
+
return v && SESSION_REF.test(v) ? v : void 0;
|
|
1256
|
+
}
|
|
1257
|
+
function launchedBy(env) {
|
|
1258
|
+
return refIn(env, LAUNCHED_BY_ENV);
|
|
1259
|
+
}
|
|
1260
|
+
function delegatedBy(env) {
|
|
1261
|
+
return refIn(env, DELEGATED_BY_ENV);
|
|
1262
|
+
}
|
|
1263
|
+
function readHook(adapter, input) {
|
|
1264
|
+
const r = adapter.fromHook(input);
|
|
1265
|
+
if (r && !r.session.parent_id) {
|
|
1266
|
+
const self = `${adapter.name}:${r.session.id}`;
|
|
1267
|
+
const launcher = launchedBy(input.env);
|
|
1268
|
+
if (launcher && launcher !== self) r.session.launched_by = launcher;
|
|
1269
|
+
const asker = delegatedBy(input.env);
|
|
1270
|
+
if (asker && asker !== self) r.session.delegated_by = asker;
|
|
1271
|
+
}
|
|
1272
|
+
return r;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// ../core/src/adapters/headless.ts
|
|
1159
1276
|
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1160
1277
|
var OUT_CAP = 1024 * 1024;
|
|
1161
1278
|
function isExe(p) {
|
|
@@ -1197,22 +1314,119 @@ function spawnable(bin, args) {
|
|
|
1197
1314
|
const target = shimTarget(bin);
|
|
1198
1315
|
return target ? { file: process.execPath, args: [target, ...args] } : null;
|
|
1199
1316
|
}
|
|
1317
|
+
var shimNote = (bin) => `${path5.basename(bin)} is a launcher script that names no program to run; point the harness's override (SESSIONPIPE_CLAUDE, SESSIONPIPE_CODEX, \u2026) at the program itself`;
|
|
1200
1318
|
function probe(bin, args) {
|
|
1201
1319
|
const s = spawnable(bin, args);
|
|
1202
1320
|
return s ? { ...s, shell: false } : { file: [`"${bin}"`, ...args].join(" "), args: [], shell: true };
|
|
1203
1321
|
}
|
|
1322
|
+
function runHeadless(bin, args, o) {
|
|
1323
|
+
return new Promise((resolve) => {
|
|
1324
|
+
let out = [];
|
|
1325
|
+
let outLen = 0;
|
|
1326
|
+
let tooBig = false;
|
|
1327
|
+
let errTail = "";
|
|
1328
|
+
let timedOut = false;
|
|
1329
|
+
let settled = false;
|
|
1330
|
+
let child;
|
|
1331
|
+
const c = spawnable(bin, args);
|
|
1332
|
+
if (!c) {
|
|
1333
|
+
resolve({ error: shimNote(bin) });
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
try {
|
|
1337
|
+
child = spawn(c.file, c.args, {
|
|
1338
|
+
cwd: o.cwd,
|
|
1339
|
+
env: o.env,
|
|
1340
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1341
|
+
detached: process.platform !== "win32",
|
|
1342
|
+
windowsHide: true
|
|
1343
|
+
});
|
|
1344
|
+
} catch (e) {
|
|
1345
|
+
resolve({ error: e.message });
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
const kill = (sig) => {
|
|
1349
|
+
try {
|
|
1350
|
+
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
|
|
1351
|
+
else child.kill(sig);
|
|
1352
|
+
} catch {
|
|
1353
|
+
try {
|
|
1354
|
+
child.kill(sig);
|
|
1355
|
+
} catch {
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
};
|
|
1359
|
+
const keep = (line) => {
|
|
1360
|
+
if (!o.keep?.(line)) return;
|
|
1361
|
+
out = [Buffer.from(line)];
|
|
1362
|
+
outLen = line.length;
|
|
1363
|
+
};
|
|
1364
|
+
let partial = "";
|
|
1365
|
+
child.stdout?.on("data", (b) => {
|
|
1366
|
+
if (o.onLine) {
|
|
1367
|
+
const chunk = partial + b.toString("utf8");
|
|
1368
|
+
const lines = chunk.split("\n");
|
|
1369
|
+
partial = lines.pop();
|
|
1370
|
+
for (const line of lines) {
|
|
1371
|
+
if (!line.trim()) continue;
|
|
1372
|
+
try {
|
|
1373
|
+
o.onLine(line);
|
|
1374
|
+
} catch {
|
|
1375
|
+
}
|
|
1376
|
+
keep(line);
|
|
1377
|
+
}
|
|
1378
|
+
if (partial.length > OUT_CAP) partial = "";
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
if (tooBig) return;
|
|
1382
|
+
if (outLen + b.length > OUT_CAP) {
|
|
1383
|
+
tooBig = true;
|
|
1384
|
+
out = [];
|
|
1385
|
+
return;
|
|
1386
|
+
}
|
|
1387
|
+
out.push(b);
|
|
1388
|
+
outLen += b.length;
|
|
1389
|
+
});
|
|
1390
|
+
child.stderr?.on("data", (b) => {
|
|
1391
|
+
errTail = (errTail + b.toString("utf8")).slice(-4e3);
|
|
1392
|
+
});
|
|
1393
|
+
const timer = setTimeout(() => {
|
|
1394
|
+
timedOut = true;
|
|
1395
|
+
kill("SIGTERM");
|
|
1396
|
+
setTimeout(() => kill("SIGKILL"), 1e4).unref();
|
|
1397
|
+
}, o.timeoutMs ?? JOB_TIMEOUT_MS);
|
|
1398
|
+
const result = (code, signal) => ({
|
|
1399
|
+
code,
|
|
1400
|
+
signal,
|
|
1401
|
+
stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
|
|
1402
|
+
tooBig,
|
|
1403
|
+
stderr: errTail,
|
|
1404
|
+
timedOut
|
|
1405
|
+
});
|
|
1406
|
+
const finish = (r) => {
|
|
1407
|
+
if (settled) return;
|
|
1408
|
+
settled = true;
|
|
1409
|
+
clearTimeout(timer);
|
|
1410
|
+
resolve(r);
|
|
1411
|
+
};
|
|
1412
|
+
child.on("error", (e) => finish({ error: e.message }));
|
|
1413
|
+
child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
|
|
1414
|
+
child.on("close", (code, signal) => {
|
|
1415
|
+
if (o.onLine && partial.trim()) {
|
|
1416
|
+
try {
|
|
1417
|
+
o.onLine(partial);
|
|
1418
|
+
} catch {
|
|
1419
|
+
}
|
|
1420
|
+
keep(partial);
|
|
1421
|
+
partial = "";
|
|
1422
|
+
}
|
|
1423
|
+
finish(result(code, signal));
|
|
1424
|
+
});
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1204
1427
|
|
|
1205
1428
|
// ../core/src/adapters/antigravity.ts
|
|
1206
|
-
import {
|
|
1207
|
-
existsSync as existsSync3,
|
|
1208
|
-
mkdirSync as mkdirSync2,
|
|
1209
|
-
readdirSync as readdirSync3,
|
|
1210
|
-
readFileSync as readFileSync4,
|
|
1211
|
-
realpathSync,
|
|
1212
|
-
statSync as statSync4,
|
|
1213
|
-
unlinkSync as unlinkSync2,
|
|
1214
|
-
writeFileSync as writeFileSync3
|
|
1215
|
-
} from "node:fs";
|
|
1429
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync4, realpathSync, statSync as statSync4 } from "node:fs";
|
|
1216
1430
|
import path6 from "node:path";
|
|
1217
1431
|
var NAME2 = "antigravity";
|
|
1218
1432
|
var HOOK_NAME = "sessionpipe";
|
|
@@ -1244,10 +1458,8 @@ var antigravity = {
|
|
|
1244
1458
|
const e = entry(cmd);
|
|
1245
1459
|
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1246
1460
|
hooks[HOOK_NAME] = e;
|
|
1247
|
-
mkdirSync2(path6.dirname(file), { recursive: true });
|
|
1248
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
1249
1461
|
return [
|
|
1250
|
-
{
|
|
1462
|
+
writeBack(file, formatJson(hooks, f), f.existed) ?? {
|
|
1251
1463
|
file,
|
|
1252
1464
|
changed: true,
|
|
1253
1465
|
created: !f.existed,
|
|
@@ -1263,12 +1475,8 @@ var antigravity = {
|
|
|
1263
1475
|
const hooks = f.value;
|
|
1264
1476
|
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1265
1477
|
delete hooks[HOOK_NAME];
|
|
1266
|
-
if (opts.created?.includes(file) && !Object.keys(hooks).length)
|
|
1267
|
-
|
|
1268
|
-
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1269
|
-
}
|
|
1270
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
1271
|
-
return [{ file, changed: true }];
|
|
1478
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) return [removeEmptied(file, formatJson(hooks, f))];
|
|
1479
|
+
return [writeBack(file, formatJson(hooks, f)) ?? { file, changed: true }];
|
|
1272
1480
|
},
|
|
1273
1481
|
installed(cmd, env = process.env) {
|
|
1274
1482
|
const file = hooksFile(env);
|
|
@@ -1490,6 +1698,50 @@ function antigravityFacts(file, opts) {
|
|
|
1490
1698
|
return out;
|
|
1491
1699
|
}
|
|
1492
1700
|
|
|
1701
|
+
// ../core/src/adapters/claude-code-control.ts
|
|
1702
|
+
var claude_code_control_exports = {};
|
|
1703
|
+
__export(claude_code_control_exports, {
|
|
1704
|
+
CLAUDE_MODES: () => CLAUDE_MODES,
|
|
1705
|
+
JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
|
|
1706
|
+
MODE_RANK: () => MODE_RANK,
|
|
1707
|
+
NO_HOME_SETTINGS: () => NO_HOME_SETTINGS,
|
|
1708
|
+
ceilingLevel: () => ceilingLevel,
|
|
1709
|
+
claudeArgs: () => claudeArgs,
|
|
1710
|
+
claudeEnv: () => claudeEnv,
|
|
1711
|
+
claudeLogin: () => claudeLogin2,
|
|
1712
|
+
cmpVersion: () => cmpVersion,
|
|
1713
|
+
defaultLevel: () => defaultLevel,
|
|
1714
|
+
detectCaps: () => detectCaps,
|
|
1715
|
+
findClaude: () => findClaude,
|
|
1716
|
+
findTranscript: () => findTranscript,
|
|
1717
|
+
findTranscripts: () => findTranscripts,
|
|
1718
|
+
homeSettings: () => homeSettings,
|
|
1719
|
+
hookMode: () => hookMode,
|
|
1720
|
+
isUuid: () => isUuid,
|
|
1721
|
+
liveProcess: () => liveProcess,
|
|
1722
|
+
liveRecord: () => liveRecord,
|
|
1723
|
+
liveRecords: () => liveRecords,
|
|
1724
|
+
loginCommand: () => loginCommand,
|
|
1725
|
+
loginHint: () => loginHint,
|
|
1726
|
+
modeFlags: () => modeFlags,
|
|
1727
|
+
modeRank: () => modeRank,
|
|
1728
|
+
offeredLevels: () => offeredLevels,
|
|
1729
|
+
outputArgs: () => outputArgs,
|
|
1730
|
+
parseAuthStatus: () => parseAuthStatus,
|
|
1731
|
+
parseCaps: () => parseCaps,
|
|
1732
|
+
parseResult: () => parseResult,
|
|
1733
|
+
peerClass: () => peerClass,
|
|
1734
|
+
recentlyWritten: () => recentlyWritten,
|
|
1735
|
+
relaunchMode: () => relaunchMode,
|
|
1736
|
+
resolveHome: () => resolveHome,
|
|
1737
|
+
runClaude: () => runClaude,
|
|
1738
|
+
sessionFolder: () => sessionFolder
|
|
1739
|
+
});
|
|
1740
|
+
import { execFile, execFileSync as execFileSync3 } from "node:child_process";
|
|
1741
|
+
import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync5, readSync as readSync2, statSync as statSync5 } from "node:fs";
|
|
1742
|
+
import os2 from "node:os";
|
|
1743
|
+
import path7 from "node:path";
|
|
1744
|
+
|
|
1493
1745
|
// ../core/src/control/verify.ts
|
|
1494
1746
|
var GRANT_MAX_MS = 24 * 36e5;
|
|
1495
1747
|
var SKEW_MS = 5 * 6e4;
|
|
@@ -1500,10 +1752,10 @@ var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
|
1500
1752
|
var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
|
|
1501
1753
|
var levelRank = (l) => START_PERMISSIONS.indexOf(l);
|
|
1502
1754
|
var FIELDS = {
|
|
1503
|
-
prompt: ["text", "files"],
|
|
1755
|
+
prompt: ["text", "files", "reply_to"],
|
|
1504
1756
|
"permission.answer": ["for", "decision"],
|
|
1505
1757
|
cancel: [],
|
|
1506
|
-
start: ["text", "cwd", "files", "permission", "model"],
|
|
1758
|
+
start: ["text", "cwd", "files", "permission", "model", "reply_to"],
|
|
1507
1759
|
"key.add": ["key", "name"],
|
|
1508
1760
|
pool: ["pool_action", "pool_account", "pool_order", "pool_code"]
|
|
1509
1761
|
};
|
|
@@ -1511,9 +1763,454 @@ var POOL_FIELDS = FIELDS.pool;
|
|
|
1511
1763
|
var enc = new TextEncoder();
|
|
1512
1764
|
var dec = new TextDecoder();
|
|
1513
1765
|
|
|
1766
|
+
// ../core/src/adapters/claude-code-control.ts
|
|
1767
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1768
|
+
var LIVE_WRITE_MS = 9e4;
|
|
1769
|
+
var cmpVersion = (a, b) => {
|
|
1770
|
+
const pa = a.split(".").map(Number);
|
|
1771
|
+
const pb = b.split(".").map(Number);
|
|
1772
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++)
|
|
1773
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1774
|
+
return 0;
|
|
1775
|
+
};
|
|
1776
|
+
function findClaude(env = process.env, home = os2.homedir()) {
|
|
1777
|
+
if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
|
|
1778
|
+
const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
|
|
1779
|
+
for (const d of String(env.PATH || "").split(path7.delimiter)) {
|
|
1780
|
+
if (!d || !path7.isAbsolute(d)) continue;
|
|
1781
|
+
for (const n of names) if (isExe(path7.join(d, n))) return path7.join(d, n);
|
|
1782
|
+
}
|
|
1783
|
+
for (const p of [
|
|
1784
|
+
path7.join(home, ".claude", "local", "claude"),
|
|
1785
|
+
path7.join(home, ".local", "bin", "claude"),
|
|
1786
|
+
"/opt/homebrew/bin/claude",
|
|
1787
|
+
"/usr/local/bin/claude"
|
|
1788
|
+
])
|
|
1789
|
+
if (isExe(p)) return p;
|
|
1790
|
+
const app = path7.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
1791
|
+
let versions = [];
|
|
1792
|
+
try {
|
|
1793
|
+
versions = readdirSync4(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
|
|
1794
|
+
} catch {
|
|
1795
|
+
}
|
|
1796
|
+
for (const v of versions) {
|
|
1797
|
+
const p = path7.join(app, v, "claude.app", "Contents", "MacOS", "claude");
|
|
1798
|
+
if (isExe(p)) return p;
|
|
1799
|
+
}
|
|
1800
|
+
return null;
|
|
1801
|
+
}
|
|
1802
|
+
function optionBlock(help, flag) {
|
|
1803
|
+
const lines = help.split("\n");
|
|
1804
|
+
const re = new RegExp(`^\\s{0,4}(?:-\\w, )?${flag.replace(/-/g, "\\-")}(?![\\w-])`);
|
|
1805
|
+
const start = lines.findIndex((l) => re.test(l));
|
|
1806
|
+
if (start < 0) return null;
|
|
1807
|
+
const out = [lines[start]];
|
|
1808
|
+
for (let i = start + 1; i < lines.length && lines[i]?.trim() && !/^\s{0,4}-/.test(lines[i]); i++)
|
|
1809
|
+
out.push(lines[i]);
|
|
1810
|
+
return out.join(" ");
|
|
1811
|
+
}
|
|
1812
|
+
function parseCaps(help) {
|
|
1813
|
+
const pm = optionBlock(help, "--permission-mode");
|
|
1814
|
+
const pp = optionBlock(help, "--permission-prompts");
|
|
1815
|
+
return {
|
|
1816
|
+
hasMode: !!pm,
|
|
1817
|
+
modes: pm ? [...pm.matchAll(/"([A-Za-z]+)"/g)].map((m) => m[1]) : [],
|
|
1818
|
+
promptsNone: !!pp && /"none"/.test(pp),
|
|
1819
|
+
fork: !!optionBlock(help, "--fork-session"),
|
|
1820
|
+
stream: !!optionBlock(help, "--include-partial-messages")
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
var LEVEL_MODE = {
|
|
1824
|
+
read: "plan",
|
|
1825
|
+
edits: "acceptEdits",
|
|
1826
|
+
auto: "auto"
|
|
1827
|
+
};
|
|
1828
|
+
function modeFlags(level, caps) {
|
|
1829
|
+
if (level === "inherit") return { args: [] };
|
|
1830
|
+
if (caps.unread)
|
|
1831
|
+
return { error: `this machine couldn't read its Claude Code's options (${caps.unread}); try again in a minute` };
|
|
1832
|
+
if (level === "auto" && !caps.modes.includes("auto"))
|
|
1833
|
+
return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
|
|
1834
|
+
if (level !== "safe") {
|
|
1835
|
+
const m = LEVEL_MODE[level];
|
|
1836
|
+
if (!caps.modes.includes(m)) return { error: `this machine's Claude Code has no ${m} permission mode` };
|
|
1837
|
+
return { args: ["--permission-mode", m] };
|
|
1838
|
+
}
|
|
1839
|
+
if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
|
|
1840
|
+
const args = [];
|
|
1841
|
+
if (caps.hasMode)
|
|
1842
|
+
args.push(
|
|
1843
|
+
"--permission-mode",
|
|
1844
|
+
caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
|
|
1845
|
+
);
|
|
1846
|
+
if (caps.promptsNone) args.push("--permission-prompts", "none");
|
|
1847
|
+
return { args };
|
|
1848
|
+
}
|
|
1849
|
+
var ceilingLevel = (mode) => mode === "inherit" ? "safe" : mode;
|
|
1850
|
+
function offeredLevels(mode, caps) {
|
|
1851
|
+
if (caps.unread) return [];
|
|
1852
|
+
return START_PERMISSIONS.filter(
|
|
1853
|
+
(l) => levelRank(l) <= levelRank(ceilingLevel(mode)) && (l === "safe" || caps.modes.includes(LEVEL_MODE[l]))
|
|
1854
|
+
);
|
|
1855
|
+
}
|
|
1856
|
+
var defaultLevel = (mode) => mode;
|
|
1857
|
+
function outputArgs(caps) {
|
|
1858
|
+
return caps.stream ? ["--output-format", "stream-json", "--verbose", "--include-partial-messages"] : ["--output-format", "json"];
|
|
1859
|
+
}
|
|
1860
|
+
function claudeArgs(o) {
|
|
1861
|
+
const mf = modeFlags(o.level, o.caps);
|
|
1862
|
+
if ("error" in mf) return mf;
|
|
1863
|
+
const where = o.resume ? ["--resume", o.resume, ...o.fork ? ["--fork-session"] : []] : ["--session-id", o.session];
|
|
1864
|
+
return {
|
|
1865
|
+
args: [
|
|
1866
|
+
"-p",
|
|
1867
|
+
...outputArgs(o.caps),
|
|
1868
|
+
...mf.args,
|
|
1869
|
+
...o.model ? ["--model", o.model] : [],
|
|
1870
|
+
...o.allowedTools?.length ? [`--allowedTools=${o.allowedTools.join(",")}`] : [],
|
|
1871
|
+
...where,
|
|
1872
|
+
o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt
|
|
1873
|
+
]
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
function claudeEnv(configDir, env = process.env) {
|
|
1877
|
+
const e = withoutLineage(env);
|
|
1878
|
+
for (const k of [
|
|
1879
|
+
"CLAUDECODE",
|
|
1880
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
1881
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
1882
|
+
"CLAUDE_CODE_ENTRYPOINT",
|
|
1883
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
1884
|
+
"CLAUDE_CODE_SSE_PORT"
|
|
1885
|
+
])
|
|
1886
|
+
delete e[k];
|
|
1887
|
+
e.SESSIONPIPE_CONTROL_JOB = "1";
|
|
1888
|
+
if (configDir !== void 0) {
|
|
1889
|
+
if (path7.resolve(configDir) === path7.resolve(os2.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
|
|
1890
|
+
else e.CLAUDE_CONFIG_DIR = configDir;
|
|
1891
|
+
}
|
|
1892
|
+
return e;
|
|
1893
|
+
}
|
|
1894
|
+
function parseAuthStatus(stdout) {
|
|
1895
|
+
const text = String(stdout || "").trim();
|
|
1896
|
+
const start = text.indexOf("{");
|
|
1897
|
+
if (start < 0) return { loggedIn: null };
|
|
1898
|
+
try {
|
|
1899
|
+
const j = JSON.parse(text.slice(start));
|
|
1900
|
+
if (typeof j.loggedIn !== "boolean") return { loggedIn: null };
|
|
1901
|
+
return {
|
|
1902
|
+
loggedIn: j.loggedIn,
|
|
1903
|
+
...typeof j.authMethod === "string" ? { method: j.authMethod } : {},
|
|
1904
|
+
...typeof j.subscriptionType === "string" ? { subscription: j.subscriptionType } : {}
|
|
1905
|
+
};
|
|
1906
|
+
} catch {
|
|
1907
|
+
return { loggedIn: null };
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
function claudeLogin2(bin, configDir, env = process.env) {
|
|
1911
|
+
const c = probe(bin, ["auth", "status", "--json"]);
|
|
1912
|
+
return new Promise((resolve) => {
|
|
1913
|
+
execFile(
|
|
1914
|
+
c.file,
|
|
1915
|
+
c.args,
|
|
1916
|
+
{ encoding: "utf8", timeout: 2e4, env: claudeEnv(configDir, env), maxBuffer: 256 * 1024, shell: c.shell },
|
|
1917
|
+
// `auth status` may exit non-zero when signed out; its JSON still says so.
|
|
1918
|
+
(_err, stdout) => resolve(parseAuthStatus(stdout))
|
|
1919
|
+
);
|
|
1920
|
+
});
|
|
1921
|
+
}
|
|
1922
|
+
function loginCommand(configDir) {
|
|
1923
|
+
const home = os2.homedir();
|
|
1924
|
+
const isDefault = !configDir || path7.resolve(configDir) === path7.resolve(home, ".claude");
|
|
1925
|
+
return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
|
|
1926
|
+
}
|
|
1927
|
+
function loginHint(configDir, platform = process.platform) {
|
|
1928
|
+
const shown = configDir ? configDir.replace(os2.homedir(), "~") : "~/.claude";
|
|
1929
|
+
const login = loginCommand(configDir);
|
|
1930
|
+
if (platform === "darwin")
|
|
1931
|
+
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.`;
|
|
1932
|
+
return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
|
|
1933
|
+
}
|
|
1934
|
+
function detectCaps(bin, timeoutMs = 3e4) {
|
|
1935
|
+
const c = probe(bin, ["--help"]);
|
|
1936
|
+
try {
|
|
1937
|
+
return parseCaps(
|
|
1938
|
+
execFileSync3(c.file, c.args, {
|
|
1939
|
+
encoding: "utf8",
|
|
1940
|
+
timeout: timeoutMs,
|
|
1941
|
+
env: claudeEnv(),
|
|
1942
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1943
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
1944
|
+
shell: c.shell
|
|
1945
|
+
})
|
|
1946
|
+
);
|
|
1947
|
+
} catch (e) {
|
|
1948
|
+
const err = e;
|
|
1949
|
+
const caps = parseCaps(String(err?.stdout || ""));
|
|
1950
|
+
if (caps.hasMode) return caps;
|
|
1951
|
+
const why = err?.code === "ETIMEDOUT" ? `timed out after ${timeoutMs / 1e3} s` : "failed";
|
|
1952
|
+
return { ...caps, unread: `\`${path7.basename(bin)} --help\` ${why}` };
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
function transcriptIn(session, dir) {
|
|
1956
|
+
let projects = [];
|
|
1957
|
+
try {
|
|
1958
|
+
projects = readdirSync4(path7.join(dir, "projects"), { withFileTypes: true });
|
|
1959
|
+
} catch {
|
|
1960
|
+
return null;
|
|
1961
|
+
}
|
|
1962
|
+
for (const p of projects) {
|
|
1963
|
+
if (!p.isDirectory()) continue;
|
|
1964
|
+
const file = path7.join(dir, "projects", p.name, `${session}.jsonl`);
|
|
1965
|
+
try {
|
|
1966
|
+
if (statSync5(file).isFile()) return { file, configDir: dir, project: p.name };
|
|
1967
|
+
} catch {
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
return null;
|
|
1971
|
+
}
|
|
1972
|
+
function findTranscript(session, dirs) {
|
|
1973
|
+
if (!UUID_RE.test(session)) return null;
|
|
1974
|
+
for (const dir of dirs) {
|
|
1975
|
+
const t = transcriptIn(session, dir);
|
|
1976
|
+
if (t) return t;
|
|
1977
|
+
}
|
|
1978
|
+
return null;
|
|
1979
|
+
}
|
|
1980
|
+
function findTranscripts(session, dirs) {
|
|
1981
|
+
if (!UUID_RE.test(session)) return [];
|
|
1982
|
+
const out = [];
|
|
1983
|
+
for (const dir of dirs) {
|
|
1984
|
+
const t = transcriptIn(session, dir);
|
|
1985
|
+
if (t) out.push(t);
|
|
1986
|
+
}
|
|
1987
|
+
return out;
|
|
1988
|
+
}
|
|
1989
|
+
var homeName = (t) => path7.basename(t.configDir);
|
|
1990
|
+
function resolveHome(session, dirs) {
|
|
1991
|
+
const all = findTranscripts(session, dirs);
|
|
1992
|
+
if (!all.length) return null;
|
|
1993
|
+
if (all.length === 1) return { transcript: all[0] };
|
|
1994
|
+
const live = all.filter((t) => liveRecords(session, t.configDir).length > 0);
|
|
1995
|
+
if (live.length === 1) return { transcript: live[0] };
|
|
1996
|
+
return {
|
|
1997
|
+
ambiguous: [...new Set(all.map(homeName))].sort(),
|
|
1998
|
+
live: [...new Set(live.map(homeName))].sort()
|
|
1999
|
+
};
|
|
2000
|
+
}
|
|
2001
|
+
var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
2002
|
+
function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
|
|
2003
|
+
let first = null;
|
|
2004
|
+
let count = 0;
|
|
2005
|
+
let fd;
|
|
2006
|
+
const look = (line) => {
|
|
2007
|
+
if (line.indexOf('"cwd"') < 0) return null;
|
|
2008
|
+
let j;
|
|
2009
|
+
try {
|
|
2010
|
+
j = JSON.parse(line.toString("utf8"));
|
|
2011
|
+
} catch {
|
|
2012
|
+
return null;
|
|
2013
|
+
}
|
|
2014
|
+
if (!j || typeof j.cwd !== "string" || !path7.isAbsolute(j.cwd)) return null;
|
|
2015
|
+
if (projectName(j.cwd) === t.project) return j.cwd;
|
|
2016
|
+
first ??= j.cwd;
|
|
2017
|
+
count++;
|
|
2018
|
+
return null;
|
|
2019
|
+
};
|
|
2020
|
+
try {
|
|
2021
|
+
fd = openSync2(t.file, "r");
|
|
2022
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
2023
|
+
let pos = 0;
|
|
2024
|
+
let rest = Buffer.alloc(0);
|
|
2025
|
+
let skipping = false;
|
|
2026
|
+
while (pos < cap2 && count < 50) {
|
|
2027
|
+
const n = readSync2(fd, buf, 0, buf.length, pos);
|
|
2028
|
+
if (!n) {
|
|
2029
|
+
if (!skipping && rest.length) {
|
|
2030
|
+
const hit = look(rest);
|
|
2031
|
+
if (hit) return hit;
|
|
2032
|
+
}
|
|
2033
|
+
break;
|
|
2034
|
+
}
|
|
2035
|
+
pos += n;
|
|
2036
|
+
let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
|
|
2037
|
+
let nl = chunk.indexOf(10);
|
|
2038
|
+
while (nl >= 0) {
|
|
2039
|
+
const line = chunk.subarray(0, nl);
|
|
2040
|
+
chunk = chunk.subarray(nl + 1);
|
|
2041
|
+
if (skipping) skipping = false;
|
|
2042
|
+
else {
|
|
2043
|
+
const hit = look(line);
|
|
2044
|
+
if (hit) return hit;
|
|
2045
|
+
}
|
|
2046
|
+
nl = chunk.indexOf(10);
|
|
2047
|
+
}
|
|
2048
|
+
if (chunk.length > 2 * 1024 * 1024) {
|
|
2049
|
+
skipping = true;
|
|
2050
|
+
rest = Buffer.alloc(0);
|
|
2051
|
+
} else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
|
|
2052
|
+
}
|
|
2053
|
+
} catch {
|
|
2054
|
+
} finally {
|
|
2055
|
+
if (fd !== void 0)
|
|
2056
|
+
try {
|
|
2057
|
+
closeSync2(fd);
|
|
2058
|
+
} catch {
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
return first;
|
|
2062
|
+
}
|
|
2063
|
+
function pidAlive(pid) {
|
|
2064
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) return false;
|
|
2065
|
+
try {
|
|
2066
|
+
process.kill(pid, 0);
|
|
2067
|
+
return true;
|
|
2068
|
+
} catch (e) {
|
|
2069
|
+
return e.code === "EPERM";
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
var registryRow = (file, configDir, j) => {
|
|
2073
|
+
const pid = Number(j.pid ?? path7.basename(file).slice(0, -5));
|
|
2074
|
+
const str = (v) => typeof v === "string" && v ? v : null;
|
|
2075
|
+
let keyFile = null;
|
|
2076
|
+
try {
|
|
2077
|
+
const want = new RegExp(`^${pid}\\.[0-9a-f]{64}\\.key$`);
|
|
2078
|
+
const hit = readdirSync4(path7.dirname(file)).find((n) => want.test(n));
|
|
2079
|
+
if (hit) keyFile = path7.join(path7.dirname(file), hit);
|
|
2080
|
+
} catch {
|
|
2081
|
+
}
|
|
2082
|
+
return {
|
|
2083
|
+
configDir,
|
|
2084
|
+
file,
|
|
2085
|
+
pid,
|
|
2086
|
+
socket: str(j.messagingSocketPath),
|
|
2087
|
+
peerProtocol: typeof j.peerProtocol === "number" ? j.peerProtocol : null,
|
|
2088
|
+
procStart: str(j.procStart),
|
|
2089
|
+
status: str(j.status),
|
|
2090
|
+
kind: str(j.kind),
|
|
2091
|
+
entrypoint: str(j.entrypoint),
|
|
2092
|
+
keyFile
|
|
2093
|
+
};
|
|
2094
|
+
};
|
|
2095
|
+
function liveRecords(session, dirs) {
|
|
2096
|
+
const out = [];
|
|
2097
|
+
for (const dir of typeof dirs === "string" ? [dirs] : dirs) {
|
|
2098
|
+
let names = [];
|
|
2099
|
+
try {
|
|
2100
|
+
names = readdirSync4(path7.join(dir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
|
|
2101
|
+
} catch {
|
|
2102
|
+
continue;
|
|
2103
|
+
}
|
|
2104
|
+
for (const n of names) {
|
|
2105
|
+
const file = path7.join(dir, "sessions", n);
|
|
2106
|
+
let j;
|
|
2107
|
+
try {
|
|
2108
|
+
j = JSON.parse(readFileSync5(file, "utf8"));
|
|
2109
|
+
} catch {
|
|
2110
|
+
continue;
|
|
2111
|
+
}
|
|
2112
|
+
if (String(j.sessionId) !== session) continue;
|
|
2113
|
+
const row = registryRow(file, dir, j);
|
|
2114
|
+
if (pidAlive(row.pid)) out.push(row);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
return out;
|
|
2118
|
+
}
|
|
2119
|
+
function liveRecord(session, dirs) {
|
|
2120
|
+
const all = liveRecords(session, dirs);
|
|
2121
|
+
return all.length === 1 ? all[0] : null;
|
|
2122
|
+
}
|
|
2123
|
+
function liveProcess(session, configDir) {
|
|
2124
|
+
return liveRecords(session, configDir).length > 0;
|
|
2125
|
+
}
|
|
2126
|
+
var MODE_RANK = {
|
|
2127
|
+
plan: 0,
|
|
2128
|
+
default: 1,
|
|
2129
|
+
manual: 1,
|
|
2130
|
+
dontAsk: 1,
|
|
2131
|
+
acceptEdits: 2,
|
|
2132
|
+
auto: 3,
|
|
2133
|
+
bypassPermissions: 4
|
|
2134
|
+
};
|
|
2135
|
+
var CLAUDE_MODES = Object.keys(MODE_RANK);
|
|
2136
|
+
var modeRank = (m) => m && m in MODE_RANK ? MODE_RANK[m] : -1;
|
|
2137
|
+
function hookMode(payload) {
|
|
2138
|
+
if (!payload || typeof payload !== "object") return null;
|
|
2139
|
+
const p = payload;
|
|
2140
|
+
const v = typeof p.permission_mode === "string" ? p.permission_mode : p.permissionMode;
|
|
2141
|
+
return typeof v === "string" && CLAUDE_MODES.includes(v) ? v : null;
|
|
2142
|
+
}
|
|
2143
|
+
function peerClass(mode) {
|
|
2144
|
+
if (mode === "bypassPermissions") return "bypass";
|
|
2145
|
+
if (mode === "default" || mode === "manual" || mode === "dontAsk" || mode === "acceptEdits") return "prompting";
|
|
2146
|
+
return null;
|
|
2147
|
+
}
|
|
2148
|
+
var NO_HOME_SETTINGS = { defaultMode: null, disableAutoMode: false, disableBypass: false };
|
|
2149
|
+
function homeSettings(configDir, home = os2.homedir()) {
|
|
2150
|
+
const file = path7.join(configDir ?? path7.join(home, ".claude"), "settings.json");
|
|
2151
|
+
let j;
|
|
2152
|
+
try {
|
|
2153
|
+
j = JSON.parse(readFileSync5(file, "utf8"));
|
|
2154
|
+
} catch {
|
|
2155
|
+
return { ...NO_HOME_SETTINGS };
|
|
2156
|
+
}
|
|
2157
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return { ...NO_HOME_SETTINGS };
|
|
2158
|
+
const p = j.permissions && typeof j.permissions === "object" ? j.permissions : {};
|
|
2159
|
+
const off = (k) => j[k] === "disable" || p[k] === "disable";
|
|
2160
|
+
return {
|
|
2161
|
+
defaultMode: typeof p.defaultMode === "string" && CLAUDE_MODES.includes(p.defaultMode) ? p.defaultMode : null,
|
|
2162
|
+
disableAutoMode: off("disableAutoMode"),
|
|
2163
|
+
disableBypass: off("disableBypassPermissionsMode")
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2166
|
+
function relaunchMode(recorded, home) {
|
|
2167
|
+
if (recorded === "plan") return recorded;
|
|
2168
|
+
if ((recorded === "dontAsk" || recorded === "acceptEdits") && modeRank(home.defaultMode) >= modeRank(recorded))
|
|
2169
|
+
return recorded;
|
|
2170
|
+
return null;
|
|
2171
|
+
}
|
|
2172
|
+
function recentlyWritten(file, ourLastEnd, now = Date.now()) {
|
|
2173
|
+
let m;
|
|
2174
|
+
try {
|
|
2175
|
+
m = statSync5(file).mtimeMs;
|
|
2176
|
+
} catch {
|
|
2177
|
+
return false;
|
|
2178
|
+
}
|
|
2179
|
+
if (now - m > LIVE_WRITE_MS) return false;
|
|
2180
|
+
return !(ourLastEnd && m <= ourLastEnd + 2e3);
|
|
2181
|
+
}
|
|
2182
|
+
function runClaude(bin, args, o) {
|
|
2183
|
+
return runHeadless(bin, args, { ...o, keep: o.keep ?? ((line) => line.includes('"type":"result"')) });
|
|
2184
|
+
}
|
|
2185
|
+
function parseResult(stdout) {
|
|
2186
|
+
const s = String(stdout || "").trim();
|
|
2187
|
+
if (!s) return null;
|
|
2188
|
+
const pick = (j) => {
|
|
2189
|
+
if (Array.isArray(j)) {
|
|
2190
|
+
for (let i = j.length - 1; i >= 0; i--) if (j[i]?.type === "result") return j[i];
|
|
2191
|
+
return null;
|
|
2192
|
+
}
|
|
2193
|
+
return j && typeof j === "object" ? j : null;
|
|
2194
|
+
};
|
|
2195
|
+
try {
|
|
2196
|
+
return pick(JSON.parse(s));
|
|
2197
|
+
} catch {
|
|
2198
|
+
}
|
|
2199
|
+
const lines = s.split("\n");
|
|
2200
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2201
|
+
try {
|
|
2202
|
+
const j = pick(JSON.parse(lines[i]));
|
|
2203
|
+
if (j && (j.type === "result" || "result" in j)) return j;
|
|
2204
|
+
} catch {
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
return null;
|
|
2208
|
+
}
|
|
2209
|
+
var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
|
|
2210
|
+
|
|
1514
2211
|
// ../core/src/adapters/codex.ts
|
|
1515
|
-
import { existsSync as existsSync4,
|
|
1516
|
-
import
|
|
2212
|
+
import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
2213
|
+
import path8 from "node:path";
|
|
1517
2214
|
|
|
1518
2215
|
// ../core/src/adapters/codex-config.ts
|
|
1519
2216
|
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
|
|
@@ -1659,35 +2356,36 @@ var CODEX_EVENTS = [
|
|
|
1659
2356
|
];
|
|
1660
2357
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1661
2358
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1662
|
-
var codexHome = (env) => env.CODEX_HOME ||
|
|
1663
|
-
var hooksFile2 = (env) =>
|
|
1664
|
-
var configFile2 = (env) =>
|
|
1665
|
-
var sessionsDir = (env) =>
|
|
2359
|
+
var codexHome = (env) => env.CODEX_HOME || path8.join(homeDir(), ".codex");
|
|
2360
|
+
var hooksFile2 = (env) => path8.join(codexHome(env), "hooks.json");
|
|
2361
|
+
var configFile2 = (env) => path8.join(codexHome(env), "config.toml");
|
|
2362
|
+
var sessionsDir = (env) => path8.join(codexHome(env), "sessions");
|
|
1666
2363
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1667
2364
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
2365
|
+
var UNTIL_APPROVED = "Codex will report nothing until you approve the hooks: start `codex` and accept the hook review";
|
|
1668
2366
|
function installNotify(cmd, env) {
|
|
1669
2367
|
const file = configFile2(env);
|
|
1670
2368
|
let text = "";
|
|
2369
|
+
let existed = true;
|
|
1671
2370
|
try {
|
|
1672
|
-
text =
|
|
1673
|
-
} catch {
|
|
2371
|
+
text = readFileSync6(file, "utf8");
|
|
2372
|
+
} catch (e) {
|
|
2373
|
+
existed = e.code !== "ENOENT";
|
|
1674
2374
|
}
|
|
1675
2375
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
1676
2376
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1677
2377
|
const n = readNotify(text);
|
|
1678
|
-
const write = (next) => {
|
|
1679
|
-
|
|
1680
|
-
|
|
2378
|
+
const write = (next, note) => {
|
|
2379
|
+
const bad = writeBack(file, next, existed);
|
|
2380
|
+
return bad ? { ...bad, note: `${bad.note}. ${UNTIL_APPROVED}` } : { file, changed: true, note };
|
|
1681
2381
|
};
|
|
1682
2382
|
if (n && !n.other) {
|
|
1683
2383
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
1684
2384
|
return { file, changed: false, note: "notify current" };
|
|
1685
|
-
write(
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
1690
|
-
};
|
|
2385
|
+
return write(
|
|
2386
|
+
withNotify(text, line),
|
|
2387
|
+
n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
2388
|
+
);
|
|
1691
2389
|
}
|
|
1692
2390
|
if (n?.other) {
|
|
1693
2391
|
const prev = previousNotify(n.argv);
|
|
@@ -1696,40 +2394,38 @@ function installNotify(cmd, env) {
|
|
|
1696
2394
|
if (JSON.stringify(prev) === JSON.stringify(argv))
|
|
1697
2395
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1698
2396
|
}
|
|
1699
|
-
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
1700
|
-
write(
|
|
1701
|
-
|
|
1702
|
-
|
|
2397
|
+
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
2398
|
+
return write(
|
|
2399
|
+
replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`),
|
|
2400
|
+
"chained after Codex Computer Use (--previous-notify)"
|
|
2401
|
+
);
|
|
1703
2402
|
}
|
|
1704
2403
|
return {
|
|
1705
2404
|
file,
|
|
1706
2405
|
changed: false,
|
|
1707
2406
|
skipped: true,
|
|
1708
|
-
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone.
|
|
2407
|
+
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. ${UNTIL_APPROVED}`
|
|
1709
2408
|
};
|
|
1710
2409
|
}
|
|
1711
|
-
write(withNotify(text, line));
|
|
1712
|
-
return { file, changed: true, note: "notify set at the top level" };
|
|
2410
|
+
return write(withNotify(text, line), "notify set at the top level");
|
|
1713
2411
|
}
|
|
1714
2412
|
function uninstallNotify(env) {
|
|
1715
2413
|
const file = configFile2(env);
|
|
1716
2414
|
let text;
|
|
1717
2415
|
try {
|
|
1718
|
-
text =
|
|
2416
|
+
text = readFileSync6(file, "utf8");
|
|
1719
2417
|
} catch {
|
|
1720
2418
|
return { file, changed: false };
|
|
1721
2419
|
}
|
|
1722
2420
|
const n = readNotify(text);
|
|
1723
2421
|
if (!n) return { file, changed: false };
|
|
1724
|
-
if (!n.other) {
|
|
1725
|
-
writeFileSync4(file, withoutNotify(text));
|
|
1726
|
-
return { file, changed: true };
|
|
1727
|
-
}
|
|
2422
|
+
if (!n.other) return writeBack(file, withoutNotify(text)) ?? { file, changed: true };
|
|
1728
2423
|
const prev = previousNotify(n.argv);
|
|
1729
|
-
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
2424
|
+
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
2425
|
+
return writeBack(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`)) ?? {
|
|
2426
|
+
file,
|
|
2427
|
+
changed: true
|
|
2428
|
+
};
|
|
1733
2429
|
return { file, changed: false };
|
|
1734
2430
|
}
|
|
1735
2431
|
function findRollout(threadId, env = process.env) {
|
|
@@ -1738,12 +2434,12 @@ function findRollout(threadId, env = process.env) {
|
|
|
1738
2434
|
const walk = (dir, depth) => {
|
|
1739
2435
|
let names = [];
|
|
1740
2436
|
try {
|
|
1741
|
-
names =
|
|
2437
|
+
names = readdirSync5(dir);
|
|
1742
2438
|
} catch {
|
|
1743
2439
|
return;
|
|
1744
2440
|
}
|
|
1745
2441
|
for (const n of names.sort().reverse()) {
|
|
1746
|
-
const p =
|
|
2442
|
+
const p = path8.join(dir, n);
|
|
1747
2443
|
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1748
2444
|
best = p;
|
|
1749
2445
|
return;
|
|
@@ -1761,8 +2457,12 @@ function rolloutMeta(file) {
|
|
|
1761
2457
|
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1762
2458
|
}
|
|
1763
2459
|
function ofAnotherThread(file, id) {
|
|
2460
|
+
const other = threadOf(file);
|
|
2461
|
+
return !!other && other !== id.toLowerCase();
|
|
2462
|
+
}
|
|
2463
|
+
function threadOf(file) {
|
|
1764
2464
|
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);
|
|
1765
|
-
return
|
|
2465
|
+
return m?.[1] ? m[1].toLowerCase() : null;
|
|
1766
2466
|
}
|
|
1767
2467
|
var codex = {
|
|
1768
2468
|
name: NAME3,
|
|
@@ -1787,7 +2487,7 @@ var codex = {
|
|
|
1787
2487
|
];
|
|
1788
2488
|
let text = "";
|
|
1789
2489
|
try {
|
|
1790
|
-
text =
|
|
2490
|
+
text = readFileSync6(configFile2(env), "utf8");
|
|
1791
2491
|
} catch {
|
|
1792
2492
|
}
|
|
1793
2493
|
const n = readNotify(text);
|
|
@@ -1841,6 +2541,8 @@ var codex = {
|
|
|
1841
2541
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
1842
2542
|
const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
|
|
1843
2543
|
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
2544
|
+
const thread = typeof s.transcript_path === "string" ? threadOf(s.transcript_path) : null;
|
|
2545
|
+
const agent_id = typeof s.agent_id === "string" && s.agent_id ? s.agent_id : typeof s.subagent_id === "string" && s.subagent_id ? s.subagent_id : thread && thread !== String(id).toLowerCase() ? thread : void 0;
|
|
1844
2546
|
const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
|
|
1845
2547
|
const events = [];
|
|
1846
2548
|
switch (event) {
|
|
@@ -1860,17 +2562,30 @@ var codex = {
|
|
|
1860
2562
|
);
|
|
1861
2563
|
break;
|
|
1862
2564
|
case "PreToolUse":
|
|
1863
|
-
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
2565
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
1864
2566
|
break;
|
|
1865
2567
|
case "PostToolUse":
|
|
1866
2568
|
if (tool)
|
|
1867
2569
|
events.push(
|
|
1868
|
-
ev("tool.ended", {
|
|
2570
|
+
ev("tool.ended", {
|
|
2571
|
+
tool,
|
|
2572
|
+
call_id,
|
|
2573
|
+
turn_id,
|
|
2574
|
+
agent_id,
|
|
2575
|
+
ok: true,
|
|
2576
|
+
input: s.tool_input,
|
|
2577
|
+
output: s.tool_response
|
|
2578
|
+
})
|
|
1869
2579
|
);
|
|
1870
2580
|
break;
|
|
1871
2581
|
case "PermissionRequest":
|
|
1872
2582
|
events.push(
|
|
1873
|
-
ev("attention.needed", {
|
|
2583
|
+
ev("attention.needed", {
|
|
2584
|
+
attention_id: call_id ?? `perm-${Date.now()}`,
|
|
2585
|
+
kind: "permission",
|
|
2586
|
+
tool,
|
|
2587
|
+
agent_id
|
|
2588
|
+
})
|
|
1874
2589
|
);
|
|
1875
2590
|
break;
|
|
1876
2591
|
case "Stop":
|
|
@@ -1914,7 +2629,7 @@ var codex = {
|
|
|
1914
2629
|
readTranscript(file, fromLine) {
|
|
1915
2630
|
let text = "";
|
|
1916
2631
|
try {
|
|
1917
|
-
text =
|
|
2632
|
+
text = readFileSync6(file, "utf8");
|
|
1918
2633
|
} catch {
|
|
1919
2634
|
return { turns: [], line: fromLine };
|
|
1920
2635
|
}
|
|
@@ -1928,7 +2643,7 @@ var codex = {
|
|
|
1928
2643
|
backfill(sinceMs, env = process.env) {
|
|
1929
2644
|
const rows2 = [];
|
|
1930
2645
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1931
|
-
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(
|
|
2646
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path8.basename(f));
|
|
1932
2647
|
if (!m?.[1]) continue;
|
|
1933
2648
|
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1934
2649
|
const facts = codexFacts(f);
|
|
@@ -2015,10 +2730,10 @@ function codexFacts(file) {
|
|
|
2015
2730
|
// ../core/src/adapters/devin.ts
|
|
2016
2731
|
import { createHash as createHash2 } from "node:crypto";
|
|
2017
2732
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
2018
|
-
import
|
|
2733
|
+
import path9 from "node:path";
|
|
2019
2734
|
|
|
2020
2735
|
// ../core/src/readers/sqlite.ts
|
|
2021
|
-
import { execFileSync as
|
|
2736
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2022
2737
|
import { existsSync as existsSync5 } from "node:fs";
|
|
2023
2738
|
import { createRequire } from "node:module";
|
|
2024
2739
|
function quiet(fn) {
|
|
@@ -2098,7 +2813,7 @@ db.close();
|
|
|
2098
2813
|
process.stdout.write(JSON.stringify(out));`;
|
|
2099
2814
|
function viaChild(file, sql, params, o) {
|
|
2100
2815
|
try {
|
|
2101
|
-
const out =
|
|
2816
|
+
const out = execFileSync4(
|
|
2102
2817
|
process.execPath,
|
|
2103
2818
|
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
2104
2819
|
{
|
|
@@ -2142,7 +2857,7 @@ function viaCli(file, sql, params, o) {
|
|
|
2142
2857
|
const text = inlineParams(sql, params);
|
|
2143
2858
|
if (!text) return null;
|
|
2144
2859
|
try {
|
|
2145
|
-
const out =
|
|
2860
|
+
const out = execFileSync4("sqlite3", ["-readonly", "-json", file, text], {
|
|
2146
2861
|
encoding: "utf8",
|
|
2147
2862
|
timeout: o.timeoutMs + 3e3,
|
|
2148
2863
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -2199,11 +2914,11 @@ function foreignEvent(file) {
|
|
|
2199
2914
|
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
2200
2915
|
}
|
|
2201
2916
|
var homeOf = (env) => env.HOME || homeDir();
|
|
2202
|
-
var appData = (env) => env.APPDATA ||
|
|
2203
|
-
var devinConfigDir = (env = process.env) => process.platform === "win32" ?
|
|
2204
|
-
var configFile3 = (env) =>
|
|
2205
|
-
var devinDataDir = (env = process.env) => process.platform === "win32" ?
|
|
2206
|
-
var sessionsDb = (env) =>
|
|
2917
|
+
var appData = (env) => env.APPDATA || path9.join(homeOf(env), "AppData", "Roaming");
|
|
2918
|
+
var devinConfigDir = (env = process.env) => process.platform === "win32" ? path9.join(appData(env), "devin") : path9.join(env.XDG_CONFIG_HOME || path9.join(homeOf(env), ".config"), "devin");
|
|
2919
|
+
var configFile3 = (env) => path9.join(devinConfigDir(env), "config.json");
|
|
2920
|
+
var devinDataDir = (env = process.env) => process.platform === "win32" ? path9.join(appData(env), "devin", "cli") : path9.join(env.XDG_DATA_HOME || path9.join(homeOf(env), ".local", "share"), "devin", "cli");
|
|
2921
|
+
var sessionsDb = (env) => path9.join(devinDataDir(env), "sessions.db");
|
|
2207
2922
|
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
2208
2923
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2209
2924
|
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
@@ -2360,7 +3075,7 @@ var CHAIN_LIMIT = 4e3;
|
|
|
2360
3075
|
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2361
3076
|
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2362
3077
|
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2363
|
-
UNION
|
|
3078
|
+
UNION
|
|
2364
3079
|
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2365
3080
|
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2366
3081
|
)
|
|
@@ -2375,7 +3090,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
|
2375
3090
|
function sessionFacts(row) {
|
|
2376
3091
|
const out = {};
|
|
2377
3092
|
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2378
|
-
if (typeof row.working_directory === "string" &&
|
|
3093
|
+
if (typeof row.working_directory === "string" && path9.isAbsolute(row.working_directory))
|
|
2379
3094
|
out.cwd = row.working_directory;
|
|
2380
3095
|
const started = Number(row.created_at);
|
|
2381
3096
|
const last = Number(row.last_activity_at);
|
|
@@ -2420,8 +3135,8 @@ function readDevinChain(db, session, fromLine) {
|
|
|
2420
3135
|
}
|
|
2421
3136
|
function devinVersion(env = process.env) {
|
|
2422
3137
|
try {
|
|
2423
|
-
const target = readlinkSync(
|
|
2424
|
-
const name =
|
|
3138
|
+
const target = readlinkSync(path9.join(devinDataDir(env), "_versions", "current"));
|
|
3139
|
+
const name = path9.basename(target);
|
|
2425
3140
|
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2426
3141
|
} catch {
|
|
2427
3142
|
return void 0;
|
|
@@ -2434,9 +3149,9 @@ function devinAccount(env = process.env) {
|
|
|
2434
3149
|
}
|
|
2435
3150
|
|
|
2436
3151
|
// ../core/src/adapters/gemini-cli.ts
|
|
2437
|
-
import { execFileSync as
|
|
2438
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2439
|
-
import
|
|
3152
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
3153
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
|
|
3154
|
+
import path10 from "node:path";
|
|
2440
3155
|
var NAME5 = "gemini-cli";
|
|
2441
3156
|
var GEMINI_EVENTS = [
|
|
2442
3157
|
"SessionStart",
|
|
@@ -2449,16 +3164,16 @@ var GEMINI_EVENTS = [
|
|
|
2449
3164
|
"SessionEnd"
|
|
2450
3165
|
];
|
|
2451
3166
|
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2452
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
2453
|
-
var settings = (env) =>
|
|
3167
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path10.join(homeDir(), ".gemini");
|
|
3168
|
+
var settings = (env) => path10.join(geminiDir(env), "settings.json");
|
|
2454
3169
|
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2455
3170
|
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
2456
3171
|
function geminiVersion(platform = process.platform, env = process.env) {
|
|
2457
3172
|
const win = platform === "win32";
|
|
2458
|
-
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [
|
|
3173
|
+
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path10.dirname(process.execPath)], env }) : "gemini";
|
|
2459
3174
|
if (!bin) throw new Error("no gemini on PATH");
|
|
2460
3175
|
const c = probe(bin, ["--version"]);
|
|
2461
|
-
return
|
|
3176
|
+
return execFileSync5(c.file, c.args, {
|
|
2462
3177
|
encoding: "utf8",
|
|
2463
3178
|
timeout: 4e3,
|
|
2464
3179
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -2491,7 +3206,7 @@ var geminiCli = {
|
|
|
2491
3206
|
name: NAME5,
|
|
2492
3207
|
events: GEMINI_EVENTS,
|
|
2493
3208
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
2494
|
-
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(
|
|
3209
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path10.join(geminiDir(env), "tmp")),
|
|
2495
3210
|
configFiles: (env = process.env) => [settings(env)],
|
|
2496
3211
|
install: (cmd, env = process.env, opts = {}) => {
|
|
2497
3212
|
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
@@ -2589,7 +3304,7 @@ var geminiCli = {
|
|
|
2589
3304
|
readTranscript(file, fromLine) {
|
|
2590
3305
|
let text = "";
|
|
2591
3306
|
try {
|
|
2592
|
-
text =
|
|
3307
|
+
text = readFileSync7(file, "utf8");
|
|
2593
3308
|
} catch {
|
|
2594
3309
|
return { turns: [], line: fromLine };
|
|
2595
3310
|
}
|
|
@@ -2629,12 +3344,12 @@ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
|
2629
3344
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2630
3345
|
|
|
2631
3346
|
// ../core/src/config.ts
|
|
2632
|
-
import { chmodSync, existsSync as existsSync8, mkdirSync as
|
|
2633
|
-
import
|
|
3347
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3348
|
+
import path11 from "node:path";
|
|
2634
3349
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
2635
3350
|
function readConfig(file = configFile()) {
|
|
2636
3351
|
try {
|
|
2637
|
-
const j = JSON.parse(
|
|
3352
|
+
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
2638
3353
|
return {
|
|
2639
3354
|
...DEFAULT_CONFIG,
|
|
2640
3355
|
...j,
|
|
@@ -2645,9 +3360,36 @@ function readConfig(file = configFile()) {
|
|
|
2645
3360
|
return { ...DEFAULT_CONFIG, sinks: [], harnesses: {} };
|
|
2646
3361
|
}
|
|
2647
3362
|
}
|
|
3363
|
+
function configProblem(file) {
|
|
3364
|
+
let text;
|
|
3365
|
+
try {
|
|
3366
|
+
text = readFileSync8(file, "utf8");
|
|
3367
|
+
} catch (e) {
|
|
3368
|
+
const code = e.code;
|
|
3369
|
+
return code === "ENOENT" ? void 0 : `could not be read (${code})`;
|
|
3370
|
+
}
|
|
3371
|
+
if (!text.trim()) return void 0;
|
|
3372
|
+
if (text.charCodeAt(0) === 65279) return "starts with a byte-order mark (save it as UTF-8 without one)";
|
|
3373
|
+
try {
|
|
3374
|
+
const j = JSON.parse(text);
|
|
3375
|
+
return j && typeof j === "object" && !Array.isArray(j) ? void 0 : "is not a JSON object";
|
|
3376
|
+
} catch (e) {
|
|
3377
|
+
const m = e.message;
|
|
3378
|
+
const at = (/line \d+ column \d+/.exec(m) ?? /position \d+/.exec(m))?.[0];
|
|
3379
|
+
return `is not valid JSON${at ? ` at ${at}` : ""}`;
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
function assertConfigWritable(file = configFile()) {
|
|
3383
|
+
const problem = configProblem(file);
|
|
3384
|
+
if (problem)
|
|
3385
|
+
throw new Error(
|
|
3386
|
+
`${tilde(file)} ${problem}, so it was not written over: that would lose its machine name and sinks. Fix it, or move it aside to start again, then run this again.`
|
|
3387
|
+
);
|
|
3388
|
+
}
|
|
2648
3389
|
function writeConfig(c, file = configFile()) {
|
|
2649
|
-
|
|
2650
|
-
|
|
3390
|
+
assertConfigWritable(file);
|
|
3391
|
+
mkdirSync2(path11.dirname(file), { recursive: true, mode: 448 });
|
|
3392
|
+
writeFileSync2(file, `${JSON.stringify(c, null, 2)}
|
|
2651
3393
|
`, { mode: 384 });
|
|
2652
3394
|
if (process.platform !== "win32" && existsSync8(file)) {
|
|
2653
3395
|
try {
|
|
@@ -2710,38 +3452,23 @@ function makeEvent(a, ctx) {
|
|
|
2710
3452
|
};
|
|
2711
3453
|
}
|
|
2712
3454
|
|
|
2713
|
-
// ../core/src/launched-by.ts
|
|
2714
|
-
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2715
|
-
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2716
|
-
function launchedBy(env) {
|
|
2717
|
-
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2718
|
-
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2719
|
-
}
|
|
2720
|
-
function readHook(adapter, input) {
|
|
2721
|
-
const r = adapter.fromHook(input);
|
|
2722
|
-
if (r && !r.session.parent_id) {
|
|
2723
|
-
const launcher = launchedBy(input.env);
|
|
2724
|
-
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2725
|
-
}
|
|
2726
|
-
return r;
|
|
2727
|
-
}
|
|
2728
|
-
|
|
2729
3455
|
// ../core/src/outbox.ts
|
|
3456
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2730
3457
|
import {
|
|
2731
3458
|
appendFileSync,
|
|
2732
|
-
closeSync as
|
|
3459
|
+
closeSync as closeSync3,
|
|
2733
3460
|
existsSync as existsSync9,
|
|
2734
|
-
mkdirSync as
|
|
2735
|
-
openSync as
|
|
2736
|
-
readdirSync as
|
|
2737
|
-
readFileSync as
|
|
2738
|
-
readSync as
|
|
3461
|
+
mkdirSync as mkdirSync3,
|
|
3462
|
+
openSync as openSync3,
|
|
3463
|
+
readdirSync as readdirSync6,
|
|
3464
|
+
readFileSync as readFileSync9,
|
|
3465
|
+
readSync as readSync3,
|
|
2739
3466
|
renameSync,
|
|
2740
|
-
statSync as
|
|
2741
|
-
unlinkSync as
|
|
2742
|
-
writeFileSync as
|
|
3467
|
+
statSync as statSync6,
|
|
3468
|
+
unlinkSync as unlinkSync2,
|
|
3469
|
+
writeFileSync as writeFileSync3
|
|
2743
3470
|
} from "node:fs";
|
|
2744
|
-
import
|
|
3471
|
+
import path12 from "node:path";
|
|
2745
3472
|
var READ_CHUNK = 1024 * 1024;
|
|
2746
3473
|
var READ_MAX = 8 * 1024 * 1024;
|
|
2747
3474
|
var Outbox = class {
|
|
@@ -2750,13 +3477,13 @@ var Outbox = class {
|
|
|
2750
3477
|
}
|
|
2751
3478
|
root;
|
|
2752
3479
|
dir(harness) {
|
|
2753
|
-
return
|
|
3480
|
+
return path12.join(this.root, "outbox", safeId(harness));
|
|
2754
3481
|
}
|
|
2755
3482
|
file(ref) {
|
|
2756
|
-
return
|
|
3483
|
+
return path12.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2757
3484
|
}
|
|
2758
3485
|
seqFile(ref) {
|
|
2759
|
-
return
|
|
3486
|
+
return path12.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2760
3487
|
}
|
|
2761
3488
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2762
3489
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
@@ -2764,19 +3491,19 @@ var Outbox = class {
|
|
|
2764
3491
|
const f = this.seqFile(ref);
|
|
2765
3492
|
let n = -1;
|
|
2766
3493
|
try {
|
|
2767
|
-
const t =
|
|
3494
|
+
const t = readFileSync9(f, "utf8").trim();
|
|
2768
3495
|
if (/^\d+$/.test(t)) n = Number(t);
|
|
2769
3496
|
} catch {
|
|
2770
3497
|
}
|
|
2771
3498
|
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2772
|
-
|
|
2773
|
-
|
|
3499
|
+
mkdirSync3(path12.dirname(f), { recursive: true, mode: 448 });
|
|
3500
|
+
writeFileSync3(f, String(n + 1), { mode: 384 });
|
|
2774
3501
|
return n;
|
|
2775
3502
|
}
|
|
2776
3503
|
lastSeqInFile(ref) {
|
|
2777
3504
|
const f = this.file(ref);
|
|
2778
3505
|
if (!existsSync9(f)) return -1;
|
|
2779
|
-
const lines =
|
|
3506
|
+
const lines = readFileSync9(f, "utf8").trim().split("\n");
|
|
2780
3507
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2781
3508
|
try {
|
|
2782
3509
|
const j = JSON.parse(lines[i]);
|
|
@@ -2788,26 +3515,26 @@ var Outbox = class {
|
|
|
2788
3515
|
}
|
|
2789
3516
|
append(ref, events) {
|
|
2790
3517
|
if (!events.length) return;
|
|
2791
|
-
|
|
3518
|
+
mkdirSync3(this.dir(ref.harness), { recursive: true, mode: 448 });
|
|
2792
3519
|
appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2793
3520
|
`, { mode: 384 });
|
|
2794
3521
|
}
|
|
2795
3522
|
/** Every session file, newest first. */
|
|
2796
3523
|
sessions() {
|
|
2797
3524
|
const out = [];
|
|
2798
|
-
const base =
|
|
3525
|
+
const base = path12.join(this.root, "outbox");
|
|
2799
3526
|
if (!existsSync9(base)) return [];
|
|
2800
|
-
for (const h of
|
|
2801
|
-
const d =
|
|
3527
|
+
for (const h of readdirSync6(base)) {
|
|
3528
|
+
const d = path12.join(base, h);
|
|
2802
3529
|
let names = [];
|
|
2803
3530
|
try {
|
|
2804
|
-
names =
|
|
3531
|
+
names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
|
|
2805
3532
|
} catch {
|
|
2806
3533
|
continue;
|
|
2807
3534
|
}
|
|
2808
3535
|
for (const n of names) {
|
|
2809
3536
|
try {
|
|
2810
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime:
|
|
3537
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path12.join(d, n)).mtimeMs });
|
|
2811
3538
|
} catch {
|
|
2812
3539
|
}
|
|
2813
3540
|
}
|
|
@@ -2823,16 +3550,16 @@ var Outbox = class {
|
|
|
2823
3550
|
read(ref, from, maxEvents = 500) {
|
|
2824
3551
|
const f = this.file(ref);
|
|
2825
3552
|
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2826
|
-
const size =
|
|
3553
|
+
const size = statSync6(f).size;
|
|
2827
3554
|
if (from >= size) return { events: [], offset: from };
|
|
2828
|
-
const fd =
|
|
3555
|
+
const fd = openSync3(f, "r");
|
|
2829
3556
|
try {
|
|
2830
3557
|
const events = [];
|
|
2831
3558
|
let offset = from;
|
|
2832
3559
|
let chunk = READ_CHUNK;
|
|
2833
3560
|
while (events.length < maxEvents && offset < size) {
|
|
2834
3561
|
const buf = Buffer.alloc(Math.min(size - offset, chunk));
|
|
2835
|
-
const n =
|
|
3562
|
+
const n = readSync3(fd, buf, 0, buf.length, offset);
|
|
2836
3563
|
const text = buf.subarray(0, n).toString("utf8");
|
|
2837
3564
|
let pos = 0;
|
|
2838
3565
|
let used = 0;
|
|
@@ -2855,37 +3582,109 @@ var Outbox = class {
|
|
|
2855
3582
|
}
|
|
2856
3583
|
return { events, offset };
|
|
2857
3584
|
} finally {
|
|
2858
|
-
|
|
3585
|
+
closeSync3(fd);
|
|
2859
3586
|
}
|
|
2860
3587
|
}
|
|
2861
|
-
/** Files older than `days` are pruned
|
|
3588
|
+
/** Files older than `days` are pruned, and each sink's cursor into them. The seq
|
|
3589
|
+
* counter stays: a session resumed after the window counts on from where it was,
|
|
3590
|
+
* so a receiver's (harness, session, seq) dedup doesn't take its new events for
|
|
3591
|
+
* ones it already has, and the sinks read the new file from its start.
|
|
3592
|
+
* It all happens under the drain's `flush` lock, since a drain holds the whole
|
|
3593
|
+
* cursor map and writes it back: a file and its cursor keys go together, so a
|
|
3594
|
+
* session resumed in between can't keep a key past its new file's end. While a
|
|
3595
|
+
* drain runs, nothing is pruned (0); the next prune does it. It never throws: it
|
|
3596
|
+
* is a cleanup, and a hook's drain comes after it. */
|
|
2862
3597
|
prune(days, now = Date.now()) {
|
|
3598
|
+
let release = null;
|
|
3599
|
+
try {
|
|
3600
|
+
release = takeNamedLock(this.root, "flush");
|
|
3601
|
+
} catch {
|
|
3602
|
+
}
|
|
3603
|
+
if (!release) return 0;
|
|
2863
3604
|
let n = 0;
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
const
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
3605
|
+
try {
|
|
3606
|
+
const cutoff = now - days * 864e5;
|
|
3607
|
+
const aged = this.sessions().filter((ref) => {
|
|
3608
|
+
try {
|
|
3609
|
+
return statSync6(this.file(ref)).mtimeMs < cutoff;
|
|
3610
|
+
} catch {
|
|
3611
|
+
return false;
|
|
3612
|
+
}
|
|
3613
|
+
});
|
|
3614
|
+
this.dropStaleCursors(new Set(aged.map((ref) => `${ref.harness}/${ref.session}`)));
|
|
3615
|
+
for (const ref of aged) {
|
|
3616
|
+
const f = this.file(ref);
|
|
3617
|
+
try {
|
|
3618
|
+
if (statSync6(f).mtimeMs < cutoff) {
|
|
3619
|
+
unlinkSync2(f);
|
|
3620
|
+
n++;
|
|
2873
3621
|
}
|
|
2874
|
-
|
|
3622
|
+
} catch {
|
|
2875
3623
|
}
|
|
2876
|
-
} catch {
|
|
2877
3624
|
}
|
|
3625
|
+
} catch {
|
|
3626
|
+
} finally {
|
|
3627
|
+
release();
|
|
2878
3628
|
}
|
|
2879
3629
|
return n;
|
|
2880
3630
|
}
|
|
3631
|
+
/** A sink's cursor into a session file about to be pruned (`gone`), or one already
|
|
3632
|
+
* missing (an earlier prune, an older release, by hand) is removed. The caller
|
|
3633
|
+
* holds the `flush` lock. */
|
|
3634
|
+
dropStaleCursors(gone) {
|
|
3635
|
+
let names = [];
|
|
3636
|
+
try {
|
|
3637
|
+
names = readdirSync6(path12.join(this.root, "cursors")).filter((n) => n.endsWith(".json"));
|
|
3638
|
+
} catch {
|
|
3639
|
+
}
|
|
3640
|
+
const stale2 = (c) => Object.keys(c).filter((k) => {
|
|
3641
|
+
if (gone.has(k)) return true;
|
|
3642
|
+
const i = k.indexOf("/");
|
|
3643
|
+
return !existsSync9(this.file({ harness: k.slice(0, i), session: k.slice(i + 1) }));
|
|
3644
|
+
});
|
|
3645
|
+
for (const n of names) {
|
|
3646
|
+
const s = new Cursors(this.root, n.slice(0, -5));
|
|
3647
|
+
const c = s.read();
|
|
3648
|
+
const keys = stale2(c);
|
|
3649
|
+
if (!keys.length) continue;
|
|
3650
|
+
for (const k of keys) delete c[k];
|
|
3651
|
+
s.write(c);
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
2881
3654
|
delete(ref) {
|
|
3655
|
+
this.dropCursors(ref);
|
|
2882
3656
|
for (const f of [this.file(ref), this.seqFile(ref)]) {
|
|
2883
3657
|
try {
|
|
2884
|
-
|
|
3658
|
+
unlinkSync2(f);
|
|
2885
3659
|
} catch {
|
|
2886
3660
|
}
|
|
2887
3661
|
}
|
|
2888
3662
|
}
|
|
3663
|
+
/** The key every sink's cursor holds for a session is removed with the session's
|
|
3664
|
+
* file: the key alone pointed into the old file, and a session that went on (still
|
|
3665
|
+
* running, or resumed later) had its new file's first events unsent until it grew
|
|
3666
|
+
* past that offset. Every file under <state>/cursors is taken (a sink since taken
|
|
3667
|
+
* out of the config still has one; one that doesn't parse reads as empty and is
|
|
3668
|
+
* left), by the key a drain writes (the file's own name, `safeId`) and by the ref
|
|
3669
|
+
* as given. The caller holds the drain's `flush` lock: a drain holds the whole
|
|
3670
|
+
* cursor map and writes it back, so a key dropped under it would come back. */
|
|
3671
|
+
dropCursors(ref) {
|
|
3672
|
+
const keys = /* @__PURE__ */ new Set([`${ref.harness}/${ref.session}`, `${safeId(ref.harness)}/${safeId(ref.session)}`]);
|
|
3673
|
+
let names = [];
|
|
3674
|
+
try {
|
|
3675
|
+
names = readdirSync6(path12.join(this.root, "cursors")).filter((n) => n.endsWith(".json"));
|
|
3676
|
+
} catch {
|
|
3677
|
+
return;
|
|
3678
|
+
}
|
|
3679
|
+
for (const n of names) {
|
|
3680
|
+
const s = new Cursors(this.root, n.slice(0, -5));
|
|
3681
|
+
const c = s.read();
|
|
3682
|
+
const had = Object.keys(c).filter((k) => keys.has(k));
|
|
3683
|
+
if (!had.length) continue;
|
|
3684
|
+
for (const k of had) delete c[k];
|
|
3685
|
+
s.write(c);
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
2889
3688
|
};
|
|
2890
3689
|
var Cursors = class {
|
|
2891
3690
|
constructor(root, sink) {
|
|
@@ -2895,20 +3694,23 @@ var Cursors = class {
|
|
|
2895
3694
|
root;
|
|
2896
3695
|
sink;
|
|
2897
3696
|
file() {
|
|
2898
|
-
return
|
|
3697
|
+
return path12.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2899
3698
|
}
|
|
3699
|
+
/** The map; empty when the file is missing, cut off mid-write, or holds anything
|
|
3700
|
+
* but an object (`null`, a list), so a drain never indexes into those. */
|
|
2900
3701
|
read() {
|
|
2901
3702
|
try {
|
|
2902
|
-
|
|
3703
|
+
const c = JSON.parse(readFileSync9(this.file(), "utf8"));
|
|
3704
|
+
return c && typeof c === "object" && !Array.isArray(c) ? c : {};
|
|
2903
3705
|
} catch {
|
|
2904
3706
|
return {};
|
|
2905
3707
|
}
|
|
2906
3708
|
}
|
|
2907
3709
|
write(c) {
|
|
2908
3710
|
const f = this.file();
|
|
2909
|
-
|
|
3711
|
+
mkdirSync3(path12.dirname(f), { recursive: true, mode: 448 });
|
|
2910
3712
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2911
|
-
|
|
3713
|
+
writeFileSync3(tmp, JSON.stringify(c), { mode: 384 });
|
|
2912
3714
|
renameSync(tmp, f);
|
|
2913
3715
|
}
|
|
2914
3716
|
key(ref) {
|
|
@@ -2919,22 +3721,44 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2919
3721
|
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
2920
3722
|
}
|
|
2921
3723
|
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
2922
|
-
const dir =
|
|
2923
|
-
|
|
2924
|
-
const f =
|
|
3724
|
+
const dir = path12.join(root, "locks");
|
|
3725
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
3726
|
+
const f = path12.join(dir, `${name}.lock`);
|
|
3727
|
+
const nonce = randomBytes2(8).toString("hex");
|
|
3728
|
+
const mine = `${process.pid} ${nonce}`;
|
|
3729
|
+
let body = null;
|
|
2925
3730
|
try {
|
|
2926
|
-
|
|
2927
|
-
|
|
3731
|
+
body = readFileSync9(f, "utf8");
|
|
3732
|
+
const stale2 = Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(body.split(" ")[0]));
|
|
3733
|
+
if (!stale2) return null;
|
|
2928
3734
|
} catch {
|
|
3735
|
+
body = null;
|
|
3736
|
+
}
|
|
3737
|
+
if (body !== null) {
|
|
3738
|
+
const aside = `${f}.${process.pid}.${nonce}.stale`;
|
|
3739
|
+
try {
|
|
3740
|
+
renameSync(f, aside);
|
|
3741
|
+
} catch {
|
|
3742
|
+
return null;
|
|
3743
|
+
}
|
|
3744
|
+
const moved = readFileSync9(aside, "utf8");
|
|
3745
|
+
unlinkSync2(aside);
|
|
3746
|
+
if (moved !== body) {
|
|
3747
|
+
try {
|
|
3748
|
+
writeFileSync3(f, moved, { flag: "wx" });
|
|
3749
|
+
} catch {
|
|
3750
|
+
}
|
|
3751
|
+
return null;
|
|
3752
|
+
}
|
|
2929
3753
|
}
|
|
2930
3754
|
try {
|
|
2931
|
-
|
|
3755
|
+
writeFileSync3(f, mine, { flag: "wx" });
|
|
2932
3756
|
} catch {
|
|
2933
3757
|
return null;
|
|
2934
3758
|
}
|
|
2935
3759
|
return () => {
|
|
2936
3760
|
try {
|
|
2937
|
-
|
|
3761
|
+
if (readFileSync9(f, "utf8") === mine) unlinkSync2(f);
|
|
2938
3762
|
} catch {
|
|
2939
3763
|
}
|
|
2940
3764
|
};
|
|
@@ -2953,7 +3777,7 @@ function processAlive(pid) {
|
|
|
2953
3777
|
import { createHash as createHash3 } from "node:crypto";
|
|
2954
3778
|
var RULESET_PII = "pii@1";
|
|
2955
3779
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2956
|
-
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
3780
|
+
var PHONE = /(?<![\w.])(?:(?<!\)[ .-]?|\+\d{1,3}[ .-]?)(\+?(?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01]))(?:(?= (?:[01]?\d|2[0-3]):)|(?!\w|[ .-]\d\d))|(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w))/g;
|
|
2957
3781
|
function looksPhone(s) {
|
|
2958
3782
|
const digits = s.replace(/\D/g, "");
|
|
2959
3783
|
if (digits.length < 7 || digits.length > 15) return false;
|
|
@@ -2964,7 +3788,7 @@ function looksPhone(s) {
|
|
|
2964
3788
|
function reducePii(text, home) {
|
|
2965
3789
|
if (!text) return text;
|
|
2966
3790
|
let out = text.replace(EMAIL, "[email]");
|
|
2967
|
-
out = out.replace(PHONE, (m) => looksPhone(m) ? "[phone]" : m);
|
|
3791
|
+
out = out.replace(PHONE, (m, date) => !date && looksPhone(m) ? "[phone]" : m);
|
|
2968
3792
|
if (home) {
|
|
2969
3793
|
const h = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2970
3794
|
out = out.replace(new RegExp(`(?<![A-Za-z0-9_])${h}(?=/|$|\\s|["'\`])`, "g"), "~");
|
|
@@ -3161,6 +3985,8 @@ var TYPE_TIER = {
|
|
|
3161
3985
|
"subagent.ended": 1,
|
|
3162
3986
|
"context.compacted": 1,
|
|
3163
3987
|
"limits.observed": 1,
|
|
3988
|
+
"delegation.started": 0,
|
|
3989
|
+
"delegation.ended": 0,
|
|
3164
3990
|
native: 3
|
|
3165
3991
|
};
|
|
3166
3992
|
var DATA_ALLOW = {
|
|
@@ -3171,17 +3997,19 @@ var DATA_ALLOW = {
|
|
|
3171
3997
|
"session.forgotten": { 0: [] },
|
|
3172
3998
|
"turn.started": { 0: ["turn_id", "submitted_by", "injected"], 1: ["prompt_chars"] },
|
|
3173
3999
|
"turn.ended": { 0: ["turn_id", "reason", "ms", "background"], 1: ["error"] },
|
|
3174
|
-
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
3175
|
-
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
4000
|
+
"turn.transcript": { 2: ["turn", "user", "assistant", "at", "injected"] },
|
|
4001
|
+
"turn.progress": { 2: ["turn", "user", "assistant", "at", "injected"] },
|
|
3176
4002
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
3177
4003
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
3178
4004
|
"files.changed": { 1: ["paths"] },
|
|
3179
|
-
"attention.needed": { 0: ["attention_id", "kind"], 1: ["tool", "message"] },
|
|
4005
|
+
"attention.needed": { 0: ["attention_id", "kind"], 1: ["tool", "message", "agent_id"] },
|
|
3180
4006
|
"attention.cleared": { 0: ["attention_id", "how"] },
|
|
3181
4007
|
"subagent.started": { 1: ["agent_id", "agent_type"] },
|
|
3182
4008
|
"subagent.ended": { 1: ["agent_id", "agent_type"] },
|
|
3183
4009
|
"context.compacted": { 1: ["trigger"] },
|
|
3184
|
-
"limits.observed": { 1: ["windows", "plan", "account_id"] }
|
|
4010
|
+
"limits.observed": { 1: ["windows", "plan", "account_id"] },
|
|
4011
|
+
"delegation.started": { 0: ["ask_id", "via", "harness", "to"], 1: ["text"] },
|
|
4012
|
+
"delegation.ended": { 0: ["ask_id", "to", "ok", "ms", "error", "relayed"], 2: ["reply"] }
|
|
3185
4013
|
};
|
|
3186
4014
|
var SESSION_ALLOW = [
|
|
3187
4015
|
"id",
|
|
@@ -3197,7 +4025,8 @@ var SESSION_ALLOW = [
|
|
|
3197
4025
|
"account_id",
|
|
3198
4026
|
"attended",
|
|
3199
4027
|
"parent_id",
|
|
3200
|
-
"launched_by"
|
|
4028
|
+
"launched_by",
|
|
4029
|
+
"delegated_by"
|
|
3201
4030
|
];
|
|
3202
4031
|
var MAX_SIDE = 2e4;
|
|
3203
4032
|
var MAX_IO = 64 * 1024;
|
|
@@ -3236,6 +4065,8 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
3236
4065
|
if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
|
|
3237
4066
|
if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
|
|
3238
4067
|
if (typeof data.message === "string") data.message = cap(data.message, 200);
|
|
4068
|
+
if (event.type === "delegation.started" && typeof data.text === "string") data.text = cap(data.text, 200);
|
|
4069
|
+
if (event.type === "delegation.ended" && typeof data.reply === "string") data.reply = cap(data.reply, MAX_SIDE);
|
|
3239
4070
|
const rulesets = [];
|
|
3240
4071
|
let out = { ...event, tier, session, data, privacy: { rulesets, pii } };
|
|
3241
4072
|
if (tier > 0) {
|
|
@@ -3262,8 +4093,8 @@ function shortId(harness, id) {
|
|
|
3262
4093
|
}
|
|
3263
4094
|
|
|
3264
4095
|
// ../core/src/sinks/file.ts
|
|
3265
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
3266
|
-
import
|
|
4096
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync4 } from "node:fs";
|
|
4097
|
+
import path13 from "node:path";
|
|
3267
4098
|
var FileSink = class {
|
|
3268
4099
|
constructor(name, file, tier, pii) {
|
|
3269
4100
|
this.name = name;
|
|
@@ -3277,7 +4108,7 @@ var FileSink = class {
|
|
|
3277
4108
|
pii;
|
|
3278
4109
|
async send(events) {
|
|
3279
4110
|
try {
|
|
3280
|
-
|
|
4111
|
+
mkdirSync4(path13.dirname(this.file), { recursive: true });
|
|
3281
4112
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
3282
4113
|
`);
|
|
3283
4114
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -3314,24 +4145,24 @@ var StdoutSink = class {
|
|
|
3314
4145
|
import {
|
|
3315
4146
|
appendFileSync as appendFileSync3,
|
|
3316
4147
|
existsSync as existsSync10,
|
|
3317
|
-
mkdirSync as
|
|
3318
|
-
readdirSync as
|
|
3319
|
-
readFileSync as
|
|
3320
|
-
renameSync as
|
|
3321
|
-
statSync as
|
|
3322
|
-
unlinkSync as
|
|
3323
|
-
writeFileSync as
|
|
4148
|
+
mkdirSync as mkdirSync7,
|
|
4149
|
+
readdirSync as readdirSync7,
|
|
4150
|
+
readFileSync as readFileSync12,
|
|
4151
|
+
renameSync as renameSync4,
|
|
4152
|
+
statSync as statSync7,
|
|
4153
|
+
unlinkSync as unlinkSync3,
|
|
4154
|
+
writeFileSync as writeFileSync6
|
|
3324
4155
|
} from "node:fs";
|
|
3325
|
-
import
|
|
3326
|
-
import
|
|
4156
|
+
import os4 from "node:os";
|
|
4157
|
+
import path17 from "node:path";
|
|
3327
4158
|
|
|
3328
4159
|
// src/control/aliases.ts
|
|
3329
|
-
import { chmodSync as chmodSync2, mkdirSync as
|
|
3330
|
-
import
|
|
4160
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4161
|
+
import path14 from "node:path";
|
|
3331
4162
|
|
|
3332
4163
|
// src/secrets.ts
|
|
3333
4164
|
import { spawnSync } from "node:child_process";
|
|
3334
|
-
import { createHash as createHash5, randomBytes as
|
|
4165
|
+
import { createHash as createHash5, randomBytes as randomBytes3 } from "node:crypto";
|
|
3335
4166
|
var SERVICE = "sessionpipe";
|
|
3336
4167
|
var defaultRunner = (cmd, args, input) => {
|
|
3337
4168
|
const r = spawnSync(cmd, args, {
|
|
@@ -3355,9 +4186,9 @@ function secretGet(store, account, run = defaultRunner) {
|
|
|
3355
4186
|
function sinkWithSecrets(s, run = defaultRunner) {
|
|
3356
4187
|
if (!s.token_in) return s;
|
|
3357
4188
|
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
3358
|
-
if (!token) return null;
|
|
3359
4189
|
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
3360
|
-
|
|
4190
|
+
if (!token && !secret) return null;
|
|
4191
|
+
return { ...s, ...token ? { token } : {}, ...secret ? { secret } : {} };
|
|
3361
4192
|
}
|
|
3362
4193
|
|
|
3363
4194
|
// src/control/store.ts
|
|
@@ -3371,10 +4202,10 @@ function startOptionsOf(v) {
|
|
|
3371
4202
|
}
|
|
3372
4203
|
|
|
3373
4204
|
// src/control/aliases.ts
|
|
3374
|
-
var aliasFile = (controlDir) =>
|
|
4205
|
+
var aliasFile = (controlDir) => path14.join(controlDir, "aliases.json");
|
|
3375
4206
|
function readAliases(file) {
|
|
3376
4207
|
try {
|
|
3377
|
-
const j = JSON.parse(
|
|
4208
|
+
const j = JSON.parse(readFileSync10(file, "utf8"));
|
|
3378
4209
|
if (!j || typeof j !== "object" || Array.isArray(j)) return {};
|
|
3379
4210
|
const out = {};
|
|
3380
4211
|
for (const [k, v] of Object.entries(j)) {
|
|
@@ -3395,12 +4226,12 @@ function receiverIdOf(all, harness, id) {
|
|
|
3395
4226
|
|
|
3396
4227
|
// src/control/local.ts
|
|
3397
4228
|
import net from "node:net";
|
|
3398
|
-
import
|
|
3399
|
-
import
|
|
4229
|
+
import os3 from "node:os";
|
|
4230
|
+
import path15 from "node:path";
|
|
3400
4231
|
function socketPath(state, env = process.env) {
|
|
3401
4232
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
3402
|
-
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${
|
|
3403
|
-
return
|
|
4233
|
+
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os3.userInfo().username}`;
|
|
4234
|
+
return path15.join(state, "control.sock");
|
|
3404
4235
|
}
|
|
3405
4236
|
function ask(sock, req, timeoutMs) {
|
|
3406
4237
|
return new Promise((resolve) => {
|
|
@@ -3432,6 +4263,76 @@ function ask(sock, req, timeoutMs) {
|
|
|
3432
4263
|
});
|
|
3433
4264
|
}
|
|
3434
4265
|
|
|
4266
|
+
// src/fold.ts
|
|
4267
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4268
|
+
import path16 from "node:path";
|
|
4269
|
+
var TOOLS_MODES = ["all", "main", "beat"];
|
|
4270
|
+
var TOOL_FOLD_MS = 6e4;
|
|
4271
|
+
var KEEP_MS = 24 * 60 * 6e4;
|
|
4272
|
+
function isToolsMode(v) {
|
|
4273
|
+
return typeof v === "string" && TOOLS_MODES.includes(v);
|
|
4274
|
+
}
|
|
4275
|
+
function toolsMode(s) {
|
|
4276
|
+
if (isToolsMode(s.tools)) return s.tools;
|
|
4277
|
+
if (isToolsMode(s.receiver_tools)) return s.receiver_tools;
|
|
4278
|
+
return "all";
|
|
4279
|
+
}
|
|
4280
|
+
var agentOf = (e) => typeof e.data.agent_id === "string" ? e.data.agent_id : "";
|
|
4281
|
+
var thins = (mode, agent) => mode === "beat" || mode === "main" && agent !== "";
|
|
4282
|
+
var askKey = (key, agent) => `${key}\0ask\0${agent}`;
|
|
4283
|
+
function sendTool(e, mode, clock, key) {
|
|
4284
|
+
if (mode === "all") return true;
|
|
4285
|
+
const agent = agentOf(e);
|
|
4286
|
+
const t = Date.parse(e.time);
|
|
4287
|
+
if (e.type === "attention.needed") {
|
|
4288
|
+
if (thins(mode, agent)) clock[askKey(key, agent)] = Number.isFinite(t) ? t : 0;
|
|
4289
|
+
return true;
|
|
4290
|
+
}
|
|
4291
|
+
if (e.type !== "tool.started" && e.type !== "tool.ended" || !thins(mode, agent)) return true;
|
|
4292
|
+
if (e.type === "tool.started") return false;
|
|
4293
|
+
if (!Number.isFinite(t)) return true;
|
|
4294
|
+
const ask2 = askKey(key, agent);
|
|
4295
|
+
const asked = clock[ask2];
|
|
4296
|
+
if (asked !== void 0 && t >= asked) {
|
|
4297
|
+
delete clock[ask2];
|
|
4298
|
+
clock[key] = t;
|
|
4299
|
+
return true;
|
|
4300
|
+
}
|
|
4301
|
+
const last = clock[key];
|
|
4302
|
+
if (last !== void 0 && t > last - TOOL_FOLD_MS && t < last + TOOL_FOLD_MS) return false;
|
|
4303
|
+
clock[key] = t;
|
|
4304
|
+
return true;
|
|
4305
|
+
}
|
|
4306
|
+
var ToolClock = class {
|
|
4307
|
+
constructor(root, sink) {
|
|
4308
|
+
this.root = root;
|
|
4309
|
+
this.sink = sink;
|
|
4310
|
+
}
|
|
4311
|
+
root;
|
|
4312
|
+
sink;
|
|
4313
|
+
file() {
|
|
4314
|
+
return path16.join(this.root, "folds", `${safeId(this.sink)}.json`);
|
|
4315
|
+
}
|
|
4316
|
+
read() {
|
|
4317
|
+
try {
|
|
4318
|
+
const j = JSON.parse(readFileSync11(this.file(), "utf8"));
|
|
4319
|
+
return j && typeof j === "object" && !Array.isArray(j) ? j : {};
|
|
4320
|
+
} catch {
|
|
4321
|
+
return {};
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
write(c) {
|
|
4325
|
+
const newest = Math.max(0, ...Object.values(c).filter(Number.isFinite));
|
|
4326
|
+
const kept = {};
|
|
4327
|
+
for (const [k, v] of Object.entries(c)) if (Number.isFinite(v) && v >= newest - KEEP_MS) kept[k] = v;
|
|
4328
|
+
const f = this.file();
|
|
4329
|
+
mkdirSync6(path16.dirname(f), { recursive: true, mode: 448 });
|
|
4330
|
+
const tmp = `${f}.${process.pid}.tmp`;
|
|
4331
|
+
writeFileSync5(tmp, JSON.stringify(kept), { mode: 384 });
|
|
4332
|
+
renameSync3(tmp, f);
|
|
4333
|
+
}
|
|
4334
|
+
};
|
|
4335
|
+
|
|
3435
4336
|
// src/http-sink.ts
|
|
3436
4337
|
import { createHash as createHash6, createHmac } from "node:crypto";
|
|
3437
4338
|
var TIMEOUT_MS = 15e3;
|
|
@@ -3531,6 +4432,11 @@ var HttpSink = class {
|
|
|
3531
4432
|
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
3532
4433
|
const sock = socketPath(state);
|
|
3533
4434
|
if (process.platform !== "win32" && !existsSync10(sock)) return;
|
|
4435
|
+
let mode = null;
|
|
4436
|
+
try {
|
|
4437
|
+
mode = claude_code_control_exports.hookMode(JSON.parse(job.stdin));
|
|
4438
|
+
} catch {
|
|
4439
|
+
}
|
|
3534
4440
|
await ask(
|
|
3535
4441
|
sock,
|
|
3536
4442
|
{
|
|
@@ -3538,7 +4444,8 @@ async function tellDaemon(state, job, session, cwd, transcript) {
|
|
|
3538
4444
|
session: `${job.harness}:${session}`,
|
|
3539
4445
|
event: job.event,
|
|
3540
4446
|
...cwd ? { cwd } : {},
|
|
3541
|
-
...transcript ? { transcript } : {}
|
|
4447
|
+
...transcript ? { transcript } : {},
|
|
4448
|
+
...mode ? { mode } : {}
|
|
3542
4449
|
},
|
|
3543
4450
|
300
|
|
3544
4451
|
);
|
|
@@ -3546,20 +4453,20 @@ async function tellDaemon(state, job, session, cwd, transcript) {
|
|
|
3546
4453
|
var MAX_TRIES = 10;
|
|
3547
4454
|
function enqueueJob(state, job) {
|
|
3548
4455
|
const jobs = jobsDir(state);
|
|
3549
|
-
|
|
3550
|
-
const f =
|
|
3551
|
-
|
|
4456
|
+
mkdirSync7(jobs, { recursive: true, mode: 448 });
|
|
4457
|
+
const f = path17.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
4458
|
+
writeFileSync6(f, JSON.stringify(job), { mode: 384 });
|
|
3552
4459
|
return f;
|
|
3553
4460
|
}
|
|
3554
4461
|
function claimJob(file) {
|
|
3555
4462
|
const claimed = `${file}.${process.pid}.running`;
|
|
3556
4463
|
try {
|
|
3557
|
-
|
|
4464
|
+
renameSync4(file, claimed);
|
|
3558
4465
|
} catch {
|
|
3559
4466
|
return null;
|
|
3560
4467
|
}
|
|
3561
4468
|
try {
|
|
3562
|
-
return { job: JSON.parse(
|
|
4469
|
+
return { job: JSON.parse(readFileSync12(claimed, "utf8")), file: claimed };
|
|
3563
4470
|
} catch {
|
|
3564
4471
|
releaseJob(claimed);
|
|
3565
4472
|
return null;
|
|
@@ -3568,15 +4475,15 @@ function claimJob(file) {
|
|
|
3568
4475
|
function releaseJob(claimed) {
|
|
3569
4476
|
if (!claimed) return;
|
|
3570
4477
|
try {
|
|
3571
|
-
|
|
4478
|
+
unlinkSync3(claimed);
|
|
3572
4479
|
} catch {
|
|
3573
4480
|
}
|
|
3574
4481
|
}
|
|
3575
|
-
async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4) {
|
|
4482
|
+
async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4, o = {}) {
|
|
3576
4483
|
const jobs = jobsDir(state);
|
|
3577
4484
|
let names = [];
|
|
3578
4485
|
try {
|
|
3579
|
-
names =
|
|
4486
|
+
names = readdirSync7(jobs);
|
|
3580
4487
|
} catch {
|
|
3581
4488
|
return 0;
|
|
3582
4489
|
}
|
|
@@ -3584,40 +4491,43 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4
|
|
|
3584
4491
|
for (const n of names) {
|
|
3585
4492
|
const m = /^(.*\.json)\.(\d+)\.running$/.exec(n);
|
|
3586
4493
|
if (!m) continue;
|
|
3587
|
-
const f =
|
|
4494
|
+
const f = path17.join(jobs, n);
|
|
3588
4495
|
try {
|
|
3589
|
-
if (processAlive(Number(m[2])) && Date.now() -
|
|
3590
|
-
|
|
4496
|
+
if (processAlive(Number(m[2])) && Date.now() - statSync7(f).mtimeMs < abandonedMs) continue;
|
|
4497
|
+
renameSync4(f, path17.join(jobs, m[1]));
|
|
3591
4498
|
queued.push(m[1]);
|
|
3592
4499
|
} catch {
|
|
3593
4500
|
}
|
|
3594
4501
|
}
|
|
3595
4502
|
let ran = 0;
|
|
3596
4503
|
for (const n of queued.sort().slice(0, limit)) {
|
|
3597
|
-
|
|
4504
|
+
if (o.deadline !== void 0 && Date.now() + SWEEP_LOCK_WAIT_MS + PASS_BUDGET_MS > o.deadline) break;
|
|
4505
|
+
const f = path17.join(jobs, n);
|
|
3598
4506
|
try {
|
|
3599
|
-
if (Date.now() -
|
|
4507
|
+
if (Date.now() - statSync7(f).mtimeMs < olderThanMs) continue;
|
|
3600
4508
|
} catch {
|
|
3601
4509
|
continue;
|
|
3602
4510
|
}
|
|
3603
4511
|
const c = claimJob(f);
|
|
3604
4512
|
if (!c) continue;
|
|
3605
4513
|
try {
|
|
3606
|
-
await runJob(c.job, { state, claimed: c.file });
|
|
4514
|
+
const r = await runJob(c.job, { state, claimed: c.file, drain: false, lockWaitMs: SWEEP_LOCK_WAIT_MS });
|
|
4515
|
+
if (r.events.length) markMore(state);
|
|
3607
4516
|
} catch {
|
|
3608
4517
|
} finally {
|
|
3609
4518
|
releaseJob(c.file);
|
|
3610
4519
|
}
|
|
3611
4520
|
ran++;
|
|
3612
4521
|
}
|
|
4522
|
+
if (ran) await drain(state, readConfig().sinks, new Outbox(state), void 0, o);
|
|
3613
4523
|
return ran;
|
|
3614
4524
|
}
|
|
3615
4525
|
function log(state, line) {
|
|
3616
4526
|
try {
|
|
3617
|
-
|
|
3618
|
-
const f =
|
|
4527
|
+
mkdirSync7(state, { recursive: true, mode: 448 });
|
|
4528
|
+
const f = path17.join(state, "log");
|
|
3619
4529
|
try {
|
|
3620
|
-
if (
|
|
4530
|
+
if (statSync7(f).size > 1024 * 1024) renameSync4(f, `${f}.1`);
|
|
3621
4531
|
} catch {
|
|
3622
4532
|
}
|
|
3623
4533
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -3626,11 +4536,11 @@ function log(state, line) {
|
|
|
3626
4536
|
}
|
|
3627
4537
|
}
|
|
3628
4538
|
function factsState(state) {
|
|
3629
|
-
const file = (h, s) =>
|
|
4539
|
+
const file = (h, s) => path17.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
3630
4540
|
return {
|
|
3631
4541
|
get(h, s) {
|
|
3632
4542
|
try {
|
|
3633
|
-
return JSON.parse(
|
|
4543
|
+
return JSON.parse(readFileSync12(file(h, s), "utf8"));
|
|
3634
4544
|
} catch {
|
|
3635
4545
|
return {};
|
|
3636
4546
|
}
|
|
@@ -3639,8 +4549,8 @@ function factsState(state) {
|
|
|
3639
4549
|
const cur = this.get(h, s);
|
|
3640
4550
|
const next = { ...cur, ...patch };
|
|
3641
4551
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
3642
|
-
|
|
3643
|
-
|
|
4552
|
+
mkdirSync7(path17.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
4553
|
+
writeFileSync6(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
3644
4554
|
}
|
|
3645
4555
|
};
|
|
3646
4556
|
}
|
|
@@ -3653,10 +4563,10 @@ function buildSinks(sinks, o = {}) {
|
|
|
3653
4563
|
o.locked?.(raw.name);
|
|
3654
4564
|
continue;
|
|
3655
4565
|
}
|
|
3656
|
-
const tier = Math.min(s.tier, s.max_tier ?? 3);
|
|
4566
|
+
const tier = Math.trunc(Math.min(Number(s.tier) || 0, s.max_tier ?? 3));
|
|
3657
4567
|
if (s.url === "stdout") out.push(new StdoutSink(s.name, tier, !!s.pii));
|
|
3658
4568
|
else if (s.url.startsWith("file:"))
|
|
3659
|
-
out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/,
|
|
4569
|
+
out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os4.homedir()), tier, !!s.pii));
|
|
3660
4570
|
else if (/^https?:\/\//.test(s.url)) out.push(new HttpSink(s, tier));
|
|
3661
4571
|
}
|
|
3662
4572
|
return out;
|
|
@@ -3664,13 +4574,14 @@ function buildSinks(sinks, o = {}) {
|
|
|
3664
4574
|
var PROGRESS_MS = 2e4;
|
|
3665
4575
|
function openTurn(read, o) {
|
|
3666
4576
|
const last = read.turns[read.turns.length - 1];
|
|
3667
|
-
const fresh = (user, at) => ({
|
|
4577
|
+
const fresh = (user, at, injected) => ({
|
|
3668
4578
|
turn: o.base + read.turns.length,
|
|
3669
4579
|
user: user.slice(0, MAX_TURN_SIDE),
|
|
3670
4580
|
assistant: "",
|
|
3671
|
-
at
|
|
4581
|
+
at,
|
|
4582
|
+
...injected ? { injected: true } : {}
|
|
3672
4583
|
});
|
|
3673
|
-
if (read.open) return fresh(read.open.user, read.open.at);
|
|
4584
|
+
if (read.open) return fresh(read.open.user, read.open.at, read.open.injected);
|
|
3674
4585
|
const prompt = o.prompt?.trim();
|
|
3675
4586
|
if (prompt && !isInjected(prompt) && !(last && last.user.includes(prompt.slice(0, 200)))) return fresh(prompt, o.at);
|
|
3676
4587
|
if (!last) return null;
|
|
@@ -3678,6 +4589,7 @@ function openTurn(read, o) {
|
|
|
3678
4589
|
turn: o.base + read.turns.length - 1,
|
|
3679
4590
|
user: last.user.slice(0, MAX_TURN_SIDE),
|
|
3680
4591
|
assistant: capTail(last.assistant, MAX_TURN_SIDE),
|
|
4592
|
+
...last.injected ? { injected: true } : {},
|
|
3681
4593
|
at: last.at
|
|
3682
4594
|
};
|
|
3683
4595
|
}
|
|
@@ -3685,6 +4597,7 @@ var MAX_TURN_SIDE = 2e4;
|
|
|
3685
4597
|
var JOB_OWN_ENV = [
|
|
3686
4598
|
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
3687
4599
|
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
4600
|
+
"SESSIONPIPE_ASKED_BY",
|
|
3688
4601
|
"SESSIONPIPE_CONTROL_JOB",
|
|
3689
4602
|
"SESSIONPIPE_LAUNCHED_BY"
|
|
3690
4603
|
];
|
|
@@ -3712,7 +4625,7 @@ async function runJob(job, opts = {}) {
|
|
|
3712
4625
|
);
|
|
3713
4626
|
return { events: [], delivered: {} };
|
|
3714
4627
|
}
|
|
3715
|
-
const id = receiverIdOf(readAliases(aliasFile(
|
|
4628
|
+
const id = receiverIdOf(readAliases(aliasFile(path17.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3716
4629
|
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3717
4630
|
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
3718
4631
|
const outbox = new Outbox(state);
|
|
@@ -3749,7 +4662,7 @@ async function runJob(job, opts = {}) {
|
|
|
3749
4662
|
learned = rest;
|
|
3750
4663
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
3751
4664
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
3752
|
-
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd &&
|
|
4665
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path17.isAbsolute(r.session.cwd))
|
|
3753
4666
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
3754
4667
|
}
|
|
3755
4668
|
const known = mem.session ?? {};
|
|
@@ -3762,10 +4675,11 @@ async function runJob(job, opts = {}) {
|
|
|
3762
4675
|
...learned.branch ? { branch: learned.branch } : {},
|
|
3763
4676
|
id,
|
|
3764
4677
|
seq: 0,
|
|
3765
|
-
machine: machineName(cfg,
|
|
4678
|
+
machine: machineName(cfg, os4.hostname()),
|
|
3766
4679
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
3767
4680
|
...r.session.model ? { model: r.session.model } : {},
|
|
3768
|
-
...r.session.launched_by ? { launched_by: r.session.launched_by } : {}
|
|
4681
|
+
...r.session.launched_by ? { launched_by: r.session.launched_by } : {},
|
|
4682
|
+
...r.session.delegated_by ? { delegated_by: r.session.delegated_by } : {}
|
|
3769
4683
|
};
|
|
3770
4684
|
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
3771
4685
|
for (const a of r.events) {
|
|
@@ -3819,7 +4733,13 @@ async function runJob(job, opts = {}) {
|
|
|
3819
4733
|
{
|
|
3820
4734
|
type: "turn.transcript",
|
|
3821
4735
|
harnessEvent: job.event,
|
|
3822
|
-
data: {
|
|
4736
|
+
data: {
|
|
4737
|
+
turn: turnNo++,
|
|
4738
|
+
user: t.user,
|
|
4739
|
+
assistant: t.assistant,
|
|
4740
|
+
at: new Date(t.at).toISOString(),
|
|
4741
|
+
...t.injected ? { injected: true } : {}
|
|
4742
|
+
}
|
|
3823
4743
|
},
|
|
3824
4744
|
{
|
|
3825
4745
|
harness: { name: job.harness, ...version ? { version } : {} },
|
|
@@ -3860,29 +4780,84 @@ async function runJob(job, opts = {}) {
|
|
|
3860
4780
|
} finally {
|
|
3861
4781
|
release();
|
|
3862
4782
|
}
|
|
3863
|
-
const delivered = await drain(state, cfg.sinks, outbox, ref) ?? {};
|
|
3864
4783
|
if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
|
|
4784
|
+
const delivered = opts.drain === false ? {} : await drain(state, cfg.sinks, outbox, ref, {
|
|
4785
|
+
...opts.clock ? { clock: opts.clock } : {},
|
|
4786
|
+
...opts.deadline !== void 0 ? { deadline: opts.deadline } : {},
|
|
4787
|
+
...opts.handoff ? { handoff: opts.handoff } : {}
|
|
4788
|
+
}) ?? {};
|
|
3865
4789
|
return { events, delivered };
|
|
3866
4790
|
}
|
|
3867
|
-
|
|
3868
|
-
|
|
4791
|
+
var REAL_CLOCK = { now: () => Date.now(), sleep: (ms) => new Promise((r) => setTimeout(r, ms)) };
|
|
4792
|
+
var PACE_MS = 1e3;
|
|
4793
|
+
var sentFile = (state) => path17.join(state, "locks", "flush.sent");
|
|
4794
|
+
function lastSent(state) {
|
|
4795
|
+
try {
|
|
4796
|
+
return Number(readFileSync12(sentFile(state), "utf8"));
|
|
4797
|
+
} catch {
|
|
4798
|
+
return 0;
|
|
4799
|
+
}
|
|
4800
|
+
}
|
|
4801
|
+
function noteSent(state, at) {
|
|
4802
|
+
try {
|
|
4803
|
+
mkdirSync7(path17.dirname(sentFile(state)), { recursive: true, mode: 448 });
|
|
4804
|
+
writeFileSync6(sentFile(state), String(at), { mode: 384 });
|
|
4805
|
+
} catch {
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
function waitingForReceiver(state, sinkConfigs, outbox, now) {
|
|
4809
|
+
const receivers = sinkConfigs.filter(
|
|
4810
|
+
(s) => !s.paused && /^https?:\/\//.test(s.url) && !((readBackoff(state, s.name)?.until ?? 0) > now)
|
|
4811
|
+
);
|
|
4812
|
+
if (!receivers.length) return false;
|
|
4813
|
+
const refs = outbox.sessions();
|
|
4814
|
+
for (const s of receivers) {
|
|
4815
|
+
const cursors = new Cursors(state, s.name);
|
|
4816
|
+
const c = cursors.read();
|
|
4817
|
+
for (const ref of refs) {
|
|
4818
|
+
try {
|
|
4819
|
+
if (statSync7(outbox.file(ref)).size > (c[cursors.key(ref)] ?? 0)) return true;
|
|
4820
|
+
} catch {
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
return false;
|
|
4825
|
+
}
|
|
4826
|
+
var PASS_BUDGET_MS = 3e3;
|
|
4827
|
+
var SWEEP_LOCK_WAIT_MS = 1e3;
|
|
4828
|
+
var moreFile = (state) => path17.join(state, "locks", "flush.more");
|
|
4829
|
+
function markMore(state) {
|
|
4830
|
+
try {
|
|
4831
|
+
mkdirSync7(path17.dirname(moreFile(state)), { recursive: true, mode: 448 });
|
|
4832
|
+
writeFileSync6(moreFile(state), "", { mode: 384 });
|
|
4833
|
+
} catch {
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
async function drain(state, sinkConfigs, outbox, first, o = {}) {
|
|
4837
|
+
const clock = o.clock ?? REAL_CLOCK;
|
|
4838
|
+
const more = moreFile(state);
|
|
3869
4839
|
let release = takeNamedLock(state, "flush");
|
|
3870
4840
|
if (!release) {
|
|
3871
|
-
|
|
3872
|
-
writeFileSync8(more, "", { mode: 384 });
|
|
3873
|
-
} catch {
|
|
3874
|
-
}
|
|
4841
|
+
markMore(state);
|
|
3875
4842
|
release = takeNamedLock(state, "flush");
|
|
3876
4843
|
if (!release) return null;
|
|
3877
4844
|
}
|
|
3878
4845
|
const delivered = {};
|
|
3879
4846
|
for (; ; ) {
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
4847
|
+
if (o.deadline !== void 0 && clock.now() + PASS_BUDGET_MS > o.deadline) {
|
|
4848
|
+
markMore(state);
|
|
4849
|
+
release();
|
|
4850
|
+
o.handoff?.();
|
|
4851
|
+
return delivered;
|
|
3883
4852
|
}
|
|
3884
4853
|
try {
|
|
3885
|
-
|
|
4854
|
+
const wait = Math.min(PACE_MS, lastSent(state) + PACE_MS - clock.now());
|
|
4855
|
+
if (wait > 0 && waitingForReceiver(state, sinkConfigs, outbox, clock.now())) await clock.sleep(wait);
|
|
4856
|
+
try {
|
|
4857
|
+
unlinkSync3(more);
|
|
4858
|
+
} catch {
|
|
4859
|
+
}
|
|
4860
|
+
for (const [k, v] of Object.entries(await flush(state, sinkConfigs, outbox, first, { clock })))
|
|
3886
4861
|
delivered[k] = (delivered[k] ?? 0) + v;
|
|
3887
4862
|
} finally {
|
|
3888
4863
|
release();
|
|
@@ -3893,10 +4868,10 @@ async function drain(state, sinkConfigs, outbox, first) {
|
|
|
3893
4868
|
}
|
|
3894
4869
|
}
|
|
3895
4870
|
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3896
|
-
var backoffFile = (state, sink) =>
|
|
4871
|
+
var backoffFile = (state, sink) => path17.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3897
4872
|
function readBackoff(state, sink) {
|
|
3898
4873
|
try {
|
|
3899
|
-
const b = JSON.parse(
|
|
4874
|
+
const b = JSON.parse(readFileSync12(backoffFile(state, sink), "utf8"));
|
|
3900
4875
|
return typeof b.until === "number" ? b : null;
|
|
3901
4876
|
} catch {
|
|
3902
4877
|
return null;
|
|
@@ -3909,15 +4884,15 @@ function noteFailure(state, sink, res, prev) {
|
|
|
3909
4884
|
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3910
4885
|
const f = backoffFile(state, sink);
|
|
3911
4886
|
try {
|
|
3912
|
-
|
|
3913
|
-
|
|
4887
|
+
mkdirSync7(path17.dirname(f), { recursive: true, mode: 448 });
|
|
4888
|
+
writeFileSync6(f, JSON.stringify(b), { mode: 384 });
|
|
3914
4889
|
} catch {
|
|
3915
4890
|
}
|
|
3916
4891
|
return b;
|
|
3917
4892
|
}
|
|
3918
4893
|
function clearBackoff(state, sink) {
|
|
3919
4894
|
try {
|
|
3920
|
-
|
|
4895
|
+
unlinkSync3(backoffFile(state, sink));
|
|
3921
4896
|
} catch {
|
|
3922
4897
|
}
|
|
3923
4898
|
}
|
|
@@ -3927,14 +4902,31 @@ var BATCH = 50;
|
|
|
3927
4902
|
var MAX_PER_RUN = 500;
|
|
3928
4903
|
async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
3929
4904
|
const delivered = {};
|
|
4905
|
+
const clock = o.clock ?? REAL_CLOCK;
|
|
3930
4906
|
const sinks = buildSinks(sinkConfigs, {
|
|
3931
4907
|
locked: (name) => log(state, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
3932
4908
|
});
|
|
4909
|
+
const modes = new Map(sinkConfigs.map((s) => [s.name, toolsMode(s)]));
|
|
3933
4910
|
const refs = outbox.sessions();
|
|
3934
4911
|
if (first) {
|
|
3935
4912
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
3936
4913
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
3937
4914
|
}
|
|
4915
|
+
const unreadable = /* @__PURE__ */ new Set();
|
|
4916
|
+
const readOrSkip = (ref, offset, room) => {
|
|
4917
|
+
try {
|
|
4918
|
+
return outbox.read(ref, offset, room);
|
|
4919
|
+
} catch (e) {
|
|
4920
|
+
const code = e.code;
|
|
4921
|
+
if (typeof code !== "string") throw e;
|
|
4922
|
+
const f = outbox.file(ref);
|
|
4923
|
+
if (!unreadable.has(f)) {
|
|
4924
|
+
unreadable.add(f);
|
|
4925
|
+
log(state, `drain: can't read ${f} (${code}); skipped, its cursors stay`);
|
|
4926
|
+
}
|
|
4927
|
+
return null;
|
|
4928
|
+
}
|
|
4929
|
+
};
|
|
3938
4930
|
const drainSink = async (sink) => {
|
|
3939
4931
|
let backoff = readBackoff(state, sink.name);
|
|
3940
4932
|
if (backoff && !o.force && backoff.until > Date.now()) {
|
|
@@ -3943,11 +4935,16 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3943
4935
|
}
|
|
3944
4936
|
const cursors = new Cursors(state, sink.name);
|
|
3945
4937
|
const c = cursors.read();
|
|
4938
|
+
const mode = modes.get(sink.name);
|
|
4939
|
+
const tools = new ToolClock(state, sink.name);
|
|
4940
|
+
let clockNow = mode === "all" ? {} : tools.read();
|
|
4941
|
+
let clockNext = { ...clockNow };
|
|
3946
4942
|
let sent = 0;
|
|
3947
4943
|
let stalled = false;
|
|
3948
4944
|
let batch = [];
|
|
3949
4945
|
let pending = {};
|
|
3950
4946
|
const deliver = async (events) => {
|
|
4947
|
+
if (sink instanceof HttpSink) noteSent(state, clock.now());
|
|
3951
4948
|
const res = await sink.send(events);
|
|
3952
4949
|
if (res.ok || res.action !== "split") return res;
|
|
3953
4950
|
if (events.length === 1) {
|
|
@@ -3955,10 +4952,20 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3955
4952
|
log(state, `sink ${sink.name}: 413 ${res.error ?? ""} \u2192 event ${e.id} (${e.type}) is too large alone, dropped`);
|
|
3956
4953
|
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected: [{ id: e.id, reason: "too_large" }] };
|
|
3957
4954
|
}
|
|
4955
|
+
const part = async (some) => {
|
|
4956
|
+
const r = await deliver(some);
|
|
4957
|
+
if (r.ok || r.action !== "drop") return r;
|
|
4958
|
+
log(
|
|
4959
|
+
state,
|
|
4960
|
+
`sink ${sink.name}: ${r.status ?? ""} ${r.error ?? ""} \u2192 drop, ${some.length} of ${events.length} events`
|
|
4961
|
+
);
|
|
4962
|
+
const rejected = some.map((e) => ({ id: e.id, reason: "dropped" }));
|
|
4963
|
+
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected };
|
|
4964
|
+
};
|
|
3958
4965
|
const half = Math.ceil(events.length / 2);
|
|
3959
|
-
const a = await
|
|
4966
|
+
const a = await part(events.slice(0, half));
|
|
3960
4967
|
if (!a.ok) return a;
|
|
3961
|
-
const b = await
|
|
4968
|
+
const b = await part(events.slice(half));
|
|
3962
4969
|
if (!b.ok) return b;
|
|
3963
4970
|
return {
|
|
3964
4971
|
ok: true,
|
|
@@ -3968,11 +4975,17 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3968
4975
|
rejected: [...a.rejected ?? [], ...b.rejected ?? []]
|
|
3969
4976
|
};
|
|
3970
4977
|
};
|
|
4978
|
+
const commitClock = () => {
|
|
4979
|
+
if (mode === "all" || JSON.stringify(clockNext) === JSON.stringify(clockNow)) return;
|
|
4980
|
+
tools.write(clockNext);
|
|
4981
|
+
clockNow = { ...clockNext };
|
|
4982
|
+
};
|
|
3971
4983
|
const send = async () => {
|
|
3972
4984
|
if (!batch.length) {
|
|
3973
4985
|
Object.assign(c, pending);
|
|
3974
4986
|
pending = {};
|
|
3975
4987
|
cursors.write(c);
|
|
4988
|
+
commitClock();
|
|
3976
4989
|
return true;
|
|
3977
4990
|
}
|
|
3978
4991
|
const res = await deliver(batch);
|
|
@@ -3988,6 +5001,7 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3988
5001
|
if (res.action !== "drop") {
|
|
3989
5002
|
batch = [];
|
|
3990
5003
|
pending = {};
|
|
5004
|
+
clockNext = { ...clockNow };
|
|
3991
5005
|
return false;
|
|
3992
5006
|
}
|
|
3993
5007
|
} else if (backoff) {
|
|
@@ -3997,6 +5011,7 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3997
5011
|
sent += batch.length;
|
|
3998
5012
|
Object.assign(c, pending);
|
|
3999
5013
|
cursors.write(c);
|
|
5014
|
+
commitClock();
|
|
4000
5015
|
batch = [];
|
|
4001
5016
|
pending = {};
|
|
4002
5017
|
return true;
|
|
@@ -4006,11 +5021,15 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4006
5021
|
let offset = pending[cursors.key(ref)] ?? c[cursors.key(ref)] ?? 0;
|
|
4007
5022
|
for (; ; ) {
|
|
4008
5023
|
const room = BATCH - batch.length;
|
|
4009
|
-
const
|
|
5024
|
+
const read = readOrSkip(ref, offset, room);
|
|
5025
|
+
if (!read) break;
|
|
5026
|
+
const { events, offset: next } = read;
|
|
4010
5027
|
if (!events.length) break;
|
|
4011
5028
|
for (const e of events) {
|
|
4012
|
-
const f = filterEvent(e, sink.tier, sink.pii, { home:
|
|
4013
|
-
if (f)
|
|
5029
|
+
const f = filterEvent(e, sink.tier, sink.pii, { home: os4.homedir() });
|
|
5030
|
+
if (!f) continue;
|
|
5031
|
+
if (sendTool(f, mode, clockNext, cursors.key(ref))) batch.push(f);
|
|
5032
|
+
else if (o.thinned) o.thinned[sink.name] = (o.thinned[sink.name] ?? 0) + 1;
|
|
4014
5033
|
}
|
|
4015
5034
|
offset = next;
|
|
4016
5035
|
pending[cursors.key(ref)] = offset;
|
|
@@ -4042,31 +5061,105 @@ function applySinkVerdict(name, res) {
|
|
|
4042
5061
|
} catch {
|
|
4043
5062
|
}
|
|
4044
5063
|
}
|
|
4045
|
-
var jobsDir = (state) =>
|
|
5064
|
+
var jobsDir = (state) => path17.join(state, "jobs");
|
|
5065
|
+
|
|
5066
|
+
// src/wellknown.ts
|
|
5067
|
+
var WELL_KNOWN_MAX_AGE_MS = 24 * 60 * 6e4;
|
|
5068
|
+
var wellKnownUrl = (sinkUrl) => `${sinkUrl.replace(/\/$/, "")}/.well-known/sessionpipe`;
|
|
5069
|
+
async function readWellKnown(sinkUrl, timeoutMs, fetchFn = fetch) {
|
|
5070
|
+
const r = await fetchFn(wellKnownUrl(sinkUrl), { signal: AbortSignal.timeout(timeoutMs) });
|
|
5071
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
5072
|
+
return await r.json();
|
|
5073
|
+
}
|
|
5074
|
+
function applyWellKnown(s, wk, tier, now = /* @__PURE__ */ new Date()) {
|
|
5075
|
+
const notes = [];
|
|
5076
|
+
if (typeof wk.max_tier === "number") {
|
|
5077
|
+
s.max_tier = wk.max_tier;
|
|
5078
|
+
if (wk.max_tier < tier)
|
|
5079
|
+
notes.push(`receiver's max_tier is ${wk.max_tier}: sending tier ${wk.max_tier}, not ${tier}`);
|
|
5080
|
+
}
|
|
5081
|
+
if (wk.endpoints) s.endpoints = wk.endpoints;
|
|
5082
|
+
const before = s.receiver_tools;
|
|
5083
|
+
if (isToolsMode(wk.tools)) s.receiver_tools = wk.tools;
|
|
5084
|
+
else delete s.receiver_tools;
|
|
5085
|
+
if (s.receiver_tools !== before && s.receiver_tools && s.receiver_tools !== "all" && !isToolsMode(s.tools))
|
|
5086
|
+
notes.push(`the receiver asks for tool events \`${s.receiver_tools}\` (--tools all keeps every one)`);
|
|
5087
|
+
s.well_known_at = now.toISOString();
|
|
5088
|
+
s.well_known_checked_at = s.well_known_at;
|
|
5089
|
+
return notes;
|
|
5090
|
+
}
|
|
5091
|
+
var stale = (s, now) => {
|
|
5092
|
+
const last = Date.parse(s.well_known_checked_at ?? s.well_known_at ?? "");
|
|
5093
|
+
return !Number.isFinite(last) || now - last >= WELL_KNOWN_MAX_AGE_MS;
|
|
5094
|
+
};
|
|
5095
|
+
async function refreshWellKnown(cfg, o = {}) {
|
|
5096
|
+
const now = o.now ?? Date.now;
|
|
5097
|
+
let changed = false;
|
|
5098
|
+
for (const s of cfg.sinks) {
|
|
5099
|
+
if (!/^https?:\/\//.test(s.url) || !stale(s, now())) continue;
|
|
5100
|
+
const at = new Date(now());
|
|
5101
|
+
try {
|
|
5102
|
+
applyWellKnown(s, await readWellKnown(s.url, o.timeoutMs ?? 3e3, o.fetchFn), s.tier, at);
|
|
5103
|
+
} catch {
|
|
5104
|
+
s.well_known_checked_at = at.toISOString();
|
|
5105
|
+
}
|
|
5106
|
+
changed = true;
|
|
5107
|
+
}
|
|
5108
|
+
return changed;
|
|
5109
|
+
}
|
|
5110
|
+
async function refreshSinks(o = {}) {
|
|
5111
|
+
try {
|
|
5112
|
+
const cfg = readConfig();
|
|
5113
|
+
if (await refreshWellKnown(cfg, o)) writeConfig(cfg);
|
|
5114
|
+
} catch {
|
|
5115
|
+
}
|
|
5116
|
+
}
|
|
4046
5117
|
|
|
4047
5118
|
// src/worker.ts
|
|
5119
|
+
var WORKER_LIFE_MS = 15e3;
|
|
5120
|
+
var DRAIN_ARG = "--drain";
|
|
5121
|
+
function startWorker(script, arg) {
|
|
5122
|
+
const p = spawn2(process.execPath, [script, arg], { detached: true, stdio: "ignore", windowsHide: true });
|
|
5123
|
+
p.unref();
|
|
5124
|
+
}
|
|
4048
5125
|
async function workerMain(argv) {
|
|
4049
5126
|
const jobFile = argv[2];
|
|
4050
5127
|
if (!jobFile) return;
|
|
4051
|
-
const
|
|
4052
|
-
|
|
4053
|
-
const
|
|
5128
|
+
const script = argv[1];
|
|
5129
|
+
let handedOff = false;
|
|
5130
|
+
const life = {
|
|
5131
|
+
deadline: Date.now() + WORKER_LIFE_MS,
|
|
5132
|
+
handoff: () => {
|
|
5133
|
+
if (handedOff) return;
|
|
5134
|
+
handedOff = true;
|
|
5135
|
+
startWorker(script, DRAIN_ARG);
|
|
5136
|
+
}
|
|
5137
|
+
};
|
|
5138
|
+
const deadline = setTimeout(() => process.exit(0), WORKER_LIFE_MS);
|
|
4054
5139
|
deadline.unref();
|
|
4055
|
-
await
|
|
5140
|
+
await refreshSinks();
|
|
5141
|
+
if (jobFile === DRAIN_ARG) {
|
|
5142
|
+
await drain(stateDir(), readConfig().sinks, new Outbox(stateDir()), void 0, life).then(() => sweepJobs(stateDir(), void 0, void 0, void 0, life)).catch(() => {
|
|
5143
|
+
}).finally(() => clearTimeout(deadline));
|
|
5144
|
+
return;
|
|
5145
|
+
}
|
|
5146
|
+
const claimed = claimJob(jobFile);
|
|
5147
|
+
if (!claimed) {
|
|
5148
|
+
clearTimeout(deadline);
|
|
5149
|
+
return;
|
|
5150
|
+
}
|
|
5151
|
+
await runJob(claimed.job, { claimed: claimed.file, ...life }).then((r) => {
|
|
4056
5152
|
if (!r.requeued || (claimed.job.tries ?? 0) + 1 >= MAX_TRIES) return;
|
|
4057
|
-
|
|
4058
|
-
detached: true,
|
|
4059
|
-
stdio: "ignore",
|
|
4060
|
-
windowsHide: true
|
|
4061
|
-
});
|
|
4062
|
-
p.unref();
|
|
5153
|
+
startWorker(script, r.requeued);
|
|
4063
5154
|
}).catch(() => {
|
|
4064
|
-
}).finally(() => releaseJob(claimed.file)).then(() => sweepJobs(stateDir())).catch(() => {
|
|
5155
|
+
}).finally(() => releaseJob(claimed.file)).then(() => sweepJobs(stateDir(), void 0, void 0, void 0, life)).catch(() => {
|
|
4065
5156
|
}).finally(() => clearTimeout(deadline));
|
|
4066
5157
|
}
|
|
4067
5158
|
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync2(process.argv[1])).href)
|
|
4068
5159
|
workerMain(process.argv).finally(() => process.exit(0));
|
|
4069
5160
|
export {
|
|
5161
|
+
DRAIN_ARG,
|
|
5162
|
+
WORKER_LIFE_MS,
|
|
4070
5163
|
workerMain
|
|
4071
5164
|
};
|
|
4072
5165
|
//# sourceMappingURL=worker.js.map
|