sessionpipe 0.13.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 +13 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +803 -343
- 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/cli.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 +2 -2
- package/dist/hook.js.map +2 -2
- package/dist/index.js +480 -146
- package/dist/index.js.map +4 -4
- package/dist/run.d.ts +40 -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 +576 -164
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe 0.13.
|
|
2
|
+
// sessionpipe 0.13.1 · Apache-2.0 · https://sessionpipe.org
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __export = (target, all) => {
|
|
5
5
|
for (var name in all)
|
|
@@ -37,7 +37,7 @@ var safeId = (s) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 160);
|
|
|
37
37
|
// ../core/src/adapters/claude-code.ts
|
|
38
38
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
39
39
|
import { createHash } from "node:crypto";
|
|
40
|
-
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";
|
|
41
41
|
import { userInfo } from "node:os";
|
|
42
42
|
import path4 from "node:path";
|
|
43
43
|
|
|
@@ -395,6 +395,41 @@ function readJson(file) {
|
|
|
395
395
|
const f = readJsonFile(file);
|
|
396
396
|
return f.ok ? f.value : {};
|
|
397
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
|
+
}
|
|
398
433
|
function entryFor(event, harness, cmd, o) {
|
|
399
434
|
const matcher = o.matcherFor(event);
|
|
400
435
|
return {
|
|
@@ -436,8 +471,8 @@ function installClaudeShaped(file, harness, events, cmd, o) {
|
|
|
436
471
|
}
|
|
437
472
|
if (changed) {
|
|
438
473
|
settings2.hooks = hooks;
|
|
439
|
-
|
|
440
|
-
|
|
474
|
+
const bad = writeBack(file, formatJson(settings2, f), f.existed);
|
|
475
|
+
if (bad) return bad;
|
|
441
476
|
}
|
|
442
477
|
return { file, changed, created: !f.existed && changed };
|
|
443
478
|
}
|
|
@@ -460,12 +495,8 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
460
495
|
}
|
|
461
496
|
if (!changed) return { file, changed: false };
|
|
462
497
|
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
463
|
-
if (opts.created && !Object.keys(settings2).length)
|
|
464
|
-
|
|
465
|
-
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
466
|
-
}
|
|
467
|
-
writeFileSync(file, formatJson(settings2, f));
|
|
468
|
-
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 };
|
|
469
500
|
}
|
|
470
501
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
471
502
|
const f = readJsonFile(file);
|
|
@@ -544,7 +575,7 @@ var claudeCode = {
|
|
|
544
575
|
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
545
576
|
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
|
|
546
577
|
const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
|
|
547
|
-
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);
|
|
548
579
|
return line ? [hooks, line] : [hooks];
|
|
549
580
|
}),
|
|
550
581
|
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
|
|
@@ -552,7 +583,9 @@ var claudeCode = {
|
|
|
552
583
|
const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
|
|
553
584
|
created: opts.created?.includes(settingsIn(d))
|
|
554
585
|
});
|
|
555
|
-
|
|
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;
|
|
556
589
|
}),
|
|
557
590
|
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
558
591
|
file: settingsIn(d),
|
|
@@ -647,7 +680,8 @@ var claudeCode = {
|
|
|
647
680
|
attention_id: call_id ?? permissionId(),
|
|
648
681
|
kind: "permission",
|
|
649
682
|
tool,
|
|
650
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
683
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0,
|
|
684
|
+
agent_id
|
|
651
685
|
})
|
|
652
686
|
);
|
|
653
687
|
break;
|
|
@@ -656,7 +690,8 @@ var claudeCode = {
|
|
|
656
690
|
ev("attention.needed", {
|
|
657
691
|
attention_id: `notif-${Date.now()}`,
|
|
658
692
|
kind: notifKind(s.notification_type),
|
|
659
|
-
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
|
|
660
695
|
})
|
|
661
696
|
);
|
|
662
697
|
break;
|
|
@@ -781,8 +816,11 @@ function statusLine(file, want, cmd) {
|
|
|
781
816
|
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
782
817
|
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
783
818
|
else delete f.value.statusLine;
|
|
784
|
-
|
|
785
|
-
|
|
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
|
+
};
|
|
786
824
|
}
|
|
787
825
|
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
788
826
|
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
@@ -1388,16 +1426,7 @@ function runHeadless(bin, args, o) {
|
|
|
1388
1426
|
}
|
|
1389
1427
|
|
|
1390
1428
|
// ../core/src/adapters/antigravity.ts
|
|
1391
|
-
import {
|
|
1392
|
-
existsSync as existsSync3,
|
|
1393
|
-
mkdirSync as mkdirSync2,
|
|
1394
|
-
readdirSync as readdirSync3,
|
|
1395
|
-
readFileSync as readFileSync4,
|
|
1396
|
-
realpathSync,
|
|
1397
|
-
statSync as statSync4,
|
|
1398
|
-
unlinkSync as unlinkSync2,
|
|
1399
|
-
writeFileSync as writeFileSync3
|
|
1400
|
-
} from "node:fs";
|
|
1429
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync4, realpathSync, statSync as statSync4 } from "node:fs";
|
|
1401
1430
|
import path6 from "node:path";
|
|
1402
1431
|
var NAME2 = "antigravity";
|
|
1403
1432
|
var HOOK_NAME = "sessionpipe";
|
|
@@ -1429,10 +1458,8 @@ var antigravity = {
|
|
|
1429
1458
|
const e = entry(cmd);
|
|
1430
1459
|
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1431
1460
|
hooks[HOOK_NAME] = e;
|
|
1432
|
-
mkdirSync2(path6.dirname(file), { recursive: true });
|
|
1433
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
1434
1461
|
return [
|
|
1435
|
-
{
|
|
1462
|
+
writeBack(file, formatJson(hooks, f), f.existed) ?? {
|
|
1436
1463
|
file,
|
|
1437
1464
|
changed: true,
|
|
1438
1465
|
created: !f.existed,
|
|
@@ -1448,12 +1475,8 @@ var antigravity = {
|
|
|
1448
1475
|
const hooks = f.value;
|
|
1449
1476
|
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1450
1477
|
delete hooks[HOOK_NAME];
|
|
1451
|
-
if (opts.created?.includes(file) && !Object.keys(hooks).length)
|
|
1452
|
-
|
|
1453
|
-
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1454
|
-
}
|
|
1455
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
1456
|
-
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 }];
|
|
1457
1480
|
},
|
|
1458
1481
|
installed(cmd, env = process.env) {
|
|
1459
1482
|
const file = hooksFile(env);
|
|
@@ -2186,7 +2209,7 @@ function parseResult(stdout) {
|
|
|
2186
2209
|
var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
|
|
2187
2210
|
|
|
2188
2211
|
// ../core/src/adapters/codex.ts
|
|
2189
|
-
import { existsSync as existsSync4,
|
|
2212
|
+
import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
2190
2213
|
import path8 from "node:path";
|
|
2191
2214
|
|
|
2192
2215
|
// ../core/src/adapters/codex-config.ts
|
|
@@ -2339,29 +2362,30 @@ var configFile2 = (env) => path8.join(codexHome(env), "config.toml");
|
|
|
2339
2362
|
var sessionsDir = (env) => path8.join(codexHome(env), "sessions");
|
|
2340
2363
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2341
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";
|
|
2342
2366
|
function installNotify(cmd, env) {
|
|
2343
2367
|
const file = configFile2(env);
|
|
2344
2368
|
let text = "";
|
|
2369
|
+
let existed = true;
|
|
2345
2370
|
try {
|
|
2346
2371
|
text = readFileSync6(file, "utf8");
|
|
2347
|
-
} catch {
|
|
2372
|
+
} catch (e) {
|
|
2373
|
+
existed = e.code !== "ENOENT";
|
|
2348
2374
|
}
|
|
2349
2375
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
2350
2376
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
2351
2377
|
const n = readNotify(text);
|
|
2352
|
-
const write = (next) => {
|
|
2353
|
-
|
|
2354
|
-
|
|
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 };
|
|
2355
2381
|
};
|
|
2356
2382
|
if (n && !n.other) {
|
|
2357
2383
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
2358
2384
|
return { file, changed: false, note: "notify current" };
|
|
2359
|
-
write(
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
2364
|
-
};
|
|
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
|
+
);
|
|
2365
2389
|
}
|
|
2366
2390
|
if (n?.other) {
|
|
2367
2391
|
const prev = previousNotify(n.argv);
|
|
@@ -2370,20 +2394,20 @@ function installNotify(cmd, env) {
|
|
|
2370
2394
|
if (JSON.stringify(prev) === JSON.stringify(argv))
|
|
2371
2395
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
2372
2396
|
}
|
|
2373
|
-
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
2374
|
-
write(
|
|
2375
|
-
|
|
2376
|
-
|
|
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
|
+
);
|
|
2377
2402
|
}
|
|
2378
2403
|
return {
|
|
2379
2404
|
file,
|
|
2380
2405
|
changed: false,
|
|
2381
2406
|
skipped: true,
|
|
2382
|
-
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}`
|
|
2383
2408
|
};
|
|
2384
2409
|
}
|
|
2385
|
-
write(withNotify(text, line));
|
|
2386
|
-
return { file, changed: true, note: "notify set at the top level" };
|
|
2410
|
+
return write(withNotify(text, line), "notify set at the top level");
|
|
2387
2411
|
}
|
|
2388
2412
|
function uninstallNotify(env) {
|
|
2389
2413
|
const file = configFile2(env);
|
|
@@ -2395,15 +2419,13 @@ function uninstallNotify(env) {
|
|
|
2395
2419
|
}
|
|
2396
2420
|
const n = readNotify(text);
|
|
2397
2421
|
if (!n) return { file, changed: false };
|
|
2398
|
-
if (!n.other) {
|
|
2399
|
-
writeFileSync4(file, withoutNotify(text));
|
|
2400
|
-
return { file, changed: true };
|
|
2401
|
-
}
|
|
2422
|
+
if (!n.other) return writeBack(file, withoutNotify(text)) ?? { file, changed: true };
|
|
2402
2423
|
const prev = previousNotify(n.argv);
|
|
2403
|
-
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
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
|
+
};
|
|
2407
2429
|
return { file, changed: false };
|
|
2408
2430
|
}
|
|
2409
2431
|
function findRollout(threadId, env = process.env) {
|
|
@@ -2435,8 +2457,12 @@ function rolloutMeta(file) {
|
|
|
2435
2457
|
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
2436
2458
|
}
|
|
2437
2459
|
function ofAnotherThread(file, id) {
|
|
2460
|
+
const other = threadOf(file);
|
|
2461
|
+
return !!other && other !== id.toLowerCase();
|
|
2462
|
+
}
|
|
2463
|
+
function threadOf(file) {
|
|
2438
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);
|
|
2439
|
-
return
|
|
2465
|
+
return m?.[1] ? m[1].toLowerCase() : null;
|
|
2440
2466
|
}
|
|
2441
2467
|
var codex = {
|
|
2442
2468
|
name: NAME3,
|
|
@@ -2515,6 +2541,8 @@ var codex = {
|
|
|
2515
2541
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
2516
2542
|
const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
|
|
2517
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;
|
|
2518
2546
|
const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
|
|
2519
2547
|
const events = [];
|
|
2520
2548
|
switch (event) {
|
|
@@ -2534,17 +2562,30 @@ var codex = {
|
|
|
2534
2562
|
);
|
|
2535
2563
|
break;
|
|
2536
2564
|
case "PreToolUse":
|
|
2537
|
-
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 }));
|
|
2538
2566
|
break;
|
|
2539
2567
|
case "PostToolUse":
|
|
2540
2568
|
if (tool)
|
|
2541
2569
|
events.push(
|
|
2542
|
-
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
|
+
})
|
|
2543
2579
|
);
|
|
2544
2580
|
break;
|
|
2545
2581
|
case "PermissionRequest":
|
|
2546
2582
|
events.push(
|
|
2547
|
-
ev("attention.needed", {
|
|
2583
|
+
ev("attention.needed", {
|
|
2584
|
+
attention_id: call_id ?? `perm-${Date.now()}`,
|
|
2585
|
+
kind: "permission",
|
|
2586
|
+
tool,
|
|
2587
|
+
agent_id
|
|
2588
|
+
})
|
|
2548
2589
|
);
|
|
2549
2590
|
break;
|
|
2550
2591
|
case "Stop":
|
|
@@ -3034,7 +3075,7 @@ var CHAIN_LIMIT = 4e3;
|
|
|
3034
3075
|
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
3035
3076
|
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
3036
3077
|
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
3037
|
-
UNION
|
|
3078
|
+
UNION
|
|
3038
3079
|
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
3039
3080
|
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
3040
3081
|
)
|
|
@@ -3303,7 +3344,7 @@ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
|
3303
3344
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
3304
3345
|
|
|
3305
3346
|
// ../core/src/config.ts
|
|
3306
|
-
import { chmodSync, existsSync as existsSync8, mkdirSync as
|
|
3347
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3307
3348
|
import path11 from "node:path";
|
|
3308
3349
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
3309
3350
|
function readConfig(file = configFile()) {
|
|
@@ -3319,9 +3360,36 @@ function readConfig(file = configFile()) {
|
|
|
3319
3360
|
return { ...DEFAULT_CONFIG, sinks: [], harnesses: {} };
|
|
3320
3361
|
}
|
|
3321
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
|
+
}
|
|
3322
3389
|
function writeConfig(c, file = configFile()) {
|
|
3323
|
-
|
|
3324
|
-
|
|
3390
|
+
assertConfigWritable(file);
|
|
3391
|
+
mkdirSync2(path11.dirname(file), { recursive: true, mode: 448 });
|
|
3392
|
+
writeFileSync2(file, `${JSON.stringify(c, null, 2)}
|
|
3325
3393
|
`, { mode: 384 });
|
|
3326
3394
|
if (process.platform !== "win32" && existsSync8(file)) {
|
|
3327
3395
|
try {
|
|
@@ -3385,19 +3453,20 @@ function makeEvent(a, ctx) {
|
|
|
3385
3453
|
}
|
|
3386
3454
|
|
|
3387
3455
|
// ../core/src/outbox.ts
|
|
3456
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
3388
3457
|
import {
|
|
3389
3458
|
appendFileSync,
|
|
3390
3459
|
closeSync as closeSync3,
|
|
3391
3460
|
existsSync as existsSync9,
|
|
3392
|
-
mkdirSync as
|
|
3461
|
+
mkdirSync as mkdirSync3,
|
|
3393
3462
|
openSync as openSync3,
|
|
3394
3463
|
readdirSync as readdirSync6,
|
|
3395
3464
|
readFileSync as readFileSync9,
|
|
3396
3465
|
readSync as readSync3,
|
|
3397
3466
|
renameSync,
|
|
3398
3467
|
statSync as statSync6,
|
|
3399
|
-
unlinkSync as
|
|
3400
|
-
writeFileSync as
|
|
3468
|
+
unlinkSync as unlinkSync2,
|
|
3469
|
+
writeFileSync as writeFileSync3
|
|
3401
3470
|
} from "node:fs";
|
|
3402
3471
|
import path12 from "node:path";
|
|
3403
3472
|
var READ_CHUNK = 1024 * 1024;
|
|
@@ -3427,8 +3496,8 @@ var Outbox = class {
|
|
|
3427
3496
|
} catch {
|
|
3428
3497
|
}
|
|
3429
3498
|
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
3430
|
-
|
|
3431
|
-
|
|
3499
|
+
mkdirSync3(path12.dirname(f), { recursive: true, mode: 448 });
|
|
3500
|
+
writeFileSync3(f, String(n + 1), { mode: 384 });
|
|
3432
3501
|
return n;
|
|
3433
3502
|
}
|
|
3434
3503
|
lastSeqInFile(ref) {
|
|
@@ -3446,7 +3515,7 @@ var Outbox = class {
|
|
|
3446
3515
|
}
|
|
3447
3516
|
append(ref, events) {
|
|
3448
3517
|
if (!events.length) return;
|
|
3449
|
-
|
|
3518
|
+
mkdirSync3(this.dir(ref.harness), { recursive: true, mode: 448 });
|
|
3450
3519
|
appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
3451
3520
|
`, { mode: 384 });
|
|
3452
3521
|
}
|
|
@@ -3516,34 +3585,106 @@ var Outbox = class {
|
|
|
3516
3585
|
closeSync3(fd);
|
|
3517
3586
|
}
|
|
3518
3587
|
}
|
|
3519
|
-
/** 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. */
|
|
3520
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;
|
|
3521
3604
|
let n = 0;
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
const
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
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++;
|
|
3531
3621
|
}
|
|
3532
|
-
|
|
3622
|
+
} catch {
|
|
3533
3623
|
}
|
|
3534
|
-
} catch {
|
|
3535
3624
|
}
|
|
3625
|
+
} catch {
|
|
3626
|
+
} finally {
|
|
3627
|
+
release();
|
|
3536
3628
|
}
|
|
3537
3629
|
return n;
|
|
3538
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
|
+
}
|
|
3539
3654
|
delete(ref) {
|
|
3655
|
+
this.dropCursors(ref);
|
|
3540
3656
|
for (const f of [this.file(ref), this.seqFile(ref)]) {
|
|
3541
3657
|
try {
|
|
3542
|
-
|
|
3658
|
+
unlinkSync2(f);
|
|
3543
3659
|
} catch {
|
|
3544
3660
|
}
|
|
3545
3661
|
}
|
|
3546
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
|
+
}
|
|
3547
3688
|
};
|
|
3548
3689
|
var Cursors = class {
|
|
3549
3690
|
constructor(root, sink) {
|
|
@@ -3555,18 +3696,21 @@ var Cursors = class {
|
|
|
3555
3696
|
file() {
|
|
3556
3697
|
return path12.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
3557
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. */
|
|
3558
3701
|
read() {
|
|
3559
3702
|
try {
|
|
3560
|
-
|
|
3703
|
+
const c = JSON.parse(readFileSync9(this.file(), "utf8"));
|
|
3704
|
+
return c && typeof c === "object" && !Array.isArray(c) ? c : {};
|
|
3561
3705
|
} catch {
|
|
3562
3706
|
return {};
|
|
3563
3707
|
}
|
|
3564
3708
|
}
|
|
3565
3709
|
write(c) {
|
|
3566
3710
|
const f = this.file();
|
|
3567
|
-
|
|
3711
|
+
mkdirSync3(path12.dirname(f), { recursive: true, mode: 448 });
|
|
3568
3712
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
3569
|
-
|
|
3713
|
+
writeFileSync3(tmp, JSON.stringify(c), { mode: 384 });
|
|
3570
3714
|
renameSync(tmp, f);
|
|
3571
3715
|
}
|
|
3572
3716
|
key(ref) {
|
|
@@ -3578,21 +3722,43 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
3578
3722
|
}
|
|
3579
3723
|
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
3580
3724
|
const dir = path12.join(root, "locks");
|
|
3581
|
-
|
|
3725
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
3582
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;
|
|
3583
3730
|
try {
|
|
3584
|
-
|
|
3585
|
-
|
|
3731
|
+
body = readFileSync9(f, "utf8");
|
|
3732
|
+
const stale2 = Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(body.split(" ")[0]));
|
|
3733
|
+
if (!stale2) return null;
|
|
3586
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
|
+
}
|
|
3587
3753
|
}
|
|
3588
3754
|
try {
|
|
3589
|
-
|
|
3755
|
+
writeFileSync3(f, mine, { flag: "wx" });
|
|
3590
3756
|
} catch {
|
|
3591
3757
|
return null;
|
|
3592
3758
|
}
|
|
3593
3759
|
return () => {
|
|
3594
3760
|
try {
|
|
3595
|
-
|
|
3761
|
+
if (readFileSync9(f, "utf8") === mine) unlinkSync2(f);
|
|
3596
3762
|
} catch {
|
|
3597
3763
|
}
|
|
3598
3764
|
};
|
|
@@ -3611,7 +3777,7 @@ function processAlive(pid) {
|
|
|
3611
3777
|
import { createHash as createHash3 } from "node:crypto";
|
|
3612
3778
|
var RULESET_PII = "pii@1";
|
|
3613
3779
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
3614
|
-
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;
|
|
3615
3781
|
function looksPhone(s) {
|
|
3616
3782
|
const digits = s.replace(/\D/g, "");
|
|
3617
3783
|
if (digits.length < 7 || digits.length > 15) return false;
|
|
@@ -3622,7 +3788,7 @@ function looksPhone(s) {
|
|
|
3622
3788
|
function reducePii(text, home) {
|
|
3623
3789
|
if (!text) return text;
|
|
3624
3790
|
let out = text.replace(EMAIL, "[email]");
|
|
3625
|
-
out = out.replace(PHONE, (m) => looksPhone(m) ? "[phone]" : m);
|
|
3791
|
+
out = out.replace(PHONE, (m, date) => !date && looksPhone(m) ? "[phone]" : m);
|
|
3626
3792
|
if (home) {
|
|
3627
3793
|
const h = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3628
3794
|
out = out.replace(new RegExp(`(?<![A-Za-z0-9_])${h}(?=/|$|\\s|["'\`])`, "g"), "~");
|
|
@@ -3836,7 +4002,7 @@ var DATA_ALLOW = {
|
|
|
3836
4002
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
3837
4003
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
3838
4004
|
"files.changed": { 1: ["paths"] },
|
|
3839
|
-
"attention.needed": { 0: ["attention_id", "kind"], 1: ["tool", "message"] },
|
|
4005
|
+
"attention.needed": { 0: ["attention_id", "kind"], 1: ["tool", "message", "agent_id"] },
|
|
3840
4006
|
"attention.cleared": { 0: ["attention_id", "how"] },
|
|
3841
4007
|
"subagent.started": { 1: ["agent_id", "agent_type"] },
|
|
3842
4008
|
"subagent.ended": { 1: ["agent_id", "agent_type"] },
|
|
@@ -3927,7 +4093,7 @@ function shortId(harness, id) {
|
|
|
3927
4093
|
}
|
|
3928
4094
|
|
|
3929
4095
|
// ../core/src/sinks/file.ts
|
|
3930
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
4096
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync4 } from "node:fs";
|
|
3931
4097
|
import path13 from "node:path";
|
|
3932
4098
|
var FileSink = class {
|
|
3933
4099
|
constructor(name, file, tier, pii) {
|
|
@@ -3942,7 +4108,7 @@ var FileSink = class {
|
|
|
3942
4108
|
pii;
|
|
3943
4109
|
async send(events) {
|
|
3944
4110
|
try {
|
|
3945
|
-
|
|
4111
|
+
mkdirSync4(path13.dirname(this.file), { recursive: true });
|
|
3946
4112
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
3947
4113
|
`);
|
|
3948
4114
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -3979,24 +4145,24 @@ var StdoutSink = class {
|
|
|
3979
4145
|
import {
|
|
3980
4146
|
appendFileSync as appendFileSync3,
|
|
3981
4147
|
existsSync as existsSync10,
|
|
3982
|
-
mkdirSync as
|
|
4148
|
+
mkdirSync as mkdirSync7,
|
|
3983
4149
|
readdirSync as readdirSync7,
|
|
3984
|
-
readFileSync as
|
|
3985
|
-
renameSync as
|
|
4150
|
+
readFileSync as readFileSync12,
|
|
4151
|
+
renameSync as renameSync4,
|
|
3986
4152
|
statSync as statSync7,
|
|
3987
|
-
unlinkSync as
|
|
3988
|
-
writeFileSync as
|
|
4153
|
+
unlinkSync as unlinkSync3,
|
|
4154
|
+
writeFileSync as writeFileSync6
|
|
3989
4155
|
} from "node:fs";
|
|
3990
4156
|
import os4 from "node:os";
|
|
3991
|
-
import
|
|
4157
|
+
import path17 from "node:path";
|
|
3992
4158
|
|
|
3993
4159
|
// src/control/aliases.ts
|
|
3994
|
-
import { chmodSync as chmodSync2, mkdirSync as
|
|
4160
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3995
4161
|
import path14 from "node:path";
|
|
3996
4162
|
|
|
3997
4163
|
// src/secrets.ts
|
|
3998
4164
|
import { spawnSync } from "node:child_process";
|
|
3999
|
-
import { createHash as createHash5, randomBytes as
|
|
4165
|
+
import { createHash as createHash5, randomBytes as randomBytes3 } from "node:crypto";
|
|
4000
4166
|
var SERVICE = "sessionpipe";
|
|
4001
4167
|
var defaultRunner = (cmd, args, input) => {
|
|
4002
4168
|
const r = spawnSync(cmd, args, {
|
|
@@ -4020,9 +4186,9 @@ function secretGet(store, account, run = defaultRunner) {
|
|
|
4020
4186
|
function sinkWithSecrets(s, run = defaultRunner) {
|
|
4021
4187
|
if (!s.token_in) return s;
|
|
4022
4188
|
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
4023
|
-
if (!token) return null;
|
|
4024
4189
|
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
4025
|
-
|
|
4190
|
+
if (!token && !secret) return null;
|
|
4191
|
+
return { ...s, ...token ? { token } : {}, ...secret ? { secret } : {} };
|
|
4026
4192
|
}
|
|
4027
4193
|
|
|
4028
4194
|
// src/control/store.ts
|
|
@@ -4097,6 +4263,76 @@ function ask(sock, req, timeoutMs) {
|
|
|
4097
4263
|
});
|
|
4098
4264
|
}
|
|
4099
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
|
+
|
|
4100
4336
|
// src/http-sink.ts
|
|
4101
4337
|
import { createHash as createHash6, createHmac } from "node:crypto";
|
|
4102
4338
|
var TIMEOUT_MS = 15e3;
|
|
@@ -4217,20 +4453,20 @@ async function tellDaemon(state, job, session, cwd, transcript) {
|
|
|
4217
4453
|
var MAX_TRIES = 10;
|
|
4218
4454
|
function enqueueJob(state, job) {
|
|
4219
4455
|
const jobs = jobsDir(state);
|
|
4220
|
-
|
|
4221
|
-
const f =
|
|
4222
|
-
|
|
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 });
|
|
4223
4459
|
return f;
|
|
4224
4460
|
}
|
|
4225
4461
|
function claimJob(file) {
|
|
4226
4462
|
const claimed = `${file}.${process.pid}.running`;
|
|
4227
4463
|
try {
|
|
4228
|
-
|
|
4464
|
+
renameSync4(file, claimed);
|
|
4229
4465
|
} catch {
|
|
4230
4466
|
return null;
|
|
4231
4467
|
}
|
|
4232
4468
|
try {
|
|
4233
|
-
return { job: JSON.parse(
|
|
4469
|
+
return { job: JSON.parse(readFileSync12(claimed, "utf8")), file: claimed };
|
|
4234
4470
|
} catch {
|
|
4235
4471
|
releaseJob(claimed);
|
|
4236
4472
|
return null;
|
|
@@ -4239,11 +4475,11 @@ function claimJob(file) {
|
|
|
4239
4475
|
function releaseJob(claimed) {
|
|
4240
4476
|
if (!claimed) return;
|
|
4241
4477
|
try {
|
|
4242
|
-
|
|
4478
|
+
unlinkSync3(claimed);
|
|
4243
4479
|
} catch {
|
|
4244
4480
|
}
|
|
4245
4481
|
}
|
|
4246
|
-
async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4) {
|
|
4482
|
+
async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4, o = {}) {
|
|
4247
4483
|
const jobs = jobsDir(state);
|
|
4248
4484
|
let names = [];
|
|
4249
4485
|
try {
|
|
@@ -4255,17 +4491,18 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4
|
|
|
4255
4491
|
for (const n of names) {
|
|
4256
4492
|
const m = /^(.*\.json)\.(\d+)\.running$/.exec(n);
|
|
4257
4493
|
if (!m) continue;
|
|
4258
|
-
const f =
|
|
4494
|
+
const f = path17.join(jobs, n);
|
|
4259
4495
|
try {
|
|
4260
4496
|
if (processAlive(Number(m[2])) && Date.now() - statSync7(f).mtimeMs < abandonedMs) continue;
|
|
4261
|
-
|
|
4497
|
+
renameSync4(f, path17.join(jobs, m[1]));
|
|
4262
4498
|
queued.push(m[1]);
|
|
4263
4499
|
} catch {
|
|
4264
4500
|
}
|
|
4265
4501
|
}
|
|
4266
4502
|
let ran = 0;
|
|
4267
4503
|
for (const n of queued.sort().slice(0, limit)) {
|
|
4268
|
-
|
|
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);
|
|
4269
4506
|
try {
|
|
4270
4507
|
if (Date.now() - statSync7(f).mtimeMs < olderThanMs) continue;
|
|
4271
4508
|
} catch {
|
|
@@ -4274,21 +4511,23 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4
|
|
|
4274
4511
|
const c = claimJob(f);
|
|
4275
4512
|
if (!c) continue;
|
|
4276
4513
|
try {
|
|
4277
|
-
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);
|
|
4278
4516
|
} catch {
|
|
4279
4517
|
} finally {
|
|
4280
4518
|
releaseJob(c.file);
|
|
4281
4519
|
}
|
|
4282
4520
|
ran++;
|
|
4283
4521
|
}
|
|
4522
|
+
if (ran) await drain(state, readConfig().sinks, new Outbox(state), void 0, o);
|
|
4284
4523
|
return ran;
|
|
4285
4524
|
}
|
|
4286
4525
|
function log(state, line) {
|
|
4287
4526
|
try {
|
|
4288
|
-
|
|
4289
|
-
const f =
|
|
4527
|
+
mkdirSync7(state, { recursive: true, mode: 448 });
|
|
4528
|
+
const f = path17.join(state, "log");
|
|
4290
4529
|
try {
|
|
4291
|
-
if (statSync7(f).size > 1024 * 1024)
|
|
4530
|
+
if (statSync7(f).size > 1024 * 1024) renameSync4(f, `${f}.1`);
|
|
4292
4531
|
} catch {
|
|
4293
4532
|
}
|
|
4294
4533
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -4297,11 +4536,11 @@ function log(state, line) {
|
|
|
4297
4536
|
}
|
|
4298
4537
|
}
|
|
4299
4538
|
function factsState(state) {
|
|
4300
|
-
const file = (h, s) =>
|
|
4539
|
+
const file = (h, s) => path17.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
4301
4540
|
return {
|
|
4302
4541
|
get(h, s) {
|
|
4303
4542
|
try {
|
|
4304
|
-
return JSON.parse(
|
|
4543
|
+
return JSON.parse(readFileSync12(file(h, s), "utf8"));
|
|
4305
4544
|
} catch {
|
|
4306
4545
|
return {};
|
|
4307
4546
|
}
|
|
@@ -4310,8 +4549,8 @@ function factsState(state) {
|
|
|
4310
4549
|
const cur = this.get(h, s);
|
|
4311
4550
|
const next = { ...cur, ...patch };
|
|
4312
4551
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
4313
|
-
|
|
4314
|
-
|
|
4552
|
+
mkdirSync7(path17.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
4553
|
+
writeFileSync6(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
4315
4554
|
}
|
|
4316
4555
|
};
|
|
4317
4556
|
}
|
|
@@ -4324,7 +4563,7 @@ function buildSinks(sinks, o = {}) {
|
|
|
4324
4563
|
o.locked?.(raw.name);
|
|
4325
4564
|
continue;
|
|
4326
4565
|
}
|
|
4327
|
-
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));
|
|
4328
4567
|
if (s.url === "stdout") out.push(new StdoutSink(s.name, tier, !!s.pii));
|
|
4329
4568
|
else if (s.url.startsWith("file:"))
|
|
4330
4569
|
out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os4.homedir()), tier, !!s.pii));
|
|
@@ -4386,7 +4625,7 @@ async function runJob(job, opts = {}) {
|
|
|
4386
4625
|
);
|
|
4387
4626
|
return { events: [], delivered: {} };
|
|
4388
4627
|
}
|
|
4389
|
-
const id = receiverIdOf(readAliases(aliasFile(
|
|
4628
|
+
const id = receiverIdOf(readAliases(aliasFile(path17.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
4390
4629
|
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
4391
4630
|
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
4392
4631
|
const outbox = new Outbox(state);
|
|
@@ -4423,7 +4662,7 @@ async function runJob(job, opts = {}) {
|
|
|
4423
4662
|
learned = rest;
|
|
4424
4663
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
4425
4664
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
4426
|
-
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))
|
|
4427
4666
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
4428
4667
|
}
|
|
4429
4668
|
const known = mem.session ?? {};
|
|
@@ -4541,29 +4780,84 @@ async function runJob(job, opts = {}) {
|
|
|
4541
4780
|
} finally {
|
|
4542
4781
|
release();
|
|
4543
4782
|
}
|
|
4544
|
-
const delivered = await drain(state, cfg.sinks, outbox, ref) ?? {};
|
|
4545
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
|
+
}) ?? {};
|
|
4546
4789
|
return { events, delivered };
|
|
4547
4790
|
}
|
|
4548
|
-
|
|
4549
|
-
|
|
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);
|
|
4550
4839
|
let release = takeNamedLock(state, "flush");
|
|
4551
4840
|
if (!release) {
|
|
4552
|
-
|
|
4553
|
-
writeFileSync8(more, "", { mode: 384 });
|
|
4554
|
-
} catch {
|
|
4555
|
-
}
|
|
4841
|
+
markMore(state);
|
|
4556
4842
|
release = takeNamedLock(state, "flush");
|
|
4557
4843
|
if (!release) return null;
|
|
4558
4844
|
}
|
|
4559
4845
|
const delivered = {};
|
|
4560
4846
|
for (; ; ) {
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4847
|
+
if (o.deadline !== void 0 && clock.now() + PASS_BUDGET_MS > o.deadline) {
|
|
4848
|
+
markMore(state);
|
|
4849
|
+
release();
|
|
4850
|
+
o.handoff?.();
|
|
4851
|
+
return delivered;
|
|
4564
4852
|
}
|
|
4565
4853
|
try {
|
|
4566
|
-
|
|
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 })))
|
|
4567
4861
|
delivered[k] = (delivered[k] ?? 0) + v;
|
|
4568
4862
|
} finally {
|
|
4569
4863
|
release();
|
|
@@ -4574,10 +4868,10 @@ async function drain(state, sinkConfigs, outbox, first) {
|
|
|
4574
4868
|
}
|
|
4575
4869
|
}
|
|
4576
4870
|
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
4577
|
-
var backoffFile = (state, sink) =>
|
|
4871
|
+
var backoffFile = (state, sink) => path17.join(state, "backoff", `${safeId(sink)}.json`);
|
|
4578
4872
|
function readBackoff(state, sink) {
|
|
4579
4873
|
try {
|
|
4580
|
-
const b = JSON.parse(
|
|
4874
|
+
const b = JSON.parse(readFileSync12(backoffFile(state, sink), "utf8"));
|
|
4581
4875
|
return typeof b.until === "number" ? b : null;
|
|
4582
4876
|
} catch {
|
|
4583
4877
|
return null;
|
|
@@ -4590,15 +4884,15 @@ function noteFailure(state, sink, res, prev) {
|
|
|
4590
4884
|
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
4591
4885
|
const f = backoffFile(state, sink);
|
|
4592
4886
|
try {
|
|
4593
|
-
|
|
4594
|
-
|
|
4887
|
+
mkdirSync7(path17.dirname(f), { recursive: true, mode: 448 });
|
|
4888
|
+
writeFileSync6(f, JSON.stringify(b), { mode: 384 });
|
|
4595
4889
|
} catch {
|
|
4596
4890
|
}
|
|
4597
4891
|
return b;
|
|
4598
4892
|
}
|
|
4599
4893
|
function clearBackoff(state, sink) {
|
|
4600
4894
|
try {
|
|
4601
|
-
|
|
4895
|
+
unlinkSync3(backoffFile(state, sink));
|
|
4602
4896
|
} catch {
|
|
4603
4897
|
}
|
|
4604
4898
|
}
|
|
@@ -4608,14 +4902,31 @@ var BATCH = 50;
|
|
|
4608
4902
|
var MAX_PER_RUN = 500;
|
|
4609
4903
|
async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
4610
4904
|
const delivered = {};
|
|
4905
|
+
const clock = o.clock ?? REAL_CLOCK;
|
|
4611
4906
|
const sinks = buildSinks(sinkConfigs, {
|
|
4612
4907
|
locked: (name) => log(state, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
4613
4908
|
});
|
|
4909
|
+
const modes = new Map(sinkConfigs.map((s) => [s.name, toolsMode(s)]));
|
|
4614
4910
|
const refs = outbox.sessions();
|
|
4615
4911
|
if (first) {
|
|
4616
4912
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
4617
4913
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
4618
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
|
+
};
|
|
4619
4930
|
const drainSink = async (sink) => {
|
|
4620
4931
|
let backoff = readBackoff(state, sink.name);
|
|
4621
4932
|
if (backoff && !o.force && backoff.until > Date.now()) {
|
|
@@ -4624,11 +4935,16 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4624
4935
|
}
|
|
4625
4936
|
const cursors = new Cursors(state, sink.name);
|
|
4626
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 };
|
|
4627
4942
|
let sent = 0;
|
|
4628
4943
|
let stalled = false;
|
|
4629
4944
|
let batch = [];
|
|
4630
4945
|
let pending = {};
|
|
4631
4946
|
const deliver = async (events) => {
|
|
4947
|
+
if (sink instanceof HttpSink) noteSent(state, clock.now());
|
|
4632
4948
|
const res = await sink.send(events);
|
|
4633
4949
|
if (res.ok || res.action !== "split") return res;
|
|
4634
4950
|
if (events.length === 1) {
|
|
@@ -4636,10 +4952,20 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4636
4952
|
log(state, `sink ${sink.name}: 413 ${res.error ?? ""} \u2192 event ${e.id} (${e.type}) is too large alone, dropped`);
|
|
4637
4953
|
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected: [{ id: e.id, reason: "too_large" }] };
|
|
4638
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
|
+
};
|
|
4639
4965
|
const half = Math.ceil(events.length / 2);
|
|
4640
|
-
const a = await
|
|
4966
|
+
const a = await part(events.slice(0, half));
|
|
4641
4967
|
if (!a.ok) return a;
|
|
4642
|
-
const b = await
|
|
4968
|
+
const b = await part(events.slice(half));
|
|
4643
4969
|
if (!b.ok) return b;
|
|
4644
4970
|
return {
|
|
4645
4971
|
ok: true,
|
|
@@ -4649,11 +4975,17 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4649
4975
|
rejected: [...a.rejected ?? [], ...b.rejected ?? []]
|
|
4650
4976
|
};
|
|
4651
4977
|
};
|
|
4978
|
+
const commitClock = () => {
|
|
4979
|
+
if (mode === "all" || JSON.stringify(clockNext) === JSON.stringify(clockNow)) return;
|
|
4980
|
+
tools.write(clockNext);
|
|
4981
|
+
clockNow = { ...clockNext };
|
|
4982
|
+
};
|
|
4652
4983
|
const send = async () => {
|
|
4653
4984
|
if (!batch.length) {
|
|
4654
4985
|
Object.assign(c, pending);
|
|
4655
4986
|
pending = {};
|
|
4656
4987
|
cursors.write(c);
|
|
4988
|
+
commitClock();
|
|
4657
4989
|
return true;
|
|
4658
4990
|
}
|
|
4659
4991
|
const res = await deliver(batch);
|
|
@@ -4669,6 +5001,7 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4669
5001
|
if (res.action !== "drop") {
|
|
4670
5002
|
batch = [];
|
|
4671
5003
|
pending = {};
|
|
5004
|
+
clockNext = { ...clockNow };
|
|
4672
5005
|
return false;
|
|
4673
5006
|
}
|
|
4674
5007
|
} else if (backoff) {
|
|
@@ -4678,6 +5011,7 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4678
5011
|
sent += batch.length;
|
|
4679
5012
|
Object.assign(c, pending);
|
|
4680
5013
|
cursors.write(c);
|
|
5014
|
+
commitClock();
|
|
4681
5015
|
batch = [];
|
|
4682
5016
|
pending = {};
|
|
4683
5017
|
return true;
|
|
@@ -4687,11 +5021,15 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4687
5021
|
let offset = pending[cursors.key(ref)] ?? c[cursors.key(ref)] ?? 0;
|
|
4688
5022
|
for (; ; ) {
|
|
4689
5023
|
const room = BATCH - batch.length;
|
|
4690
|
-
const
|
|
5024
|
+
const read = readOrSkip(ref, offset, room);
|
|
5025
|
+
if (!read) break;
|
|
5026
|
+
const { events, offset: next } = read;
|
|
4691
5027
|
if (!events.length) break;
|
|
4692
5028
|
for (const e of events) {
|
|
4693
5029
|
const f = filterEvent(e, sink.tier, sink.pii, { home: os4.homedir() });
|
|
4694
|
-
if (f)
|
|
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;
|
|
4695
5033
|
}
|
|
4696
5034
|
offset = next;
|
|
4697
5035
|
pending[cursors.key(ref)] = offset;
|
|
@@ -4723,31 +5061,105 @@ function applySinkVerdict(name, res) {
|
|
|
4723
5061
|
} catch {
|
|
4724
5062
|
}
|
|
4725
5063
|
}
|
|
4726
|
-
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
|
+
}
|
|
4727
5117
|
|
|
4728
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
|
+
}
|
|
4729
5125
|
async function workerMain(argv) {
|
|
4730
5126
|
const jobFile = argv[2];
|
|
4731
5127
|
if (!jobFile) return;
|
|
4732
|
-
const
|
|
4733
|
-
|
|
4734
|
-
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);
|
|
4735
5139
|
deadline.unref();
|
|
4736
|
-
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) => {
|
|
4737
5152
|
if (!r.requeued || (claimed.job.tries ?? 0) + 1 >= MAX_TRIES) return;
|
|
4738
|
-
|
|
4739
|
-
detached: true,
|
|
4740
|
-
stdio: "ignore",
|
|
4741
|
-
windowsHide: true
|
|
4742
|
-
});
|
|
4743
|
-
p.unref();
|
|
5153
|
+
startWorker(script, r.requeued);
|
|
4744
5154
|
}).catch(() => {
|
|
4745
|
-
}).finally(() => releaseJob(claimed.file)).then(() => sweepJobs(stateDir())).catch(() => {
|
|
5155
|
+
}).finally(() => releaseJob(claimed.file)).then(() => sweepJobs(stateDir(), void 0, void 0, void 0, life)).catch(() => {
|
|
4746
5156
|
}).finally(() => clearTimeout(deadline));
|
|
4747
5157
|
}
|
|
4748
5158
|
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync2(process.argv[1])).href)
|
|
4749
5159
|
workerMain(process.argv).finally(() => process.exit(0));
|
|
4750
5160
|
export {
|
|
5161
|
+
DRAIN_ARG,
|
|
5162
|
+
WORKER_LIFE_MS,
|
|
4751
5163
|
workerMain
|
|
4752
5164
|
};
|
|
4753
5165
|
//# sourceMappingURL=worker.js.map
|