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/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe 0.13.
|
|
1
|
+
// sessionpipe 0.13.1 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
3
|
var __export = (target, all) => {
|
|
4
4
|
for (var name in all)
|
|
@@ -104,16 +104,16 @@ var HttpSink = class {
|
|
|
104
104
|
import {
|
|
105
105
|
appendFileSync as appendFileSync3,
|
|
106
106
|
existsSync as existsSync10,
|
|
107
|
-
mkdirSync as
|
|
107
|
+
mkdirSync as mkdirSync7,
|
|
108
108
|
readdirSync as readdirSync7,
|
|
109
|
-
readFileSync as
|
|
110
|
-
renameSync as
|
|
109
|
+
readFileSync as readFileSync12,
|
|
110
|
+
renameSync as renameSync4,
|
|
111
111
|
statSync as statSync7,
|
|
112
|
-
unlinkSync as
|
|
113
|
-
writeFileSync as
|
|
112
|
+
unlinkSync as unlinkSync3,
|
|
113
|
+
writeFileSync as writeFileSync6
|
|
114
114
|
} from "node:fs";
|
|
115
115
|
import os4 from "node:os";
|
|
116
|
-
import
|
|
116
|
+
import path17 from "node:path";
|
|
117
117
|
|
|
118
118
|
// ../core/src/paths.ts
|
|
119
119
|
import os from "node:os";
|
|
@@ -141,7 +141,7 @@ var safeId = (s) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 160);
|
|
|
141
141
|
// ../core/src/adapters/claude-code.ts
|
|
142
142
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
143
143
|
import { createHash as createHash2 } from "node:crypto";
|
|
144
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2
|
|
144
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
145
145
|
import { userInfo } from "node:os";
|
|
146
146
|
import path4 from "node:path";
|
|
147
147
|
|
|
@@ -499,6 +499,41 @@ function readJson(file) {
|
|
|
499
499
|
const f = readJsonFile(file);
|
|
500
500
|
return f.ok ? f.value : {};
|
|
501
501
|
}
|
|
502
|
+
function refused(file, e, what, existed) {
|
|
503
|
+
const { code, syscall } = e;
|
|
504
|
+
const why = `can't ${what} it (${code ?? String(e)})`;
|
|
505
|
+
if (what === "remove" || syscall === "open" || syscall === "mkdir")
|
|
506
|
+
return { file, changed: false, skipped: true, note: `${why}; not touched` };
|
|
507
|
+
return {
|
|
508
|
+
file,
|
|
509
|
+
changed: true,
|
|
510
|
+
skipped: true,
|
|
511
|
+
...existed ? {} : { created: true },
|
|
512
|
+
note: `${why} part way; check the file before the harness reads it`
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function writeBack(file, text, existed = true) {
|
|
516
|
+
try {
|
|
517
|
+
mkdirSync(path3.dirname(file), { recursive: true });
|
|
518
|
+
writeFileSync(file, text);
|
|
519
|
+
return null;
|
|
520
|
+
} catch (e) {
|
|
521
|
+
return refused(file, e, "write", existed);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function removeEmptied(file, empty) {
|
|
525
|
+
try {
|
|
526
|
+
unlinkSync(file);
|
|
527
|
+
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
528
|
+
} catch (e) {
|
|
529
|
+
const bad = refused(file, e, "remove", true);
|
|
530
|
+
return writeBack(file, empty) ?? {
|
|
531
|
+
file,
|
|
532
|
+
changed: true,
|
|
533
|
+
note: `emptied; ${bad.note.replace(/; not touched$/, "")}, so the empty file stays`
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
}
|
|
502
537
|
function entryFor(event, harness, cmd, o) {
|
|
503
538
|
const matcher = o.matcherFor(event);
|
|
504
539
|
return {
|
|
@@ -540,8 +575,8 @@ function installClaudeShaped(file, harness, events, cmd, o) {
|
|
|
540
575
|
}
|
|
541
576
|
if (changed) {
|
|
542
577
|
settings2.hooks = hooks;
|
|
543
|
-
|
|
544
|
-
|
|
578
|
+
const bad = writeBack(file, formatJson(settings2, f), f.existed);
|
|
579
|
+
if (bad) return bad;
|
|
545
580
|
}
|
|
546
581
|
return { file, changed, created: !f.existed && changed };
|
|
547
582
|
}
|
|
@@ -564,12 +599,8 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
564
599
|
}
|
|
565
600
|
if (!changed) return { file, changed: false };
|
|
566
601
|
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
567
|
-
if (opts.created && !Object.keys(settings2).length)
|
|
568
|
-
|
|
569
|
-
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
570
|
-
}
|
|
571
|
-
writeFileSync(file, formatJson(settings2, f));
|
|
572
|
-
return { file, changed: true };
|
|
602
|
+
if (opts.created && !Object.keys(settings2).length) return removeEmptied(file, formatJson(settings2, f));
|
|
603
|
+
return writeBack(file, formatJson(settings2, f)) ?? { file, changed: true };
|
|
573
604
|
}
|
|
574
605
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
575
606
|
const f = readJsonFile(file);
|
|
@@ -648,7 +679,7 @@ var claudeCode = {
|
|
|
648
679
|
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
649
680
|
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
|
|
650
681
|
const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
|
|
651
|
-
const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
682
|
+
const line = opts.statusLine === void 0 || hooks.skipped ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
652
683
|
return line ? [hooks, line] : [hooks];
|
|
653
684
|
}),
|
|
654
685
|
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
|
|
@@ -656,7 +687,9 @@ var claudeCode = {
|
|
|
656
687
|
const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
|
|
657
688
|
created: opts.created?.includes(settingsIn(d))
|
|
658
689
|
});
|
|
659
|
-
|
|
690
|
+
if (line?.changed && !line.skipped && hooks.skipped)
|
|
691
|
+
return { ...hooks, note: `status line removed, hooks not: ${hooks.note?.replace(/; not touched$/, "")}` };
|
|
692
|
+
return line && !hooks.changed && !hooks.skipped ? line : hooks;
|
|
660
693
|
}),
|
|
661
694
|
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
662
695
|
file: settingsIn(d),
|
|
@@ -751,7 +784,8 @@ var claudeCode = {
|
|
|
751
784
|
attention_id: call_id ?? permissionId(),
|
|
752
785
|
kind: "permission",
|
|
753
786
|
tool,
|
|
754
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
787
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0,
|
|
788
|
+
agent_id
|
|
755
789
|
})
|
|
756
790
|
);
|
|
757
791
|
break;
|
|
@@ -760,7 +794,8 @@ var claudeCode = {
|
|
|
760
794
|
ev("attention.needed", {
|
|
761
795
|
attention_id: `notif-${Date.now()}`,
|
|
762
796
|
kind: notifKind(s.notification_type),
|
|
763
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
797
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0,
|
|
798
|
+
agent_id
|
|
764
799
|
})
|
|
765
800
|
);
|
|
766
801
|
break;
|
|
@@ -885,8 +920,11 @@ function statusLine(file, want, cmd) {
|
|
|
885
920
|
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
886
921
|
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
887
922
|
else delete f.value.statusLine;
|
|
888
|
-
|
|
889
|
-
|
|
923
|
+
return writeBack(file, formatJson(f.value, f), f.existed) ?? {
|
|
924
|
+
file,
|
|
925
|
+
changed: true,
|
|
926
|
+
note: want ? "status line written (account limits)" : "status line removed"
|
|
927
|
+
};
|
|
890
928
|
}
|
|
891
929
|
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
892
930
|
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
@@ -1492,16 +1530,7 @@ function runHeadless(bin, args, o) {
|
|
|
1492
1530
|
}
|
|
1493
1531
|
|
|
1494
1532
|
// ../core/src/adapters/antigravity.ts
|
|
1495
|
-
import {
|
|
1496
|
-
existsSync as existsSync3,
|
|
1497
|
-
mkdirSync as mkdirSync2,
|
|
1498
|
-
readdirSync as readdirSync3,
|
|
1499
|
-
readFileSync as readFileSync4,
|
|
1500
|
-
realpathSync,
|
|
1501
|
-
statSync as statSync4,
|
|
1502
|
-
unlinkSync as unlinkSync2,
|
|
1503
|
-
writeFileSync as writeFileSync3
|
|
1504
|
-
} from "node:fs";
|
|
1533
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync4, realpathSync, statSync as statSync4 } from "node:fs";
|
|
1505
1534
|
import path6 from "node:path";
|
|
1506
1535
|
var NAME2 = "antigravity";
|
|
1507
1536
|
var HOOK_NAME = "sessionpipe";
|
|
@@ -1533,10 +1562,8 @@ var antigravity = {
|
|
|
1533
1562
|
const e = entry(cmd);
|
|
1534
1563
|
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1535
1564
|
hooks[HOOK_NAME] = e;
|
|
1536
|
-
mkdirSync2(path6.dirname(file), { recursive: true });
|
|
1537
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
1538
1565
|
return [
|
|
1539
|
-
{
|
|
1566
|
+
writeBack(file, formatJson(hooks, f), f.existed) ?? {
|
|
1540
1567
|
file,
|
|
1541
1568
|
changed: true,
|
|
1542
1569
|
created: !f.existed,
|
|
@@ -1552,12 +1579,8 @@ var antigravity = {
|
|
|
1552
1579
|
const hooks = f.value;
|
|
1553
1580
|
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1554
1581
|
delete hooks[HOOK_NAME];
|
|
1555
|
-
if (opts.created?.includes(file) && !Object.keys(hooks).length)
|
|
1556
|
-
|
|
1557
|
-
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1558
|
-
}
|
|
1559
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
1560
|
-
return [{ file, changed: true }];
|
|
1582
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) return [removeEmptied(file, formatJson(hooks, f))];
|
|
1583
|
+
return [writeBack(file, formatJson(hooks, f)) ?? { file, changed: true }];
|
|
1561
1584
|
},
|
|
1562
1585
|
installed(cmd, env = process.env) {
|
|
1563
1586
|
const file = hooksFile(env);
|
|
@@ -2290,7 +2313,7 @@ function parseResult(stdout) {
|
|
|
2290
2313
|
var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
|
|
2291
2314
|
|
|
2292
2315
|
// ../core/src/adapters/codex.ts
|
|
2293
|
-
import { existsSync as existsSync4,
|
|
2316
|
+
import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
2294
2317
|
import path8 from "node:path";
|
|
2295
2318
|
|
|
2296
2319
|
// ../core/src/adapters/codex-config.ts
|
|
@@ -2443,29 +2466,30 @@ var configFile2 = (env) => path8.join(codexHome(env), "config.toml");
|
|
|
2443
2466
|
var sessionsDir = (env) => path8.join(codexHome(env), "sessions");
|
|
2444
2467
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2445
2468
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
2469
|
+
var UNTIL_APPROVED = "Codex will report nothing until you approve the hooks: start `codex` and accept the hook review";
|
|
2446
2470
|
function installNotify(cmd, env) {
|
|
2447
2471
|
const file = configFile2(env);
|
|
2448
2472
|
let text = "";
|
|
2473
|
+
let existed = true;
|
|
2449
2474
|
try {
|
|
2450
2475
|
text = readFileSync6(file, "utf8");
|
|
2451
|
-
} catch {
|
|
2476
|
+
} catch (e) {
|
|
2477
|
+
existed = e.code !== "ENOENT";
|
|
2452
2478
|
}
|
|
2453
2479
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
2454
2480
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
2455
2481
|
const n = readNotify(text);
|
|
2456
|
-
const write = (next) => {
|
|
2457
|
-
|
|
2458
|
-
|
|
2482
|
+
const write = (next, note) => {
|
|
2483
|
+
const bad = writeBack(file, next, existed);
|
|
2484
|
+
return bad ? { ...bad, note: `${bad.note}. ${UNTIL_APPROVED}` } : { file, changed: true, note };
|
|
2459
2485
|
};
|
|
2460
2486
|
if (n && !n.other) {
|
|
2461
2487
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
2462
2488
|
return { file, changed: false, note: "notify current" };
|
|
2463
|
-
write(
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
2468
|
-
};
|
|
2489
|
+
return write(
|
|
2490
|
+
withNotify(text, line),
|
|
2491
|
+
n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
2492
|
+
);
|
|
2469
2493
|
}
|
|
2470
2494
|
if (n?.other) {
|
|
2471
2495
|
const prev = previousNotify(n.argv);
|
|
@@ -2474,20 +2498,20 @@ function installNotify(cmd, env) {
|
|
|
2474
2498
|
if (JSON.stringify(prev) === JSON.stringify(argv))
|
|
2475
2499
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
2476
2500
|
}
|
|
2477
|
-
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
2478
|
-
write(
|
|
2479
|
-
|
|
2480
|
-
|
|
2501
|
+
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
2502
|
+
return write(
|
|
2503
|
+
replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`),
|
|
2504
|
+
"chained after Codex Computer Use (--previous-notify)"
|
|
2505
|
+
);
|
|
2481
2506
|
}
|
|
2482
2507
|
return {
|
|
2483
2508
|
file,
|
|
2484
2509
|
changed: false,
|
|
2485
2510
|
skipped: true,
|
|
2486
|
-
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone.
|
|
2511
|
+
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. ${UNTIL_APPROVED}`
|
|
2487
2512
|
};
|
|
2488
2513
|
}
|
|
2489
|
-
write(withNotify(text, line));
|
|
2490
|
-
return { file, changed: true, note: "notify set at the top level" };
|
|
2514
|
+
return write(withNotify(text, line), "notify set at the top level");
|
|
2491
2515
|
}
|
|
2492
2516
|
function uninstallNotify(env) {
|
|
2493
2517
|
const file = configFile2(env);
|
|
@@ -2499,15 +2523,13 @@ function uninstallNotify(env) {
|
|
|
2499
2523
|
}
|
|
2500
2524
|
const n = readNotify(text);
|
|
2501
2525
|
if (!n) return { file, changed: false };
|
|
2502
|
-
if (!n.other) {
|
|
2503
|
-
writeFileSync4(file, withoutNotify(text));
|
|
2504
|
-
return { file, changed: true };
|
|
2505
|
-
}
|
|
2526
|
+
if (!n.other) return writeBack(file, withoutNotify(text)) ?? { file, changed: true };
|
|
2506
2527
|
const prev = previousNotify(n.argv);
|
|
2507
|
-
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2528
|
+
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
2529
|
+
return writeBack(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`)) ?? {
|
|
2530
|
+
file,
|
|
2531
|
+
changed: true
|
|
2532
|
+
};
|
|
2511
2533
|
return { file, changed: false };
|
|
2512
2534
|
}
|
|
2513
2535
|
function findRollout(threadId, env = process.env) {
|
|
@@ -2539,8 +2561,12 @@ function rolloutMeta(file) {
|
|
|
2539
2561
|
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
2540
2562
|
}
|
|
2541
2563
|
function ofAnotherThread(file, id) {
|
|
2564
|
+
const other = threadOf(file);
|
|
2565
|
+
return !!other && other !== id.toLowerCase();
|
|
2566
|
+
}
|
|
2567
|
+
function threadOf(file) {
|
|
2542
2568
|
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);
|
|
2543
|
-
return
|
|
2569
|
+
return m?.[1] ? m[1].toLowerCase() : null;
|
|
2544
2570
|
}
|
|
2545
2571
|
var codex = {
|
|
2546
2572
|
name: NAME3,
|
|
@@ -2619,6 +2645,8 @@ var codex = {
|
|
|
2619
2645
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
2620
2646
|
const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
|
|
2621
2647
|
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
2648
|
+
const thread = typeof s.transcript_path === "string" ? threadOf(s.transcript_path) : null;
|
|
2649
|
+
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;
|
|
2622
2650
|
const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
|
|
2623
2651
|
const events = [];
|
|
2624
2652
|
switch (event) {
|
|
@@ -2638,17 +2666,30 @@ var codex = {
|
|
|
2638
2666
|
);
|
|
2639
2667
|
break;
|
|
2640
2668
|
case "PreToolUse":
|
|
2641
|
-
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
2669
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
2642
2670
|
break;
|
|
2643
2671
|
case "PostToolUse":
|
|
2644
2672
|
if (tool)
|
|
2645
2673
|
events.push(
|
|
2646
|
-
ev("tool.ended", {
|
|
2674
|
+
ev("tool.ended", {
|
|
2675
|
+
tool,
|
|
2676
|
+
call_id,
|
|
2677
|
+
turn_id,
|
|
2678
|
+
agent_id,
|
|
2679
|
+
ok: true,
|
|
2680
|
+
input: s.tool_input,
|
|
2681
|
+
output: s.tool_response
|
|
2682
|
+
})
|
|
2647
2683
|
);
|
|
2648
2684
|
break;
|
|
2649
2685
|
case "PermissionRequest":
|
|
2650
2686
|
events.push(
|
|
2651
|
-
ev("attention.needed", {
|
|
2687
|
+
ev("attention.needed", {
|
|
2688
|
+
attention_id: call_id ?? `perm-${Date.now()}`,
|
|
2689
|
+
kind: "permission",
|
|
2690
|
+
tool,
|
|
2691
|
+
agent_id
|
|
2692
|
+
})
|
|
2652
2693
|
);
|
|
2653
2694
|
break;
|
|
2654
2695
|
case "Stop":
|
|
@@ -3138,7 +3179,7 @@ var CHAIN_LIMIT = 4e3;
|
|
|
3138
3179
|
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
3139
3180
|
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
3140
3181
|
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
3141
|
-
UNION
|
|
3182
|
+
UNION
|
|
3142
3183
|
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
3143
3184
|
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
3144
3185
|
)
|
|
@@ -3407,7 +3448,7 @@ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
|
3407
3448
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
3408
3449
|
|
|
3409
3450
|
// ../core/src/config.ts
|
|
3410
|
-
import { chmodSync, existsSync as existsSync8, mkdirSync as
|
|
3451
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3411
3452
|
import path11 from "node:path";
|
|
3412
3453
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
3413
3454
|
function readConfig(file = configFile()) {
|
|
@@ -3423,9 +3464,36 @@ function readConfig(file = configFile()) {
|
|
|
3423
3464
|
return { ...DEFAULT_CONFIG, sinks: [], harnesses: {} };
|
|
3424
3465
|
}
|
|
3425
3466
|
}
|
|
3467
|
+
function configProblem(file) {
|
|
3468
|
+
let text;
|
|
3469
|
+
try {
|
|
3470
|
+
text = readFileSync8(file, "utf8");
|
|
3471
|
+
} catch (e) {
|
|
3472
|
+
const code = e.code;
|
|
3473
|
+
return code === "ENOENT" ? void 0 : `could not be read (${code})`;
|
|
3474
|
+
}
|
|
3475
|
+
if (!text.trim()) return void 0;
|
|
3476
|
+
if (text.charCodeAt(0) === 65279) return "starts with a byte-order mark (save it as UTF-8 without one)";
|
|
3477
|
+
try {
|
|
3478
|
+
const j = JSON.parse(text);
|
|
3479
|
+
return j && typeof j === "object" && !Array.isArray(j) ? void 0 : "is not a JSON object";
|
|
3480
|
+
} catch (e) {
|
|
3481
|
+
const m = e.message;
|
|
3482
|
+
const at = (/line \d+ column \d+/.exec(m) ?? /position \d+/.exec(m))?.[0];
|
|
3483
|
+
return `is not valid JSON${at ? ` at ${at}` : ""}`;
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
function assertConfigWritable(file = configFile()) {
|
|
3487
|
+
const problem = configProblem(file);
|
|
3488
|
+
if (problem)
|
|
3489
|
+
throw new Error(
|
|
3490
|
+
`${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.`
|
|
3491
|
+
);
|
|
3492
|
+
}
|
|
3426
3493
|
function writeConfig(c, file = configFile()) {
|
|
3427
|
-
|
|
3428
|
-
|
|
3494
|
+
assertConfigWritable(file);
|
|
3495
|
+
mkdirSync2(path11.dirname(file), { recursive: true, mode: 448 });
|
|
3496
|
+
writeFileSync2(file, `${JSON.stringify(c, null, 2)}
|
|
3429
3497
|
`, { mode: 384 });
|
|
3430
3498
|
if (process.platform !== "win32" && existsSync8(file)) {
|
|
3431
3499
|
try {
|
|
@@ -3489,19 +3557,20 @@ function makeEvent(a, ctx) {
|
|
|
3489
3557
|
}
|
|
3490
3558
|
|
|
3491
3559
|
// ../core/src/outbox.ts
|
|
3560
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
3492
3561
|
import {
|
|
3493
3562
|
appendFileSync,
|
|
3494
3563
|
closeSync as closeSync3,
|
|
3495
3564
|
existsSync as existsSync9,
|
|
3496
|
-
mkdirSync as
|
|
3565
|
+
mkdirSync as mkdirSync3,
|
|
3497
3566
|
openSync as openSync3,
|
|
3498
3567
|
readdirSync as readdirSync6,
|
|
3499
3568
|
readFileSync as readFileSync9,
|
|
3500
3569
|
readSync as readSync3,
|
|
3501
3570
|
renameSync,
|
|
3502
3571
|
statSync as statSync6,
|
|
3503
|
-
unlinkSync as
|
|
3504
|
-
writeFileSync as
|
|
3572
|
+
unlinkSync as unlinkSync2,
|
|
3573
|
+
writeFileSync as writeFileSync3
|
|
3505
3574
|
} from "node:fs";
|
|
3506
3575
|
import path12 from "node:path";
|
|
3507
3576
|
var READ_CHUNK = 1024 * 1024;
|
|
@@ -3531,8 +3600,8 @@ var Outbox = class {
|
|
|
3531
3600
|
} catch {
|
|
3532
3601
|
}
|
|
3533
3602
|
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
3534
|
-
|
|
3535
|
-
|
|
3603
|
+
mkdirSync3(path12.dirname(f), { recursive: true, mode: 448 });
|
|
3604
|
+
writeFileSync3(f, String(n + 1), { mode: 384 });
|
|
3536
3605
|
return n;
|
|
3537
3606
|
}
|
|
3538
3607
|
lastSeqInFile(ref) {
|
|
@@ -3550,7 +3619,7 @@ var Outbox = class {
|
|
|
3550
3619
|
}
|
|
3551
3620
|
append(ref, events) {
|
|
3552
3621
|
if (!events.length) return;
|
|
3553
|
-
|
|
3622
|
+
mkdirSync3(this.dir(ref.harness), { recursive: true, mode: 448 });
|
|
3554
3623
|
appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
3555
3624
|
`, { mode: 384 });
|
|
3556
3625
|
}
|
|
@@ -3620,34 +3689,106 @@ var Outbox = class {
|
|
|
3620
3689
|
closeSync3(fd);
|
|
3621
3690
|
}
|
|
3622
3691
|
}
|
|
3623
|
-
/** Files older than `days` are pruned
|
|
3692
|
+
/** Files older than `days` are pruned, and each sink's cursor into them. The seq
|
|
3693
|
+
* counter stays: a session resumed after the window counts on from where it was,
|
|
3694
|
+
* so a receiver's (harness, session, seq) dedup doesn't take its new events for
|
|
3695
|
+
* ones it already has, and the sinks read the new file from its start.
|
|
3696
|
+
* It all happens under the drain's `flush` lock, since a drain holds the whole
|
|
3697
|
+
* cursor map and writes it back: a file and its cursor keys go together, so a
|
|
3698
|
+
* session resumed in between can't keep a key past its new file's end. While a
|
|
3699
|
+
* drain runs, nothing is pruned (0); the next prune does it. It never throws: it
|
|
3700
|
+
* is a cleanup, and a hook's drain comes after it. */
|
|
3624
3701
|
prune(days, now = Date.now()) {
|
|
3702
|
+
let release = null;
|
|
3703
|
+
try {
|
|
3704
|
+
release = takeNamedLock(this.root, "flush");
|
|
3705
|
+
} catch {
|
|
3706
|
+
}
|
|
3707
|
+
if (!release) return 0;
|
|
3625
3708
|
let n = 0;
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
const
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3709
|
+
try {
|
|
3710
|
+
const cutoff = now - days * 864e5;
|
|
3711
|
+
const aged = this.sessions().filter((ref) => {
|
|
3712
|
+
try {
|
|
3713
|
+
return statSync6(this.file(ref)).mtimeMs < cutoff;
|
|
3714
|
+
} catch {
|
|
3715
|
+
return false;
|
|
3716
|
+
}
|
|
3717
|
+
});
|
|
3718
|
+
this.dropStaleCursors(new Set(aged.map((ref) => `${ref.harness}/${ref.session}`)));
|
|
3719
|
+
for (const ref of aged) {
|
|
3720
|
+
const f = this.file(ref);
|
|
3721
|
+
try {
|
|
3722
|
+
if (statSync6(f).mtimeMs < cutoff) {
|
|
3723
|
+
unlinkSync2(f);
|
|
3724
|
+
n++;
|
|
3635
3725
|
}
|
|
3636
|
-
|
|
3726
|
+
} catch {
|
|
3637
3727
|
}
|
|
3638
|
-
} catch {
|
|
3639
3728
|
}
|
|
3729
|
+
} catch {
|
|
3730
|
+
} finally {
|
|
3731
|
+
release();
|
|
3640
3732
|
}
|
|
3641
3733
|
return n;
|
|
3642
3734
|
}
|
|
3735
|
+
/** A sink's cursor into a session file about to be pruned (`gone`), or one already
|
|
3736
|
+
* missing (an earlier prune, an older release, by hand) is removed. The caller
|
|
3737
|
+
* holds the `flush` lock. */
|
|
3738
|
+
dropStaleCursors(gone) {
|
|
3739
|
+
let names = [];
|
|
3740
|
+
try {
|
|
3741
|
+
names = readdirSync6(path12.join(this.root, "cursors")).filter((n) => n.endsWith(".json"));
|
|
3742
|
+
} catch {
|
|
3743
|
+
}
|
|
3744
|
+
const stale = (c) => Object.keys(c).filter((k) => {
|
|
3745
|
+
if (gone.has(k)) return true;
|
|
3746
|
+
const i = k.indexOf("/");
|
|
3747
|
+
return !existsSync9(this.file({ harness: k.slice(0, i), session: k.slice(i + 1) }));
|
|
3748
|
+
});
|
|
3749
|
+
for (const n of names) {
|
|
3750
|
+
const s = new Cursors(this.root, n.slice(0, -5));
|
|
3751
|
+
const c = s.read();
|
|
3752
|
+
const keys = stale(c);
|
|
3753
|
+
if (!keys.length) continue;
|
|
3754
|
+
for (const k of keys) delete c[k];
|
|
3755
|
+
s.write(c);
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3643
3758
|
delete(ref) {
|
|
3759
|
+
this.dropCursors(ref);
|
|
3644
3760
|
for (const f of [this.file(ref), this.seqFile(ref)]) {
|
|
3645
3761
|
try {
|
|
3646
|
-
|
|
3762
|
+
unlinkSync2(f);
|
|
3647
3763
|
} catch {
|
|
3648
3764
|
}
|
|
3649
3765
|
}
|
|
3650
3766
|
}
|
|
3767
|
+
/** The key every sink's cursor holds for a session is removed with the session's
|
|
3768
|
+
* file: the key alone pointed into the old file, and a session that went on (still
|
|
3769
|
+
* running, or resumed later) had its new file's first events unsent until it grew
|
|
3770
|
+
* past that offset. Every file under <state>/cursors is taken (a sink since taken
|
|
3771
|
+
* out of the config still has one; one that doesn't parse reads as empty and is
|
|
3772
|
+
* left), by the key a drain writes (the file's own name, `safeId`) and by the ref
|
|
3773
|
+
* as given. The caller holds the drain's `flush` lock: a drain holds the whole
|
|
3774
|
+
* cursor map and writes it back, so a key dropped under it would come back. */
|
|
3775
|
+
dropCursors(ref) {
|
|
3776
|
+
const keys = /* @__PURE__ */ new Set([`${ref.harness}/${ref.session}`, `${safeId(ref.harness)}/${safeId(ref.session)}`]);
|
|
3777
|
+
let names = [];
|
|
3778
|
+
try {
|
|
3779
|
+
names = readdirSync6(path12.join(this.root, "cursors")).filter((n) => n.endsWith(".json"));
|
|
3780
|
+
} catch {
|
|
3781
|
+
return;
|
|
3782
|
+
}
|
|
3783
|
+
for (const n of names) {
|
|
3784
|
+
const s = new Cursors(this.root, n.slice(0, -5));
|
|
3785
|
+
const c = s.read();
|
|
3786
|
+
const had = Object.keys(c).filter((k) => keys.has(k));
|
|
3787
|
+
if (!had.length) continue;
|
|
3788
|
+
for (const k of had) delete c[k];
|
|
3789
|
+
s.write(c);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3651
3792
|
};
|
|
3652
3793
|
var Cursors = class {
|
|
3653
3794
|
constructor(root, sink) {
|
|
@@ -3659,18 +3800,21 @@ var Cursors = class {
|
|
|
3659
3800
|
file() {
|
|
3660
3801
|
return path12.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
3661
3802
|
}
|
|
3803
|
+
/** The map; empty when the file is missing, cut off mid-write, or holds anything
|
|
3804
|
+
* but an object (`null`, a list), so a drain never indexes into those. */
|
|
3662
3805
|
read() {
|
|
3663
3806
|
try {
|
|
3664
|
-
|
|
3807
|
+
const c = JSON.parse(readFileSync9(this.file(), "utf8"));
|
|
3808
|
+
return c && typeof c === "object" && !Array.isArray(c) ? c : {};
|
|
3665
3809
|
} catch {
|
|
3666
3810
|
return {};
|
|
3667
3811
|
}
|
|
3668
3812
|
}
|
|
3669
3813
|
write(c) {
|
|
3670
3814
|
const f = this.file();
|
|
3671
|
-
|
|
3815
|
+
mkdirSync3(path12.dirname(f), { recursive: true, mode: 448 });
|
|
3672
3816
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
3673
|
-
|
|
3817
|
+
writeFileSync3(tmp, JSON.stringify(c), { mode: 384 });
|
|
3674
3818
|
renameSync(tmp, f);
|
|
3675
3819
|
}
|
|
3676
3820
|
key(ref) {
|
|
@@ -3682,21 +3826,43 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
3682
3826
|
}
|
|
3683
3827
|
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
3684
3828
|
const dir = path12.join(root, "locks");
|
|
3685
|
-
|
|
3829
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
3686
3830
|
const f = path12.join(dir, `${name}.lock`);
|
|
3831
|
+
const nonce = randomBytes2(8).toString("hex");
|
|
3832
|
+
const mine = `${process.pid} ${nonce}`;
|
|
3833
|
+
let body = null;
|
|
3687
3834
|
try {
|
|
3688
|
-
|
|
3689
|
-
|
|
3835
|
+
body = readFileSync9(f, "utf8");
|
|
3836
|
+
const stale = Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(body.split(" ")[0]));
|
|
3837
|
+
if (!stale) return null;
|
|
3690
3838
|
} catch {
|
|
3839
|
+
body = null;
|
|
3840
|
+
}
|
|
3841
|
+
if (body !== null) {
|
|
3842
|
+
const aside = `${f}.${process.pid}.${nonce}.stale`;
|
|
3843
|
+
try {
|
|
3844
|
+
renameSync(f, aside);
|
|
3845
|
+
} catch {
|
|
3846
|
+
return null;
|
|
3847
|
+
}
|
|
3848
|
+
const moved = readFileSync9(aside, "utf8");
|
|
3849
|
+
unlinkSync2(aside);
|
|
3850
|
+
if (moved !== body) {
|
|
3851
|
+
try {
|
|
3852
|
+
writeFileSync3(f, moved, { flag: "wx" });
|
|
3853
|
+
} catch {
|
|
3854
|
+
}
|
|
3855
|
+
return null;
|
|
3856
|
+
}
|
|
3691
3857
|
}
|
|
3692
3858
|
try {
|
|
3693
|
-
|
|
3859
|
+
writeFileSync3(f, mine, { flag: "wx" });
|
|
3694
3860
|
} catch {
|
|
3695
3861
|
return null;
|
|
3696
3862
|
}
|
|
3697
3863
|
return () => {
|
|
3698
3864
|
try {
|
|
3699
|
-
|
|
3865
|
+
if (readFileSync9(f, "utf8") === mine) unlinkSync2(f);
|
|
3700
3866
|
} catch {
|
|
3701
3867
|
}
|
|
3702
3868
|
};
|
|
@@ -3715,7 +3881,7 @@ function processAlive(pid) {
|
|
|
3715
3881
|
import { createHash as createHash4 } from "node:crypto";
|
|
3716
3882
|
var RULESET_PII = "pii@1";
|
|
3717
3883
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
3718
|
-
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
3884
|
+
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;
|
|
3719
3885
|
function looksPhone(s) {
|
|
3720
3886
|
const digits = s.replace(/\D/g, "");
|
|
3721
3887
|
if (digits.length < 7 || digits.length > 15) return false;
|
|
@@ -3726,7 +3892,7 @@ function looksPhone(s) {
|
|
|
3726
3892
|
function reducePii(text, home) {
|
|
3727
3893
|
if (!text) return text;
|
|
3728
3894
|
let out = text.replace(EMAIL, "[email]");
|
|
3729
|
-
out = out.replace(PHONE, (m) => looksPhone(m) ? "[phone]" : m);
|
|
3895
|
+
out = out.replace(PHONE, (m, date) => !date && looksPhone(m) ? "[phone]" : m);
|
|
3730
3896
|
if (home) {
|
|
3731
3897
|
const h = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3732
3898
|
out = out.replace(new RegExp(`(?<![A-Za-z0-9_])${h}(?=/|$|\\s|["'\`])`, "g"), "~");
|
|
@@ -3940,7 +4106,7 @@ var DATA_ALLOW = {
|
|
|
3940
4106
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
3941
4107
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
3942
4108
|
"files.changed": { 1: ["paths"] },
|
|
3943
|
-
"attention.needed": { 0: ["attention_id", "kind"], 1: ["tool", "message"] },
|
|
4109
|
+
"attention.needed": { 0: ["attention_id", "kind"], 1: ["tool", "message", "agent_id"] },
|
|
3944
4110
|
"attention.cleared": { 0: ["attention_id", "how"] },
|
|
3945
4111
|
"subagent.started": { 1: ["agent_id", "agent_type"] },
|
|
3946
4112
|
"subagent.ended": { 1: ["agent_id", "agent_type"] },
|
|
@@ -4031,7 +4197,7 @@ function shortId(harness, id) {
|
|
|
4031
4197
|
}
|
|
4032
4198
|
|
|
4033
4199
|
// ../core/src/sinks/file.ts
|
|
4034
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
4200
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync4 } from "node:fs";
|
|
4035
4201
|
import path13 from "node:path";
|
|
4036
4202
|
var FileSink = class {
|
|
4037
4203
|
constructor(name, file, tier, pii) {
|
|
@@ -4046,7 +4212,7 @@ var FileSink = class {
|
|
|
4046
4212
|
pii;
|
|
4047
4213
|
async send(events) {
|
|
4048
4214
|
try {
|
|
4049
|
-
|
|
4215
|
+
mkdirSync4(path13.dirname(this.file), { recursive: true });
|
|
4050
4216
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
4051
4217
|
`);
|
|
4052
4218
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -4080,12 +4246,12 @@ var StdoutSink = class {
|
|
|
4080
4246
|
};
|
|
4081
4247
|
|
|
4082
4248
|
// src/control/aliases.ts
|
|
4083
|
-
import { chmodSync as chmodSync2, mkdirSync as
|
|
4249
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4084
4250
|
import path14 from "node:path";
|
|
4085
4251
|
|
|
4086
4252
|
// src/secrets.ts
|
|
4087
4253
|
import { spawnSync } from "node:child_process";
|
|
4088
|
-
import { createHash as createHash6, randomBytes as
|
|
4254
|
+
import { createHash as createHash6, randomBytes as randomBytes3 } from "node:crypto";
|
|
4089
4255
|
var SERVICE = "sessionpipe";
|
|
4090
4256
|
var defaultRunner = (cmd, args, input) => {
|
|
4091
4257
|
const r = spawnSync(cmd, args, {
|
|
@@ -4109,9 +4275,9 @@ function secretGet(store, account, run = defaultRunner) {
|
|
|
4109
4275
|
function sinkWithSecrets(s, run = defaultRunner) {
|
|
4110
4276
|
if (!s.token_in) return s;
|
|
4111
4277
|
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
4112
|
-
if (!token) return null;
|
|
4113
4278
|
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
4114
|
-
|
|
4279
|
+
if (!token && !secret) return null;
|
|
4280
|
+
return { ...s, ...token ? { token } : {}, ...secret ? { secret } : {} };
|
|
4115
4281
|
}
|
|
4116
4282
|
|
|
4117
4283
|
// src/control/store.ts
|
|
@@ -4186,6 +4352,76 @@ function ask(sock, req, timeoutMs) {
|
|
|
4186
4352
|
});
|
|
4187
4353
|
}
|
|
4188
4354
|
|
|
4355
|
+
// src/fold.ts
|
|
4356
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4357
|
+
import path16 from "node:path";
|
|
4358
|
+
var TOOLS_MODES = ["all", "main", "beat"];
|
|
4359
|
+
var TOOL_FOLD_MS = 6e4;
|
|
4360
|
+
var KEEP_MS = 24 * 60 * 6e4;
|
|
4361
|
+
function isToolsMode(v) {
|
|
4362
|
+
return typeof v === "string" && TOOLS_MODES.includes(v);
|
|
4363
|
+
}
|
|
4364
|
+
function toolsMode(s) {
|
|
4365
|
+
if (isToolsMode(s.tools)) return s.tools;
|
|
4366
|
+
if (isToolsMode(s.receiver_tools)) return s.receiver_tools;
|
|
4367
|
+
return "all";
|
|
4368
|
+
}
|
|
4369
|
+
var agentOf = (e) => typeof e.data.agent_id === "string" ? e.data.agent_id : "";
|
|
4370
|
+
var thins = (mode, agent) => mode === "beat" || mode === "main" && agent !== "";
|
|
4371
|
+
var askKey = (key, agent) => `${key}\0ask\0${agent}`;
|
|
4372
|
+
function sendTool(e, mode, clock, key) {
|
|
4373
|
+
if (mode === "all") return true;
|
|
4374
|
+
const agent = agentOf(e);
|
|
4375
|
+
const t = Date.parse(e.time);
|
|
4376
|
+
if (e.type === "attention.needed") {
|
|
4377
|
+
if (thins(mode, agent)) clock[askKey(key, agent)] = Number.isFinite(t) ? t : 0;
|
|
4378
|
+
return true;
|
|
4379
|
+
}
|
|
4380
|
+
if (e.type !== "tool.started" && e.type !== "tool.ended" || !thins(mode, agent)) return true;
|
|
4381
|
+
if (e.type === "tool.started") return false;
|
|
4382
|
+
if (!Number.isFinite(t)) return true;
|
|
4383
|
+
const ask2 = askKey(key, agent);
|
|
4384
|
+
const asked = clock[ask2];
|
|
4385
|
+
if (asked !== void 0 && t >= asked) {
|
|
4386
|
+
delete clock[ask2];
|
|
4387
|
+
clock[key] = t;
|
|
4388
|
+
return true;
|
|
4389
|
+
}
|
|
4390
|
+
const last = clock[key];
|
|
4391
|
+
if (last !== void 0 && t > last - TOOL_FOLD_MS && t < last + TOOL_FOLD_MS) return false;
|
|
4392
|
+
clock[key] = t;
|
|
4393
|
+
return true;
|
|
4394
|
+
}
|
|
4395
|
+
var ToolClock = class {
|
|
4396
|
+
constructor(root, sink) {
|
|
4397
|
+
this.root = root;
|
|
4398
|
+
this.sink = sink;
|
|
4399
|
+
}
|
|
4400
|
+
root;
|
|
4401
|
+
sink;
|
|
4402
|
+
file() {
|
|
4403
|
+
return path16.join(this.root, "folds", `${safeId(this.sink)}.json`);
|
|
4404
|
+
}
|
|
4405
|
+
read() {
|
|
4406
|
+
try {
|
|
4407
|
+
const j = JSON.parse(readFileSync11(this.file(), "utf8"));
|
|
4408
|
+
return j && typeof j === "object" && !Array.isArray(j) ? j : {};
|
|
4409
|
+
} catch {
|
|
4410
|
+
return {};
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
write(c) {
|
|
4414
|
+
const newest = Math.max(0, ...Object.values(c).filter(Number.isFinite));
|
|
4415
|
+
const kept = {};
|
|
4416
|
+
for (const [k, v] of Object.entries(c)) if (Number.isFinite(v) && v >= newest - KEEP_MS) kept[k] = v;
|
|
4417
|
+
const f = this.file();
|
|
4418
|
+
mkdirSync6(path16.dirname(f), { recursive: true, mode: 448 });
|
|
4419
|
+
const tmp = `${f}.${process.pid}.tmp`;
|
|
4420
|
+
writeFileSync5(tmp, JSON.stringify(kept), { mode: 384 });
|
|
4421
|
+
renameSync3(tmp, f);
|
|
4422
|
+
}
|
|
4423
|
+
};
|
|
4424
|
+
|
|
4189
4425
|
// src/run.ts
|
|
4190
4426
|
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
4191
4427
|
const sock = socketPath(state);
|
|
@@ -4210,24 +4446,24 @@ async function tellDaemon(state, job, session, cwd, transcript) {
|
|
|
4210
4446
|
}
|
|
4211
4447
|
function enqueueJob(state, job) {
|
|
4212
4448
|
const jobs = jobsDir(state);
|
|
4213
|
-
|
|
4214
|
-
const f =
|
|
4215
|
-
|
|
4449
|
+
mkdirSync7(jobs, { recursive: true, mode: 448 });
|
|
4450
|
+
const f = path17.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
4451
|
+
writeFileSync6(f, JSON.stringify(job), { mode: 384 });
|
|
4216
4452
|
return f;
|
|
4217
4453
|
}
|
|
4218
4454
|
function releaseJob(claimed) {
|
|
4219
4455
|
if (!claimed) return;
|
|
4220
4456
|
try {
|
|
4221
|
-
|
|
4457
|
+
unlinkSync3(claimed);
|
|
4222
4458
|
} catch {
|
|
4223
4459
|
}
|
|
4224
4460
|
}
|
|
4225
4461
|
function log(state, line) {
|
|
4226
4462
|
try {
|
|
4227
|
-
|
|
4228
|
-
const f =
|
|
4463
|
+
mkdirSync7(state, { recursive: true, mode: 448 });
|
|
4464
|
+
const f = path17.join(state, "log");
|
|
4229
4465
|
try {
|
|
4230
|
-
if (statSync7(f).size > 1024 * 1024)
|
|
4466
|
+
if (statSync7(f).size > 1024 * 1024) renameSync4(f, `${f}.1`);
|
|
4231
4467
|
} catch {
|
|
4232
4468
|
}
|
|
4233
4469
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -4236,11 +4472,11 @@ function log(state, line) {
|
|
|
4236
4472
|
}
|
|
4237
4473
|
}
|
|
4238
4474
|
function factsState(state) {
|
|
4239
|
-
const file = (h, s) =>
|
|
4475
|
+
const file = (h, s) => path17.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
4240
4476
|
return {
|
|
4241
4477
|
get(h, s) {
|
|
4242
4478
|
try {
|
|
4243
|
-
return JSON.parse(
|
|
4479
|
+
return JSON.parse(readFileSync12(file(h, s), "utf8"));
|
|
4244
4480
|
} catch {
|
|
4245
4481
|
return {};
|
|
4246
4482
|
}
|
|
@@ -4249,8 +4485,8 @@ function factsState(state) {
|
|
|
4249
4485
|
const cur = this.get(h, s);
|
|
4250
4486
|
const next = { ...cur, ...patch };
|
|
4251
4487
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
4252
|
-
|
|
4253
|
-
|
|
4488
|
+
mkdirSync7(path17.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
4489
|
+
writeFileSync6(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
4254
4490
|
}
|
|
4255
4491
|
};
|
|
4256
4492
|
}
|
|
@@ -4263,7 +4499,7 @@ function buildSinks(sinks, o = {}) {
|
|
|
4263
4499
|
o.locked?.(raw.name);
|
|
4264
4500
|
continue;
|
|
4265
4501
|
}
|
|
4266
|
-
const tier = Math.min(s.tier, s.max_tier ?? 3);
|
|
4502
|
+
const tier = Math.trunc(Math.min(Number(s.tier) || 0, s.max_tier ?? 3));
|
|
4267
4503
|
if (s.url === "stdout") out.push(new StdoutSink(s.name, tier, !!s.pii));
|
|
4268
4504
|
else if (s.url.startsWith("file:"))
|
|
4269
4505
|
out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os4.homedir()), tier, !!s.pii));
|
|
@@ -4325,7 +4561,7 @@ async function runJob(job, opts = {}) {
|
|
|
4325
4561
|
);
|
|
4326
4562
|
return { events: [], delivered: {} };
|
|
4327
4563
|
}
|
|
4328
|
-
const id = receiverIdOf(readAliases(aliasFile(
|
|
4564
|
+
const id = receiverIdOf(readAliases(aliasFile(path17.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
4329
4565
|
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
4330
4566
|
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
4331
4567
|
const outbox = new Outbox(state);
|
|
@@ -4362,7 +4598,7 @@ async function runJob(job, opts = {}) {
|
|
|
4362
4598
|
learned = rest;
|
|
4363
4599
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
4364
4600
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
4365
|
-
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd &&
|
|
4601
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path17.isAbsolute(r.session.cwd))
|
|
4366
4602
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
4367
4603
|
}
|
|
4368
4604
|
const known = mem.session ?? {};
|
|
@@ -4480,29 +4716,83 @@ async function runJob(job, opts = {}) {
|
|
|
4480
4716
|
} finally {
|
|
4481
4717
|
release();
|
|
4482
4718
|
}
|
|
4483
|
-
const delivered = await drain(state, cfg.sinks, outbox, ref) ?? {};
|
|
4484
4719
|
if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
|
|
4720
|
+
const delivered = opts.drain === false ? {} : await drain(state, cfg.sinks, outbox, ref, {
|
|
4721
|
+
...opts.clock ? { clock: opts.clock } : {},
|
|
4722
|
+
...opts.deadline !== void 0 ? { deadline: opts.deadline } : {},
|
|
4723
|
+
...opts.handoff ? { handoff: opts.handoff } : {}
|
|
4724
|
+
}) ?? {};
|
|
4485
4725
|
return { events, delivered };
|
|
4486
4726
|
}
|
|
4487
|
-
|
|
4488
|
-
|
|
4727
|
+
var REAL_CLOCK = { now: () => Date.now(), sleep: (ms) => new Promise((r) => setTimeout(r, ms)) };
|
|
4728
|
+
var PACE_MS = 1e3;
|
|
4729
|
+
var sentFile = (state) => path17.join(state, "locks", "flush.sent");
|
|
4730
|
+
function lastSent(state) {
|
|
4731
|
+
try {
|
|
4732
|
+
return Number(readFileSync12(sentFile(state), "utf8"));
|
|
4733
|
+
} catch {
|
|
4734
|
+
return 0;
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
function noteSent(state, at) {
|
|
4738
|
+
try {
|
|
4739
|
+
mkdirSync7(path17.dirname(sentFile(state)), { recursive: true, mode: 448 });
|
|
4740
|
+
writeFileSync6(sentFile(state), String(at), { mode: 384 });
|
|
4741
|
+
} catch {
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4744
|
+
function waitingForReceiver(state, sinkConfigs, outbox, now) {
|
|
4745
|
+
const receivers = sinkConfigs.filter(
|
|
4746
|
+
(s) => !s.paused && /^https?:\/\//.test(s.url) && !((readBackoff(state, s.name)?.until ?? 0) > now)
|
|
4747
|
+
);
|
|
4748
|
+
if (!receivers.length) return false;
|
|
4749
|
+
const refs = outbox.sessions();
|
|
4750
|
+
for (const s of receivers) {
|
|
4751
|
+
const cursors = new Cursors(state, s.name);
|
|
4752
|
+
const c = cursors.read();
|
|
4753
|
+
for (const ref of refs) {
|
|
4754
|
+
try {
|
|
4755
|
+
if (statSync7(outbox.file(ref)).size > (c[cursors.key(ref)] ?? 0)) return true;
|
|
4756
|
+
} catch {
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
return false;
|
|
4761
|
+
}
|
|
4762
|
+
var PASS_BUDGET_MS = 3e3;
|
|
4763
|
+
var moreFile = (state) => path17.join(state, "locks", "flush.more");
|
|
4764
|
+
function markMore(state) {
|
|
4765
|
+
try {
|
|
4766
|
+
mkdirSync7(path17.dirname(moreFile(state)), { recursive: true, mode: 448 });
|
|
4767
|
+
writeFileSync6(moreFile(state), "", { mode: 384 });
|
|
4768
|
+
} catch {
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
async function drain(state, sinkConfigs, outbox, first, o = {}) {
|
|
4772
|
+
const clock = o.clock ?? REAL_CLOCK;
|
|
4773
|
+
const more = moreFile(state);
|
|
4489
4774
|
let release = takeNamedLock(state, "flush");
|
|
4490
4775
|
if (!release) {
|
|
4491
|
-
|
|
4492
|
-
writeFileSync8(more, "", { mode: 384 });
|
|
4493
|
-
} catch {
|
|
4494
|
-
}
|
|
4776
|
+
markMore(state);
|
|
4495
4777
|
release = takeNamedLock(state, "flush");
|
|
4496
4778
|
if (!release) return null;
|
|
4497
4779
|
}
|
|
4498
4780
|
const delivered = {};
|
|
4499
4781
|
for (; ; ) {
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4782
|
+
if (o.deadline !== void 0 && clock.now() + PASS_BUDGET_MS > o.deadline) {
|
|
4783
|
+
markMore(state);
|
|
4784
|
+
release();
|
|
4785
|
+
o.handoff?.();
|
|
4786
|
+
return delivered;
|
|
4503
4787
|
}
|
|
4504
4788
|
try {
|
|
4505
|
-
|
|
4789
|
+
const wait = Math.min(PACE_MS, lastSent(state) + PACE_MS - clock.now());
|
|
4790
|
+
if (wait > 0 && waitingForReceiver(state, sinkConfigs, outbox, clock.now())) await clock.sleep(wait);
|
|
4791
|
+
try {
|
|
4792
|
+
unlinkSync3(more);
|
|
4793
|
+
} catch {
|
|
4794
|
+
}
|
|
4795
|
+
for (const [k, v] of Object.entries(await flush(state, sinkConfigs, outbox, first, { clock })))
|
|
4506
4796
|
delivered[k] = (delivered[k] ?? 0) + v;
|
|
4507
4797
|
} finally {
|
|
4508
4798
|
release();
|
|
@@ -4513,10 +4803,10 @@ async function drain(state, sinkConfigs, outbox, first) {
|
|
|
4513
4803
|
}
|
|
4514
4804
|
}
|
|
4515
4805
|
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
4516
|
-
var backoffFile = (state, sink) =>
|
|
4806
|
+
var backoffFile = (state, sink) => path17.join(state, "backoff", `${safeId(sink)}.json`);
|
|
4517
4807
|
function readBackoff(state, sink) {
|
|
4518
4808
|
try {
|
|
4519
|
-
const b = JSON.parse(
|
|
4809
|
+
const b = JSON.parse(readFileSync12(backoffFile(state, sink), "utf8"));
|
|
4520
4810
|
return typeof b.until === "number" ? b : null;
|
|
4521
4811
|
} catch {
|
|
4522
4812
|
return null;
|
|
@@ -4529,15 +4819,15 @@ function noteFailure(state, sink, res, prev) {
|
|
|
4529
4819
|
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
4530
4820
|
const f = backoffFile(state, sink);
|
|
4531
4821
|
try {
|
|
4532
|
-
|
|
4533
|
-
|
|
4822
|
+
mkdirSync7(path17.dirname(f), { recursive: true, mode: 448 });
|
|
4823
|
+
writeFileSync6(f, JSON.stringify(b), { mode: 384 });
|
|
4534
4824
|
} catch {
|
|
4535
4825
|
}
|
|
4536
4826
|
return b;
|
|
4537
4827
|
}
|
|
4538
4828
|
function clearBackoff(state, sink) {
|
|
4539
4829
|
try {
|
|
4540
|
-
|
|
4830
|
+
unlinkSync3(backoffFile(state, sink));
|
|
4541
4831
|
} catch {
|
|
4542
4832
|
}
|
|
4543
4833
|
}
|
|
@@ -4547,14 +4837,31 @@ var BATCH = 50;
|
|
|
4547
4837
|
var MAX_PER_RUN = 500;
|
|
4548
4838
|
async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
4549
4839
|
const delivered = {};
|
|
4840
|
+
const clock = o.clock ?? REAL_CLOCK;
|
|
4550
4841
|
const sinks = buildSinks(sinkConfigs, {
|
|
4551
4842
|
locked: (name) => log(state, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
4552
4843
|
});
|
|
4844
|
+
const modes = new Map(sinkConfigs.map((s) => [s.name, toolsMode(s)]));
|
|
4553
4845
|
const refs = outbox.sessions();
|
|
4554
4846
|
if (first) {
|
|
4555
4847
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
4556
4848
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
4557
4849
|
}
|
|
4850
|
+
const unreadable = /* @__PURE__ */ new Set();
|
|
4851
|
+
const readOrSkip = (ref, offset, room) => {
|
|
4852
|
+
try {
|
|
4853
|
+
return outbox.read(ref, offset, room);
|
|
4854
|
+
} catch (e) {
|
|
4855
|
+
const code = e.code;
|
|
4856
|
+
if (typeof code !== "string") throw e;
|
|
4857
|
+
const f = outbox.file(ref);
|
|
4858
|
+
if (!unreadable.has(f)) {
|
|
4859
|
+
unreadable.add(f);
|
|
4860
|
+
log(state, `drain: can't read ${f} (${code}); skipped, its cursors stay`);
|
|
4861
|
+
}
|
|
4862
|
+
return null;
|
|
4863
|
+
}
|
|
4864
|
+
};
|
|
4558
4865
|
const drainSink = async (sink) => {
|
|
4559
4866
|
let backoff = readBackoff(state, sink.name);
|
|
4560
4867
|
if (backoff && !o.force && backoff.until > Date.now()) {
|
|
@@ -4563,11 +4870,16 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4563
4870
|
}
|
|
4564
4871
|
const cursors = new Cursors(state, sink.name);
|
|
4565
4872
|
const c = cursors.read();
|
|
4873
|
+
const mode = modes.get(sink.name);
|
|
4874
|
+
const tools = new ToolClock(state, sink.name);
|
|
4875
|
+
let clockNow = mode === "all" ? {} : tools.read();
|
|
4876
|
+
let clockNext = { ...clockNow };
|
|
4566
4877
|
let sent = 0;
|
|
4567
4878
|
let stalled = false;
|
|
4568
4879
|
let batch = [];
|
|
4569
4880
|
let pending = {};
|
|
4570
4881
|
const deliver = async (events) => {
|
|
4882
|
+
if (sink instanceof HttpSink) noteSent(state, clock.now());
|
|
4571
4883
|
const res = await sink.send(events);
|
|
4572
4884
|
if (res.ok || res.action !== "split") return res;
|
|
4573
4885
|
if (events.length === 1) {
|
|
@@ -4575,10 +4887,20 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4575
4887
|
log(state, `sink ${sink.name}: 413 ${res.error ?? ""} \u2192 event ${e.id} (${e.type}) is too large alone, dropped`);
|
|
4576
4888
|
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected: [{ id: e.id, reason: "too_large" }] };
|
|
4577
4889
|
}
|
|
4890
|
+
const part = async (some) => {
|
|
4891
|
+
const r = await deliver(some);
|
|
4892
|
+
if (r.ok || r.action !== "drop") return r;
|
|
4893
|
+
log(
|
|
4894
|
+
state,
|
|
4895
|
+
`sink ${sink.name}: ${r.status ?? ""} ${r.error ?? ""} \u2192 drop, ${some.length} of ${events.length} events`
|
|
4896
|
+
);
|
|
4897
|
+
const rejected = some.map((e) => ({ id: e.id, reason: "dropped" }));
|
|
4898
|
+
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected };
|
|
4899
|
+
};
|
|
4578
4900
|
const half = Math.ceil(events.length / 2);
|
|
4579
|
-
const a = await
|
|
4901
|
+
const a = await part(events.slice(0, half));
|
|
4580
4902
|
if (!a.ok) return a;
|
|
4581
|
-
const b = await
|
|
4903
|
+
const b = await part(events.slice(half));
|
|
4582
4904
|
if (!b.ok) return b;
|
|
4583
4905
|
return {
|
|
4584
4906
|
ok: true,
|
|
@@ -4588,11 +4910,17 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4588
4910
|
rejected: [...a.rejected ?? [], ...b.rejected ?? []]
|
|
4589
4911
|
};
|
|
4590
4912
|
};
|
|
4913
|
+
const commitClock = () => {
|
|
4914
|
+
if (mode === "all" || JSON.stringify(clockNext) === JSON.stringify(clockNow)) return;
|
|
4915
|
+
tools.write(clockNext);
|
|
4916
|
+
clockNow = { ...clockNext };
|
|
4917
|
+
};
|
|
4591
4918
|
const send = async () => {
|
|
4592
4919
|
if (!batch.length) {
|
|
4593
4920
|
Object.assign(c, pending);
|
|
4594
4921
|
pending = {};
|
|
4595
4922
|
cursors.write(c);
|
|
4923
|
+
commitClock();
|
|
4596
4924
|
return true;
|
|
4597
4925
|
}
|
|
4598
4926
|
const res = await deliver(batch);
|
|
@@ -4608,6 +4936,7 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4608
4936
|
if (res.action !== "drop") {
|
|
4609
4937
|
batch = [];
|
|
4610
4938
|
pending = {};
|
|
4939
|
+
clockNext = { ...clockNow };
|
|
4611
4940
|
return false;
|
|
4612
4941
|
}
|
|
4613
4942
|
} else if (backoff) {
|
|
@@ -4617,6 +4946,7 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4617
4946
|
sent += batch.length;
|
|
4618
4947
|
Object.assign(c, pending);
|
|
4619
4948
|
cursors.write(c);
|
|
4949
|
+
commitClock();
|
|
4620
4950
|
batch = [];
|
|
4621
4951
|
pending = {};
|
|
4622
4952
|
return true;
|
|
@@ -4626,11 +4956,15 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
4626
4956
|
let offset = pending[cursors.key(ref)] ?? c[cursors.key(ref)] ?? 0;
|
|
4627
4957
|
for (; ; ) {
|
|
4628
4958
|
const room = BATCH - batch.length;
|
|
4629
|
-
const
|
|
4959
|
+
const read = readOrSkip(ref, offset, room);
|
|
4960
|
+
if (!read) break;
|
|
4961
|
+
const { events, offset: next } = read;
|
|
4630
4962
|
if (!events.length) break;
|
|
4631
4963
|
for (const e of events) {
|
|
4632
4964
|
const f = filterEvent(e, sink.tier, sink.pii, { home: os4.homedir() });
|
|
4633
|
-
if (f)
|
|
4965
|
+
if (!f) continue;
|
|
4966
|
+
if (sendTool(f, mode, clockNext, cursors.key(ref))) batch.push(f);
|
|
4967
|
+
else if (o.thinned) o.thinned[sink.name] = (o.thinned[sink.name] ?? 0) + 1;
|
|
4634
4968
|
}
|
|
4635
4969
|
offset = next;
|
|
4636
4970
|
pending[cursors.key(ref)] = offset;
|
|
@@ -4662,7 +4996,7 @@ function applySinkVerdict(name, res) {
|
|
|
4662
4996
|
} catch {
|
|
4663
4997
|
}
|
|
4664
4998
|
}
|
|
4665
|
-
var jobsDir = (state) =>
|
|
4999
|
+
var jobsDir = (state) => path17.join(state, "jobs");
|
|
4666
5000
|
export {
|
|
4667
5001
|
HttpSink,
|
|
4668
5002
|
buildSinks,
|