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/cli.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 = (target2, all) => {
|
|
5
5
|
for (var name in all)
|
|
@@ -12,14 +12,14 @@ import {
|
|
|
12
12
|
existsSync as existsSync21,
|
|
13
13
|
mkdtempSync,
|
|
14
14
|
readdirSync as readdirSync8,
|
|
15
|
-
readFileSync as
|
|
15
|
+
readFileSync as readFileSync24,
|
|
16
16
|
realpathSync as realpathSync9,
|
|
17
17
|
rmSync as rmSync3,
|
|
18
18
|
statSync as statSync14,
|
|
19
|
-
unlinkSync as
|
|
19
|
+
unlinkSync as unlinkSync8
|
|
20
20
|
} from "node:fs";
|
|
21
21
|
import os15 from "node:os";
|
|
22
|
-
import
|
|
22
|
+
import path39 from "node:path";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
24
24
|
|
|
25
25
|
// ../core/src/adapters/claude-code-pool.ts
|
|
@@ -76,7 +76,7 @@ var safeId = (s) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 160);
|
|
|
76
76
|
// ../core/src/adapters/claude-code.ts
|
|
77
77
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
78
78
|
import { createHash } from "node:crypto";
|
|
79
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2
|
|
79
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
80
80
|
import { userInfo } from "node:os";
|
|
81
81
|
import path4 from "node:path";
|
|
82
82
|
|
|
@@ -434,6 +434,41 @@ function readJson(file) {
|
|
|
434
434
|
const f = readJsonFile(file);
|
|
435
435
|
return f.ok ? f.value : {};
|
|
436
436
|
}
|
|
437
|
+
function refused(file, e, what, existed) {
|
|
438
|
+
const { code, syscall } = e;
|
|
439
|
+
const why2 = `can't ${what} it (${code ?? String(e)})`;
|
|
440
|
+
if (what === "remove" || syscall === "open" || syscall === "mkdir")
|
|
441
|
+
return { file, changed: false, skipped: true, note: `${why2}; not touched` };
|
|
442
|
+
return {
|
|
443
|
+
file,
|
|
444
|
+
changed: true,
|
|
445
|
+
skipped: true,
|
|
446
|
+
...existed ? {} : { created: true },
|
|
447
|
+
note: `${why2} part way; check the file before the harness reads it`
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function writeBack(file, text, existed = true) {
|
|
451
|
+
try {
|
|
452
|
+
mkdirSync(path3.dirname(file), { recursive: true });
|
|
453
|
+
writeFileSync(file, text);
|
|
454
|
+
return null;
|
|
455
|
+
} catch (e) {
|
|
456
|
+
return refused(file, e, "write", existed);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
function removeEmptied(file, empty) {
|
|
460
|
+
try {
|
|
461
|
+
unlinkSync(file);
|
|
462
|
+
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
463
|
+
} catch (e) {
|
|
464
|
+
const bad2 = refused(file, e, "remove", true);
|
|
465
|
+
return writeBack(file, empty) ?? {
|
|
466
|
+
file,
|
|
467
|
+
changed: true,
|
|
468
|
+
note: `emptied; ${bad2.note.replace(/; not touched$/, "")}, so the empty file stays`
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
}
|
|
437
472
|
function entryFor(event, harness, cmd, o) {
|
|
438
473
|
const matcher = o.matcherFor(event);
|
|
439
474
|
return {
|
|
@@ -475,8 +510,8 @@ function installClaudeShaped(file, harness, events, cmd, o) {
|
|
|
475
510
|
}
|
|
476
511
|
if (changed) {
|
|
477
512
|
settings2.hooks = hooks;
|
|
478
|
-
|
|
479
|
-
|
|
513
|
+
const bad2 = writeBack(file, formatJson(settings2, f), f.existed);
|
|
514
|
+
if (bad2) return bad2;
|
|
480
515
|
}
|
|
481
516
|
return { file, changed, created: !f.existed && changed };
|
|
482
517
|
}
|
|
@@ -499,12 +534,8 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
499
534
|
}
|
|
500
535
|
if (!changed) return { file, changed: false };
|
|
501
536
|
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
502
|
-
if (opts.created && !Object.keys(settings2).length)
|
|
503
|
-
|
|
504
|
-
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
505
|
-
}
|
|
506
|
-
writeFileSync(file, formatJson(settings2, f));
|
|
507
|
-
return { file, changed: true };
|
|
537
|
+
if (opts.created && !Object.keys(settings2).length) return removeEmptied(file, formatJson(settings2, f));
|
|
538
|
+
return writeBack(file, formatJson(settings2, f)) ?? { file, changed: true };
|
|
508
539
|
}
|
|
509
540
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
510
541
|
const f = readJsonFile(file);
|
|
@@ -583,7 +614,7 @@ var claudeCode = {
|
|
|
583
614
|
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
584
615
|
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
|
|
585
616
|
const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
|
|
586
|
-
const line2 = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
617
|
+
const line2 = opts.statusLine === void 0 || hooks.skipped ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
587
618
|
return line2 ? [hooks, line2] : [hooks];
|
|
588
619
|
}),
|
|
589
620
|
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
|
|
@@ -591,7 +622,9 @@ var claudeCode = {
|
|
|
591
622
|
const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
|
|
592
623
|
created: opts.created?.includes(settingsIn(d))
|
|
593
624
|
});
|
|
594
|
-
|
|
625
|
+
if (line2?.changed && !line2.skipped && hooks.skipped)
|
|
626
|
+
return { ...hooks, note: `status line removed, hooks not: ${hooks.note?.replace(/; not touched$/, "")}` };
|
|
627
|
+
return line2 && !hooks.changed && !hooks.skipped ? line2 : hooks;
|
|
595
628
|
}),
|
|
596
629
|
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
597
630
|
file: settingsIn(d),
|
|
@@ -686,7 +719,8 @@ var claudeCode = {
|
|
|
686
719
|
attention_id: call_id ?? permissionId(),
|
|
687
720
|
kind: "permission",
|
|
688
721
|
tool,
|
|
689
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
722
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0,
|
|
723
|
+
agent_id
|
|
690
724
|
})
|
|
691
725
|
);
|
|
692
726
|
break;
|
|
@@ -695,7 +729,8 @@ var claudeCode = {
|
|
|
695
729
|
ev("attention.needed", {
|
|
696
730
|
attention_id: `notif-${Date.now()}`,
|
|
697
731
|
kind: notifKind(s.notification_type),
|
|
698
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
732
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0,
|
|
733
|
+
agent_id
|
|
699
734
|
})
|
|
700
735
|
);
|
|
701
736
|
break;
|
|
@@ -820,8 +855,11 @@ function statusLine(file, want, cmd) {
|
|
|
820
855
|
if (want ? ours && cur?.command === command2 && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
821
856
|
if (want) f.value.statusLine = { type: "command", command: command2, refreshInterval: LINE_REFRESH_S };
|
|
822
857
|
else delete f.value.statusLine;
|
|
823
|
-
|
|
824
|
-
|
|
858
|
+
return writeBack(file, formatJson(f.value, f), f.existed) ?? {
|
|
859
|
+
file,
|
|
860
|
+
changed: true,
|
|
861
|
+
note: want ? "status line written (account limits)" : "status line removed"
|
|
862
|
+
};
|
|
825
863
|
}
|
|
826
864
|
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
827
865
|
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
@@ -2183,18 +2221,18 @@ function startAcp(o) {
|
|
|
2183
2221
|
);
|
|
2184
2222
|
const stop = str(res?.stopReason);
|
|
2185
2223
|
const words = t.text.trim() || `(${label} finished without a written reply.)`;
|
|
2186
|
-
const
|
|
2187
|
-
if (stop === "end_turn" || stop === "max_tokens") return { reply: words +
|
|
2224
|
+
const refused2 = deniedNote(t.denied, t.count);
|
|
2225
|
+
if (stop === "end_turn" || stop === "max_tokens") return { reply: words + refused2 };
|
|
2188
2226
|
if (stop === "refusal") return { reply: `${words}
|
|
2189
2227
|
|
|
2190
|
-
(${label} refused the request.)${
|
|
2228
|
+
(${label} refused the request.)${refused2}` };
|
|
2191
2229
|
if (stop === "cancelled")
|
|
2192
2230
|
return timedOut ? {
|
|
2193
2231
|
reply: "",
|
|
2194
2232
|
error: `${label} was still working after ${Math.round(limit / 6e4)} minutes, so it was stopped`
|
|
2195
2233
|
} : { reply: `${words}
|
|
2196
2234
|
|
|
2197
|
-
(The turn was cancelled.)${
|
|
2235
|
+
(The turn was cancelled.)${refused2}` };
|
|
2198
2236
|
return { reply: "", error: `${label} ended the turn with ${stop || "no stop reason"}` };
|
|
2199
2237
|
} catch (e) {
|
|
2200
2238
|
return { reply: "", error: e.message };
|
|
@@ -2255,16 +2293,7 @@ function startAcp(o) {
|
|
|
2255
2293
|
}
|
|
2256
2294
|
|
|
2257
2295
|
// ../core/src/adapters/antigravity.ts
|
|
2258
|
-
import {
|
|
2259
|
-
existsSync as existsSync3,
|
|
2260
|
-
mkdirSync as mkdirSync2,
|
|
2261
|
-
readdirSync as readdirSync3,
|
|
2262
|
-
readFileSync as readFileSync5,
|
|
2263
|
-
realpathSync,
|
|
2264
|
-
statSync as statSync4,
|
|
2265
|
-
unlinkSync as unlinkSync2,
|
|
2266
|
-
writeFileSync as writeFileSync3
|
|
2267
|
-
} from "node:fs";
|
|
2296
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync5, realpathSync, statSync as statSync4 } from "node:fs";
|
|
2268
2297
|
import path8 from "node:path";
|
|
2269
2298
|
var NAME2 = "antigravity";
|
|
2270
2299
|
var HOOK_NAME = "sessionpipe";
|
|
@@ -2296,10 +2325,8 @@ var antigravity = {
|
|
|
2296
2325
|
const e = entry(cmd);
|
|
2297
2326
|
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
2298
2327
|
hooks[HOOK_NAME] = e;
|
|
2299
|
-
mkdirSync2(path8.dirname(file), { recursive: true });
|
|
2300
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
2301
2328
|
return [
|
|
2302
|
-
{
|
|
2329
|
+
writeBack(file, formatJson(hooks, f), f.existed) ?? {
|
|
2303
2330
|
file,
|
|
2304
2331
|
changed: true,
|
|
2305
2332
|
created: !f.existed,
|
|
@@ -2315,12 +2342,8 @@ var antigravity = {
|
|
|
2315
2342
|
const hooks = f.value;
|
|
2316
2343
|
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
2317
2344
|
delete hooks[HOOK_NAME];
|
|
2318
|
-
if (opts.created?.includes(file) && !Object.keys(hooks).length)
|
|
2319
|
-
|
|
2320
|
-
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
2321
|
-
}
|
|
2322
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
2323
|
-
return [{ file, changed: true }];
|
|
2345
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) return [removeEmptied(file, formatJson(hooks, f))];
|
|
2346
|
+
return [writeBack(file, formatJson(hooks, f)) ?? { file, changed: true }];
|
|
2324
2347
|
},
|
|
2325
2348
|
installed(cmd, env = process.env) {
|
|
2326
2349
|
const file = hooksFile(env);
|
|
@@ -3711,7 +3734,7 @@ function parseResult(stdout) {
|
|
|
3711
3734
|
var isUuid = (s) => typeof s === "string" && UUID_RE3.test(s);
|
|
3712
3735
|
|
|
3713
3736
|
// ../core/src/adapters/codex.ts
|
|
3714
|
-
import { existsSync as existsSync4,
|
|
3737
|
+
import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync7 } from "node:fs";
|
|
3715
3738
|
import path11 from "node:path";
|
|
3716
3739
|
|
|
3717
3740
|
// ../core/src/adapters/codex-config.ts
|
|
@@ -3864,29 +3887,30 @@ var configFile2 = (env) => path11.join(codexHome(env), "config.toml");
|
|
|
3864
3887
|
var sessionsDir = (env) => path11.join(codexHome(env), "sessions");
|
|
3865
3888
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
3866
3889
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
3890
|
+
var UNTIL_APPROVED = "Codex will report nothing until you approve the hooks: start `codex` and accept the hook review";
|
|
3867
3891
|
function installNotify(cmd, env) {
|
|
3868
3892
|
const file = configFile2(env);
|
|
3869
3893
|
let text = "";
|
|
3894
|
+
let existed = true;
|
|
3870
3895
|
try {
|
|
3871
3896
|
text = readFileSync7(file, "utf8");
|
|
3872
|
-
} catch {
|
|
3897
|
+
} catch (e) {
|
|
3898
|
+
existed = e.code !== "ENOENT";
|
|
3873
3899
|
}
|
|
3874
3900
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
3875
3901
|
const line2 = `notify = ${JSON.stringify(argv)}`;
|
|
3876
3902
|
const n = readNotify(text);
|
|
3877
|
-
const write = (next) => {
|
|
3878
|
-
|
|
3879
|
-
|
|
3903
|
+
const write = (next, note) => {
|
|
3904
|
+
const bad2 = writeBack(file, next, existed);
|
|
3905
|
+
return bad2 ? { ...bad2, note: `${bad2.note}. ${UNTIL_APPROVED}` } : { file, changed: true, note };
|
|
3880
3906
|
};
|
|
3881
3907
|
if (n && !n.other) {
|
|
3882
3908
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
3883
3909
|
return { file, changed: false, note: "notify current" };
|
|
3884
|
-
write(
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
3889
|
-
};
|
|
3910
|
+
return write(
|
|
3911
|
+
withNotify(text, line2),
|
|
3912
|
+
n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
3913
|
+
);
|
|
3890
3914
|
}
|
|
3891
3915
|
if (n?.other) {
|
|
3892
3916
|
const prev = previousNotify(n.argv);
|
|
@@ -3895,20 +3919,20 @@ function installNotify(cmd, env) {
|
|
|
3895
3919
|
if (JSON.stringify(prev) === JSON.stringify(argv))
|
|
3896
3920
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
3897
3921
|
}
|
|
3898
|
-
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
3899
|
-
write(
|
|
3900
|
-
|
|
3901
|
-
|
|
3922
|
+
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
3923
|
+
return write(
|
|
3924
|
+
replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`),
|
|
3925
|
+
"chained after Codex Computer Use (--previous-notify)"
|
|
3926
|
+
);
|
|
3902
3927
|
}
|
|
3903
3928
|
return {
|
|
3904
3929
|
file,
|
|
3905
3930
|
changed: false,
|
|
3906
3931
|
skipped: true,
|
|
3907
|
-
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone.
|
|
3932
|
+
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. ${UNTIL_APPROVED}`
|
|
3908
3933
|
};
|
|
3909
3934
|
}
|
|
3910
|
-
write(withNotify(text, line2));
|
|
3911
|
-
return { file, changed: true, note: "notify set at the top level" };
|
|
3935
|
+
return write(withNotify(text, line2), "notify set at the top level");
|
|
3912
3936
|
}
|
|
3913
3937
|
function uninstallNotify(env) {
|
|
3914
3938
|
const file = configFile2(env);
|
|
@@ -3920,15 +3944,13 @@ function uninstallNotify(env) {
|
|
|
3920
3944
|
}
|
|
3921
3945
|
const n = readNotify(text);
|
|
3922
3946
|
if (!n) return { file, changed: false };
|
|
3923
|
-
if (!n.other) {
|
|
3924
|
-
writeFileSync4(file, withoutNotify(text));
|
|
3925
|
-
return { file, changed: true };
|
|
3926
|
-
}
|
|
3947
|
+
if (!n.other) return writeBack(file, withoutNotify(text)) ?? { file, changed: true };
|
|
3927
3948
|
const prev = previousNotify(n.argv);
|
|
3928
|
-
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3949
|
+
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
3950
|
+
return writeBack(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`)) ?? {
|
|
3951
|
+
file,
|
|
3952
|
+
changed: true
|
|
3953
|
+
};
|
|
3932
3954
|
return { file, changed: false };
|
|
3933
3955
|
}
|
|
3934
3956
|
function findRollout(threadId, env = process.env) {
|
|
@@ -3960,8 +3982,12 @@ function rolloutMeta(file) {
|
|
|
3960
3982
|
return scanLines(file, (line2) => parseLines([line2])[0] ?? {});
|
|
3961
3983
|
}
|
|
3962
3984
|
function ofAnotherThread(file, id) {
|
|
3985
|
+
const other = threadOf(file);
|
|
3986
|
+
return !!other && other !== id.toLowerCase();
|
|
3987
|
+
}
|
|
3988
|
+
function threadOf(file) {
|
|
3963
3989
|
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);
|
|
3964
|
-
return
|
|
3990
|
+
return m?.[1] ? m[1].toLowerCase() : null;
|
|
3965
3991
|
}
|
|
3966
3992
|
var codex = {
|
|
3967
3993
|
name: NAME3,
|
|
@@ -4040,6 +4066,8 @@ var codex = {
|
|
|
4040
4066
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
4041
4067
|
const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
|
|
4042
4068
|
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
4069
|
+
const thread = typeof s.transcript_path === "string" ? threadOf(s.transcript_path) : null;
|
|
4070
|
+
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;
|
|
4043
4071
|
const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
|
|
4044
4072
|
const events = [];
|
|
4045
4073
|
switch (event) {
|
|
@@ -4059,17 +4087,30 @@ var codex = {
|
|
|
4059
4087
|
);
|
|
4060
4088
|
break;
|
|
4061
4089
|
case "PreToolUse":
|
|
4062
|
-
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
4090
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
4063
4091
|
break;
|
|
4064
4092
|
case "PostToolUse":
|
|
4065
4093
|
if (tool)
|
|
4066
4094
|
events.push(
|
|
4067
|
-
ev("tool.ended", {
|
|
4095
|
+
ev("tool.ended", {
|
|
4096
|
+
tool,
|
|
4097
|
+
call_id,
|
|
4098
|
+
turn_id,
|
|
4099
|
+
agent_id,
|
|
4100
|
+
ok: true,
|
|
4101
|
+
input: s.tool_input,
|
|
4102
|
+
output: s.tool_response
|
|
4103
|
+
})
|
|
4068
4104
|
);
|
|
4069
4105
|
break;
|
|
4070
4106
|
case "PermissionRequest":
|
|
4071
4107
|
events.push(
|
|
4072
|
-
ev("attention.needed", {
|
|
4108
|
+
ev("attention.needed", {
|
|
4109
|
+
attention_id: call_id ?? `perm-${Date.now()}`,
|
|
4110
|
+
kind: "permission",
|
|
4111
|
+
tool,
|
|
4112
|
+
agent_id
|
|
4113
|
+
})
|
|
4073
4114
|
);
|
|
4074
4115
|
break;
|
|
4075
4116
|
case "Stop":
|
|
@@ -4727,7 +4768,7 @@ var CHAIN_LIMIT = 4e3;
|
|
|
4727
4768
|
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
4728
4769
|
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
4729
4770
|
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
4730
|
-
UNION
|
|
4771
|
+
UNION
|
|
4731
4772
|
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
4732
4773
|
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
4733
4774
|
)
|
|
@@ -5005,7 +5046,7 @@ function drivableHarnesses(env = process.env) {
|
|
|
5005
5046
|
}
|
|
5006
5047
|
|
|
5007
5048
|
// ../core/src/config.ts
|
|
5008
|
-
import { chmodSync, existsSync as existsSync8, mkdirSync as
|
|
5049
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync2 } from "node:fs";
|
|
5009
5050
|
import path15 from "node:path";
|
|
5010
5051
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
5011
5052
|
function readConfig(file = configFile()) {
|
|
@@ -5021,9 +5062,36 @@ function readConfig(file = configFile()) {
|
|
|
5021
5062
|
return { ...DEFAULT_CONFIG, sinks: [], harnesses: {} };
|
|
5022
5063
|
}
|
|
5023
5064
|
}
|
|
5065
|
+
function configProblem(file) {
|
|
5066
|
+
let text;
|
|
5067
|
+
try {
|
|
5068
|
+
text = readFileSync10(file, "utf8");
|
|
5069
|
+
} catch (e) {
|
|
5070
|
+
const code = e.code;
|
|
5071
|
+
return code === "ENOENT" ? void 0 : `could not be read (${code})`;
|
|
5072
|
+
}
|
|
5073
|
+
if (!text.trim()) return void 0;
|
|
5074
|
+
if (text.charCodeAt(0) === 65279) return "starts with a byte-order mark (save it as UTF-8 without one)";
|
|
5075
|
+
try {
|
|
5076
|
+
const j = JSON.parse(text);
|
|
5077
|
+
return j && typeof j === "object" && !Array.isArray(j) ? void 0 : "is not a JSON object";
|
|
5078
|
+
} catch (e) {
|
|
5079
|
+
const m = e.message;
|
|
5080
|
+
const at = (/line \d+ column \d+/.exec(m) ?? /position \d+/.exec(m))?.[0];
|
|
5081
|
+
return `is not valid JSON${at ? ` at ${at}` : ""}`;
|
|
5082
|
+
}
|
|
5083
|
+
}
|
|
5084
|
+
function assertConfigWritable(file = configFile()) {
|
|
5085
|
+
const problem = configProblem(file);
|
|
5086
|
+
if (problem)
|
|
5087
|
+
throw new Error(
|
|
5088
|
+
`${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.`
|
|
5089
|
+
);
|
|
5090
|
+
}
|
|
5024
5091
|
function writeConfig(c, file = configFile()) {
|
|
5025
|
-
|
|
5026
|
-
|
|
5092
|
+
assertConfigWritable(file);
|
|
5093
|
+
mkdirSync2(path15.dirname(file), { recursive: true, mode: 448 });
|
|
5094
|
+
writeFileSync2(file, `${JSON.stringify(c, null, 2)}
|
|
5027
5095
|
`, { mode: 384 });
|
|
5028
5096
|
if (process.platform !== "win32" && existsSync8(file)) {
|
|
5029
5097
|
try {
|
|
@@ -5087,19 +5155,20 @@ function makeEvent(a, ctx) {
|
|
|
5087
5155
|
}
|
|
5088
5156
|
|
|
5089
5157
|
// ../core/src/outbox.ts
|
|
5158
|
+
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
5090
5159
|
import {
|
|
5091
5160
|
appendFileSync,
|
|
5092
5161
|
closeSync as closeSync3,
|
|
5093
5162
|
existsSync as existsSync9,
|
|
5094
|
-
mkdirSync as
|
|
5163
|
+
mkdirSync as mkdirSync3,
|
|
5095
5164
|
openSync as openSync3,
|
|
5096
5165
|
readdirSync as readdirSync6,
|
|
5097
5166
|
readFileSync as readFileSync11,
|
|
5098
5167
|
readSync as readSync3,
|
|
5099
5168
|
renameSync,
|
|
5100
5169
|
statSync as statSync6,
|
|
5101
|
-
unlinkSync as
|
|
5102
|
-
writeFileSync as
|
|
5170
|
+
unlinkSync as unlinkSync2,
|
|
5171
|
+
writeFileSync as writeFileSync3
|
|
5103
5172
|
} from "node:fs";
|
|
5104
5173
|
import path16 from "node:path";
|
|
5105
5174
|
var READ_CHUNK = 1024 * 1024;
|
|
@@ -5129,8 +5198,8 @@ var Outbox = class {
|
|
|
5129
5198
|
} catch {
|
|
5130
5199
|
}
|
|
5131
5200
|
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
5132
|
-
|
|
5133
|
-
|
|
5201
|
+
mkdirSync3(path16.dirname(f), { recursive: true, mode: 448 });
|
|
5202
|
+
writeFileSync3(f, String(n + 1), { mode: 384 });
|
|
5134
5203
|
return n;
|
|
5135
5204
|
}
|
|
5136
5205
|
lastSeqInFile(ref) {
|
|
@@ -5148,7 +5217,7 @@ var Outbox = class {
|
|
|
5148
5217
|
}
|
|
5149
5218
|
append(ref, events) {
|
|
5150
5219
|
if (!events.length) return;
|
|
5151
|
-
|
|
5220
|
+
mkdirSync3(this.dir(ref.harness), { recursive: true, mode: 448 });
|
|
5152
5221
|
appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
5153
5222
|
`, { mode: 384 });
|
|
5154
5223
|
}
|
|
@@ -5218,34 +5287,106 @@ var Outbox = class {
|
|
|
5218
5287
|
closeSync3(fd);
|
|
5219
5288
|
}
|
|
5220
5289
|
}
|
|
5221
|
-
/** Files older than `days` are pruned
|
|
5290
|
+
/** Files older than `days` are pruned, and each sink's cursor into them. The seq
|
|
5291
|
+
* counter stays: a session resumed after the window counts on from where it was,
|
|
5292
|
+
* so a receiver's (harness, session, seq) dedup doesn't take its new events for
|
|
5293
|
+
* ones it already has, and the sinks read the new file from its start.
|
|
5294
|
+
* It all happens under the drain's `flush` lock, since a drain holds the whole
|
|
5295
|
+
* cursor map and writes it back: a file and its cursor keys go together, so a
|
|
5296
|
+
* session resumed in between can't keep a key past its new file's end. While a
|
|
5297
|
+
* drain runs, nothing is pruned (0); the next prune does it. It never throws: it
|
|
5298
|
+
* is a cleanup, and a hook's drain comes after it. */
|
|
5222
5299
|
prune(days, now = Date.now()) {
|
|
5300
|
+
let release = null;
|
|
5301
|
+
try {
|
|
5302
|
+
release = takeNamedLock(this.root, "flush");
|
|
5303
|
+
} catch {
|
|
5304
|
+
}
|
|
5305
|
+
if (!release) return 0;
|
|
5223
5306
|
let n = 0;
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
const
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
5307
|
+
try {
|
|
5308
|
+
const cutoff = now - days * 864e5;
|
|
5309
|
+
const aged = this.sessions().filter((ref) => {
|
|
5310
|
+
try {
|
|
5311
|
+
return statSync6(this.file(ref)).mtimeMs < cutoff;
|
|
5312
|
+
} catch {
|
|
5313
|
+
return false;
|
|
5314
|
+
}
|
|
5315
|
+
});
|
|
5316
|
+
this.dropStaleCursors(new Set(aged.map((ref) => `${ref.harness}/${ref.session}`)));
|
|
5317
|
+
for (const ref of aged) {
|
|
5318
|
+
const f = this.file(ref);
|
|
5319
|
+
try {
|
|
5320
|
+
if (statSync6(f).mtimeMs < cutoff) {
|
|
5321
|
+
unlinkSync2(f);
|
|
5322
|
+
n++;
|
|
5233
5323
|
}
|
|
5234
|
-
|
|
5324
|
+
} catch {
|
|
5235
5325
|
}
|
|
5236
|
-
} catch {
|
|
5237
5326
|
}
|
|
5327
|
+
} catch {
|
|
5328
|
+
} finally {
|
|
5329
|
+
release();
|
|
5238
5330
|
}
|
|
5239
5331
|
return n;
|
|
5240
5332
|
}
|
|
5333
|
+
/** A sink's cursor into a session file about to be pruned (`gone`), or one already
|
|
5334
|
+
* missing (an earlier prune, an older release, by hand) is removed. The caller
|
|
5335
|
+
* holds the `flush` lock. */
|
|
5336
|
+
dropStaleCursors(gone2) {
|
|
5337
|
+
let names = [];
|
|
5338
|
+
try {
|
|
5339
|
+
names = readdirSync6(path16.join(this.root, "cursors")).filter((n) => n.endsWith(".json"));
|
|
5340
|
+
} catch {
|
|
5341
|
+
}
|
|
5342
|
+
const stale = (c) => Object.keys(c).filter((k) => {
|
|
5343
|
+
if (gone2.has(k)) return true;
|
|
5344
|
+
const i = k.indexOf("/");
|
|
5345
|
+
return !existsSync9(this.file({ harness: k.slice(0, i), session: k.slice(i + 1) }));
|
|
5346
|
+
});
|
|
5347
|
+
for (const n of names) {
|
|
5348
|
+
const s = new Cursors(this.root, n.slice(0, -5));
|
|
5349
|
+
const c = s.read();
|
|
5350
|
+
const keys = stale(c);
|
|
5351
|
+
if (!keys.length) continue;
|
|
5352
|
+
for (const k of keys) delete c[k];
|
|
5353
|
+
s.write(c);
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5241
5356
|
delete(ref) {
|
|
5357
|
+
this.dropCursors(ref);
|
|
5242
5358
|
for (const f of [this.file(ref), this.seqFile(ref)]) {
|
|
5243
5359
|
try {
|
|
5244
|
-
|
|
5360
|
+
unlinkSync2(f);
|
|
5245
5361
|
} catch {
|
|
5246
5362
|
}
|
|
5247
5363
|
}
|
|
5248
5364
|
}
|
|
5365
|
+
/** The key every sink's cursor holds for a session is removed with the session's
|
|
5366
|
+
* file: the key alone pointed into the old file, and a session that went on (still
|
|
5367
|
+
* running, or resumed later) had its new file's first events unsent until it grew
|
|
5368
|
+
* past that offset. Every file under <state>/cursors is taken (a sink since taken
|
|
5369
|
+
* out of the config still has one; one that doesn't parse reads as empty and is
|
|
5370
|
+
* left), by the key a drain writes (the file's own name, `safeId`) and by the ref
|
|
5371
|
+
* as given. The caller holds the drain's `flush` lock: a drain holds the whole
|
|
5372
|
+
* cursor map and writes it back, so a key dropped under it would come back. */
|
|
5373
|
+
dropCursors(ref) {
|
|
5374
|
+
const keys = /* @__PURE__ */ new Set([`${ref.harness}/${ref.session}`, `${safeId(ref.harness)}/${safeId(ref.session)}`]);
|
|
5375
|
+
let names = [];
|
|
5376
|
+
try {
|
|
5377
|
+
names = readdirSync6(path16.join(this.root, "cursors")).filter((n) => n.endsWith(".json"));
|
|
5378
|
+
} catch {
|
|
5379
|
+
return;
|
|
5380
|
+
}
|
|
5381
|
+
for (const n of names) {
|
|
5382
|
+
const s = new Cursors(this.root, n.slice(0, -5));
|
|
5383
|
+
const c = s.read();
|
|
5384
|
+
const had = Object.keys(c).filter((k) => keys.has(k));
|
|
5385
|
+
if (!had.length) continue;
|
|
5386
|
+
for (const k of had) delete c[k];
|
|
5387
|
+
s.write(c);
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5249
5390
|
};
|
|
5250
5391
|
var Cursors = class {
|
|
5251
5392
|
constructor(root, sink2) {
|
|
@@ -5257,18 +5398,21 @@ var Cursors = class {
|
|
|
5257
5398
|
file() {
|
|
5258
5399
|
return path16.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
5259
5400
|
}
|
|
5401
|
+
/** The map; empty when the file is missing, cut off mid-write, or holds anything
|
|
5402
|
+
* but an object (`null`, a list), so a drain never indexes into those. */
|
|
5260
5403
|
read() {
|
|
5261
5404
|
try {
|
|
5262
|
-
|
|
5405
|
+
const c = JSON.parse(readFileSync11(this.file(), "utf8"));
|
|
5406
|
+
return c && typeof c === "object" && !Array.isArray(c) ? c : {};
|
|
5263
5407
|
} catch {
|
|
5264
5408
|
return {};
|
|
5265
5409
|
}
|
|
5266
5410
|
}
|
|
5267
5411
|
write(c) {
|
|
5268
5412
|
const f = this.file();
|
|
5269
|
-
|
|
5413
|
+
mkdirSync3(path16.dirname(f), { recursive: true, mode: 448 });
|
|
5270
5414
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
5271
|
-
|
|
5415
|
+
writeFileSync3(tmp, JSON.stringify(c), { mode: 384 });
|
|
5272
5416
|
renameSync(tmp, f);
|
|
5273
5417
|
}
|
|
5274
5418
|
key(ref) {
|
|
@@ -5280,25 +5424,54 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
5280
5424
|
}
|
|
5281
5425
|
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
5282
5426
|
const dir = path16.join(root, "locks");
|
|
5283
|
-
|
|
5427
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
5284
5428
|
const f = path16.join(dir, `${name}.lock`);
|
|
5429
|
+
const nonce = randomBytes3(8).toString("hex");
|
|
5430
|
+
const mine = `${process.pid} ${nonce}`;
|
|
5431
|
+
let body = null;
|
|
5285
5432
|
try {
|
|
5286
|
-
|
|
5287
|
-
|
|
5433
|
+
body = readFileSync11(f, "utf8");
|
|
5434
|
+
const stale = Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(body.split(" ")[0]));
|
|
5435
|
+
if (!stale) return null;
|
|
5288
5436
|
} catch {
|
|
5437
|
+
body = null;
|
|
5438
|
+
}
|
|
5439
|
+
if (body !== null) {
|
|
5440
|
+
const aside = `${f}.${process.pid}.${nonce}.stale`;
|
|
5441
|
+
try {
|
|
5442
|
+
renameSync(f, aside);
|
|
5443
|
+
} catch {
|
|
5444
|
+
return null;
|
|
5445
|
+
}
|
|
5446
|
+
const moved = readFileSync11(aside, "utf8");
|
|
5447
|
+
unlinkSync2(aside);
|
|
5448
|
+
if (moved !== body) {
|
|
5449
|
+
try {
|
|
5450
|
+
writeFileSync3(f, moved, { flag: "wx" });
|
|
5451
|
+
} catch {
|
|
5452
|
+
}
|
|
5453
|
+
return null;
|
|
5454
|
+
}
|
|
5289
5455
|
}
|
|
5290
5456
|
try {
|
|
5291
|
-
|
|
5457
|
+
writeFileSync3(f, mine, { flag: "wx" });
|
|
5292
5458
|
} catch {
|
|
5293
5459
|
return null;
|
|
5294
5460
|
}
|
|
5295
5461
|
return () => {
|
|
5296
5462
|
try {
|
|
5297
|
-
|
|
5463
|
+
if (readFileSync11(f, "utf8") === mine) unlinkSync2(f);
|
|
5298
5464
|
} catch {
|
|
5299
5465
|
}
|
|
5300
5466
|
};
|
|
5301
5467
|
}
|
|
5468
|
+
async function waitNamedLock(root, name, forMs, everyMs = 100) {
|
|
5469
|
+
for (let waited = 0; ; waited += everyMs) {
|
|
5470
|
+
const release = takeNamedLock(root, name);
|
|
5471
|
+
if (release || waited >= forMs) return release;
|
|
5472
|
+
await new Promise((r) => setTimeout(r, everyMs));
|
|
5473
|
+
}
|
|
5474
|
+
}
|
|
5302
5475
|
function processAlive(pid) {
|
|
5303
5476
|
if (!Number.isInteger(pid) || pid <= 0) return true;
|
|
5304
5477
|
try {
|
|
@@ -5313,7 +5486,7 @@ function processAlive(pid) {
|
|
|
5313
5486
|
import { createHash as createHash4 } from "node:crypto";
|
|
5314
5487
|
var RULESET_PII = "pii@1";
|
|
5315
5488
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
5316
|
-
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
5489
|
+
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;
|
|
5317
5490
|
function looksPhone(s) {
|
|
5318
5491
|
const digits = s.replace(/\D/g, "");
|
|
5319
5492
|
if (digits.length < 7 || digits.length > 15) return false;
|
|
@@ -5324,7 +5497,7 @@ function looksPhone(s) {
|
|
|
5324
5497
|
function reducePii(text, home) {
|
|
5325
5498
|
if (!text) return text;
|
|
5326
5499
|
let out2 = text.replace(EMAIL, "[email]");
|
|
5327
|
-
out2 = out2.replace(PHONE, (m) => looksPhone(m) ? "[phone]" : m);
|
|
5500
|
+
out2 = out2.replace(PHONE, (m, date) => !date && looksPhone(m) ? "[phone]" : m);
|
|
5328
5501
|
if (home) {
|
|
5329
5502
|
const h = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5330
5503
|
out2 = out2.replace(new RegExp(`(?<![A-Za-z0-9_])${h}(?=/|$|\\s|["'\`])`, "g"), "~");
|
|
@@ -5538,7 +5711,7 @@ var DATA_ALLOW = {
|
|
|
5538
5711
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
5539
5712
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
5540
5713
|
"files.changed": { 1: ["paths"] },
|
|
5541
|
-
"attention.needed": { 0: ["attention_id", "kind"], 1: ["tool", "message"] },
|
|
5714
|
+
"attention.needed": { 0: ["attention_id", "kind"], 1: ["tool", "message", "agent_id"] },
|
|
5542
5715
|
"attention.cleared": { 0: ["attention_id", "how"] },
|
|
5543
5716
|
"subagent.started": { 1: ["agent_id", "agent_type"] },
|
|
5544
5717
|
"subagent.ended": { 1: ["agent_id", "agent_type"] },
|
|
@@ -5629,7 +5802,7 @@ function shortId(harness, id) {
|
|
|
5629
5802
|
}
|
|
5630
5803
|
|
|
5631
5804
|
// ../core/src/sinks/file.ts
|
|
5632
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
5805
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync4 } from "node:fs";
|
|
5633
5806
|
import path17 from "node:path";
|
|
5634
5807
|
var FileSink = class {
|
|
5635
5808
|
constructor(name, file, tier, pii) {
|
|
@@ -5644,7 +5817,7 @@ var FileSink = class {
|
|
|
5644
5817
|
pii;
|
|
5645
5818
|
async send(events) {
|
|
5646
5819
|
try {
|
|
5647
|
-
|
|
5820
|
+
mkdirSync4(path17.dirname(this.file), { recursive: true });
|
|
5648
5821
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
5649
5822
|
`);
|
|
5650
5823
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -5678,9 +5851,9 @@ var StdoutSink = class {
|
|
|
5678
5851
|
};
|
|
5679
5852
|
|
|
5680
5853
|
// src/ask.ts
|
|
5681
|
-
import { existsSync as existsSync14, mkdirSync as
|
|
5854
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync18, statSync as statSync8, unlinkSync as unlinkSync5, writeFileSync as writeFileSync10 } from "node:fs";
|
|
5682
5855
|
import os12 from "node:os";
|
|
5683
|
-
import
|
|
5856
|
+
import path29 from "node:path";
|
|
5684
5857
|
|
|
5685
5858
|
// src/connect.ts
|
|
5686
5859
|
import path18 from "node:path";
|
|
@@ -5714,10 +5887,17 @@ function connectFolders(o) {
|
|
|
5714
5887
|
}
|
|
5715
5888
|
return picked.length ? { folders: picked, from: "recent" } : { folders: [], from: "none" };
|
|
5716
5889
|
}
|
|
5890
|
+
function tierArg(argv, fallback) {
|
|
5891
|
+
const i = argv.indexOf("--tier");
|
|
5892
|
+
if (i < 0) return fallback;
|
|
5893
|
+
const v = argv[i + 1] ?? "";
|
|
5894
|
+
const n = /^-?\d+$/.test(v.trim()) ? Number(v) : Number.NaN;
|
|
5895
|
+
if (Number.isNaN(n)) throw new Error(`--tier takes 0, 1, 2 or 3${v ? `, not "${v}"` : ""}`);
|
|
5896
|
+
return Math.max(0, Math.min(3, n));
|
|
5897
|
+
}
|
|
5717
5898
|
function connectTier(flag2, current) {
|
|
5718
|
-
if (flag2 !== void 0
|
|
5719
|
-
|
|
5720
|
-
return Math.max(2, Math.min(3, current ?? 0));
|
|
5899
|
+
if (flag2 !== void 0) return flag2;
|
|
5900
|
+
return Math.max(2, Math.min(3, Math.trunc(Number(current) || 0)));
|
|
5721
5901
|
}
|
|
5722
5902
|
|
|
5723
5903
|
// src/control/local.ts
|
|
@@ -5795,13 +5975,13 @@ function askMany(sock, req, onReply, timeoutMs) {
|
|
|
5795
5975
|
}
|
|
5796
5976
|
|
|
5797
5977
|
// src/control/pair.ts
|
|
5798
|
-
import { randomBytes as
|
|
5799
|
-
import { chmodSync as chmodSync3, closeSync as closeSync5, mkdirSync as
|
|
5978
|
+
import { randomBytes as randomBytes5 } from "node:crypto";
|
|
5979
|
+
import { chmodSync as chmodSync3, closeSync as closeSync5, mkdirSync as mkdirSync7, openSync as openSync5 } from "node:fs";
|
|
5800
5980
|
import path22 from "node:path";
|
|
5801
5981
|
|
|
5802
5982
|
// ../core/src/service/index.ts
|
|
5803
5983
|
import { execFileSync as execFileSync6 } from "node:child_process";
|
|
5804
|
-
import { existsSync as existsSync10, mkdirSync as
|
|
5984
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
5805
5985
|
import os8 from "node:os";
|
|
5806
5986
|
import path20 from "node:path";
|
|
5807
5987
|
var execQuiet = (cmd, args2) => execFileSync6(cmd, args2, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 15e3 });
|
|
@@ -5879,8 +6059,8 @@ function installService(o) {
|
|
|
5879
6059
|
};
|
|
5880
6060
|
const write = (file) => {
|
|
5881
6061
|
if (o.dryRun) return;
|
|
5882
|
-
|
|
5883
|
-
|
|
6062
|
+
mkdirSync5(path20.dirname(file.path), { recursive: true });
|
|
6063
|
+
writeFileSync4(file.path, file.text);
|
|
5884
6064
|
};
|
|
5885
6065
|
if (platform === "darwin") {
|
|
5886
6066
|
write(f.plist);
|
|
@@ -5931,7 +6111,7 @@ function uninstallService(o) {
|
|
|
5931
6111
|
};
|
|
5932
6112
|
const remove = (file) => {
|
|
5933
6113
|
files.push(file);
|
|
5934
|
-
if (!o.dryRun)
|
|
6114
|
+
if (!o.dryRun) unlinkSync3(file);
|
|
5935
6115
|
};
|
|
5936
6116
|
if (platform === "darwin" && existsSync10(f.plist.path)) {
|
|
5937
6117
|
run("launchctl", ["bootout", `gui/${uid}/${o.label}`]);
|
|
@@ -5953,17 +6133,17 @@ import {
|
|
|
5953
6133
|
closeSync as closeSync4,
|
|
5954
6134
|
existsSync as existsSync11,
|
|
5955
6135
|
fsyncSync,
|
|
5956
|
-
mkdirSync as
|
|
6136
|
+
mkdirSync as mkdirSync6,
|
|
5957
6137
|
openSync as openSync4,
|
|
5958
6138
|
readFileSync as readFileSync12,
|
|
5959
6139
|
renameSync as renameSync2,
|
|
5960
|
-
writeFileSync as
|
|
6140
|
+
writeFileSync as writeFileSync5
|
|
5961
6141
|
} from "node:fs";
|
|
5962
6142
|
import path21 from "node:path";
|
|
5963
6143
|
|
|
5964
6144
|
// src/secrets.ts
|
|
5965
6145
|
import { spawnSync } from "node:child_process";
|
|
5966
|
-
import { createHash as createHash6, randomBytes as
|
|
6146
|
+
import { createHash as createHash6, randomBytes as randomBytes4 } from "node:crypto";
|
|
5967
6147
|
var SERVICE2 = "sessionpipe";
|
|
5968
6148
|
var defaultRunner = (cmd, args2, input) => {
|
|
5969
6149
|
const r = spawnSync(cmd, args2, {
|
|
@@ -6006,8 +6186,8 @@ function secretDel(store, account, run = defaultRunner) {
|
|
|
6006
6186
|
else run("secret-tool", ["clear", "service", SERVICE2, "account", account]);
|
|
6007
6187
|
}
|
|
6008
6188
|
function probe2(store, run = defaultRunner) {
|
|
6009
|
-
const account = `probe:${
|
|
6010
|
-
const ok = secretSet(store, account,
|
|
6189
|
+
const account = `probe:${randomBytes4(6).toString("hex")}`;
|
|
6190
|
+
const ok = secretSet(store, account, randomBytes4(12).toString("base64url"), run);
|
|
6011
6191
|
secretDel(store, account, run);
|
|
6012
6192
|
return ok;
|
|
6013
6193
|
}
|
|
@@ -6018,13 +6198,18 @@ function bestStore(env = process.env, platform = process.platform, run = default
|
|
|
6018
6198
|
for (const s of candidates) if (probe2(s, run)) return s;
|
|
6019
6199
|
return "file";
|
|
6020
6200
|
}
|
|
6021
|
-
var storeLabel = (s) => s === "keychain" ? "the macOS Keychain" : s === "secret-service" ? "the Secret Service keyring" : "a 0600 file";
|
|
6201
|
+
var storeLabel = (s, platform = process.platform) => s === "keychain" ? "the macOS Keychain" : s === "secret-service" ? "the Secret Service keyring" : platform === "win32" ? "a file under your profile" : "a 0600 file";
|
|
6202
|
+
var fileKept = (platform = process.platform) => platform === "win32" ? "your profile's permissions" : "0600";
|
|
6203
|
+
function keysFileNote(platform = process.platform, env = process.env) {
|
|
6204
|
+
if (platform === "win32") return "under your profile's permissions (Windows has no keychain sessionpipe uses)";
|
|
6205
|
+
return `at 0600 \u2014 no unlocked keychain in this session${env.SSH_CONNECTION ? " (an ssh shell)" : ""}`;
|
|
6206
|
+
}
|
|
6022
6207
|
function sinkWithSecrets(s, run = defaultRunner) {
|
|
6023
6208
|
if (!s.token_in) return s;
|
|
6024
6209
|
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
6025
|
-
if (!token) return null;
|
|
6026
6210
|
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
6027
|
-
|
|
6211
|
+
if (!token && !secret) return null;
|
|
6212
|
+
return { ...s, ...token ? { token } : {}, ...secret ? { secret } : {} };
|
|
6028
6213
|
}
|
|
6029
6214
|
function moveSinkSecrets(to, o = {}) {
|
|
6030
6215
|
const run = o.run ?? defaultRunner;
|
|
@@ -6081,9 +6266,9 @@ var askOn = (c) => c.ask !== false;
|
|
|
6081
6266
|
var controlFile = (env = process.env) => path21.join(path21.dirname(configFile(env)), "control.json");
|
|
6082
6267
|
var controlState = (env = process.env) => path21.join(stateDir(env), "control");
|
|
6083
6268
|
function writePrivate(file, value) {
|
|
6084
|
-
|
|
6269
|
+
mkdirSync6(path21.dirname(file), { recursive: true, mode: 448 });
|
|
6085
6270
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
6086
|
-
|
|
6271
|
+
writeFileSync5(tmp, `${JSON.stringify(value, null, 2)}
|
|
6087
6272
|
`, { mode: 384 });
|
|
6088
6273
|
renameSync2(tmp, file);
|
|
6089
6274
|
try {
|
|
@@ -6182,7 +6367,7 @@ var NonceStore = class {
|
|
|
6182
6367
|
const t = this.seenAt.get(nonce);
|
|
6183
6368
|
if (t !== void 0 && t > this.now() - NONCE_TTL_MS) return true;
|
|
6184
6369
|
const at = this.now();
|
|
6185
|
-
|
|
6370
|
+
mkdirSync6(path21.dirname(this.file), { recursive: true, mode: 448 });
|
|
6186
6371
|
const fd = openSync4(this.file, "a", 384);
|
|
6187
6372
|
try {
|
|
6188
6373
|
appendFileSync3(fd, `${JSON.stringify({ n: nonce, t: at })}
|
|
@@ -6200,8 +6385,8 @@ var NonceStore = class {
|
|
|
6200
6385
|
for (const [n, t] of this.seenAt) if (t <= cut2) this.seenAt.delete(n);
|
|
6201
6386
|
if (!existsSync11(this.file) && !this.seenAt.size) return;
|
|
6202
6387
|
const body = [...this.seenAt].map(([n, t]) => JSON.stringify({ n, t })).join("\n");
|
|
6203
|
-
|
|
6204
|
-
|
|
6388
|
+
mkdirSync6(path21.dirname(this.file), { recursive: true, mode: 448 });
|
|
6389
|
+
writeFileSync5(`${this.file}.tmp`, body ? `${body}
|
|
6205
6390
|
` : "", { mode: 384 });
|
|
6206
6391
|
renameSync2(`${this.file}.tmp`, this.file);
|
|
6207
6392
|
}
|
|
@@ -6230,13 +6415,13 @@ var AckOutbox = class {
|
|
|
6230
6415
|
}
|
|
6231
6416
|
}
|
|
6232
6417
|
add(a) {
|
|
6233
|
-
|
|
6418
|
+
mkdirSync6(path21.dirname(this.file), { recursive: true, mode: 448 });
|
|
6234
6419
|
appendFileSync3(this.file, `${JSON.stringify(a)}
|
|
6235
6420
|
`, { mode: 384 });
|
|
6236
6421
|
}
|
|
6237
6422
|
keep(left) {
|
|
6238
|
-
|
|
6239
|
-
|
|
6423
|
+
mkdirSync6(path21.dirname(this.file), { recursive: true, mode: 448 });
|
|
6424
|
+
writeFileSync5(`${this.file}.tmp`, left.map((a) => JSON.stringify(a)).join("\n") + (left.length ? "\n" : ""), {
|
|
6240
6425
|
mode: 384
|
|
6241
6426
|
});
|
|
6242
6427
|
renameSync2(`${this.file}.tmp`, this.file);
|
|
@@ -6290,7 +6475,7 @@ var writeState = (file, s) => writePrivate(file, {
|
|
|
6290
6475
|
});
|
|
6291
6476
|
|
|
6292
6477
|
// src/control/pair.ts
|
|
6293
|
-
var VERSION = true ? "0.13.
|
|
6478
|
+
var VERSION = true ? "0.13.1" : "0.0.0";
|
|
6294
6479
|
var RENEW_FOR_MS = 60 * 6e4;
|
|
6295
6480
|
async function json(r) {
|
|
6296
6481
|
try {
|
|
@@ -6339,7 +6524,7 @@ async function pair(o) {
|
|
|
6339
6524
|
throw new Error(
|
|
6340
6525
|
`${o.url} needs a key to pair: add it as a sink first (\`sessionpipe sink add ${o.url} --token \u2026\`) or pass --token.`
|
|
6341
6526
|
);
|
|
6342
|
-
const pollKey = o.token ? null :
|
|
6527
|
+
const pollKey = o.token ? null : randomBytes5(32).toString("base64url");
|
|
6343
6528
|
const cfg = readControl(o.env) ?? { name: o.name, folders: [], mode: o.mode, receivers: [] };
|
|
6344
6529
|
cfg.name = o.name || cfg.name;
|
|
6345
6530
|
cfg.mode = o.mode;
|
|
@@ -6502,7 +6687,7 @@ var spec = (o) => ({
|
|
|
6502
6687
|
});
|
|
6503
6688
|
function installService2(o) {
|
|
6504
6689
|
const logDir = controlState(o.env);
|
|
6505
|
-
|
|
6690
|
+
mkdirSync7(logDir, { recursive: true, mode: 448 });
|
|
6506
6691
|
const log2 = path22.join(logDir, "daemon.log");
|
|
6507
6692
|
try {
|
|
6508
6693
|
closeSync5(openSync5(log2, "a", 384));
|
|
@@ -6548,12 +6733,12 @@ ${RE_ARM}`;
|
|
|
6548
6733
|
|
|
6549
6734
|
// src/runtime.ts
|
|
6550
6735
|
import { execFileSync as execFileSync7 } from "node:child_process";
|
|
6551
|
-
import { readFileSync as
|
|
6736
|
+
import { readFileSync as readFileSync17, realpathSync as realpathSync3 } from "node:fs";
|
|
6552
6737
|
import os11 from "node:os";
|
|
6553
|
-
import
|
|
6738
|
+
import path28 from "node:path";
|
|
6554
6739
|
|
|
6555
6740
|
// src/launcher.ts
|
|
6556
|
-
import { accessSync as accessSync2, chmodSync as chmodSync4, constants as constants2, mkdirSync as
|
|
6741
|
+
import { accessSync as accessSync2, chmodSync as chmodSync4, constants as constants2, mkdirSync as mkdirSync8, readFileSync as readFileSync13, renameSync as renameSync3, rmSync, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6557
6742
|
import os9 from "node:os";
|
|
6558
6743
|
import path23 from "node:path";
|
|
6559
6744
|
var LAUNCHER = "sessionpipe-hook";
|
|
@@ -6585,14 +6770,14 @@ function writeIfChanged(file, body, mode) {
|
|
|
6585
6770
|
} catch {
|
|
6586
6771
|
}
|
|
6587
6772
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
6588
|
-
|
|
6773
|
+
writeFileSync6(tmp, body, { mode });
|
|
6589
6774
|
renameSync3(tmp, file);
|
|
6590
6775
|
chmodSync4(file, mode);
|
|
6591
6776
|
return true;
|
|
6592
6777
|
}
|
|
6593
6778
|
function installLauncher(o) {
|
|
6594
6779
|
const dir = dataDir(o.env);
|
|
6595
|
-
|
|
6780
|
+
mkdirSync8(dir, { recursive: true, mode: 493 });
|
|
6596
6781
|
let changed = false;
|
|
6597
6782
|
for (const f of COPIED)
|
|
6598
6783
|
changed = writeIfChanged(path23.join(dir, f), readFileSync13(path23.join(o.distDir, f)), 420) || changed;
|
|
@@ -6650,19 +6835,19 @@ function launcherState(version, env = process.env) {
|
|
|
6650
6835
|
import {
|
|
6651
6836
|
appendFileSync as appendFileSync4,
|
|
6652
6837
|
existsSync as existsSync12,
|
|
6653
|
-
mkdirSync as
|
|
6838
|
+
mkdirSync as mkdirSync11,
|
|
6654
6839
|
readdirSync as readdirSync7,
|
|
6655
|
-
readFileSync as
|
|
6656
|
-
renameSync as
|
|
6840
|
+
readFileSync as readFileSync16,
|
|
6841
|
+
renameSync as renameSync6,
|
|
6657
6842
|
statSync as statSync7,
|
|
6658
|
-
unlinkSync as
|
|
6659
|
-
writeFileSync as
|
|
6843
|
+
unlinkSync as unlinkSync4,
|
|
6844
|
+
writeFileSync as writeFileSync9
|
|
6660
6845
|
} from "node:fs";
|
|
6661
6846
|
import os10 from "node:os";
|
|
6662
|
-
import
|
|
6847
|
+
import path26 from "node:path";
|
|
6663
6848
|
|
|
6664
6849
|
// src/control/aliases.ts
|
|
6665
|
-
import { chmodSync as chmodSync5, mkdirSync as
|
|
6850
|
+
import { chmodSync as chmodSync5, mkdirSync as mkdirSync9, readFileSync as readFileSync14, renameSync as renameSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
6666
6851
|
import path24 from "node:path";
|
|
6667
6852
|
var aliasFile = (controlDir) => path24.join(controlDir, "aliases.json");
|
|
6668
6853
|
function readAliases(file) {
|
|
@@ -6681,9 +6866,9 @@ function readAliases(file) {
|
|
|
6681
6866
|
}
|
|
6682
6867
|
}
|
|
6683
6868
|
function writeAliases(file, all) {
|
|
6684
|
-
|
|
6869
|
+
mkdirSync9(path24.dirname(file), { recursive: true, mode: 448 });
|
|
6685
6870
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
6686
|
-
|
|
6871
|
+
writeFileSync7(tmp, `${JSON.stringify(all, null, 2)}
|
|
6687
6872
|
`, { mode: 384 });
|
|
6688
6873
|
renameSync4(tmp, file);
|
|
6689
6874
|
try {
|
|
@@ -6697,6 +6882,76 @@ function receiverIdOf(all, harness, id) {
|
|
|
6697
6882
|
return null;
|
|
6698
6883
|
}
|
|
6699
6884
|
|
|
6885
|
+
// src/fold.ts
|
|
6886
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync15, renameSync as renameSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
6887
|
+
import path25 from "node:path";
|
|
6888
|
+
var TOOLS_MODES = ["all", "main", "beat"];
|
|
6889
|
+
var TOOL_FOLD_MS = 6e4;
|
|
6890
|
+
var KEEP_MS = 24 * 60 * 6e4;
|
|
6891
|
+
function isToolsMode(v) {
|
|
6892
|
+
return typeof v === "string" && TOOLS_MODES.includes(v);
|
|
6893
|
+
}
|
|
6894
|
+
function toolsMode(s) {
|
|
6895
|
+
if (isToolsMode(s.tools)) return s.tools;
|
|
6896
|
+
if (isToolsMode(s.receiver_tools)) return s.receiver_tools;
|
|
6897
|
+
return "all";
|
|
6898
|
+
}
|
|
6899
|
+
var agentOf = (e) => typeof e.data.agent_id === "string" ? e.data.agent_id : "";
|
|
6900
|
+
var thins = (mode, agent) => mode === "beat" || mode === "main" && agent !== "";
|
|
6901
|
+
var askKey = (key, agent) => `${key}\0ask\0${agent}`;
|
|
6902
|
+
function sendTool(e, mode, clock, key) {
|
|
6903
|
+
if (mode === "all") return true;
|
|
6904
|
+
const agent = agentOf(e);
|
|
6905
|
+
const t = Date.parse(e.time);
|
|
6906
|
+
if (e.type === "attention.needed") {
|
|
6907
|
+
if (thins(mode, agent)) clock[askKey(key, agent)] = Number.isFinite(t) ? t : 0;
|
|
6908
|
+
return true;
|
|
6909
|
+
}
|
|
6910
|
+
if (e.type !== "tool.started" && e.type !== "tool.ended" || !thins(mode, agent)) return true;
|
|
6911
|
+
if (e.type === "tool.started") return false;
|
|
6912
|
+
if (!Number.isFinite(t)) return true;
|
|
6913
|
+
const ask2 = askKey(key, agent);
|
|
6914
|
+
const asked = clock[ask2];
|
|
6915
|
+
if (asked !== void 0 && t >= asked) {
|
|
6916
|
+
delete clock[ask2];
|
|
6917
|
+
clock[key] = t;
|
|
6918
|
+
return true;
|
|
6919
|
+
}
|
|
6920
|
+
const last = clock[key];
|
|
6921
|
+
if (last !== void 0 && t > last - TOOL_FOLD_MS && t < last + TOOL_FOLD_MS) return false;
|
|
6922
|
+
clock[key] = t;
|
|
6923
|
+
return true;
|
|
6924
|
+
}
|
|
6925
|
+
var ToolClock = class {
|
|
6926
|
+
constructor(root, sink2) {
|
|
6927
|
+
this.root = root;
|
|
6928
|
+
this.sink = sink2;
|
|
6929
|
+
}
|
|
6930
|
+
root;
|
|
6931
|
+
sink;
|
|
6932
|
+
file() {
|
|
6933
|
+
return path25.join(this.root, "folds", `${safeId(this.sink)}.json`);
|
|
6934
|
+
}
|
|
6935
|
+
read() {
|
|
6936
|
+
try {
|
|
6937
|
+
const j = JSON.parse(readFileSync15(this.file(), "utf8"));
|
|
6938
|
+
return j && typeof j === "object" && !Array.isArray(j) ? j : {};
|
|
6939
|
+
} catch {
|
|
6940
|
+
return {};
|
|
6941
|
+
}
|
|
6942
|
+
}
|
|
6943
|
+
write(c) {
|
|
6944
|
+
const newest = Math.max(0, ...Object.values(c).filter(Number.isFinite));
|
|
6945
|
+
const kept2 = {};
|
|
6946
|
+
for (const [k, v] of Object.entries(c)) if (Number.isFinite(v) && v >= newest - KEEP_MS) kept2[k] = v;
|
|
6947
|
+
const f = this.file();
|
|
6948
|
+
mkdirSync10(path25.dirname(f), { recursive: true, mode: 448 });
|
|
6949
|
+
const tmp = `${f}.${process.pid}.tmp`;
|
|
6950
|
+
writeFileSync8(tmp, JSON.stringify(kept2), { mode: 384 });
|
|
6951
|
+
renameSync5(tmp, f);
|
|
6952
|
+
}
|
|
6953
|
+
};
|
|
6954
|
+
|
|
6700
6955
|
// src/http-sink.ts
|
|
6701
6956
|
import { createHash as createHash7, createHmac } from "node:crypto";
|
|
6702
6957
|
var TIMEOUT_MS = 15e3;
|
|
@@ -6814,27 +7069,27 @@ async function tellDaemon(state2, job, session, cwd, transcript) {
|
|
|
6814
7069
|
300
|
|
6815
7070
|
);
|
|
6816
7071
|
}
|
|
6817
|
-
var VERSION2 = true ? "0.13.
|
|
7072
|
+
var VERSION2 = true ? "0.13.1" : "0.0.0";
|
|
6818
7073
|
function enqueueJob(state2, job) {
|
|
6819
7074
|
const jobs = jobsDir(state2);
|
|
6820
|
-
|
|
6821
|
-
const f =
|
|
6822
|
-
|
|
7075
|
+
mkdirSync11(jobs, { recursive: true, mode: 448 });
|
|
7076
|
+
const f = path26.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
7077
|
+
writeFileSync9(f, JSON.stringify(job), { mode: 384 });
|
|
6823
7078
|
return f;
|
|
6824
7079
|
}
|
|
6825
7080
|
function releaseJob(claimed) {
|
|
6826
7081
|
if (!claimed) return;
|
|
6827
7082
|
try {
|
|
6828
|
-
|
|
7083
|
+
unlinkSync4(claimed);
|
|
6829
7084
|
} catch {
|
|
6830
7085
|
}
|
|
6831
7086
|
}
|
|
6832
7087
|
function log(state2, line2) {
|
|
6833
7088
|
try {
|
|
6834
|
-
|
|
6835
|
-
const f =
|
|
7089
|
+
mkdirSync11(state2, { recursive: true, mode: 448 });
|
|
7090
|
+
const f = path26.join(state2, "log");
|
|
6836
7091
|
try {
|
|
6837
|
-
if (statSync7(f).size > 1024 * 1024)
|
|
7092
|
+
if (statSync7(f).size > 1024 * 1024) renameSync6(f, `${f}.1`);
|
|
6838
7093
|
} catch {
|
|
6839
7094
|
}
|
|
6840
7095
|
appendFileSync4(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line2}
|
|
@@ -6843,11 +7098,11 @@ function log(state2, line2) {
|
|
|
6843
7098
|
}
|
|
6844
7099
|
}
|
|
6845
7100
|
function factsState(state2) {
|
|
6846
|
-
const file = (h, s) =>
|
|
7101
|
+
const file = (h, s) => path26.join(state2, "facts", safeId(h), `${safeId(s)}.json`);
|
|
6847
7102
|
return {
|
|
6848
7103
|
get(h, s) {
|
|
6849
7104
|
try {
|
|
6850
|
-
return JSON.parse(
|
|
7105
|
+
return JSON.parse(readFileSync16(file(h, s), "utf8"));
|
|
6851
7106
|
} catch {
|
|
6852
7107
|
return {};
|
|
6853
7108
|
}
|
|
@@ -6856,8 +7111,8 @@ function factsState(state2) {
|
|
|
6856
7111
|
const cur = this.get(h, s);
|
|
6857
7112
|
const next = { ...cur, ...patch };
|
|
6858
7113
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
6859
|
-
|
|
6860
|
-
|
|
7114
|
+
mkdirSync11(path26.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
7115
|
+
writeFileSync9(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
6861
7116
|
}
|
|
6862
7117
|
};
|
|
6863
7118
|
}
|
|
@@ -6870,7 +7125,7 @@ function buildSinks(sinks, o = {}) {
|
|
|
6870
7125
|
o.locked?.(raw.name);
|
|
6871
7126
|
continue;
|
|
6872
7127
|
}
|
|
6873
|
-
const tier = Math.min(s.tier, s.max_tier ?? 3);
|
|
7128
|
+
const tier = Math.trunc(Math.min(Number(s.tier) || 0, s.max_tier ?? 3));
|
|
6874
7129
|
if (s.url === "stdout") out2.push(new StdoutSink(s.name, tier, !!s.pii));
|
|
6875
7130
|
else if (s.url.startsWith("file:"))
|
|
6876
7131
|
out2.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os10.homedir()), tier, !!s.pii));
|
|
@@ -6932,7 +7187,7 @@ async function runJob(job, opts = {}) {
|
|
|
6932
7187
|
);
|
|
6933
7188
|
return { events: [], delivered: {} };
|
|
6934
7189
|
}
|
|
6935
|
-
const id = receiverIdOf(readAliases(aliasFile(
|
|
7190
|
+
const id = receiverIdOf(readAliases(aliasFile(path26.join(state2, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
6936
7191
|
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
6937
7192
|
if (!claudeEnd) await tellDaemon(state2, job, id, r.session.cwd, r.transcript);
|
|
6938
7193
|
const outbox = new Outbox(state2);
|
|
@@ -6969,7 +7224,7 @@ async function runJob(job, opts = {}) {
|
|
|
6969
7224
|
learned = rest;
|
|
6970
7225
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
6971
7226
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
6972
|
-
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd &&
|
|
7227
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path26.isAbsolute(r.session.cwd))
|
|
6973
7228
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
6974
7229
|
}
|
|
6975
7230
|
const known = mem.session ?? {};
|
|
@@ -7087,8 +7342,12 @@ async function runJob(job, opts = {}) {
|
|
|
7087
7342
|
} finally {
|
|
7088
7343
|
release();
|
|
7089
7344
|
}
|
|
7090
|
-
const delivered = await drain(state2, cfg.sinks, outbox, ref) ?? {};
|
|
7091
7345
|
if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
|
|
7346
|
+
const delivered = opts.drain === false ? {} : await drain(state2, cfg.sinks, outbox, ref, {
|
|
7347
|
+
...opts.clock ? { clock: opts.clock } : {},
|
|
7348
|
+
...opts.deadline !== void 0 ? { deadline: opts.deadline } : {},
|
|
7349
|
+
...opts.handoff ? { handoff: opts.handoff } : {}
|
|
7350
|
+
}) ?? {};
|
|
7092
7351
|
return { events, delivered };
|
|
7093
7352
|
}
|
|
7094
7353
|
async function appendObserved(state2, harness, id, data, opts = {}) {
|
|
@@ -7127,25 +7386,75 @@ async function appendObserved(state2, harness, id, data, opts = {}) {
|
|
|
7127
7386
|
await drain(state2, cfg.sinks, outbox, ref);
|
|
7128
7387
|
return e;
|
|
7129
7388
|
}
|
|
7130
|
-
|
|
7131
|
-
|
|
7389
|
+
var REAL_CLOCK = { now: () => Date.now(), sleep: (ms2) => new Promise((r) => setTimeout(r, ms2)) };
|
|
7390
|
+
var PACE_MS = 1e3;
|
|
7391
|
+
var sentFile = (state2) => path26.join(state2, "locks", "flush.sent");
|
|
7392
|
+
function lastSent(state2) {
|
|
7393
|
+
try {
|
|
7394
|
+
return Number(readFileSync16(sentFile(state2), "utf8"));
|
|
7395
|
+
} catch {
|
|
7396
|
+
return 0;
|
|
7397
|
+
}
|
|
7398
|
+
}
|
|
7399
|
+
function noteSent(state2, at) {
|
|
7400
|
+
try {
|
|
7401
|
+
mkdirSync11(path26.dirname(sentFile(state2)), { recursive: true, mode: 448 });
|
|
7402
|
+
writeFileSync9(sentFile(state2), String(at), { mode: 384 });
|
|
7403
|
+
} catch {
|
|
7404
|
+
}
|
|
7405
|
+
}
|
|
7406
|
+
function waitingForReceiver(state2, sinkConfigs, outbox, now) {
|
|
7407
|
+
const receivers = sinkConfigs.filter(
|
|
7408
|
+
(s) => !s.paused && /^https?:\/\//.test(s.url) && !((readBackoff(state2, s.name)?.until ?? 0) > now)
|
|
7409
|
+
);
|
|
7410
|
+
if (!receivers.length) return false;
|
|
7411
|
+
const refs = outbox.sessions();
|
|
7412
|
+
for (const s of receivers) {
|
|
7413
|
+
const cursors = new Cursors(state2, s.name);
|
|
7414
|
+
const c = cursors.read();
|
|
7415
|
+
for (const ref of refs) {
|
|
7416
|
+
try {
|
|
7417
|
+
if (statSync7(outbox.file(ref)).size > (c[cursors.key(ref)] ?? 0)) return true;
|
|
7418
|
+
} catch {
|
|
7419
|
+
}
|
|
7420
|
+
}
|
|
7421
|
+
}
|
|
7422
|
+
return false;
|
|
7423
|
+
}
|
|
7424
|
+
var PASS_BUDGET_MS = 3e3;
|
|
7425
|
+
var moreFile = (state2) => path26.join(state2, "locks", "flush.more");
|
|
7426
|
+
function markMore(state2) {
|
|
7427
|
+
try {
|
|
7428
|
+
mkdirSync11(path26.dirname(moreFile(state2)), { recursive: true, mode: 448 });
|
|
7429
|
+
writeFileSync9(moreFile(state2), "", { mode: 384 });
|
|
7430
|
+
} catch {
|
|
7431
|
+
}
|
|
7432
|
+
}
|
|
7433
|
+
async function drain(state2, sinkConfigs, outbox, first, o = {}) {
|
|
7434
|
+
const clock = o.clock ?? REAL_CLOCK;
|
|
7435
|
+
const more = moreFile(state2);
|
|
7132
7436
|
let release = takeNamedLock(state2, "flush");
|
|
7133
7437
|
if (!release) {
|
|
7134
|
-
|
|
7135
|
-
writeFileSync11(more, "", { mode: 384 });
|
|
7136
|
-
} catch {
|
|
7137
|
-
}
|
|
7438
|
+
markMore(state2);
|
|
7138
7439
|
release = takeNamedLock(state2, "flush");
|
|
7139
7440
|
if (!release) return null;
|
|
7140
7441
|
}
|
|
7141
7442
|
const delivered = {};
|
|
7142
7443
|
for (; ; ) {
|
|
7143
|
-
|
|
7144
|
-
|
|
7145
|
-
|
|
7444
|
+
if (o.deadline !== void 0 && clock.now() + PASS_BUDGET_MS > o.deadline) {
|
|
7445
|
+
markMore(state2);
|
|
7446
|
+
release();
|
|
7447
|
+
o.handoff?.();
|
|
7448
|
+
return delivered;
|
|
7146
7449
|
}
|
|
7147
7450
|
try {
|
|
7148
|
-
|
|
7451
|
+
const wait = Math.min(PACE_MS, lastSent(state2) + PACE_MS - clock.now());
|
|
7452
|
+
if (wait > 0 && waitingForReceiver(state2, sinkConfigs, outbox, clock.now())) await clock.sleep(wait);
|
|
7453
|
+
try {
|
|
7454
|
+
unlinkSync4(more);
|
|
7455
|
+
} catch {
|
|
7456
|
+
}
|
|
7457
|
+
for (const [k, v] of Object.entries(await flush(state2, sinkConfigs, outbox, first, { clock })))
|
|
7149
7458
|
delivered[k] = (delivered[k] ?? 0) + v;
|
|
7150
7459
|
} finally {
|
|
7151
7460
|
release();
|
|
@@ -7156,10 +7465,10 @@ async function drain(state2, sinkConfigs, outbox, first) {
|
|
|
7156
7465
|
}
|
|
7157
7466
|
}
|
|
7158
7467
|
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
7159
|
-
var backoffFile = (state2, sink2) =>
|
|
7468
|
+
var backoffFile = (state2, sink2) => path26.join(state2, "backoff", `${safeId(sink2)}.json`);
|
|
7160
7469
|
function readBackoff(state2, sink2) {
|
|
7161
7470
|
try {
|
|
7162
|
-
const b = JSON.parse(
|
|
7471
|
+
const b = JSON.parse(readFileSync16(backoffFile(state2, sink2), "utf8"));
|
|
7163
7472
|
return typeof b.until === "number" ? b : null;
|
|
7164
7473
|
} catch {
|
|
7165
7474
|
return null;
|
|
@@ -7172,15 +7481,15 @@ function noteFailure(state2, sink2, res, prev) {
|
|
|
7172
7481
|
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
7173
7482
|
const f = backoffFile(state2, sink2);
|
|
7174
7483
|
try {
|
|
7175
|
-
|
|
7176
|
-
|
|
7484
|
+
mkdirSync11(path26.dirname(f), { recursive: true, mode: 448 });
|
|
7485
|
+
writeFileSync9(f, JSON.stringify(b), { mode: 384 });
|
|
7177
7486
|
} catch {
|
|
7178
7487
|
}
|
|
7179
7488
|
return b;
|
|
7180
7489
|
}
|
|
7181
7490
|
function clearBackoff(state2, sink2) {
|
|
7182
7491
|
try {
|
|
7183
|
-
|
|
7492
|
+
unlinkSync4(backoffFile(state2, sink2));
|
|
7184
7493
|
} catch {
|
|
7185
7494
|
}
|
|
7186
7495
|
}
|
|
@@ -7191,14 +7500,31 @@ var BATCH = 50;
|
|
|
7191
7500
|
var MAX_PER_RUN = 500;
|
|
7192
7501
|
async function flush(state2, sinkConfigs, outbox, first, o = {}) {
|
|
7193
7502
|
const delivered = {};
|
|
7503
|
+
const clock = o.clock ?? REAL_CLOCK;
|
|
7194
7504
|
const sinks = buildSinks(sinkConfigs, {
|
|
7195
7505
|
locked: (name) => log(state2, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
7196
7506
|
});
|
|
7507
|
+
const modes = new Map(sinkConfigs.map((s) => [s.name, toolsMode(s)]));
|
|
7197
7508
|
const refs = outbox.sessions();
|
|
7198
7509
|
if (first) {
|
|
7199
7510
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
7200
7511
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
7201
7512
|
}
|
|
7513
|
+
const unreadable = /* @__PURE__ */ new Set();
|
|
7514
|
+
const readOrSkip = (ref, offset, room) => {
|
|
7515
|
+
try {
|
|
7516
|
+
return outbox.read(ref, offset, room);
|
|
7517
|
+
} catch (e) {
|
|
7518
|
+
const code = e.code;
|
|
7519
|
+
if (typeof code !== "string") throw e;
|
|
7520
|
+
const f = outbox.file(ref);
|
|
7521
|
+
if (!unreadable.has(f)) {
|
|
7522
|
+
unreadable.add(f);
|
|
7523
|
+
log(state2, `drain: can't read ${f} (${code}); skipped, its cursors stay`);
|
|
7524
|
+
}
|
|
7525
|
+
return null;
|
|
7526
|
+
}
|
|
7527
|
+
};
|
|
7202
7528
|
const drainSink = async (sink2) => {
|
|
7203
7529
|
let backoff = readBackoff(state2, sink2.name);
|
|
7204
7530
|
if (backoff && !o.force && backoff.until > Date.now()) {
|
|
@@ -7207,11 +7533,16 @@ async function flush(state2, sinkConfigs, outbox, first, o = {}) {
|
|
|
7207
7533
|
}
|
|
7208
7534
|
const cursors = new Cursors(state2, sink2.name);
|
|
7209
7535
|
const c = cursors.read();
|
|
7536
|
+
const mode = modes.get(sink2.name);
|
|
7537
|
+
const tools = new ToolClock(state2, sink2.name);
|
|
7538
|
+
let clockNow = mode === "all" ? {} : tools.read();
|
|
7539
|
+
let clockNext = { ...clockNow };
|
|
7210
7540
|
let sent = 0;
|
|
7211
7541
|
let stalled = false;
|
|
7212
7542
|
let batch = [];
|
|
7213
7543
|
let pending = {};
|
|
7214
7544
|
const deliver = async (events) => {
|
|
7545
|
+
if (sink2 instanceof HttpSink) noteSent(state2, clock.now());
|
|
7215
7546
|
const res = await sink2.send(events);
|
|
7216
7547
|
if (res.ok || res.action !== "split") return res;
|
|
7217
7548
|
if (events.length === 1) {
|
|
@@ -7219,10 +7550,20 @@ async function flush(state2, sinkConfigs, outbox, first, o = {}) {
|
|
|
7219
7550
|
log(state2, `sink ${sink2.name}: 413 ${res.error ?? ""} \u2192 event ${e.id} (${e.type}) is too large alone, dropped`);
|
|
7220
7551
|
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected: [{ id: e.id, reason: "too_large" }] };
|
|
7221
7552
|
}
|
|
7553
|
+
const part = async (some) => {
|
|
7554
|
+
const r = await deliver(some);
|
|
7555
|
+
if (r.ok || r.action !== "drop") return r;
|
|
7556
|
+
log(
|
|
7557
|
+
state2,
|
|
7558
|
+
`sink ${sink2.name}: ${r.status ?? ""} ${r.error ?? ""} \u2192 drop, ${some.length} of ${events.length} events`
|
|
7559
|
+
);
|
|
7560
|
+
const rejected = some.map((e) => ({ id: e.id, reason: "dropped" }));
|
|
7561
|
+
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected };
|
|
7562
|
+
};
|
|
7222
7563
|
const half = Math.ceil(events.length / 2);
|
|
7223
|
-
const a = await
|
|
7564
|
+
const a = await part(events.slice(0, half));
|
|
7224
7565
|
if (!a.ok) return a;
|
|
7225
|
-
const b = await
|
|
7566
|
+
const b = await part(events.slice(half));
|
|
7226
7567
|
if (!b.ok) return b;
|
|
7227
7568
|
return {
|
|
7228
7569
|
ok: true,
|
|
@@ -7232,11 +7573,17 @@ async function flush(state2, sinkConfigs, outbox, first, o = {}) {
|
|
|
7232
7573
|
rejected: [...a.rejected ?? [], ...b.rejected ?? []]
|
|
7233
7574
|
};
|
|
7234
7575
|
};
|
|
7576
|
+
const commitClock = () => {
|
|
7577
|
+
if (mode === "all" || JSON.stringify(clockNext) === JSON.stringify(clockNow)) return;
|
|
7578
|
+
tools.write(clockNext);
|
|
7579
|
+
clockNow = { ...clockNext };
|
|
7580
|
+
};
|
|
7235
7581
|
const send = async () => {
|
|
7236
7582
|
if (!batch.length) {
|
|
7237
7583
|
Object.assign(c, pending);
|
|
7238
7584
|
pending = {};
|
|
7239
7585
|
cursors.write(c);
|
|
7586
|
+
commitClock();
|
|
7240
7587
|
return true;
|
|
7241
7588
|
}
|
|
7242
7589
|
const res = await deliver(batch);
|
|
@@ -7252,6 +7599,7 @@ async function flush(state2, sinkConfigs, outbox, first, o = {}) {
|
|
|
7252
7599
|
if (res.action !== "drop") {
|
|
7253
7600
|
batch = [];
|
|
7254
7601
|
pending = {};
|
|
7602
|
+
clockNext = { ...clockNow };
|
|
7255
7603
|
return false;
|
|
7256
7604
|
}
|
|
7257
7605
|
} else if (backoff) {
|
|
@@ -7261,6 +7609,7 @@ async function flush(state2, sinkConfigs, outbox, first, o = {}) {
|
|
|
7261
7609
|
sent += batch.length;
|
|
7262
7610
|
Object.assign(c, pending);
|
|
7263
7611
|
cursors.write(c);
|
|
7612
|
+
commitClock();
|
|
7264
7613
|
batch = [];
|
|
7265
7614
|
pending = {};
|
|
7266
7615
|
return true;
|
|
@@ -7270,11 +7619,15 @@ async function flush(state2, sinkConfigs, outbox, first, o = {}) {
|
|
|
7270
7619
|
let offset = pending[cursors.key(ref)] ?? c[cursors.key(ref)] ?? 0;
|
|
7271
7620
|
for (; ; ) {
|
|
7272
7621
|
const room = BATCH - batch.length;
|
|
7273
|
-
const
|
|
7622
|
+
const read = readOrSkip(ref, offset, room);
|
|
7623
|
+
if (!read) break;
|
|
7624
|
+
const { events, offset: next } = read;
|
|
7274
7625
|
if (!events.length) break;
|
|
7275
7626
|
for (const e of events) {
|
|
7276
7627
|
const f = filterEvent(e, sink2.tier, sink2.pii, { home: os10.homedir() });
|
|
7277
|
-
if (f)
|
|
7628
|
+
if (!f) continue;
|
|
7629
|
+
if (sendTool(f, mode, clockNext, cursors.key(ref))) batch.push(f);
|
|
7630
|
+
else if (o.thinned) o.thinned[sink2.name] = (o.thinned[sink2.name] ?? 0) + 1;
|
|
7278
7631
|
}
|
|
7279
7632
|
offset = next;
|
|
7280
7633
|
pending[cursors.key(ref)] = offset;
|
|
@@ -7306,12 +7659,12 @@ function applySinkVerdict(name, res) {
|
|
|
7306
7659
|
} catch {
|
|
7307
7660
|
}
|
|
7308
7661
|
}
|
|
7309
|
-
var jobsDir = (state2) =>
|
|
7662
|
+
var jobsDir = (state2) => path26.join(state2, "jobs");
|
|
7310
7663
|
|
|
7311
7664
|
// src/update.ts
|
|
7312
7665
|
import { spawn as spawn4 } from "node:child_process";
|
|
7313
7666
|
import { accessSync as accessSync3, constants as constants3, existsSync as existsSync13, realpathSync as realpathSync2 } from "node:fs";
|
|
7314
|
-
import
|
|
7667
|
+
import path27 from "node:path";
|
|
7315
7668
|
|
|
7316
7669
|
// ../core/src/version.ts
|
|
7317
7670
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -7399,11 +7752,11 @@ function globalPrefix(distDir2, platform = process.platform) {
|
|
|
7399
7752
|
if (viaNpx(distDir2)) return null;
|
|
7400
7753
|
const prefix = prefixOf(distDir2, platform);
|
|
7401
7754
|
if (!prefix) return null;
|
|
7402
|
-
const bin = platform === "win32" ?
|
|
7755
|
+
const bin = platform === "win32" ? path27.join(prefix, "sessionpipe.cmd") : path27.join(prefix, "bin", "sessionpipe");
|
|
7403
7756
|
return existsSync13(bin) ? prefix : null;
|
|
7404
7757
|
}
|
|
7405
7758
|
function unwritable(prefix, platform = process.platform) {
|
|
7406
|
-
const dir = platform === "win32" ?
|
|
7759
|
+
const dir = platform === "win32" ? path27.join(prefix, "node_modules") : path27.join(prefix, "lib", "node_modules");
|
|
7407
7760
|
try {
|
|
7408
7761
|
accessSync3(dir, constants3.W_OK);
|
|
7409
7762
|
return null;
|
|
@@ -7412,8 +7765,8 @@ function unwritable(prefix, platform = process.platform) {
|
|
|
7412
7765
|
}
|
|
7413
7766
|
}
|
|
7414
7767
|
function npmArgv(node, platform = process.platform) {
|
|
7415
|
-
const dir =
|
|
7416
|
-
const candidates = platform === "win32" ? [
|
|
7768
|
+
const dir = path27.dirname(node);
|
|
7769
|
+
const candidates = platform === "win32" ? [path27.join(dir, "node_modules", "npm", "bin", "npm-cli.js")] : [path27.join(dir, "npm"), path27.join(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js")];
|
|
7417
7770
|
for (const c of candidates) {
|
|
7418
7771
|
try {
|
|
7419
7772
|
const real = realpathSync2(c);
|
|
@@ -7458,7 +7811,7 @@ var execCapture = (argv, o) => new Promise((resolve) => {
|
|
|
7458
7811
|
});
|
|
7459
7812
|
var lastLine = (r) => (r.error || r.stderr.trim().split("\n").filter(Boolean).slice(-2).join(" ") || `exit ${r.code}`).slice(0, 300);
|
|
7460
7813
|
async function installedVersion(o) {
|
|
7461
|
-
const r = await (o.exec ?? execCapture)([o.node,
|
|
7814
|
+
const r = await (o.exec ?? execCapture)([o.node, path27.join(o.distDir, "cli.js"), "--version"], {
|
|
7462
7815
|
env: o.env ?? process.env,
|
|
7463
7816
|
timeoutMs: 3e4
|
|
7464
7817
|
});
|
|
@@ -7470,7 +7823,7 @@ async function installVersion(version, o) {
|
|
|
7470
7823
|
const platform = o.platform ?? process.platform;
|
|
7471
7824
|
const exec = o.exec ?? execCapture;
|
|
7472
7825
|
const base = o.env ?? process.env;
|
|
7473
|
-
const env = { ...base, PATH: [
|
|
7826
|
+
const env = { ...base, PATH: [path27.dirname(o.node), base.PATH].filter(Boolean).join(path27.delimiter) };
|
|
7474
7827
|
const prefix = globalPrefix(o.distDir, platform);
|
|
7475
7828
|
if (!prefix)
|
|
7476
7829
|
return { ok: false, ms: Date.now() - t0, stage: "prefix", error: "this copy isn't a global npm install" };
|
|
@@ -7501,7 +7854,7 @@ async function installVersion(version, o) {
|
|
|
7501
7854
|
let hooks = "no harness set up here; nothing to re-arm";
|
|
7502
7855
|
if (o.harnesses.length) {
|
|
7503
7856
|
const h = await exec(
|
|
7504
|
-
[o.node,
|
|
7857
|
+
[o.node, path27.join(o.distDir, "cli.js"), "install", "--backfill", "0", ...o.harnesses.map((n) => `--${n}`)],
|
|
7505
7858
|
{ env, timeoutMs: 2 * 6e4 }
|
|
7506
7859
|
);
|
|
7507
7860
|
hooks = h.code === 0 ? `hooks re-armed for ${o.harnesses.join(", ")}` : `re-arming the hooks failed (${lastLine(h)}); \`sessionpipe install\` does it`;
|
|
@@ -7542,7 +7895,7 @@ function installLocation() {
|
|
|
7542
7895
|
}
|
|
7543
7896
|
return {
|
|
7544
7897
|
prefix,
|
|
7545
|
-
target: /\/Cellar\/node(?:@\d+)?\/[^/]+/.test(prefix) ?
|
|
7898
|
+
target: /\/Cellar\/node(?:@\d+)?\/[^/]+/.test(prefix) ? path28.join(os11.homedir(), ".local") : prefix
|
|
7546
7899
|
};
|
|
7547
7900
|
}
|
|
7548
7901
|
function assertSetupVersion(allowDowngrade, target2) {
|
|
@@ -7557,7 +7910,7 @@ function assertSetupVersion(allowDowngrade, target2) {
|
|
|
7557
7910
|
check(hooks.version, hooks.file);
|
|
7558
7911
|
const prefix = target2 ?? installLocation().target;
|
|
7559
7912
|
if (!prefix) return;
|
|
7560
|
-
const pkg =
|
|
7913
|
+
const pkg = path28.join(
|
|
7561
7914
|
prefix,
|
|
7562
7915
|
...process.platform === "win32" ? [] : ["lib"],
|
|
7563
7916
|
"node_modules",
|
|
@@ -7566,7 +7919,7 @@ function assertSetupVersion(allowDowngrade, target2) {
|
|
|
7566
7919
|
);
|
|
7567
7920
|
let installed;
|
|
7568
7921
|
try {
|
|
7569
|
-
installed = JSON.parse(
|
|
7922
|
+
installed = JSON.parse(readFileSync17(pkg, "utf8")).version;
|
|
7570
7923
|
} catch {
|
|
7571
7924
|
}
|
|
7572
7925
|
if (typeof installed === "string") check(installed, pkg);
|
|
@@ -7580,9 +7933,9 @@ function rerunGlobally(argv, out2, setup2) {
|
|
|
7580
7933
|
execFileSync7("npm", ["install", "-g", `sessionpipe@${VERSION2}`, ...target2 ? ["--prefix", target2] : []], {
|
|
7581
7934
|
stdio: ["ignore", "ignore", "inherit"]
|
|
7582
7935
|
});
|
|
7583
|
-
bin = process.platform === "win32" ?
|
|
7936
|
+
bin = process.platform === "win32" ? path28.join(target2, "sessionpipe.cmd") : target2 ? path28.join(target2, "bin", "sessionpipe") : "sessionpipe";
|
|
7584
7937
|
if (target2 && target2 !== prefix)
|
|
7585
|
-
out2(` Installed into ${target2}; make sure ${
|
|
7938
|
+
out2(` Installed into ${target2}; make sure ${path28.join(target2, "bin")} is on your PATH.`);
|
|
7586
7939
|
} catch {
|
|
7587
7940
|
out2(
|
|
7588
7941
|
" ! couldn't install globally (`npm install -g sessionpipe` failed). Run it yourself, then retry this command."
|
|
@@ -7663,7 +8016,7 @@ async function askOnce(rest, flags, io, env, harnesses) {
|
|
|
7663
8016
|
session = target2;
|
|
7664
8017
|
} else if (!HARNESS.test(target2)) return usage(`${target2} isn't an agent's name (claude-code, codex, gemini-cli, \u2026)`);
|
|
7665
8018
|
const given = flags["--text"];
|
|
7666
|
-
const text = given === "-" ? (io.stdin ?? (() =>
|
|
8019
|
+
const text = given === "-" ? (io.stdin ?? (() => readFileSync18(0, "utf8")))() : typeof given === "string" ? given : rest.slice(1).join(" ");
|
|
7667
8020
|
if (!text.trim()) return usage("nothing to ask: put the task in quotes after the agent, or pipe it with `--text -`");
|
|
7668
8021
|
if (text.length > TEXT_MAX2)
|
|
7669
8022
|
return usage(
|
|
@@ -7674,7 +8027,7 @@ async function askOnce(rest, flags, io, env, harnesses) {
|
|
|
7674
8027
|
return usage("--permission is read or safe: an asked agent never runs wider than this machine's mode");
|
|
7675
8028
|
const model = flags["--model"];
|
|
7676
8029
|
if (model === "") return usage("--model needs a name");
|
|
7677
|
-
const cwd =
|
|
8030
|
+
const cwd = path29.resolve(typeof flags["--cwd"] === "string" ? flags["--cwd"] : process.cwd());
|
|
7678
8031
|
const from = waitSession(void 0, env) ?? void 0;
|
|
7679
8032
|
const chain = (env.SESSIONPIPE_ASK_CHAIN ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
7680
8033
|
const req = {
|
|
@@ -7799,9 +8152,9 @@ function on(argv, io, env, distDir2, harnesses, noService, yes) {
|
|
|
7799
8152
|
);
|
|
7800
8153
|
if (noService) io.out(" Daemon: not installed as a service (--no-service); run `sessionpipe control run` yourself");
|
|
7801
8154
|
else {
|
|
7802
|
-
let cli =
|
|
8155
|
+
let cli = path29.join(distDir2 ?? ".", "cli.js");
|
|
7803
8156
|
try {
|
|
7804
|
-
cli = statSync8(cli).isFile() ?
|
|
8157
|
+
cli = statSync8(cli).isFile() ? path29.resolve(cli) : cli;
|
|
7805
8158
|
} catch {
|
|
7806
8159
|
}
|
|
7807
8160
|
io.out(` Daemon: ${installService2({ node: stableNode(process.execPath), cli, env })}`);
|
|
@@ -7861,8 +8214,8 @@ ${ASK_CLOSE}`;
|
|
|
7861
8214
|
function setupFiles(env = process.env) {
|
|
7862
8215
|
const home = env.HOME || homeDir();
|
|
7863
8216
|
return [
|
|
7864
|
-
...claudeDirs(env, home).map((d) =>
|
|
7865
|
-
|
|
8217
|
+
...claudeDirs(env, home).map((d) => path29.join(d, "CLAUDE.md")),
|
|
8218
|
+
path29.join(env.CODEX_HOME || path29.join(home, ".codex"), "AGENTS.md")
|
|
7866
8219
|
];
|
|
7867
8220
|
}
|
|
7868
8221
|
var sectionAt = (text) => {
|
|
@@ -7891,8 +8244,8 @@ function setupAsk(env, agents, remove) {
|
|
|
7891
8244
|
const section = askSection(agents);
|
|
7892
8245
|
const out2 = [];
|
|
7893
8246
|
for (const file of setupFiles(env)) {
|
|
7894
|
-
const had = existsSync14(file) ?
|
|
7895
|
-
if (had === null && (remove || !existsSync14(
|
|
8247
|
+
const had = existsSync14(file) ? readFileSync18(file, "utf8") : null;
|
|
8248
|
+
if (had === null && (remove || !existsSync14(path29.dirname(file)))) {
|
|
7896
8249
|
out2.push({ file, did: remove ? "absent" : "skipped" });
|
|
7897
8250
|
continue;
|
|
7898
8251
|
}
|
|
@@ -7900,8 +8253,8 @@ function setupAsk(env, agents, remove) {
|
|
|
7900
8253
|
const next2 = withoutAskSection(had);
|
|
7901
8254
|
if (next2 === had) out2.push({ file, did: "absent" });
|
|
7902
8255
|
else {
|
|
7903
|
-
if (next2 === "")
|
|
7904
|
-
else
|
|
8256
|
+
if (next2 === "") unlinkSync5(file);
|
|
8257
|
+
else writeFileSync10(file, next2);
|
|
7905
8258
|
out2.push({ file, did: "removed" });
|
|
7906
8259
|
}
|
|
7907
8260
|
continue;
|
|
@@ -7909,8 +8262,8 @@ function setupAsk(env, agents, remove) {
|
|
|
7909
8262
|
const next = withAskSection(had, section);
|
|
7910
8263
|
if (next === had) out2.push({ file, did: "unchanged" });
|
|
7911
8264
|
else {
|
|
7912
|
-
|
|
7913
|
-
|
|
8265
|
+
mkdirSync12(path29.dirname(file), { recursive: true });
|
|
8266
|
+
writeFileSync10(file, next);
|
|
7914
8267
|
out2.push({ file, did: had === null ? "added" : "updated" });
|
|
7915
8268
|
}
|
|
7916
8269
|
}
|
|
@@ -7934,15 +8287,15 @@ function setup(io, env, harnesses, remove) {
|
|
|
7934
8287
|
}
|
|
7935
8288
|
|
|
7936
8289
|
// src/control/autoupdate.ts
|
|
7937
|
-
import { readFileSync as
|
|
7938
|
-
import
|
|
8290
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
8291
|
+
import path30 from "node:path";
|
|
7939
8292
|
var DAY_MS = 24 * 36e5;
|
|
7940
8293
|
var LABEL4 = "org.sessionpipe.control";
|
|
7941
8294
|
var UNIT2 = "sessionpipe-control.service";
|
|
7942
|
-
var updateFile = (env = process.env) =>
|
|
8295
|
+
var updateFile = (env = process.env) => path30.join(controlState(env), "update.json");
|
|
7943
8296
|
function readUpdate(file) {
|
|
7944
8297
|
try {
|
|
7945
|
-
const j = JSON.parse(
|
|
8298
|
+
const j = JSON.parse(readFileSync19(file, "utf8"));
|
|
7946
8299
|
return j && typeof j === "object" ? j : {};
|
|
7947
8300
|
} catch {
|
|
7948
8301
|
return {};
|
|
@@ -7963,7 +8316,8 @@ function autoUpdateOff(o) {
|
|
|
7963
8316
|
}
|
|
7964
8317
|
function updateSummary(o) {
|
|
7965
8318
|
const daemonEnv = o.rec.off?.startsWith("SESSIONPIPE_NO_UPDATE_CHECK") ? "SESSIONPIPE_NO_UPDATE_CHECK is set in the daemon's environment" : null;
|
|
7966
|
-
const
|
|
8319
|
+
const byHand = (o.platform ?? process.platform) === "win32" ? "it runs in the control daemon, which has no service on Windows yet: it checks while `sessionpipe control run` is running" : null;
|
|
8320
|
+
const off3 = o.off ?? (o.paired ? daemonEnv ?? byHand : "it runs in the control daemon, which isn't set up here (`sessionpipe connect`)");
|
|
7967
8321
|
return {
|
|
7968
8322
|
auto: !off3,
|
|
7969
8323
|
off: off3,
|
|
@@ -8005,7 +8359,7 @@ function restartVia(o) {
|
|
|
8005
8359
|
if (o.platform === "linux") {
|
|
8006
8360
|
let cg = "";
|
|
8007
8361
|
try {
|
|
8008
|
-
cg = (o.cgroup ?? (() =>
|
|
8362
|
+
cg = (o.cgroup ?? (() => readFileSync19("/proc/self/cgroup", "utf8")))();
|
|
8009
8363
|
} catch {
|
|
8010
8364
|
}
|
|
8011
8365
|
const inUnit = cg.split("\n").some((l) => l.endsWith(`/${UNIT2}`) || l.includes(`/${UNIT2}/`));
|
|
@@ -8197,14 +8551,14 @@ var AutoUpdater = class {
|
|
|
8197
8551
|
// src/control/cli.ts
|
|
8198
8552
|
import { existsSync as existsSync20, realpathSync as realpathSync7, statSync as statSync13, unwatchFile, watchFile } from "node:fs";
|
|
8199
8553
|
import os14 from "node:os";
|
|
8200
|
-
import
|
|
8554
|
+
import path37 from "node:path";
|
|
8201
8555
|
|
|
8202
8556
|
// src/pool/index.ts
|
|
8203
|
-
import { randomBytes as
|
|
8557
|
+
import { randomBytes as randomBytes6 } from "node:crypto";
|
|
8204
8558
|
import { existsSync as existsSync17 } from "node:fs";
|
|
8205
8559
|
import http2 from "node:http";
|
|
8206
8560
|
import net2 from "node:net";
|
|
8207
|
-
import
|
|
8561
|
+
import path33 from "node:path";
|
|
8208
8562
|
|
|
8209
8563
|
// src/pool/credentials.ts
|
|
8210
8564
|
var REFRESH_EARLY_MS = 5 * 6e4;
|
|
@@ -8346,14 +8700,14 @@ import {
|
|
|
8346
8700
|
chmodSync as chmodSync6,
|
|
8347
8701
|
existsSync as existsSync15,
|
|
8348
8702
|
lstatSync,
|
|
8349
|
-
mkdirSync as
|
|
8350
|
-
readFileSync as
|
|
8703
|
+
mkdirSync as mkdirSync13,
|
|
8704
|
+
readFileSync as readFileSync20,
|
|
8351
8705
|
realpathSync as realpathSync4,
|
|
8352
8706
|
statSync as statSync9,
|
|
8353
|
-
unlinkSync as
|
|
8354
|
-
writeFileSync as
|
|
8707
|
+
unlinkSync as unlinkSync6,
|
|
8708
|
+
writeFileSync as writeFileSync11
|
|
8355
8709
|
} from "node:fs";
|
|
8356
|
-
import
|
|
8710
|
+
import path31 from "node:path";
|
|
8357
8711
|
var POOL_ENV_KEYS = [
|
|
8358
8712
|
"ANTHROPIC_BASE_URL",
|
|
8359
8713
|
"ANTHROPIC_AUTH_TOKEN",
|
|
@@ -8376,7 +8730,7 @@ var anySet = (env) => POOL_ENV_KEYS.some((k) => env[k] !== void 0);
|
|
|
8376
8730
|
function readJsonFile2(file) {
|
|
8377
8731
|
let raw;
|
|
8378
8732
|
try {
|
|
8379
|
-
raw =
|
|
8733
|
+
raw = readFileSync20(file, "utf8");
|
|
8380
8734
|
} catch {
|
|
8381
8735
|
return { ok: true, existed: false, value: {}, shape: { indent: " ", eol: "\n", finalNewline: true }, raw: "" };
|
|
8382
8736
|
}
|
|
@@ -8402,7 +8756,7 @@ function formatJson2(value, f) {
|
|
|
8402
8756
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
8403
8757
|
return f.finalNewline ? text + f.eol : text;
|
|
8404
8758
|
}
|
|
8405
|
-
var settingsIn2 = (dir) =>
|
|
8759
|
+
var settingsIn2 = (dir) => path31.join(dir, "settings.json");
|
|
8406
8760
|
var envOf = (settings2) => settings2.env && typeof settings2.env === "object" && !Array.isArray(settings2.env) ? settings2.env : null;
|
|
8407
8761
|
var target = (file) => {
|
|
8408
8762
|
try {
|
|
@@ -8422,7 +8776,7 @@ var same = (a, b) => {
|
|
|
8422
8776
|
};
|
|
8423
8777
|
return a === b || real(a) === real(b);
|
|
8424
8778
|
};
|
|
8425
|
-
var kept = (keep, file) =>
|
|
8779
|
+
var kept = (keep, file) => path31.join(keep, `${createHash8("sha256").update(file).digest("hex").slice(0, 16)}.json`);
|
|
8426
8780
|
var looseDir = (dir) => {
|
|
8427
8781
|
try {
|
|
8428
8782
|
return process.platform !== "win32" && (statSync9(dir).mode & 18) !== 0;
|
|
@@ -8460,11 +8814,11 @@ function installPoolEnv(dirs, o) {
|
|
|
8460
8814
|
if (!changed) return { file, changed: false, ...note };
|
|
8461
8815
|
settings2.env = env;
|
|
8462
8816
|
if (o.keep && f.existed && !existsSync15(kept(o.keep, file))) {
|
|
8463
|
-
|
|
8464
|
-
|
|
8817
|
+
mkdirSync13(o.keep, { recursive: true, mode: 448 });
|
|
8818
|
+
writeFileSync11(kept(o.keep, file), f.raw, { mode: 384 });
|
|
8465
8819
|
}
|
|
8466
|
-
|
|
8467
|
-
|
|
8820
|
+
mkdirSync13(path31.dirname(file), { recursive: true, mode: 448 });
|
|
8821
|
+
writeFileSync11(file, formatJson2(settings2, f.shape));
|
|
8468
8822
|
try {
|
|
8469
8823
|
chmodSync6(file, 384);
|
|
8470
8824
|
} catch {
|
|
@@ -8485,20 +8839,20 @@ function uninstallPoolEnv(dirs, o) {
|
|
|
8485
8839
|
for (const k of POOL_ENV_KEYS) delete env[k];
|
|
8486
8840
|
if (!Object.keys(env).length) delete settings2.env;
|
|
8487
8841
|
if (o.created?.some((c) => same(c, file)) && !Object.keys(settings2).length) {
|
|
8488
|
-
|
|
8842
|
+
unlinkSync6(file);
|
|
8489
8843
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
8490
8844
|
}
|
|
8491
8845
|
const original = o.keep ? kept(o.keep, file) : null;
|
|
8492
8846
|
let text = formatJson2(settings2, f.shape);
|
|
8493
8847
|
if (original && existsSync15(original)) {
|
|
8494
|
-
const raw =
|
|
8848
|
+
const raw = readFileSync20(original, "utf8");
|
|
8495
8849
|
try {
|
|
8496
8850
|
if (JSON.stringify(JSON.parse(raw)) === JSON.stringify(settings2)) text = raw;
|
|
8497
8851
|
} catch {
|
|
8498
8852
|
}
|
|
8499
|
-
|
|
8853
|
+
unlinkSync6(original);
|
|
8500
8854
|
}
|
|
8501
|
-
|
|
8855
|
+
writeFileSync11(file, text);
|
|
8502
8856
|
return { file, changed: true };
|
|
8503
8857
|
});
|
|
8504
8858
|
}
|
|
@@ -9003,16 +9357,16 @@ function startPoolServer(deps) {
|
|
|
9003
9357
|
}
|
|
9004
9358
|
|
|
9005
9359
|
// src/pool/store.ts
|
|
9006
|
-
import { existsSync as existsSync16, readFileSync as
|
|
9007
|
-
import
|
|
9008
|
-
var poolFile = (env = process.env) =>
|
|
9009
|
-
var poolSecretsFile = (env = process.env) =>
|
|
9010
|
-
var poolStateFile = (env = process.env) =>
|
|
9360
|
+
import { existsSync as existsSync16, readFileSync as readFileSync21 } from "node:fs";
|
|
9361
|
+
import path32 from "node:path";
|
|
9362
|
+
var poolFile = (env = process.env) => path32.join(path32.dirname(configFile(env)), "pool.json");
|
|
9363
|
+
var poolSecretsFile = (env = process.env) => path32.join(path32.dirname(configFile(env)), "pool.secrets.json");
|
|
9364
|
+
var poolStateFile = (env = process.env) => path32.join(stateDir(env), "pool", "state.json");
|
|
9011
9365
|
var EMPTY = { on: false, port: 0, accounts: [] };
|
|
9012
9366
|
var UUID_RE5 = /^[0-9a-f-]{8,64}$/i;
|
|
9013
9367
|
function readJson2(file) {
|
|
9014
9368
|
try {
|
|
9015
|
-
const j = JSON.parse(
|
|
9369
|
+
const j = JSON.parse(readFileSync21(file, "utf8"));
|
|
9016
9370
|
return j && typeof j === "object" && !Array.isArray(j) ? j : null;
|
|
9017
9371
|
} catch {
|
|
9018
9372
|
return null;
|
|
@@ -9270,7 +9624,7 @@ function openPool(deps) {
|
|
|
9270
9624
|
const mint = () => {
|
|
9271
9625
|
placeSecrets();
|
|
9272
9626
|
const before = secrets2.get(cfg, TOKEN_NAME);
|
|
9273
|
-
const t =
|
|
9627
|
+
const t = randomBytes6(32).toString("base64url");
|
|
9274
9628
|
if (!secrets2.set(cfg, TOKEN_NAME, t)) throw new PoolError("pool", "the secrets store refused the pool's token");
|
|
9275
9629
|
if (before && secrets2.set(cfg, PREVIOUS_TOKEN_NAME, before)) cfg.rotated_at = new Date(now()).toISOString();
|
|
9276
9630
|
saveConfig();
|
|
@@ -9298,7 +9652,7 @@ function openPool(deps) {
|
|
|
9298
9652
|
token: token ?? "",
|
|
9299
9653
|
lastPort: cfg.last_port,
|
|
9300
9654
|
lastToken,
|
|
9301
|
-
keep:
|
|
9655
|
+
keep: path33.join(stateDir(env), "pool", "settings")
|
|
9302
9656
|
});
|
|
9303
9657
|
const resync = () => {
|
|
9304
9658
|
const by = new Map(policy.accounts.map((a) => [a.id, a]));
|
|
@@ -9838,15 +10192,15 @@ async function runChannel(session, input, write, o) {
|
|
|
9838
10192
|
|
|
9839
10193
|
// src/control/daemon.ts
|
|
9840
10194
|
import { randomUUID } from "node:crypto";
|
|
9841
|
-
import { chmodSync as chmodSync7, existsSync as existsSync19, mkdirSync as
|
|
10195
|
+
import { chmodSync as chmodSync7, existsSync as existsSync19, mkdirSync as mkdirSync15, readFileSync as readFileSync23, realpathSync as realpathSync6, statSync as statSync12, unlinkSync as unlinkSync7 } from "node:fs";
|
|
9842
10196
|
import net4 from "node:net";
|
|
9843
10197
|
import os13 from "node:os";
|
|
9844
|
-
import
|
|
10198
|
+
import path36 from "node:path";
|
|
9845
10199
|
|
|
9846
10200
|
// src/control/files.ts
|
|
9847
10201
|
import { createHash as createHash10 } from "node:crypto";
|
|
9848
|
-
import { existsSync as existsSync18, lstatSync as lstatSync2, mkdirSync as
|
|
9849
|
-
import
|
|
10202
|
+
import { existsSync as existsSync18, lstatSync as lstatSync2, mkdirSync as mkdirSync14, realpathSync as realpathSync5, rmSync as rmSync2, writeFileSync as writeFileSync12 } from "node:fs";
|
|
10203
|
+
import path34 from "node:path";
|
|
9850
10204
|
var FETCH_TIMEOUT_MS = 5 * 6e4;
|
|
9851
10205
|
var FileProblem = class extends Error {
|
|
9852
10206
|
};
|
|
@@ -9899,30 +10253,30 @@ async function fetchOne(f, o) {
|
|
|
9899
10253
|
}
|
|
9900
10254
|
var tooLong = (label, f) => new FileProblem(`${label} is larger than its signed size (${f.size} bytes)`);
|
|
9901
10255
|
function realDir(p, why2) {
|
|
9902
|
-
|
|
10256
|
+
mkdirSync14(p, { recursive: true, mode: 448 });
|
|
9903
10257
|
if (lstatSync2(p).isSymbolicLink()) throw new FileProblem(`${why2} is a symlink`);
|
|
9904
10258
|
}
|
|
9905
10259
|
function writeFiles(cwd, session, files, message) {
|
|
9906
|
-
const top =
|
|
9907
|
-
const parent =
|
|
10260
|
+
const top = path34.join(cwd, ".sessionpipe");
|
|
10261
|
+
const parent = path34.join(top, "files");
|
|
9908
10262
|
const sub = message ? `${session}/${message}` : session;
|
|
9909
|
-
const dir =
|
|
10263
|
+
const dir = path34.join(parent, session, ...message ? [message] : []);
|
|
9910
10264
|
let made = false;
|
|
9911
10265
|
try {
|
|
9912
10266
|
for (const part of [session, message])
|
|
9913
10267
|
if (part === "." || part === "..") throw new FileProblem(`no folder ${part}`);
|
|
9914
10268
|
realDir(top, ".sessionpipe");
|
|
9915
|
-
const ignore =
|
|
9916
|
-
if (!existsSync18(ignore))
|
|
10269
|
+
const ignore = path34.join(top, ".gitignore");
|
|
10270
|
+
if (!existsSync18(ignore)) writeFileSync12(ignore, "*\n", { flag: "wx", mode: 384 });
|
|
9917
10271
|
realDir(parent, ".sessionpipe/files");
|
|
9918
|
-
if (message) realDir(
|
|
10272
|
+
if (message) realDir(path34.join(parent, session), `.sessionpipe/files/${session}`);
|
|
9919
10273
|
if (existsSync18(dir)) throw new FileProblem(`.sessionpipe/files/${sub} already exists`);
|
|
9920
|
-
|
|
10274
|
+
mkdirSync14(dir, { mode: 448 });
|
|
9921
10275
|
made = true;
|
|
9922
10276
|
if (realpathSync5.native(dir) !== dir) throw new FileProblem(".sessionpipe leads outside the session's folder");
|
|
9923
10277
|
const rel = [];
|
|
9924
10278
|
for (const f of files) {
|
|
9925
|
-
|
|
10279
|
+
writeFileSync12(path34.join(dir, f.name), f.bytes, { flag: "wx", mode: 384 });
|
|
9926
10280
|
rel.push(`.sessionpipe/files/${sub}/${f.name}`);
|
|
9927
10281
|
}
|
|
9928
10282
|
return rel;
|
|
@@ -9947,9 +10301,9 @@ ${lines.join("\n")}`;
|
|
|
9947
10301
|
|
|
9948
10302
|
// src/control/inbox.ts
|
|
9949
10303
|
import { execFileSync as execFileSync8 } from "node:child_process";
|
|
9950
|
-
import { readFileSync as
|
|
10304
|
+
import { readFileSync as readFileSync22, statSync as statSync10 } from "node:fs";
|
|
9951
10305
|
import net3 from "node:net";
|
|
9952
|
-
import
|
|
10306
|
+
import path35 from "node:path";
|
|
9953
10307
|
var PEER_PROTOCOL = 1;
|
|
9954
10308
|
var CONFIRM_MS = 8e3;
|
|
9955
10309
|
var IDLE_STATUS = "idle";
|
|
@@ -10006,9 +10360,9 @@ function vetInbox(rec, o) {
|
|
|
10006
10360
|
code: "not_a_window",
|
|
10007
10361
|
detail: "the live process holding that session isn't one of this computer's own Claude Code windows"
|
|
10008
10362
|
};
|
|
10009
|
-
if (!rec.socket || !
|
|
10363
|
+
if (!rec.socket || !path35.isAbsolute(rec.socket))
|
|
10010
10364
|
return { code: "no_inbox", detail: "that session's Claude Code published no local inbox" };
|
|
10011
|
-
if (o.configDir &&
|
|
10365
|
+
if (o.configDir && path35.resolve(o.configDir) !== path35.resolve(rec.configDir))
|
|
10012
10366
|
return {
|
|
10013
10367
|
code: "other_home",
|
|
10014
10368
|
detail: "the live process is signed in to a different Claude Code account from the one holding that transcript"
|
|
@@ -10085,7 +10439,7 @@ var psStart = (pid) => execFileSync8("ps", ["-o", "lstart=", "-p", String(pid)],
|
|
|
10085
10439
|
function peerToken(rec) {
|
|
10086
10440
|
if (!rec.keyFile) return null;
|
|
10087
10441
|
try {
|
|
10088
|
-
const j = JSON.parse(
|
|
10442
|
+
const j = JSON.parse(readFileSync22(rec.keyFile, "utf8"));
|
|
10089
10443
|
return typeof j.peerToken === "string" && TOKEN_RE.test(j.peerToken) ? j.peerToken : null;
|
|
10090
10444
|
} catch {
|
|
10091
10445
|
return null;
|
|
@@ -10480,7 +10834,7 @@ function startNamed(cmd) {
|
|
|
10480
10834
|
return null;
|
|
10481
10835
|
}
|
|
10482
10836
|
}
|
|
10483
|
-
var VERSION3 = true ? "0.13.
|
|
10837
|
+
var VERSION3 = true ? "0.13.1" : "0.0.0";
|
|
10484
10838
|
var MAX_REPLY = 2e4;
|
|
10485
10839
|
var HELLO_EVERY_MS = 10 * 6e4;
|
|
10486
10840
|
var TURN_FALLBACK_MS = 30 * 6e4;
|
|
@@ -10524,7 +10878,7 @@ function askProblem(req) {
|
|
|
10524
10878
|
if (r.from !== void 0 && !parseSessionRef(r.from)) return "from is the asking session, <harness>:<id>";
|
|
10525
10879
|
if (typeof r.text !== "string" || !r.text.trim() || r.text.length > TEXT_MAX)
|
|
10526
10880
|
return `an ask needs text, at most ${TEXT_MAX} characters`;
|
|
10527
|
-
if (typeof r.cwd !== "string" || !
|
|
10881
|
+
if (typeof r.cwd !== "string" || !path36.isAbsolute(r.cwd) || r.cwd.length > 1024) return "cwd is an absolute folder";
|
|
10528
10882
|
if (!Array.isArray(r.chain) || r.chain.length > ASK_CHAIN_MAX || r.chain.some((c) => !parseSessionRef(c)))
|
|
10529
10883
|
return "chain is a list of sessions, <harness>:<id>, outermost first: the asks this one is nested in";
|
|
10530
10884
|
if (r.permission !== void 0 && r.permission !== "read" && r.permission !== "safe")
|
|
@@ -10562,9 +10916,9 @@ var ControlDaemon = class {
|
|
|
10562
10916
|
this.dir = dir;
|
|
10563
10917
|
this.sock = sock;
|
|
10564
10918
|
this.deps = deps;
|
|
10565
|
-
this.nonces = new NonceStore(
|
|
10566
|
-
this.acks = new AckOutbox(
|
|
10567
|
-
this.state = readState(
|
|
10919
|
+
this.nonces = new NonceStore(path36.join(dir, "nonces.jsonl"), deps.now);
|
|
10920
|
+
this.acks = new AckOutbox(path36.join(dir, "acks.jsonl"));
|
|
10921
|
+
this.state = readState(path36.join(dir, "state.json"), deps.now());
|
|
10568
10922
|
this.aliases = readAliases(aliasFile(dir));
|
|
10569
10923
|
}
|
|
10570
10924
|
cfg;
|
|
@@ -10645,7 +10999,7 @@ var ControlDaemon = class {
|
|
|
10645
10999
|
// --- lifecycle ------------------------------------------------------------------
|
|
10646
11000
|
async start() {
|
|
10647
11001
|
this.running = true;
|
|
10648
|
-
|
|
11002
|
+
mkdirSync15(this.dir, { recursive: true, mode: 448 });
|
|
10649
11003
|
await this.listen();
|
|
10650
11004
|
await this.syncPool();
|
|
10651
11005
|
for (const r of this.cfg.receivers) void this.pollLoop(r);
|
|
@@ -10688,7 +11042,7 @@ var ControlDaemon = class {
|
|
|
10688
11042
|
this.server = null;
|
|
10689
11043
|
if (process.platform !== "win32" && this.sockId && socketId(this.sock) === this.sockId)
|
|
10690
11044
|
try {
|
|
10691
|
-
|
|
11045
|
+
unlinkSync7(this.sock);
|
|
10692
11046
|
} catch {
|
|
10693
11047
|
}
|
|
10694
11048
|
this.sockId = null;
|
|
@@ -10838,7 +11192,7 @@ var ControlDaemon = class {
|
|
|
10838
11192
|
if (other?.op === "status") throw new DaemonRunning(other.status ?? {});
|
|
10839
11193
|
if (process.platform !== "win32") {
|
|
10840
11194
|
try {
|
|
10841
|
-
if (existsSync19(this.sock))
|
|
11195
|
+
if (existsSync19(this.sock)) unlinkSync7(this.sock);
|
|
10842
11196
|
} catch {
|
|
10843
11197
|
}
|
|
10844
11198
|
}
|
|
@@ -11479,9 +11833,9 @@ ${IN_PLACE_ANSWER}`,
|
|
|
11479
11833
|
/** The folder the worker recorded for a session from its hooks (run.ts, facts). */
|
|
11480
11834
|
factsCwd(harness, id) {
|
|
11481
11835
|
try {
|
|
11482
|
-
const f =
|
|
11483
|
-
const cwd = JSON.parse(
|
|
11484
|
-
return typeof cwd === "string" &&
|
|
11836
|
+
const f = path36.join(path36.dirname(this.dir), "facts", safeId(harness), `${safeId(id)}.json`);
|
|
11837
|
+
const cwd = JSON.parse(readFileSync23(f, "utf8")).cwd;
|
|
11838
|
+
return typeof cwd === "string" && path36.isAbsolute(cwd) ? cwd : void 0;
|
|
11485
11839
|
} catch {
|
|
11486
11840
|
return void 0;
|
|
11487
11841
|
}
|
|
@@ -11668,7 +12022,7 @@ ${IN_PLACE_ANSWER}`,
|
|
|
11668
12022
|
* absolute, or under this user's home as `~/…` (a start's `cwd` may say either). */
|
|
11669
12023
|
allowed(folder) {
|
|
11670
12024
|
const dir = untilde(folder);
|
|
11671
|
-
if (!
|
|
12025
|
+
if (!path36.isAbsolute(dir)) return null;
|
|
11672
12026
|
let r;
|
|
11673
12027
|
try {
|
|
11674
12028
|
r = realpathSync6.native(dir);
|
|
@@ -11682,7 +12036,7 @@ ${IN_PLACE_ANSWER}`,
|
|
|
11682
12036
|
rf = realpathSync6.native(f);
|
|
11683
12037
|
} catch {
|
|
11684
12038
|
}
|
|
11685
|
-
if (r === rf || r.startsWith(rf.endsWith(
|
|
12039
|
+
if (r === rf || r.startsWith(rf.endsWith(path36.sep) ? rf : rf + path36.sep)) return r;
|
|
11686
12040
|
}
|
|
11687
12041
|
return null;
|
|
11688
12042
|
}
|
|
@@ -11813,7 +12167,7 @@ ${IN_PLACE_ANSWER}`,
|
|
|
11813
12167
|
* logged and nothing else: it costs a record, never a delivery. */
|
|
11814
12168
|
saveState(what) {
|
|
11815
12169
|
try {
|
|
11816
|
-
writeState(
|
|
12170
|
+
writeState(path36.join(this.dir, "state.json"), this.state);
|
|
11817
12171
|
} catch (e) {
|
|
11818
12172
|
this.deps.log(
|
|
11819
12173
|
`couldn't record ${what}: ${String(
|
|
@@ -12565,7 +12919,7 @@ ${COPY_NOTE}` : "");
|
|
|
12565
12919
|
recordStart(session, o) {
|
|
12566
12920
|
this.state.started[session] = o;
|
|
12567
12921
|
try {
|
|
12568
|
-
writeState(
|
|
12922
|
+
writeState(path36.join(this.dir, "state.json"), this.state);
|
|
12569
12923
|
} catch (e) {
|
|
12570
12924
|
this.deps.log(
|
|
12571
12925
|
`couldn't record what ${session.slice(0, 8)} runs with: ${String(
|
|
@@ -13043,7 +13397,7 @@ ${COPY_NOTE}` : "");
|
|
|
13043
13397
|
);
|
|
13044
13398
|
}
|
|
13045
13399
|
async record(on2, type, via, data) {
|
|
13046
|
-
const state2 =
|
|
13400
|
+
const state2 = path36.dirname(this.dir);
|
|
13047
13401
|
const ref = { harness: on2.harness, session: on2.id };
|
|
13048
13402
|
let release = takeLock(state2, ref);
|
|
13049
13403
|
for (let i = 0; !release && i < 40; i++) {
|
|
@@ -13257,13 +13611,13 @@ function changeFolders(folders, op, dirs, home = os14.homedir()) {
|
|
|
13257
13611
|
try {
|
|
13258
13612
|
return realpathSync7.native(d);
|
|
13259
13613
|
} catch {
|
|
13260
|
-
return
|
|
13614
|
+
return path37.resolve(d);
|
|
13261
13615
|
}
|
|
13262
13616
|
};
|
|
13263
13617
|
const next = [...folders];
|
|
13264
13618
|
const said = [];
|
|
13265
13619
|
for (const d of dirs) {
|
|
13266
|
-
const abs =
|
|
13620
|
+
const abs = path37.resolve(d);
|
|
13267
13621
|
const at = next.findIndex((f) => real(f) === real(abs));
|
|
13268
13622
|
if (op === "remove") {
|
|
13269
13623
|
if (at < 0) said.push(` \xB7 ${abs} wasn't allowed`);
|
|
@@ -13300,6 +13654,7 @@ async function controlMain(argv, out2, distDir2) {
|
|
|
13300
13654
|
const raw = argv[2];
|
|
13301
13655
|
if (!raw || raw.startsWith("-")) return out2(`usage:
|
|
13302
13656
|
${CONTROL_HELP}`);
|
|
13657
|
+
assertConfigWritable();
|
|
13303
13658
|
if (viaNpx(distDir2)) return rerunGlobally(argv, out2);
|
|
13304
13659
|
const url = (/^https?:\/\//.test(raw) ? raw : `https://${raw}`).replace(/\/$/, "");
|
|
13305
13660
|
const cfg = readConfig();
|
|
@@ -13319,7 +13674,7 @@ ${CONTROL_HELP}`);
|
|
|
13319
13674
|
);
|
|
13320
13675
|
}
|
|
13321
13676
|
const mode = parseMode(flag2("--mode")) ?? (existing?.mode && existing.receivers.length ? existing.mode : await askMode(out2));
|
|
13322
|
-
const tier = argv
|
|
13677
|
+
const tier = tierArg(argv, 0);
|
|
13323
13678
|
await pair({
|
|
13324
13679
|
url,
|
|
13325
13680
|
token,
|
|
@@ -13340,7 +13695,7 @@ ${CONTROL_HELP}`);
|
|
|
13340
13695
|
const c = readControl();
|
|
13341
13696
|
out2(` Folders sessions may run in: ${c.folders.join(", ")}`);
|
|
13342
13697
|
out2(` Mode: ${c.mode} (${MODE_IS[c.mode]})`);
|
|
13343
|
-
let cli =
|
|
13698
|
+
let cli = path37.join(distDir2, "cli.js");
|
|
13344
13699
|
try {
|
|
13345
13700
|
cli = realpathSync7(cli);
|
|
13346
13701
|
} catch {
|
|
@@ -13559,7 +13914,7 @@ async function runDaemon(out2, distDir2) {
|
|
|
13559
13914
|
log2(`exiting ${UPDATE_EXIT} so ${via === "supervisor" ? "the supervising sessionpipe" : via} starts ${to}`);
|
|
13560
13915
|
process.exit(UPDATE_EXIT);
|
|
13561
13916
|
}
|
|
13562
|
-
let cli =
|
|
13917
|
+
let cli = path37.join(distDir2, "cli.js");
|
|
13563
13918
|
try {
|
|
13564
13919
|
cli = realpathSync7(cli);
|
|
13565
13920
|
} catch {
|
|
@@ -13642,7 +13997,7 @@ function usageTable(harnesses) {
|
|
|
13642
13997
|
connect: "connect <receiver> [--machine NAME] [--rename-machine] [--allow-downgrade] [--tier 0-3] [--mode safe|auto|inherit] [--folder DIR]\u2026 [--status-line|--no-status-line] [--no-service]",
|
|
13643
13998
|
install: `install [${harnesses.map((h) => `--${h}`).join(" ")}] [--machine NAME] [--rename-machine] [--allow-downgrade] [--backfill DAYS] [--sink URL --tier N] [--status-line|--no-status-line]`,
|
|
13644
13999
|
sink: "sink add|list|remove|test (use --help after a subcommand for its options)",
|
|
13645
|
-
"sink add": "sink add <url|file:PATH|stdout> [--tier 0-3] [--token T] [--secret S] [--pii] [--name N]",
|
|
14000
|
+
"sink add": "sink add <url|file:PATH|stdout> [--tier 0-3] [--token T] [--secret S] [--pii] [--tools all|main|beat] [--name N]",
|
|
13646
14001
|
"sink list": "sink list",
|
|
13647
14002
|
"sink remove": "sink remove <name>",
|
|
13648
14003
|
"sink test": "sink test <name>",
|
|
@@ -13653,7 +14008,7 @@ function usageTable(harnesses) {
|
|
|
13653
14008
|
tail: "tail [--session ID] [--tier 0-3] [--harness NAME] [--all] [--json]",
|
|
13654
14009
|
backfill: `backfill [--days 30] [${harnesses.map((h) => `--${h}`).join(" ")}]`,
|
|
13655
14010
|
forget: "forget <harness> <session>",
|
|
13656
|
-
replay: "replay [--sink NAME] [--from-start]",
|
|
14011
|
+
replay: "replay [--sink NAME] [--from-start] [--tools all|main|beat]",
|
|
13657
14012
|
update: "update [off|on] (install the latest now; off/on: the daemon's daily update)",
|
|
13658
14013
|
"update off": "update off",
|
|
13659
14014
|
"update on": "update on",
|
|
@@ -13705,7 +14060,7 @@ function commandHelp(args2, harnesses) {
|
|
|
13705
14060
|
|
|
13706
14061
|
// src/pool/cli.ts
|
|
13707
14062
|
import { realpathSync as realpathSync8 } from "node:fs";
|
|
13708
|
-
import
|
|
14063
|
+
import path38 from "node:path";
|
|
13709
14064
|
var POOL_HELP = ` sessionpipe pool [--json] (the accounts in order, the room each has left)
|
|
13710
14065
|
sessionpipe pool on | off (point every Claude Code here at the pool, or stop)
|
|
13711
14066
|
sessionpipe pool add --import | --login [--code -]
|
|
@@ -13714,7 +14069,7 @@ var VENDOR_MS = 3e4;
|
|
|
13714
14069
|
var LOCAL_MS = 5e3;
|
|
13715
14070
|
var DAEMON_DOWN = " ! the daemon isn't running, so Claude Code can't reach Claude while the pool is on: `sessionpipe control run` (or `sessionpipe pool on` again to install the service)";
|
|
13716
14071
|
function cliPath(distDir2) {
|
|
13717
|
-
const cli =
|
|
14072
|
+
const cli = path38.join(distDir2, "cli.js");
|
|
13718
14073
|
try {
|
|
13719
14074
|
return realpathSync8(cli);
|
|
13720
14075
|
} catch {
|
|
@@ -13913,6 +14268,32 @@ function readSecret(stdin = process.stdin, err = (s) => void process.stderr.writ
|
|
|
13913
14268
|
});
|
|
13914
14269
|
}
|
|
13915
14270
|
|
|
14271
|
+
// src/wellknown.ts
|
|
14272
|
+
var WELL_KNOWN_MAX_AGE_MS = 24 * 60 * 6e4;
|
|
14273
|
+
var wellKnownUrl = (sinkUrl) => `${sinkUrl.replace(/\/$/, "")}/.well-known/sessionpipe`;
|
|
14274
|
+
async function readWellKnown(sinkUrl, timeoutMs, fetchFn = fetch) {
|
|
14275
|
+
const r = await fetchFn(wellKnownUrl(sinkUrl), { signal: AbortSignal.timeout(timeoutMs) });
|
|
14276
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
14277
|
+
return await r.json();
|
|
14278
|
+
}
|
|
14279
|
+
function applyWellKnown(s, wk, tier, now = /* @__PURE__ */ new Date()) {
|
|
14280
|
+
const notes = [];
|
|
14281
|
+
if (typeof wk.max_tier === "number") {
|
|
14282
|
+
s.max_tier = wk.max_tier;
|
|
14283
|
+
if (wk.max_tier < tier)
|
|
14284
|
+
notes.push(`receiver's max_tier is ${wk.max_tier}: sending tier ${wk.max_tier}, not ${tier}`);
|
|
14285
|
+
}
|
|
14286
|
+
if (wk.endpoints) s.endpoints = wk.endpoints;
|
|
14287
|
+
const before = s.receiver_tools;
|
|
14288
|
+
if (isToolsMode(wk.tools)) s.receiver_tools = wk.tools;
|
|
14289
|
+
else delete s.receiver_tools;
|
|
14290
|
+
if (s.receiver_tools !== before && s.receiver_tools && s.receiver_tools !== "all" && !isToolsMode(s.tools))
|
|
14291
|
+
notes.push(`the receiver asks for tool events \`${s.receiver_tools}\` (--tools all keeps every one)`);
|
|
14292
|
+
s.well_known_at = now.toISOString();
|
|
14293
|
+
s.well_known_checked_at = s.well_known_at;
|
|
14294
|
+
return notes;
|
|
14295
|
+
}
|
|
14296
|
+
|
|
13916
14297
|
// src/cli.ts
|
|
13917
14298
|
var print = (s = "") => {
|
|
13918
14299
|
process.stdout.write(`${s}
|
|
@@ -13926,7 +14307,7 @@ var flag = (name) => {
|
|
|
13926
14307
|
return i >= 0 ? args[i + 1] : void 0;
|
|
13927
14308
|
};
|
|
13928
14309
|
var has = (name) => args.includes(name);
|
|
13929
|
-
var distDir =
|
|
14310
|
+
var distDir = path39.dirname(fileURLToPath(import.meta.url));
|
|
13930
14311
|
async function main(argv, io = { out: print, err: (s) => void process.stderr.write(s), exit: (c) => process.exit(c) }) {
|
|
13931
14312
|
args = argv;
|
|
13932
14313
|
out = io.out;
|
|
@@ -13952,7 +14333,7 @@ function hookCommand() {
|
|
|
13952
14333
|
return (a) => ({ argv: [l, ...a], command: `${JSON.stringify(l)} ${a.join(" ")}` });
|
|
13953
14334
|
}
|
|
13954
14335
|
const node = stableNode(process.execPath);
|
|
13955
|
-
let script =
|
|
14336
|
+
let script = path39.join(distDir, "hook.js");
|
|
13956
14337
|
try {
|
|
13957
14338
|
script = realpathSync9(script);
|
|
13958
14339
|
} catch {
|
|
@@ -13962,6 +14343,15 @@ function hookCommand() {
|
|
|
13962
14343
|
command: `${JSON.stringify(node)} ${JSON.stringify(script)} ${a.join(" ")}`
|
|
13963
14344
|
});
|
|
13964
14345
|
}
|
|
14346
|
+
function strayWord(valued = []) {
|
|
14347
|
+
const word2 = args.slice(1).find((a, i) => !a.startsWith("-") && !valued.some((v) => v === args[i]));
|
|
14348
|
+
if (word2 === void 0) return false;
|
|
14349
|
+
const harness = ADAPTERS.some((a) => a.name === word2) ? `; to name that harness, pass --${word2}` : "";
|
|
14350
|
+
out(` ! \`sessionpipe ${args[0]}\` takes options only, and "${word2}" is not one${harness}.`);
|
|
14351
|
+
out(` Nothing was changed. See \`sessionpipe ${args[0]} --help\`.`);
|
|
14352
|
+
process.exitCode = 2;
|
|
14353
|
+
return true;
|
|
14354
|
+
}
|
|
13965
14355
|
function selectedAdapters() {
|
|
13966
14356
|
const named = ADAPTERS.filter((a) => has(`--${a.name}`));
|
|
13967
14357
|
if (named.length) return named;
|
|
@@ -13975,6 +14365,7 @@ async function command(cmd) {
|
|
|
13975
14365
|
);
|
|
13976
14366
|
return usage ? out(usage) : help();
|
|
13977
14367
|
}
|
|
14368
|
+
if (["connect", "install", "sink", "control", "tail"].includes(cmd)) tierArg(args, 0);
|
|
13978
14369
|
switch (cmd) {
|
|
13979
14370
|
case "connect":
|
|
13980
14371
|
return connect();
|
|
@@ -14037,12 +14428,12 @@ Usage:
|
|
|
14037
14428
|
(everything, in one go: hooks, a sink, signed messages, keys)
|
|
14038
14429
|
sessionpipe install [--claude-code --codex --gemini-cli --antigravity --devin] [--machine NAME] [--rename-machine] [--allow-downgrade] [--backfill DAYS] [--sink URL --tier N] [--status-line|--no-status-line]
|
|
14039
14430
|
sessionpipe uninstall [--claude-code --codex --gemini-cli --antigravity --devin | --all] [--keep-state]
|
|
14040
|
-
sessionpipe sink add <url|file:PATH|stdout> [--tier 0-3] [--token T] [--pii] [--name N]
|
|
14431
|
+
sessionpipe sink add <url|file:PATH|stdout> [--tier 0-3] [--token T] [--pii] [--tools all|main|beat] [--name N]
|
|
14041
14432
|
sessionpipe sink list | remove <name> | test <name>
|
|
14042
14433
|
sessionpipe secrets [move keychain|secret-service|file] (where the machine's keys live)
|
|
14043
14434
|
sessionpipe status [--json] | doctor [--json]
|
|
14044
14435
|
sessionpipe tail [--session ID] [--tier N] [--harness NAME]
|
|
14045
|
-
sessionpipe backfill [--days 30] | forget <harness> <session> | replay --sink NAME
|
|
14436
|
+
sessionpipe backfill [--days 30] | forget <harness> <session> | replay --sink NAME [--from-start] [--tools all|main|beat]
|
|
14046
14437
|
sessionpipe update [off|on] (install the latest now; off/on: the daemon's daily update)
|
|
14047
14438
|
sessionpipe titles [on|off|refresh] (on: name Claude Code sessions as claude.ai does; off by default)
|
|
14048
14439
|
${CONTROL_HELP}
|
|
@@ -14052,6 +14443,7 @@ ${ASK_HELP}
|
|
|
14052
14443
|
Docs: https://sessionpipe.org \xB7 nothing leaves this machine until you add a sink.`);
|
|
14053
14444
|
}
|
|
14054
14445
|
function setupConfig() {
|
|
14446
|
+
assertConfigWritable();
|
|
14055
14447
|
const cfg = readConfig();
|
|
14056
14448
|
const requested = flag("--machine");
|
|
14057
14449
|
if ((has("--machine") || has("--rename-machine")) && (!requested || requested.startsWith("-")))
|
|
@@ -14077,6 +14469,7 @@ function setupConfig() {
|
|
|
14077
14469
|
return cfg;
|
|
14078
14470
|
}
|
|
14079
14471
|
async function install() {
|
|
14472
|
+
if (strayWord(["--machine", "--backfill", "--sink", "--tier", "--name", "--token", "--secret"])) return;
|
|
14080
14473
|
const cfg = setupConfig();
|
|
14081
14474
|
if (viaNpx(distDir)) return rerunGlobally(args, out, { allowDowngrade: has("--allow-downgrade") });
|
|
14082
14475
|
assertSetupVersion(has("--allow-downgrade"));
|
|
@@ -14095,11 +14488,17 @@ async function install() {
|
|
|
14095
14488
|
out(
|
|
14096
14489
|
" No sink above tier 0: hooking one tool event per harness (the heartbeat's). `sink add \u2026 --tier 1` or higher hooks the rest."
|
|
14097
14490
|
);
|
|
14098
|
-
installHarnesses(adapters, cfg, lean);
|
|
14491
|
+
const skipped = installHarnesses(adapters, cfg, lean);
|
|
14099
14492
|
writeConfig(cfg);
|
|
14100
14493
|
const days = Number(flag("--backfill") ?? 30);
|
|
14101
14494
|
if (days > 0) await backfillRows(adapters, days, cfg);
|
|
14102
14495
|
out("");
|
|
14496
|
+
if (skipped.length === adapters.length) {
|
|
14497
|
+
process.exitCode = 1;
|
|
14498
|
+
return out(
|
|
14499
|
+
` \u2717 Nothing was hooked: ${skipped.join(", ")} ${skipped.length > 1 ? "were" : "was"} left alone, for the reason above. Fix that and run \`sessionpipe install\` again.`
|
|
14500
|
+
);
|
|
14501
|
+
}
|
|
14103
14502
|
const live = cfg.sinks.filter((s) => !s.paused);
|
|
14104
14503
|
if (live.length) out(` Reporting to ${live.map((s) => `${s.name} (tier ${s.tier})`).join(", ")}.`);
|
|
14105
14504
|
else
|
|
@@ -14141,6 +14540,7 @@ function rehook(cfg, wasLean) {
|
|
|
14141
14540
|
writeConfig(cfg);
|
|
14142
14541
|
}
|
|
14143
14542
|
function installHarnesses(adapters, cfg, lean) {
|
|
14543
|
+
const skipped = [];
|
|
14144
14544
|
if (usesLauncher()) {
|
|
14145
14545
|
const node = stableNode(process.execPath);
|
|
14146
14546
|
const l = installLauncher({ distDir, node, version: VERSION2 });
|
|
@@ -14176,7 +14576,9 @@ function installHarnesses(adapters, cfg, lean) {
|
|
|
14176
14576
|
out(
|
|
14177
14577
|
" note: Codex runs non-managed hooks only after you approve them: start `codex` and accept the hook review. The notify fallback reports turn ends meanwhile."
|
|
14178
14578
|
);
|
|
14579
|
+
if (reports.every((r) => r.skipped)) skipped.push(a.name);
|
|
14179
14580
|
}
|
|
14581
|
+
return skipped;
|
|
14180
14582
|
}
|
|
14181
14583
|
async function connect() {
|
|
14182
14584
|
const raw = args[1];
|
|
@@ -14191,7 +14593,7 @@ async function connect() {
|
|
|
14191
14593
|
const host = new URL(url).host;
|
|
14192
14594
|
const name = machineName(cfg, os15.hostname());
|
|
14193
14595
|
const before = cfg.sinks.find((x) => x.url.replace(/\/$/, "") === url);
|
|
14194
|
-
const tier = connectTier(
|
|
14596
|
+
const tier = connectTier(has("--tier") ? tierArg(args, 0) : void 0, before?.tier);
|
|
14195
14597
|
out(` Connecting ${name} to ${host}.`);
|
|
14196
14598
|
const adapters = ADAPTERS.filter((a) => a.detect());
|
|
14197
14599
|
if (!adapters.length)
|
|
@@ -14268,10 +14670,7 @@ async function connect() {
|
|
|
14268
14670
|
const b = moveControlSecrets(store);
|
|
14269
14671
|
const kept2 = [...a.kept, ...b.kept];
|
|
14270
14672
|
out(` \u2713 Keys: in ${storeLabel(store)}${kept2.length ? ` (left in the file: ${kept2.join(", ")})` : ""}`);
|
|
14271
|
-
} else
|
|
14272
|
-
out(
|
|
14273
|
-
` Keys: in ${tilde(path38.dirname(configFile()))} at 0600 \u2014 no unlocked keychain in this session${process.env.SSH_CONNECTION ? " (an ssh shell)" : ""}`
|
|
14274
|
-
);
|
|
14673
|
+
} else out(` Keys: in ${tilde(path39.dirname(configFile()))} ${keysFileNote()}`);
|
|
14275
14674
|
if (rows2.length) queueBackfill(rows2, 30, readConfig());
|
|
14276
14675
|
out("");
|
|
14277
14676
|
out(` Done: ${name} reports to ${host} and takes messages you sign on a device it trusts.`);
|
|
@@ -14317,11 +14716,11 @@ function secrets() {
|
|
|
14317
14716
|
return;
|
|
14318
14717
|
}
|
|
14319
14718
|
const cfg = readConfig();
|
|
14320
|
-
const where = (s) => s.token_in ? `${storeLabel(s.token_in)}${sinkWithSecrets(s) ? "" : " \u2014 can't be read from this session (locked?)"}` : s.token || s.secret ? `${tilde(configFile())} (
|
|
14719
|
+
const where = (s) => s.token_in ? `${storeLabel(s.token_in)}${sinkWithSecrets(s) ? "" : " \u2014 can't be read from this session (locked?)"}` : s.token || s.secret ? `${tilde(configFile())} (${fileKept()})` : "no key";
|
|
14321
14720
|
for (const s of cfg.sinks) out(` sink ${s.name.padEnd(20)} ${where(s)}`);
|
|
14322
14721
|
for (const r of readControl()?.receivers ?? [])
|
|
14323
14722
|
out(
|
|
14324
|
-
` control ${new URL(r.url).host.padEnd(17)} ${r.token_in ? `${storeLabel(r.token_in)}${r.token ? "" : " \u2014 can't be read from this session (locked?)"}` :
|
|
14723
|
+
` control ${new URL(r.url).host.padEnd(17)} ${r.token_in ? `${storeLabel(r.token_in)}${r.token ? "" : " \u2014 can't be read from this session (locked?)"}` : `control.json (${fileKept()})`}`
|
|
14325
14724
|
);
|
|
14326
14725
|
if (!cfg.sinks.length && !readControl()?.receivers.length) out(" no keys on this machine");
|
|
14327
14726
|
out(` best store from this session: ${storeLabel(bestStore())}`);
|
|
@@ -14341,6 +14740,7 @@ function unisolated() {
|
|
|
14341
14740
|
}).map((a) => a.name);
|
|
14342
14741
|
}
|
|
14343
14742
|
async function uninstall() {
|
|
14743
|
+
if (strayWord()) return;
|
|
14344
14744
|
const named = ADAPTERS.filter((a) => has(`--${a.name}`));
|
|
14345
14745
|
const all = !named.length;
|
|
14346
14746
|
if (all && !has("--all")) {
|
|
@@ -14355,13 +14755,14 @@ async function uninstall() {
|
|
|
14355
14755
|
return;
|
|
14356
14756
|
}
|
|
14357
14757
|
}
|
|
14758
|
+
assertConfigWritable();
|
|
14358
14759
|
if (all || named.some((a) => a.name === "claude-code")) await poolOffForUninstall(out);
|
|
14359
14760
|
const cfg = readConfig();
|
|
14360
14761
|
for (const a of all ? ADAPTERS : named) {
|
|
14361
14762
|
const created = cfg.harnesses[a.name]?.created ?? [];
|
|
14362
14763
|
for (const r of a.uninstall(process.env, { created }))
|
|
14363
|
-
if (r.
|
|
14364
|
-
else if (r.
|
|
14764
|
+
if (r.skipped) out(` ! ${a.name}: ${tilde(r.file)} \u2014 ${r.note}`);
|
|
14765
|
+
else if (r.changed) out(` \u2713 ${a.name}: ${r.note ?? `hooks removed from ${tilde(r.file)}`}`);
|
|
14365
14766
|
delete cfg.harnesses[a.name];
|
|
14366
14767
|
}
|
|
14367
14768
|
writeConfig(cfg);
|
|
@@ -14389,7 +14790,7 @@ async function sink() {
|
|
|
14389
14790
|
if (!cfg.sinks.length) out(" no sinks");
|
|
14390
14791
|
for (const s of cfg.sinks)
|
|
14391
14792
|
out(
|
|
14392
|
-
` ${s.name.padEnd(16)} ${s.url.padEnd(44)} tier ${s.tier}${s.max_tier !== void 0 && s.max_tier < s.tier ? ` (capped ${s.max_tier})` : ""}${s.pii ? " pii" : ""}${s.control ? " control" : ""}${s.paused ? ` PAUSED ${s.paused}` : ""}`
|
|
14793
|
+
` ${s.name.padEnd(16)} ${s.url.padEnd(44)} tier ${s.tier}${s.max_tier !== void 0 && s.max_tier < s.tier ? ` (capped ${s.max_tier})` : ""}${s.pii ? " pii" : ""}${s.control ? " control" : ""}${s.paused ? ` PAUSED ${s.paused}` : ""}${toolsNote(s.tier, s.max_tier, toolsMode(s))}`
|
|
14393
14794
|
);
|
|
14394
14795
|
return;
|
|
14395
14796
|
}
|
|
@@ -14423,16 +14824,25 @@ async function sink() {
|
|
|
14423
14824
|
return;
|
|
14424
14825
|
}
|
|
14425
14826
|
out(
|
|
14426
|
-
"usage: sessionpipe sink add <url|file:PATH|stdout> [--tier N] [--token T] [--pii] [--name N] | list | remove <name> | test <name>"
|
|
14827
|
+
"usage: sessionpipe sink add <url|file:PATH|stdout> [--tier N] [--token T] [--pii] [--tools all|main|beat] [--name N] | list | remove <name> | test <name>"
|
|
14427
14828
|
);
|
|
14428
14829
|
}
|
|
14429
14830
|
async function sinkAdd(url, cfg) {
|
|
14831
|
+
assertConfigWritable();
|
|
14430
14832
|
const tier = Math.max(0, Math.min(3, Number(flag("--tier") ?? 0)));
|
|
14431
14833
|
const name = flag("--name") ?? (url === "stdout" ? "stdout" : url.startsWith("file:") ? "file" : new URL(url).host);
|
|
14432
14834
|
if (has("--control"))
|
|
14433
14835
|
out(" --control is ignored: control is set up per machine (`sessionpipe control pair <url>`, spec/CONTROL.md)");
|
|
14836
|
+
const tools = flag("--tools");
|
|
14837
|
+
if (tools !== void 0 && !isToolsMode(tools)) {
|
|
14838
|
+
out(` ! --tools takes all, main or beat (got ${JSON.stringify(tools)}): nothing was changed`);
|
|
14839
|
+
process.exitCode = 2;
|
|
14840
|
+
return;
|
|
14841
|
+
}
|
|
14434
14842
|
const s = { name, url, tier, pii: has("--pii") };
|
|
14435
14843
|
const prev = cfg.sinks.find((x) => x.name === name && x.url === url);
|
|
14844
|
+
const keptTools = tools ?? prev?.tools;
|
|
14845
|
+
if (keptTools) s.tools = keptTools;
|
|
14436
14846
|
const token = flag("--token");
|
|
14437
14847
|
const secret = flag("--secret");
|
|
14438
14848
|
if (prev?.token_in && !token && !secret) s.token_in = prev.token_in;
|
|
@@ -14449,19 +14859,12 @@ async function sinkAdd(url, cfg) {
|
|
|
14449
14859
|
writeConfig(cfg);
|
|
14450
14860
|
out(` \u2713 sink ${name}: ${url} at tier ${Math.min(tier, s.max_tier ?? 3)}${s.pii ? ", pii" : ""}`);
|
|
14451
14861
|
out(` tier ${tier} sends: ${TIER_TEXT[Math.min(tier, s.max_tier ?? 3)]}`);
|
|
14862
|
+
if (Math.min(tier, s.max_tier ?? 3) >= 1) out(` tool events: ${TOOLS_TEXT[toolsMode(s)]}`);
|
|
14452
14863
|
}
|
|
14453
14864
|
async function learnReceiver(s, tier) {
|
|
14454
14865
|
if (!/^https?:\/\//.test(s.url)) return;
|
|
14455
14866
|
try {
|
|
14456
|
-
const
|
|
14457
|
-
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
14458
|
-
const wk = await r.json();
|
|
14459
|
-
if (typeof wk.max_tier === "number") {
|
|
14460
|
-
s.max_tier = wk.max_tier;
|
|
14461
|
-
if (wk.max_tier < tier) out(` receiver's max_tier is ${wk.max_tier}: sending tier ${wk.max_tier}, not ${tier}`);
|
|
14462
|
-
}
|
|
14463
|
-
if (wk.endpoints) s.endpoints = wk.endpoints;
|
|
14464
|
-
s.well_known_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
14867
|
+
for (const note of applyWellKnown(s, await readWellKnown(s.url, 8e3), tier)) out(` ${note}`);
|
|
14465
14868
|
} catch (e) {
|
|
14466
14869
|
out(
|
|
14467
14870
|
` ! ${s.url} has no readable /.well-known/sessionpipe (${e.message}); added anyway, at tier ${tier}`
|
|
@@ -14474,6 +14877,14 @@ var TIER_TEXT = {
|
|
|
14474
14877
|
2: "tier 1 + your prompts and the assistant's text, secrets removed",
|
|
14475
14878
|
3: "everything, including tool input and output"
|
|
14476
14879
|
};
|
|
14880
|
+
function toolsNote(tier, maxTier, mode) {
|
|
14881
|
+
return Math.min(tier, maxTier ?? 3) >= 1 ? ` \xB7 tools ${mode}` : "";
|
|
14882
|
+
}
|
|
14883
|
+
var TOOLS_TEXT = {
|
|
14884
|
+
all: "every tool call (--tools main: a subagent's at most one a minute)",
|
|
14885
|
+
main: "the main thread's tool calls as they happen; a subagent's at most one a minute per session (--tools all: every one)",
|
|
14886
|
+
beat: "at most one tool call a minute per session (--tools all: every one)"
|
|
14887
|
+
};
|
|
14477
14888
|
function collectRows(adapters, days) {
|
|
14478
14889
|
const since = Date.now() - days * 864e5;
|
|
14479
14890
|
const rows2 = [];
|
|
@@ -14549,40 +14960,83 @@ async function queueBackfill(rows2, days, cfg, renames = false) {
|
|
|
14549
14960
|
void drain(state, cfg.sinks, outbox);
|
|
14550
14961
|
return { named, renamed };
|
|
14551
14962
|
}
|
|
14552
|
-
function backfill() {
|
|
14963
|
+
async function backfill() {
|
|
14964
|
+
if (strayWord(["--days"])) return;
|
|
14553
14965
|
return backfillRows(selectedAdapters(), Number(flag("--days") ?? 30), readConfig());
|
|
14554
14966
|
}
|
|
14967
|
+
async function flushLock() {
|
|
14968
|
+
const release = await waitNamedLock(state, "flush", 1e4);
|
|
14969
|
+
if (!release) {
|
|
14970
|
+
process.exitCode = 1;
|
|
14971
|
+
out(" \u2717 a drain is running (the flush lock is held); nothing was changed, try again in a moment");
|
|
14972
|
+
}
|
|
14973
|
+
return release;
|
|
14974
|
+
}
|
|
14975
|
+
async function drainIfMarked(cfg, outbox) {
|
|
14976
|
+
if (existsSync21(path39.join(state, "locks", "flush.more"))) await drain(state, cfg.sinks, outbox);
|
|
14977
|
+
}
|
|
14555
14978
|
async function forget() {
|
|
14556
14979
|
const [, harness, session] = args;
|
|
14557
14980
|
if (!harness || !session) return out("usage: sessionpipe forget <harness> <session>");
|
|
14558
14981
|
const cfg = readConfig();
|
|
14559
14982
|
const outbox = new Outbox(state);
|
|
14560
14983
|
const ref = { harness, session };
|
|
14561
|
-
const
|
|
14562
|
-
|
|
14563
|
-
|
|
14564
|
-
|
|
14565
|
-
|
|
14984
|
+
const release = await flushLock();
|
|
14985
|
+
if (!release) return;
|
|
14986
|
+
try {
|
|
14987
|
+
const e = makeEvent(
|
|
14988
|
+
{ type: "session.forgotten", harnessEvent: "forget", data: {} },
|
|
14989
|
+
{
|
|
14990
|
+
harness: { name: harness },
|
|
14991
|
+
session: { id: session, seq: outbox.nextSeq(ref), machine: machineName(cfg, os15.hostname()) }
|
|
14992
|
+
}
|
|
14993
|
+
);
|
|
14994
|
+
const sinks = buildSinks(cfg.sinks);
|
|
14995
|
+
for (const s of sinks) {
|
|
14996
|
+
const r = await s.send([filterEvent(e, s.tier, s.pii)]);
|
|
14997
|
+
out(r.ok ? ` \u2713 ${s.name}: asked to delete ${harness}:${session}` : ` \u2717 ${s.name}: ${r.error ?? r.status}`);
|
|
14566
14998
|
}
|
|
14567
|
-
|
|
14568
|
-
|
|
14569
|
-
|
|
14570
|
-
|
|
14571
|
-
out(r.ok ? ` \u2713 ${s.name}: asked to delete ${harness}:${session}` : ` \u2717 ${s.name}: ${r.error ?? r.status}`);
|
|
14999
|
+
outbox.delete(ref);
|
|
15000
|
+
out(" local outbox for the session removed, and every sink's cursor into it");
|
|
15001
|
+
} finally {
|
|
15002
|
+
release();
|
|
14572
15003
|
}
|
|
14573
|
-
outbox
|
|
14574
|
-
out(" local outbox for the session removed");
|
|
15004
|
+
await drainIfMarked(cfg, outbox);
|
|
14575
15005
|
}
|
|
14576
15006
|
async function replay() {
|
|
14577
15007
|
const name = flag("--sink");
|
|
15008
|
+
const tools = flag("--tools");
|
|
15009
|
+
if (tools !== void 0 && !isToolsMode(tools)) {
|
|
15010
|
+
out(` ! --tools takes all, main or beat (got ${JSON.stringify(tools)}): nothing was sent`);
|
|
15011
|
+
process.exitCode = 2;
|
|
15012
|
+
return;
|
|
15013
|
+
}
|
|
14578
15014
|
const cfg = readConfig();
|
|
14579
|
-
const
|
|
14580
|
-
if (!
|
|
14581
|
-
|
|
14582
|
-
|
|
15015
|
+
const chosen = name ? cfg.sinks.filter((s) => s.name === name) : cfg.sinks;
|
|
15016
|
+
if (!chosen.length) return out(" no such sink");
|
|
15017
|
+
const sinks = tools ? chosen.map((s) => ({ ...s, tools })) : chosen;
|
|
15018
|
+
const release = await flushLock();
|
|
15019
|
+
if (!release) return;
|
|
15020
|
+
const outbox = new Outbox(state);
|
|
15021
|
+
try {
|
|
15022
|
+
for (const s of sinks) {
|
|
15023
|
+
if (has("--from-start")) {
|
|
15024
|
+
new Cursors(state, s.name).write({});
|
|
15025
|
+
new ToolClock(state, s.name).write({});
|
|
15026
|
+
}
|
|
15027
|
+
}
|
|
15028
|
+
const thinned = {};
|
|
15029
|
+
const d = await flush(state, sinks, outbox, void 0, { force: true, thinned });
|
|
15030
|
+
for (const [k, v] of Object.entries(d)) {
|
|
15031
|
+
const t = thinned[k] ?? 0;
|
|
15032
|
+
out(
|
|
15033
|
+
` ${k}: ${v} event${v === 1 ? "" : "s"} delivered${t ? `, ${t} tool event${t === 1 ? "" : "s"} thinned (--tools all sends every one)` : ""}`
|
|
15034
|
+
);
|
|
15035
|
+
}
|
|
15036
|
+
} finally {
|
|
15037
|
+
release();
|
|
14583
15038
|
}
|
|
14584
|
-
|
|
14585
|
-
for (const [k, v] of Object.entries(d)) out(` ${k}: ${v} event${v === 1 ? "" : "s"} delivered`);
|
|
15039
|
+
await drainIfMarked(cfg, outbox);
|
|
14586
15040
|
}
|
|
14587
15041
|
async function controlHealth() {
|
|
14588
15042
|
if (!pairedCount()) return null;
|
|
@@ -14657,13 +15111,14 @@ async function status2() {
|
|
|
14657
15111
|
tier: s.tier,
|
|
14658
15112
|
max_tier: s.max_tier ?? null,
|
|
14659
15113
|
pii: !!s.pii,
|
|
15114
|
+
tools: toolsMode(s),
|
|
14660
15115
|
control: !!s.control,
|
|
14661
15116
|
paused: s.paused ?? null,
|
|
14662
15117
|
pending_sessions: pending
|
|
14663
15118
|
});
|
|
14664
15119
|
if (!json2)
|
|
14665
15120
|
out(
|
|
14666
|
-
` sink ${s.name.padEnd(16)} tier ${Math.min(s.tier, s.max_tier ?? 3)} ${s.paused ? `PAUSED ${s.paused}` : pending ? `${pending} session file(s) with undelivered events` : "up to date"}`
|
|
15121
|
+
` sink ${s.name.padEnd(16)} tier ${Math.min(s.tier, s.max_tier ?? 3)} ${s.paused ? `PAUSED ${s.paused}` : pending ? `${pending} session file(s) with undelivered events` : "up to date"}${toolsNote(s.tier, s.max_tier, toolsMode(s))}`
|
|
14667
15122
|
);
|
|
14668
15123
|
}
|
|
14669
15124
|
const control = await controlHealth();
|
|
@@ -14709,12 +15164,12 @@ async function status2() {
|
|
|
14709
15164
|
else if (jobs) out(` jobs: ${jobs} waiting`);
|
|
14710
15165
|
}
|
|
14711
15166
|
function hookWall(runs = 8) {
|
|
14712
|
-
const hook =
|
|
14713
|
-
const tmp = mkdtempSync(
|
|
15167
|
+
const hook = path39.join(distDir, "hook.js");
|
|
15168
|
+
const tmp = mkdtempSync(path39.join(os15.tmpdir(), "sessionpipe-doctor-"));
|
|
14714
15169
|
const env = {
|
|
14715
15170
|
...process.env,
|
|
14716
15171
|
SESSIONPIPE_STATE: tmp,
|
|
14717
|
-
SESSIONPIPE_CONFIG:
|
|
15172
|
+
SESSIONPIPE_CONFIG: path39.join(tmp, "config.json"),
|
|
14718
15173
|
SESSIONPIPE_NO_WORKER: "1"
|
|
14719
15174
|
};
|
|
14720
15175
|
const input = JSON.stringify({
|
|
@@ -14745,7 +15200,7 @@ function hookWall(runs = 8) {
|
|
|
14745
15200
|
}
|
|
14746
15201
|
function timing() {
|
|
14747
15202
|
try {
|
|
14748
|
-
const lines =
|
|
15203
|
+
const lines = readFileSync24(path39.join(state, "timing.jsonl"), "utf8").trim().split("\n").slice(-100);
|
|
14749
15204
|
const ms2 = lines.map((l) => JSON.parse(l).ms).filter((x) => x >= 0).sort((a, b) => a - b);
|
|
14750
15205
|
if (!ms2.length) return null;
|
|
14751
15206
|
const q = (p) => Math.round(
|
|
@@ -14874,6 +15329,7 @@ async function doctor() {
|
|
|
14874
15329
|
tier: s.tier,
|
|
14875
15330
|
max_tier: s.max_tier ?? null,
|
|
14876
15331
|
pii: !!s.pii,
|
|
15332
|
+
tools: toolsMode(s),
|
|
14877
15333
|
control: !!s.control,
|
|
14878
15334
|
paused: s.paused ?? null,
|
|
14879
15335
|
token: s.token_in ? `in ${s.token_in}` : s.token ? `${s.token.slice(0, 4)}\u2026` : null
|
|
@@ -14893,7 +15349,7 @@ async function doctor() {
|
|
|
14893
15349
|
for (const r of st) out(` ${h.padEnd(12)} ${r.file.padEnd(44)} ${r.state}`);
|
|
14894
15350
|
for (const s of report.sinks)
|
|
14895
15351
|
out(
|
|
14896
|
-
` sink ${s.name.padEnd(16)} tier ${s.tier}${s.max_tier !== null ? ` (receiver max ${s.max_tier})` : ""}${s.paused ? ` PAUSED ${s.paused}` : ""}`
|
|
15352
|
+
` sink ${s.name.padEnd(16)} tier ${s.tier}${s.max_tier !== null ? ` (receiver max ${s.max_tier})` : ""}${s.paused ? ` PAUSED ${s.paused}` : ""}${toolsNote(s.tier, s.max_tier, s.tools)}`
|
|
14897
15353
|
);
|
|
14898
15354
|
controlLines(health);
|
|
14899
15355
|
const lr = report.launcher;
|
|
@@ -14917,7 +15373,7 @@ async function doctor() {
|
|
|
14917
15373
|
if (!warnings.length) out(" no warnings");
|
|
14918
15374
|
}
|
|
14919
15375
|
async function tail() {
|
|
14920
|
-
const tier =
|
|
15376
|
+
const tier = tierArg(args, 1);
|
|
14921
15377
|
const onlySession = flag("--session");
|
|
14922
15378
|
const onlyHarness = flag("--harness");
|
|
14923
15379
|
const outbox = new Outbox(state);
|
|
@@ -14966,7 +15422,7 @@ async function tail() {
|
|
|
14966
15422
|
}
|
|
14967
15423
|
return `${t} ${e.harness.name.padEnd(11)} ${e.session.id.slice(0, 4)}\u2026 ${e.type.padEnd(17)} ${detail}`;
|
|
14968
15424
|
};
|
|
14969
|
-
out(`sessionpipe tail \xB7 tier ${tier} \xB7 ${tilde(
|
|
15425
|
+
out(`sessionpipe tail \xB7 tier ${tier} \xB7 ${tilde(path39.join(state, "outbox"))} (Ctrl-C to stop)`);
|
|
14970
15426
|
for (const ref of outbox.sessions()) {
|
|
14971
15427
|
try {
|
|
14972
15428
|
offsets.set(`${ref.harness}/${ref.session}`, has("--all") ? 0 : statSync14(outbox.file(ref)).size);
|
|
@@ -14992,9 +15448,9 @@ async function tail() {
|
|
|
14992
15448
|
async function workerFromArgv() {
|
|
14993
15449
|
const f = args[1];
|
|
14994
15450
|
if (!f) return;
|
|
14995
|
-
const job = JSON.parse(
|
|
15451
|
+
const job = JSON.parse(readFileSync24(f, "utf8"));
|
|
14996
15452
|
try {
|
|
14997
|
-
|
|
15453
|
+
unlinkSync8(f);
|
|
14998
15454
|
} catch {
|
|
14999
15455
|
}
|
|
15000
15456
|
await runJob(job);
|
|
@@ -15018,6 +15474,10 @@ async function update() {
|
|
|
15018
15474
|
sub === "off" ? " \u2713 The control daemon no longer checks npm or updates itself. `sessionpipe update` still updates by hand; `sessionpipe update on` undoes this." : " \u2713 The control daemon checks npm once a day and installs a new release when it's idle."
|
|
15019
15475
|
);
|
|
15020
15476
|
}
|
|
15477
|
+
if (sub !== void 0) {
|
|
15478
|
+
process.exitCode = 2;
|
|
15479
|
+
return out("usage: sessionpipe update [off|on]");
|
|
15480
|
+
}
|
|
15021
15481
|
if (process.env.SESSIONPIPE_NO_UPDATE_CHECK) return out(" update check is off (SESSIONPIPE_NO_UPDATE_CHECK)");
|
|
15022
15482
|
let latest;
|
|
15023
15483
|
try {
|