sessionpipe 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -7
- package/dist/ask.d.ts +40 -0
- package/dist/ask.d.ts.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +4439 -2047
- package/dist/cli.js.map +4 -4
- package/dist/connect.d.ts +6 -1
- package/dist/connect.d.ts.map +1 -1
- package/dist/control/autoupdate.d.ts +1 -0
- package/dist/control/autoupdate.d.ts.map +1 -1
- package/dist/control/channel.d.ts +43 -0
- package/dist/control/channel.d.ts.map +1 -0
- package/dist/control/cli.d.ts +13 -1
- package/dist/control/cli.d.ts.map +1 -1
- package/dist/control/daemon.d.ts +189 -2
- package/dist/control/daemon.d.ts.map +1 -1
- package/dist/control/inbox.d.ts +119 -0
- package/dist/control/inbox.d.ts.map +1 -0
- package/dist/control/local.d.ts +69 -4
- package/dist/control/local.d.ts.map +1 -1
- package/dist/control/pair.d.ts.map +1 -1
- package/dist/control/reply.d.ts +25 -1
- package/dist/control/reply.d.ts.map +1 -1
- package/dist/control/route.d.ts +50 -2
- package/dist/control/route.d.ts.map +1 -1
- package/dist/control/sdk.d.ts.map +1 -1
- package/dist/control/store.d.ts +49 -4
- package/dist/control/store.d.ts.map +1 -1
- package/dist/fold.d.ts +29 -0
- package/dist/fold.d.ts.map +1 -0
- package/dist/help.d.ts.map +1 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +15 -5
- package/dist/hook.js.map +2 -2
- package/dist/index.js +1272 -257
- package/dist/index.js.map +4 -4
- package/dist/run.d.ts +41 -5
- package/dist/run.d.ts.map +1 -1
- package/dist/secrets.d.ts +14 -4
- package/dist/secrets.d.ts.map +1 -1
- package/dist/wellknown.d.ts +31 -0
- package/dist/wellknown.d.ts.map +1 -0
- package/dist/worker.d.ts +7 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +1372 -279
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
// sessionpipe 0.
|
|
1
|
+
// sessionpipe 0.13.1 · Apache-2.0 · https://sessionpipe.org
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
2
7
|
|
|
3
8
|
// src/http-sink.ts
|
|
4
9
|
import { createHash, createHmac } from "node:crypto";
|
|
@@ -99,16 +104,16 @@ var HttpSink = class {
|
|
|
99
104
|
import {
|
|
100
105
|
appendFileSync as appendFileSync3,
|
|
101
106
|
existsSync as existsSync10,
|
|
102
|
-
mkdirSync as
|
|
103
|
-
readdirSync as
|
|
104
|
-
readFileSync as
|
|
105
|
-
renameSync as
|
|
106
|
-
statSync as
|
|
107
|
-
unlinkSync as
|
|
108
|
-
writeFileSync as
|
|
107
|
+
mkdirSync as mkdirSync7,
|
|
108
|
+
readdirSync as readdirSync7,
|
|
109
|
+
readFileSync as readFileSync12,
|
|
110
|
+
renameSync as renameSync4,
|
|
111
|
+
statSync as statSync7,
|
|
112
|
+
unlinkSync as unlinkSync3,
|
|
113
|
+
writeFileSync as writeFileSync6
|
|
109
114
|
} from "node:fs";
|
|
110
|
-
import
|
|
111
|
-
import
|
|
115
|
+
import os4 from "node:os";
|
|
116
|
+
import path17 from "node:path";
|
|
112
117
|
|
|
113
118
|
// ../core/src/paths.ts
|
|
114
119
|
import os from "node:os";
|
|
@@ -136,7 +141,7 @@ var safeId = (s) => String(s).replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 160);
|
|
|
136
141
|
// ../core/src/adapters/claude-code.ts
|
|
137
142
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
138
143
|
import { createHash as createHash2 } from "node:crypto";
|
|
139
|
-
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2
|
|
144
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
|
|
140
145
|
import { userInfo } from "node:os";
|
|
141
146
|
import path4 from "node:path";
|
|
142
147
|
|
|
@@ -288,7 +293,14 @@ ${m.text}`;
|
|
|
288
293
|
continue;
|
|
289
294
|
}
|
|
290
295
|
if (cur) turns.push(cur);
|
|
291
|
-
cur = {
|
|
296
|
+
cur = {
|
|
297
|
+
user: m.text,
|
|
298
|
+
assistant: "",
|
|
299
|
+
at: m.at,
|
|
300
|
+
startLine: m.line,
|
|
301
|
+
endLine: m.line,
|
|
302
|
+
...m.injected ? { injected: true } : {}
|
|
303
|
+
};
|
|
292
304
|
} else {
|
|
293
305
|
if (!cur) cur = { user: "", assistant: "", at: m.at, startLine: m.line, endLine: m.line };
|
|
294
306
|
cur.assistant += (cur.assistant ? "\n\n" : "") + m.text;
|
|
@@ -298,10 +310,17 @@ ${m.text}`;
|
|
|
298
310
|
if (cur?.assistant) turns.push(cur);
|
|
299
311
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
300
312
|
const out = {
|
|
301
|
-
turns: turns.map((t) => ({
|
|
313
|
+
turns: turns.map((t) => ({
|
|
314
|
+
user: t.user,
|
|
315
|
+
assistant: t.assistant,
|
|
316
|
+
at: t.at,
|
|
317
|
+
endLine: t.endLine,
|
|
318
|
+
...t.injected ? { injected: true } : {}
|
|
319
|
+
})),
|
|
302
320
|
line
|
|
303
321
|
};
|
|
304
|
-
if (cur && !cur.assistant && cur.user)
|
|
322
|
+
if (cur && !cur.assistant && cur.user)
|
|
323
|
+
out.open = { user: cur.user, at: cur.at, ...cur.injected ? { injected: true } : {} };
|
|
305
324
|
return out;
|
|
306
325
|
}
|
|
307
326
|
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
@@ -324,27 +343,51 @@ var textParts = (c, kinds) => {
|
|
|
324
343
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
325
344
|
).map((b) => b.text).join("\n");
|
|
326
345
|
};
|
|
346
|
+
var SPOKEN = /* @__PURE__ */ new Set(["human", "peer"]);
|
|
347
|
+
var PEER_INTRO = /sent a message[^\n]*:$/;
|
|
348
|
+
var PEER_OUTRO = "\n\nThis came from another ";
|
|
349
|
+
var ENVELOPE = /^<cross-session-message\b[^>]*>\n([\s\S]*)\n<\/cross-session-message>$/;
|
|
350
|
+
function peerAsk(origin, text) {
|
|
351
|
+
const body = origin?.body;
|
|
352
|
+
let t = typeof body === "string" && body.trim() ? body : text;
|
|
353
|
+
if (t !== body) {
|
|
354
|
+
const nl = t.indexOf("\n");
|
|
355
|
+
if (nl > 0 && PEER_INTRO.test(t.slice(0, nl))) t = t.slice(nl + 1);
|
|
356
|
+
const outro = t.indexOf(PEER_OUTRO);
|
|
357
|
+
if (outro > 0) t = t.slice(0, outro);
|
|
358
|
+
}
|
|
359
|
+
return (ENVELOPE.exec(t.trim())?.[1] ?? t).trim();
|
|
360
|
+
}
|
|
327
361
|
var queuedPrompt = (a) => {
|
|
328
362
|
if (!a || typeof a !== "object") return "";
|
|
329
363
|
const q = a;
|
|
330
364
|
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
331
|
-
|
|
332
|
-
|
|
365
|
+
const kind = q.origin?.kind ?? "human";
|
|
366
|
+
if (!SPOKEN.has(kind)) return "";
|
|
367
|
+
const text = textParts(q.prompt, ["text"]).trim();
|
|
368
|
+
return kind === "peer" ? peerAsk(q.origin, text) : text;
|
|
333
369
|
};
|
|
370
|
+
var channelText = (text) => /^<channel\b[^>]*>([\s\S]*)<\/channel>$/.exec(text)?.[1]?.trim() ?? "";
|
|
334
371
|
function readClaude(lines, from) {
|
|
335
372
|
const msgs = [];
|
|
336
373
|
for (let i = from; i < lines.length; i++) {
|
|
337
374
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
338
375
|
if (!j || j.isSidechain) continue;
|
|
339
376
|
const origin = j.origin;
|
|
340
|
-
|
|
341
|
-
|
|
377
|
+
const channel = j.type === "user" && origin?.kind === "channel";
|
|
378
|
+
const peer = origin?.kind === "peer";
|
|
379
|
+
if (j.type === "user" && (j.isCompactSummary || j.repeatsCompactSummary)) continue;
|
|
380
|
+
if (j.type === "user" && !channel && !peer && (j.isMeta || origin?.kind && origin.kind !== "human")) continue;
|
|
342
381
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
343
382
|
const c = j.message?.content;
|
|
344
|
-
if (
|
|
345
|
-
const text = textParts(c, ["text"]).trim();
|
|
383
|
+
if (channel) {
|
|
384
|
+
const text = channelText(textParts(c, ["text"]).trim());
|
|
385
|
+
if (text) msgs.push({ role: "user", text, at, line: i, injected: true });
|
|
386
|
+
} else if (j.type === "user") {
|
|
387
|
+
const raw = textParts(c, ["text"]).trim();
|
|
388
|
+
const text = peer ? peerAsk(origin, raw) : raw;
|
|
346
389
|
if (!text || isInjected(text)) continue;
|
|
347
|
-
msgs.push({ role: "user", text, at, line: i });
|
|
390
|
+
msgs.push({ role: "user", text, at, line: i, ...peer ? { injected: true } : {} });
|
|
348
391
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
349
392
|
const text = textParts(c, ["text"]).trim();
|
|
350
393
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
@@ -456,6 +499,41 @@ function readJson(file) {
|
|
|
456
499
|
const f = readJsonFile(file);
|
|
457
500
|
return f.ok ? f.value : {};
|
|
458
501
|
}
|
|
502
|
+
function refused(file, e, what, existed) {
|
|
503
|
+
const { code, syscall } = e;
|
|
504
|
+
const why = `can't ${what} it (${code ?? String(e)})`;
|
|
505
|
+
if (what === "remove" || syscall === "open" || syscall === "mkdir")
|
|
506
|
+
return { file, changed: false, skipped: true, note: `${why}; not touched` };
|
|
507
|
+
return {
|
|
508
|
+
file,
|
|
509
|
+
changed: true,
|
|
510
|
+
skipped: true,
|
|
511
|
+
...existed ? {} : { created: true },
|
|
512
|
+
note: `${why} part way; check the file before the harness reads it`
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function writeBack(file, text, existed = true) {
|
|
516
|
+
try {
|
|
517
|
+
mkdirSync(path3.dirname(file), { recursive: true });
|
|
518
|
+
writeFileSync(file, text);
|
|
519
|
+
return null;
|
|
520
|
+
} catch (e) {
|
|
521
|
+
return refused(file, e, "write", existed);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function removeEmptied(file, empty) {
|
|
525
|
+
try {
|
|
526
|
+
unlinkSync(file);
|
|
527
|
+
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
528
|
+
} catch (e) {
|
|
529
|
+
const bad = refused(file, e, "remove", true);
|
|
530
|
+
return writeBack(file, empty) ?? {
|
|
531
|
+
file,
|
|
532
|
+
changed: true,
|
|
533
|
+
note: `emptied; ${bad.note.replace(/; not touched$/, "")}, so the empty file stays`
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
}
|
|
459
537
|
function entryFor(event, harness, cmd, o) {
|
|
460
538
|
const matcher = o.matcherFor(event);
|
|
461
539
|
return {
|
|
@@ -497,8 +575,8 @@ function installClaudeShaped(file, harness, events, cmd, o) {
|
|
|
497
575
|
}
|
|
498
576
|
if (changed) {
|
|
499
577
|
settings2.hooks = hooks;
|
|
500
|
-
|
|
501
|
-
|
|
578
|
+
const bad = writeBack(file, formatJson(settings2, f), f.existed);
|
|
579
|
+
if (bad) return bad;
|
|
502
580
|
}
|
|
503
581
|
return { file, changed, created: !f.existed && changed };
|
|
504
582
|
}
|
|
@@ -521,12 +599,8 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
521
599
|
}
|
|
522
600
|
if (!changed) return { file, changed: false };
|
|
523
601
|
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
524
|
-
if (opts.created && !Object.keys(settings2).length)
|
|
525
|
-
|
|
526
|
-
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
527
|
-
}
|
|
528
|
-
writeFileSync(file, formatJson(settings2, f));
|
|
529
|
-
return { file, changed: true };
|
|
602
|
+
if (opts.created && !Object.keys(settings2).length) return removeEmptied(file, formatJson(settings2, f));
|
|
603
|
+
return writeBack(file, formatJson(settings2, f)) ?? { file, changed: true };
|
|
530
604
|
}
|
|
531
605
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
532
606
|
const f = readJsonFile(file);
|
|
@@ -605,7 +679,7 @@ var claudeCode = {
|
|
|
605
679
|
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
606
680
|
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).flatMap((d) => {
|
|
607
681
|
const hooks = installClaudeShaped(settingsIn(d), NAME, eventsFor(opts.lean), cmd, OPTS);
|
|
608
|
-
const line = opts.statusLine === void 0 ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
682
|
+
const line = opts.statusLine === void 0 || hooks.skipped ? null : statusLine(settingsIn(d), opts.statusLine, cmd);
|
|
609
683
|
return line ? [hooks, line] : [hooks];
|
|
610
684
|
}),
|
|
611
685
|
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map((d) => {
|
|
@@ -613,7 +687,9 @@ var claudeCode = {
|
|
|
613
687
|
const hooks = uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, {
|
|
614
688
|
created: opts.created?.includes(settingsIn(d))
|
|
615
689
|
});
|
|
616
|
-
|
|
690
|
+
if (line?.changed && !line.skipped && hooks.skipped)
|
|
691
|
+
return { ...hooks, note: `status line removed, hooks not: ${hooks.note?.replace(/; not touched$/, "")}` };
|
|
692
|
+
return line && !hooks.changed && !hooks.skipped ? line : hooks;
|
|
617
693
|
}),
|
|
618
694
|
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
619
695
|
file: settingsIn(d),
|
|
@@ -708,7 +784,8 @@ var claudeCode = {
|
|
|
708
784
|
attention_id: call_id ?? permissionId(),
|
|
709
785
|
kind: "permission",
|
|
710
786
|
tool,
|
|
711
|
-
message: tool ? `${tool}${describe(s.tool_input)}` : void 0
|
|
787
|
+
message: tool ? `${tool}${describe(s.tool_input)}` : void 0,
|
|
788
|
+
agent_id
|
|
712
789
|
})
|
|
713
790
|
);
|
|
714
791
|
break;
|
|
@@ -717,7 +794,8 @@ var claudeCode = {
|
|
|
717
794
|
ev("attention.needed", {
|
|
718
795
|
attention_id: `notif-${Date.now()}`,
|
|
719
796
|
kind: notifKind(s.notification_type),
|
|
720
|
-
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0
|
|
797
|
+
message: typeof s.message === "string" ? s.message.slice(0, 200) : void 0,
|
|
798
|
+
agent_id
|
|
721
799
|
})
|
|
722
800
|
);
|
|
723
801
|
break;
|
|
@@ -842,8 +920,11 @@ function statusLine(file, want, cmd) {
|
|
|
842
920
|
if (want ? ours && cur?.command === command && cur?.refreshInterval === LINE_REFRESH_S : !ours) return null;
|
|
843
921
|
if (want) f.value.statusLine = { type: "command", command, refreshInterval: LINE_REFRESH_S };
|
|
844
922
|
else delete f.value.statusLine;
|
|
845
|
-
|
|
846
|
-
|
|
923
|
+
return writeBack(file, formatJson(f.value, f), f.existed) ?? {
|
|
924
|
+
file,
|
|
925
|
+
changed: true,
|
|
926
|
+
note: want ? "status line written (account limits)" : "status line removed"
|
|
927
|
+
};
|
|
847
928
|
}
|
|
848
929
|
var epochIso = (t) => typeof t === "number" && t > 0 && t < 1e11 ? new Date(t * 1e3).toISOString() : void 0;
|
|
849
930
|
var WINDOW_MINUTES = { five_hour: 300, seven_day: 10080 };
|
|
@@ -1258,8 +1339,44 @@ async function claudeAiTitle(bridge, token) {
|
|
|
1258
1339
|
}
|
|
1259
1340
|
|
|
1260
1341
|
// ../core/src/adapters/headless.ts
|
|
1342
|
+
import { spawn } from "node:child_process";
|
|
1261
1343
|
import { accessSync, constants, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
1262
1344
|
import path5 from "node:path";
|
|
1345
|
+
|
|
1346
|
+
// ../core/src/launched-by.ts
|
|
1347
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
1348
|
+
var DELEGATED_BY_ENV = "SESSIONPIPE_ASKED_BY";
|
|
1349
|
+
var ASK_CHAIN_ENV = "SESSIONPIPE_ASK_CHAIN";
|
|
1350
|
+
var LINEAGE_ENV = [LAUNCHED_BY_ENV, DELEGATED_BY_ENV, ASK_CHAIN_ENV];
|
|
1351
|
+
var SESSION_REF = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
1352
|
+
function withoutLineage(env) {
|
|
1353
|
+
const e = { ...env };
|
|
1354
|
+
for (const k of LINEAGE_ENV) delete e[k];
|
|
1355
|
+
return e;
|
|
1356
|
+
}
|
|
1357
|
+
function refIn(env, name) {
|
|
1358
|
+
const v = env[name]?.trim();
|
|
1359
|
+
return v && SESSION_REF.test(v) ? v : void 0;
|
|
1360
|
+
}
|
|
1361
|
+
function launchedBy(env) {
|
|
1362
|
+
return refIn(env, LAUNCHED_BY_ENV);
|
|
1363
|
+
}
|
|
1364
|
+
function delegatedBy(env) {
|
|
1365
|
+
return refIn(env, DELEGATED_BY_ENV);
|
|
1366
|
+
}
|
|
1367
|
+
function readHook(adapter, input) {
|
|
1368
|
+
const r = adapter.fromHook(input);
|
|
1369
|
+
if (r && !r.session.parent_id) {
|
|
1370
|
+
const self = `${adapter.name}:${r.session.id}`;
|
|
1371
|
+
const launcher = launchedBy(input.env);
|
|
1372
|
+
if (launcher && launcher !== self) r.session.launched_by = launcher;
|
|
1373
|
+
const asker = delegatedBy(input.env);
|
|
1374
|
+
if (asker && asker !== self) r.session.delegated_by = asker;
|
|
1375
|
+
}
|
|
1376
|
+
return r;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
// ../core/src/adapters/headless.ts
|
|
1263
1380
|
var JOB_TIMEOUT_MS = 30 * 6e4;
|
|
1264
1381
|
var OUT_CAP = 1024 * 1024;
|
|
1265
1382
|
function isExe(p) {
|
|
@@ -1301,22 +1418,119 @@ function spawnable(bin, args) {
|
|
|
1301
1418
|
const target = shimTarget(bin);
|
|
1302
1419
|
return target ? { file: process.execPath, args: [target, ...args] } : null;
|
|
1303
1420
|
}
|
|
1421
|
+
var shimNote = (bin) => `${path5.basename(bin)} is a launcher script that names no program to run; point the harness's override (SESSIONPIPE_CLAUDE, SESSIONPIPE_CODEX, \u2026) at the program itself`;
|
|
1304
1422
|
function probe(bin, args) {
|
|
1305
1423
|
const s = spawnable(bin, args);
|
|
1306
1424
|
return s ? { ...s, shell: false } : { file: [`"${bin}"`, ...args].join(" "), args: [], shell: true };
|
|
1307
1425
|
}
|
|
1426
|
+
function runHeadless(bin, args, o) {
|
|
1427
|
+
return new Promise((resolve) => {
|
|
1428
|
+
let out = [];
|
|
1429
|
+
let outLen = 0;
|
|
1430
|
+
let tooBig = false;
|
|
1431
|
+
let errTail = "";
|
|
1432
|
+
let timedOut = false;
|
|
1433
|
+
let settled = false;
|
|
1434
|
+
let child;
|
|
1435
|
+
const c = spawnable(bin, args);
|
|
1436
|
+
if (!c) {
|
|
1437
|
+
resolve({ error: shimNote(bin) });
|
|
1438
|
+
return;
|
|
1439
|
+
}
|
|
1440
|
+
try {
|
|
1441
|
+
child = spawn(c.file, c.args, {
|
|
1442
|
+
cwd: o.cwd,
|
|
1443
|
+
env: o.env,
|
|
1444
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
1445
|
+
detached: process.platform !== "win32",
|
|
1446
|
+
windowsHide: true
|
|
1447
|
+
});
|
|
1448
|
+
} catch (e) {
|
|
1449
|
+
resolve({ error: e.message });
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
1452
|
+
const kill = (sig) => {
|
|
1453
|
+
try {
|
|
1454
|
+
if (process.platform !== "win32" && child.pid) process.kill(-child.pid, sig);
|
|
1455
|
+
else child.kill(sig);
|
|
1456
|
+
} catch {
|
|
1457
|
+
try {
|
|
1458
|
+
child.kill(sig);
|
|
1459
|
+
} catch {
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
const keep = (line) => {
|
|
1464
|
+
if (!o.keep?.(line)) return;
|
|
1465
|
+
out = [Buffer.from(line)];
|
|
1466
|
+
outLen = line.length;
|
|
1467
|
+
};
|
|
1468
|
+
let partial = "";
|
|
1469
|
+
child.stdout?.on("data", (b) => {
|
|
1470
|
+
if (o.onLine) {
|
|
1471
|
+
const chunk = partial + b.toString("utf8");
|
|
1472
|
+
const lines = chunk.split("\n");
|
|
1473
|
+
partial = lines.pop();
|
|
1474
|
+
for (const line of lines) {
|
|
1475
|
+
if (!line.trim()) continue;
|
|
1476
|
+
try {
|
|
1477
|
+
o.onLine(line);
|
|
1478
|
+
} catch {
|
|
1479
|
+
}
|
|
1480
|
+
keep(line);
|
|
1481
|
+
}
|
|
1482
|
+
if (partial.length > OUT_CAP) partial = "";
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
if (tooBig) return;
|
|
1486
|
+
if (outLen + b.length > OUT_CAP) {
|
|
1487
|
+
tooBig = true;
|
|
1488
|
+
out = [];
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
out.push(b);
|
|
1492
|
+
outLen += b.length;
|
|
1493
|
+
});
|
|
1494
|
+
child.stderr?.on("data", (b) => {
|
|
1495
|
+
errTail = (errTail + b.toString("utf8")).slice(-4e3);
|
|
1496
|
+
});
|
|
1497
|
+
const timer = setTimeout(() => {
|
|
1498
|
+
timedOut = true;
|
|
1499
|
+
kill("SIGTERM");
|
|
1500
|
+
setTimeout(() => kill("SIGKILL"), 1e4).unref();
|
|
1501
|
+
}, o.timeoutMs ?? JOB_TIMEOUT_MS);
|
|
1502
|
+
const result = (code, signal) => ({
|
|
1503
|
+
code,
|
|
1504
|
+
signal,
|
|
1505
|
+
stdout: tooBig ? null : Buffer.concat(out).toString("utf8"),
|
|
1506
|
+
tooBig,
|
|
1507
|
+
stderr: errTail,
|
|
1508
|
+
timedOut
|
|
1509
|
+
});
|
|
1510
|
+
const finish = (r) => {
|
|
1511
|
+
if (settled) return;
|
|
1512
|
+
settled = true;
|
|
1513
|
+
clearTimeout(timer);
|
|
1514
|
+
resolve(r);
|
|
1515
|
+
};
|
|
1516
|
+
child.on("error", (e) => finish({ error: e.message }));
|
|
1517
|
+
child.on("exit", (code, signal) => setTimeout(() => finish(result(code, signal)), 2e3).unref());
|
|
1518
|
+
child.on("close", (code, signal) => {
|
|
1519
|
+
if (o.onLine && partial.trim()) {
|
|
1520
|
+
try {
|
|
1521
|
+
o.onLine(partial);
|
|
1522
|
+
} catch {
|
|
1523
|
+
}
|
|
1524
|
+
keep(partial);
|
|
1525
|
+
partial = "";
|
|
1526
|
+
}
|
|
1527
|
+
finish(result(code, signal));
|
|
1528
|
+
});
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1308
1531
|
|
|
1309
1532
|
// ../core/src/adapters/antigravity.ts
|
|
1310
|
-
import {
|
|
1311
|
-
existsSync as existsSync3,
|
|
1312
|
-
mkdirSync as mkdirSync2,
|
|
1313
|
-
readdirSync as readdirSync3,
|
|
1314
|
-
readFileSync as readFileSync4,
|
|
1315
|
-
realpathSync,
|
|
1316
|
-
statSync as statSync4,
|
|
1317
|
-
unlinkSync as unlinkSync2,
|
|
1318
|
-
writeFileSync as writeFileSync3
|
|
1319
|
-
} from "node:fs";
|
|
1533
|
+
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync4, realpathSync, statSync as statSync4 } from "node:fs";
|
|
1320
1534
|
import path6 from "node:path";
|
|
1321
1535
|
var NAME2 = "antigravity";
|
|
1322
1536
|
var HOOK_NAME = "sessionpipe";
|
|
@@ -1348,10 +1562,8 @@ var antigravity = {
|
|
|
1348
1562
|
const e = entry(cmd);
|
|
1349
1563
|
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
1350
1564
|
hooks[HOOK_NAME] = e;
|
|
1351
|
-
mkdirSync2(path6.dirname(file), { recursive: true });
|
|
1352
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
1353
1565
|
return [
|
|
1354
|
-
{
|
|
1566
|
+
writeBack(file, formatJson(hooks, f), f.existed) ?? {
|
|
1355
1567
|
file,
|
|
1356
1568
|
changed: true,
|
|
1357
1569
|
created: !f.existed,
|
|
@@ -1367,12 +1579,8 @@ var antigravity = {
|
|
|
1367
1579
|
const hooks = f.value;
|
|
1368
1580
|
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
1369
1581
|
delete hooks[HOOK_NAME];
|
|
1370
|
-
if (opts.created?.includes(file) && !Object.keys(hooks).length)
|
|
1371
|
-
|
|
1372
|
-
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
1373
|
-
}
|
|
1374
|
-
writeFileSync3(file, formatJson(hooks, f));
|
|
1375
|
-
return [{ file, changed: true }];
|
|
1582
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) return [removeEmptied(file, formatJson(hooks, f))];
|
|
1583
|
+
return [writeBack(file, formatJson(hooks, f)) ?? { file, changed: true }];
|
|
1376
1584
|
},
|
|
1377
1585
|
installed(cmd, env = process.env) {
|
|
1378
1586
|
const file = hooksFile(env);
|
|
@@ -1594,6 +1802,50 @@ function antigravityFacts(file, opts) {
|
|
|
1594
1802
|
return out;
|
|
1595
1803
|
}
|
|
1596
1804
|
|
|
1805
|
+
// ../core/src/adapters/claude-code-control.ts
|
|
1806
|
+
var claude_code_control_exports = {};
|
|
1807
|
+
__export(claude_code_control_exports, {
|
|
1808
|
+
CLAUDE_MODES: () => CLAUDE_MODES,
|
|
1809
|
+
JOB_TIMEOUT_MS: () => JOB_TIMEOUT_MS,
|
|
1810
|
+
MODE_RANK: () => MODE_RANK,
|
|
1811
|
+
NO_HOME_SETTINGS: () => NO_HOME_SETTINGS,
|
|
1812
|
+
ceilingLevel: () => ceilingLevel,
|
|
1813
|
+
claudeArgs: () => claudeArgs,
|
|
1814
|
+
claudeEnv: () => claudeEnv,
|
|
1815
|
+
claudeLogin: () => claudeLogin2,
|
|
1816
|
+
cmpVersion: () => cmpVersion,
|
|
1817
|
+
defaultLevel: () => defaultLevel,
|
|
1818
|
+
detectCaps: () => detectCaps,
|
|
1819
|
+
findClaude: () => findClaude,
|
|
1820
|
+
findTranscript: () => findTranscript,
|
|
1821
|
+
findTranscripts: () => findTranscripts,
|
|
1822
|
+
homeSettings: () => homeSettings,
|
|
1823
|
+
hookMode: () => hookMode,
|
|
1824
|
+
isUuid: () => isUuid,
|
|
1825
|
+
liveProcess: () => liveProcess,
|
|
1826
|
+
liveRecord: () => liveRecord,
|
|
1827
|
+
liveRecords: () => liveRecords,
|
|
1828
|
+
loginCommand: () => loginCommand,
|
|
1829
|
+
loginHint: () => loginHint,
|
|
1830
|
+
modeFlags: () => modeFlags,
|
|
1831
|
+
modeRank: () => modeRank,
|
|
1832
|
+
offeredLevels: () => offeredLevels,
|
|
1833
|
+
outputArgs: () => outputArgs,
|
|
1834
|
+
parseAuthStatus: () => parseAuthStatus,
|
|
1835
|
+
parseCaps: () => parseCaps,
|
|
1836
|
+
parseResult: () => parseResult,
|
|
1837
|
+
peerClass: () => peerClass,
|
|
1838
|
+
recentlyWritten: () => recentlyWritten,
|
|
1839
|
+
relaunchMode: () => relaunchMode,
|
|
1840
|
+
resolveHome: () => resolveHome,
|
|
1841
|
+
runClaude: () => runClaude,
|
|
1842
|
+
sessionFolder: () => sessionFolder
|
|
1843
|
+
});
|
|
1844
|
+
import { execFile, execFileSync as execFileSync3 } from "node:child_process";
|
|
1845
|
+
import { closeSync as closeSync2, openSync as openSync2, readdirSync as readdirSync4, readFileSync as readFileSync5, readSync as readSync2, statSync as statSync5 } from "node:fs";
|
|
1846
|
+
import os2 from "node:os";
|
|
1847
|
+
import path7 from "node:path";
|
|
1848
|
+
|
|
1597
1849
|
// ../core/src/control/verify.ts
|
|
1598
1850
|
var GRANT_MAX_MS = 24 * 36e5;
|
|
1599
1851
|
var SKEW_MS = 5 * 6e4;
|
|
@@ -1604,10 +1856,10 @@ var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
|
1604
1856
|
var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
|
|
1605
1857
|
var levelRank = (l) => START_PERMISSIONS.indexOf(l);
|
|
1606
1858
|
var FIELDS = {
|
|
1607
|
-
prompt: ["text", "files"],
|
|
1859
|
+
prompt: ["text", "files", "reply_to"],
|
|
1608
1860
|
"permission.answer": ["for", "decision"],
|
|
1609
1861
|
cancel: [],
|
|
1610
|
-
start: ["text", "cwd", "files", "permission", "model"],
|
|
1862
|
+
start: ["text", "cwd", "files", "permission", "model", "reply_to"],
|
|
1611
1863
|
"key.add": ["key", "name"],
|
|
1612
1864
|
pool: ["pool_action", "pool_account", "pool_order", "pool_code"]
|
|
1613
1865
|
};
|
|
@@ -1615,9 +1867,454 @@ var POOL_FIELDS = FIELDS.pool;
|
|
|
1615
1867
|
var enc = new TextEncoder();
|
|
1616
1868
|
var dec = new TextDecoder();
|
|
1617
1869
|
|
|
1870
|
+
// ../core/src/adapters/claude-code-control.ts
|
|
1871
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1872
|
+
var LIVE_WRITE_MS = 9e4;
|
|
1873
|
+
var cmpVersion = (a, b) => {
|
|
1874
|
+
const pa = a.split(".").map(Number);
|
|
1875
|
+
const pb = b.split(".").map(Number);
|
|
1876
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++)
|
|
1877
|
+
if ((pa[i] ?? 0) !== (pb[i] ?? 0)) return (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
1878
|
+
return 0;
|
|
1879
|
+
};
|
|
1880
|
+
function findClaude(env = process.env, home = os2.homedir()) {
|
|
1881
|
+
if (env.SESSIONPIPE_CLAUDE && isExe(env.SESSIONPIPE_CLAUDE)) return env.SESSIONPIPE_CLAUDE;
|
|
1882
|
+
const names = process.platform === "win32" ? ["claude.exe", "claude.cmd"] : ["claude"];
|
|
1883
|
+
for (const d of String(env.PATH || "").split(path7.delimiter)) {
|
|
1884
|
+
if (!d || !path7.isAbsolute(d)) continue;
|
|
1885
|
+
for (const n of names) if (isExe(path7.join(d, n))) return path7.join(d, n);
|
|
1886
|
+
}
|
|
1887
|
+
for (const p of [
|
|
1888
|
+
path7.join(home, ".claude", "local", "claude"),
|
|
1889
|
+
path7.join(home, ".local", "bin", "claude"),
|
|
1890
|
+
"/opt/homebrew/bin/claude",
|
|
1891
|
+
"/usr/local/bin/claude"
|
|
1892
|
+
])
|
|
1893
|
+
if (isExe(p)) return p;
|
|
1894
|
+
const app = path7.join(home, "Library", "Application Support", "Claude", "claude-code");
|
|
1895
|
+
let versions = [];
|
|
1896
|
+
try {
|
|
1897
|
+
versions = readdirSync4(app).filter((n) => /^\d+(\.\d+)*$/.test(n)).sort(cmpVersion).reverse();
|
|
1898
|
+
} catch {
|
|
1899
|
+
}
|
|
1900
|
+
for (const v of versions) {
|
|
1901
|
+
const p = path7.join(app, v, "claude.app", "Contents", "MacOS", "claude");
|
|
1902
|
+
if (isExe(p)) return p;
|
|
1903
|
+
}
|
|
1904
|
+
return null;
|
|
1905
|
+
}
|
|
1906
|
+
function optionBlock(help, flag) {
|
|
1907
|
+
const lines = help.split("\n");
|
|
1908
|
+
const re = new RegExp(`^\\s{0,4}(?:-\\w, )?${flag.replace(/-/g, "\\-")}(?![\\w-])`);
|
|
1909
|
+
const start = lines.findIndex((l) => re.test(l));
|
|
1910
|
+
if (start < 0) return null;
|
|
1911
|
+
const out = [lines[start]];
|
|
1912
|
+
for (let i = start + 1; i < lines.length && lines[i]?.trim() && !/^\s{0,4}-/.test(lines[i]); i++)
|
|
1913
|
+
out.push(lines[i]);
|
|
1914
|
+
return out.join(" ");
|
|
1915
|
+
}
|
|
1916
|
+
function parseCaps(help) {
|
|
1917
|
+
const pm = optionBlock(help, "--permission-mode");
|
|
1918
|
+
const pp = optionBlock(help, "--permission-prompts");
|
|
1919
|
+
return {
|
|
1920
|
+
hasMode: !!pm,
|
|
1921
|
+
modes: pm ? [...pm.matchAll(/"([A-Za-z]+)"/g)].map((m) => m[1]) : [],
|
|
1922
|
+
promptsNone: !!pp && /"none"/.test(pp),
|
|
1923
|
+
fork: !!optionBlock(help, "--fork-session"),
|
|
1924
|
+
stream: !!optionBlock(help, "--include-partial-messages")
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
var LEVEL_MODE = {
|
|
1928
|
+
read: "plan",
|
|
1929
|
+
edits: "acceptEdits",
|
|
1930
|
+
auto: "auto"
|
|
1931
|
+
};
|
|
1932
|
+
function modeFlags(level, caps) {
|
|
1933
|
+
if (level === "inherit") return { args: [] };
|
|
1934
|
+
if (caps.unread)
|
|
1935
|
+
return { error: `this machine couldn't read its Claude Code's options (${caps.unread}); try again in a minute` };
|
|
1936
|
+
if (level === "auto" && !caps.modes.includes("auto"))
|
|
1937
|
+
return { error: "this machine is in auto mode, but its Claude Code has no auto permission mode" };
|
|
1938
|
+
if (level !== "safe") {
|
|
1939
|
+
const m = LEVEL_MODE[level];
|
|
1940
|
+
if (!caps.modes.includes(m)) return { error: `this machine's Claude Code has no ${m} permission mode` };
|
|
1941
|
+
return { args: ["--permission-mode", m] };
|
|
1942
|
+
}
|
|
1943
|
+
if (caps.modes.includes("dontAsk")) return { args: ["--permission-mode", "dontAsk"] };
|
|
1944
|
+
const args = [];
|
|
1945
|
+
if (caps.hasMode)
|
|
1946
|
+
args.push(
|
|
1947
|
+
"--permission-mode",
|
|
1948
|
+
caps.modes.includes("manual") && !caps.modes.includes("default") ? "manual" : "default"
|
|
1949
|
+
);
|
|
1950
|
+
if (caps.promptsNone) args.push("--permission-prompts", "none");
|
|
1951
|
+
return { args };
|
|
1952
|
+
}
|
|
1953
|
+
var ceilingLevel = (mode) => mode === "inherit" ? "safe" : mode;
|
|
1954
|
+
function offeredLevels(mode, caps) {
|
|
1955
|
+
if (caps.unread) return [];
|
|
1956
|
+
return START_PERMISSIONS.filter(
|
|
1957
|
+
(l) => levelRank(l) <= levelRank(ceilingLevel(mode)) && (l === "safe" || caps.modes.includes(LEVEL_MODE[l]))
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
var defaultLevel = (mode) => mode;
|
|
1961
|
+
function outputArgs(caps) {
|
|
1962
|
+
return caps.stream ? ["--output-format", "stream-json", "--verbose", "--include-partial-messages"] : ["--output-format", "json"];
|
|
1963
|
+
}
|
|
1964
|
+
function claudeArgs(o) {
|
|
1965
|
+
const mf = modeFlags(o.level, o.caps);
|
|
1966
|
+
if ("error" in mf) return mf;
|
|
1967
|
+
const where = o.resume ? ["--resume", o.resume, ...o.fork ? ["--fork-session"] : []] : ["--session-id", o.session];
|
|
1968
|
+
return {
|
|
1969
|
+
args: [
|
|
1970
|
+
"-p",
|
|
1971
|
+
...outputArgs(o.caps),
|
|
1972
|
+
...mf.args,
|
|
1973
|
+
...o.model ? ["--model", o.model] : [],
|
|
1974
|
+
...o.allowedTools?.length ? [`--allowedTools=${o.allowedTools.join(",")}`] : [],
|
|
1975
|
+
...where,
|
|
1976
|
+
o.prompt.startsWith("-") ? ` ${o.prompt}` : o.prompt
|
|
1977
|
+
]
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
function claudeEnv(configDir, env = process.env) {
|
|
1981
|
+
const e = withoutLineage(env);
|
|
1982
|
+
for (const k of [
|
|
1983
|
+
"CLAUDECODE",
|
|
1984
|
+
"CLAUDE_CODE_SESSION_ID",
|
|
1985
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
1986
|
+
"CLAUDE_CODE_ENTRYPOINT",
|
|
1987
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
1988
|
+
"CLAUDE_CODE_SSE_PORT"
|
|
1989
|
+
])
|
|
1990
|
+
delete e[k];
|
|
1991
|
+
e.SESSIONPIPE_CONTROL_JOB = "1";
|
|
1992
|
+
if (configDir !== void 0) {
|
|
1993
|
+
if (path7.resolve(configDir) === path7.resolve(os2.homedir(), ".claude")) delete e.CLAUDE_CONFIG_DIR;
|
|
1994
|
+
else e.CLAUDE_CONFIG_DIR = configDir;
|
|
1995
|
+
}
|
|
1996
|
+
return e;
|
|
1997
|
+
}
|
|
1998
|
+
function parseAuthStatus(stdout) {
|
|
1999
|
+
const text = String(stdout || "").trim();
|
|
2000
|
+
const start = text.indexOf("{");
|
|
2001
|
+
if (start < 0) return { loggedIn: null };
|
|
2002
|
+
try {
|
|
2003
|
+
const j = JSON.parse(text.slice(start));
|
|
2004
|
+
if (typeof j.loggedIn !== "boolean") return { loggedIn: null };
|
|
2005
|
+
return {
|
|
2006
|
+
loggedIn: j.loggedIn,
|
|
2007
|
+
...typeof j.authMethod === "string" ? { method: j.authMethod } : {},
|
|
2008
|
+
...typeof j.subscriptionType === "string" ? { subscription: j.subscriptionType } : {}
|
|
2009
|
+
};
|
|
2010
|
+
} catch {
|
|
2011
|
+
return { loggedIn: null };
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
function claudeLogin2(bin, configDir, env = process.env) {
|
|
2015
|
+
const c = probe(bin, ["auth", "status", "--json"]);
|
|
2016
|
+
return new Promise((resolve) => {
|
|
2017
|
+
execFile(
|
|
2018
|
+
c.file,
|
|
2019
|
+
c.args,
|
|
2020
|
+
{ encoding: "utf8", timeout: 2e4, env: claudeEnv(configDir, env), maxBuffer: 256 * 1024, shell: c.shell },
|
|
2021
|
+
// `auth status` may exit non-zero when signed out; its JSON still says so.
|
|
2022
|
+
(_err, stdout) => resolve(parseAuthStatus(stdout))
|
|
2023
|
+
);
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
function loginCommand(configDir) {
|
|
2027
|
+
const home = os2.homedir();
|
|
2028
|
+
const isDefault = !configDir || path7.resolve(configDir) === path7.resolve(home, ".claude");
|
|
2029
|
+
return isDefault ? "`claude auth login`" : `\`CLAUDE_CONFIG_DIR=${configDir.replace(home, "~")} claude auth login\``;
|
|
2030
|
+
}
|
|
2031
|
+
function loginHint(configDir, platform = process.platform) {
|
|
2032
|
+
const shown = configDir ? configDir.replace(os2.homedir(), "~") : "~/.claude";
|
|
2033
|
+
const login = loginCommand(configDir);
|
|
2034
|
+
if (platform === "darwin")
|
|
2035
|
+
return `Claude Code (${shown}) isn't signed in as this machine's background service sees it. On a Mac its login lives in the login keychain, which a background service can read only while someone is logged in at the screen: log in at the Mac, or run ${login} there.`;
|
|
2036
|
+
return `Claude Code (${shown}) isn't signed in on this machine: run ${login} there.`;
|
|
2037
|
+
}
|
|
2038
|
+
function detectCaps(bin, timeoutMs = 3e4) {
|
|
2039
|
+
const c = probe(bin, ["--help"]);
|
|
2040
|
+
try {
|
|
2041
|
+
return parseCaps(
|
|
2042
|
+
execFileSync3(c.file, c.args, {
|
|
2043
|
+
encoding: "utf8",
|
|
2044
|
+
timeout: timeoutMs,
|
|
2045
|
+
env: claudeEnv(),
|
|
2046
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2047
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
2048
|
+
shell: c.shell
|
|
2049
|
+
})
|
|
2050
|
+
);
|
|
2051
|
+
} catch (e) {
|
|
2052
|
+
const err = e;
|
|
2053
|
+
const caps = parseCaps(String(err?.stdout || ""));
|
|
2054
|
+
if (caps.hasMode) return caps;
|
|
2055
|
+
const why = err?.code === "ETIMEDOUT" ? `timed out after ${timeoutMs / 1e3} s` : "failed";
|
|
2056
|
+
return { ...caps, unread: `\`${path7.basename(bin)} --help\` ${why}` };
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
function transcriptIn(session, dir) {
|
|
2060
|
+
let projects = [];
|
|
2061
|
+
try {
|
|
2062
|
+
projects = readdirSync4(path7.join(dir, "projects"), { withFileTypes: true });
|
|
2063
|
+
} catch {
|
|
2064
|
+
return null;
|
|
2065
|
+
}
|
|
2066
|
+
for (const p of projects) {
|
|
2067
|
+
if (!p.isDirectory()) continue;
|
|
2068
|
+
const file = path7.join(dir, "projects", p.name, `${session}.jsonl`);
|
|
2069
|
+
try {
|
|
2070
|
+
if (statSync5(file).isFile()) return { file, configDir: dir, project: p.name };
|
|
2071
|
+
} catch {
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
return null;
|
|
2075
|
+
}
|
|
2076
|
+
function findTranscript(session, dirs) {
|
|
2077
|
+
if (!UUID_RE.test(session)) return null;
|
|
2078
|
+
for (const dir of dirs) {
|
|
2079
|
+
const t = transcriptIn(session, dir);
|
|
2080
|
+
if (t) return t;
|
|
2081
|
+
}
|
|
2082
|
+
return null;
|
|
2083
|
+
}
|
|
2084
|
+
function findTranscripts(session, dirs) {
|
|
2085
|
+
if (!UUID_RE.test(session)) return [];
|
|
2086
|
+
const out = [];
|
|
2087
|
+
for (const dir of dirs) {
|
|
2088
|
+
const t = transcriptIn(session, dir);
|
|
2089
|
+
if (t) out.push(t);
|
|
2090
|
+
}
|
|
2091
|
+
return out;
|
|
2092
|
+
}
|
|
2093
|
+
var homeName = (t) => path7.basename(t.configDir);
|
|
2094
|
+
function resolveHome(session, dirs) {
|
|
2095
|
+
const all = findTranscripts(session, dirs);
|
|
2096
|
+
if (!all.length) return null;
|
|
2097
|
+
if (all.length === 1) return { transcript: all[0] };
|
|
2098
|
+
const live = all.filter((t) => liveRecords(session, t.configDir).length > 0);
|
|
2099
|
+
if (live.length === 1) return { transcript: live[0] };
|
|
2100
|
+
return {
|
|
2101
|
+
ambiguous: [...new Set(all.map(homeName))].sort(),
|
|
2102
|
+
live: [...new Set(live.map(homeName))].sort()
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
var projectName = (cwd) => cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
2106
|
+
function sessionFolder(t, cap2 = 8 * 1024 * 1024) {
|
|
2107
|
+
let first = null;
|
|
2108
|
+
let count = 0;
|
|
2109
|
+
let fd;
|
|
2110
|
+
const look = (line) => {
|
|
2111
|
+
if (line.indexOf('"cwd"') < 0) return null;
|
|
2112
|
+
let j;
|
|
2113
|
+
try {
|
|
2114
|
+
j = JSON.parse(line.toString("utf8"));
|
|
2115
|
+
} catch {
|
|
2116
|
+
return null;
|
|
2117
|
+
}
|
|
2118
|
+
if (!j || typeof j.cwd !== "string" || !path7.isAbsolute(j.cwd)) return null;
|
|
2119
|
+
if (projectName(j.cwd) === t.project) return j.cwd;
|
|
2120
|
+
first ??= j.cwd;
|
|
2121
|
+
count++;
|
|
2122
|
+
return null;
|
|
2123
|
+
};
|
|
2124
|
+
try {
|
|
2125
|
+
fd = openSync2(t.file, "r");
|
|
2126
|
+
const buf = Buffer.alloc(256 * 1024);
|
|
2127
|
+
let pos = 0;
|
|
2128
|
+
let rest = Buffer.alloc(0);
|
|
2129
|
+
let skipping = false;
|
|
2130
|
+
while (pos < cap2 && count < 50) {
|
|
2131
|
+
const n = readSync2(fd, buf, 0, buf.length, pos);
|
|
2132
|
+
if (!n) {
|
|
2133
|
+
if (!skipping && rest.length) {
|
|
2134
|
+
const hit = look(rest);
|
|
2135
|
+
if (hit) return hit;
|
|
2136
|
+
}
|
|
2137
|
+
break;
|
|
2138
|
+
}
|
|
2139
|
+
pos += n;
|
|
2140
|
+
let chunk = rest.length ? Buffer.concat([rest, buf.subarray(0, n)]) : buf.subarray(0, n);
|
|
2141
|
+
let nl = chunk.indexOf(10);
|
|
2142
|
+
while (nl >= 0) {
|
|
2143
|
+
const line = chunk.subarray(0, nl);
|
|
2144
|
+
chunk = chunk.subarray(nl + 1);
|
|
2145
|
+
if (skipping) skipping = false;
|
|
2146
|
+
else {
|
|
2147
|
+
const hit = look(line);
|
|
2148
|
+
if (hit) return hit;
|
|
2149
|
+
}
|
|
2150
|
+
nl = chunk.indexOf(10);
|
|
2151
|
+
}
|
|
2152
|
+
if (chunk.length > 2 * 1024 * 1024) {
|
|
2153
|
+
skipping = true;
|
|
2154
|
+
rest = Buffer.alloc(0);
|
|
2155
|
+
} else rest = skipping ? Buffer.alloc(0) : Buffer.from(chunk);
|
|
2156
|
+
}
|
|
2157
|
+
} catch {
|
|
2158
|
+
} finally {
|
|
2159
|
+
if (fd !== void 0)
|
|
2160
|
+
try {
|
|
2161
|
+
closeSync2(fd);
|
|
2162
|
+
} catch {
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
return first;
|
|
2166
|
+
}
|
|
2167
|
+
function pidAlive(pid) {
|
|
2168
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) return false;
|
|
2169
|
+
try {
|
|
2170
|
+
process.kill(pid, 0);
|
|
2171
|
+
return true;
|
|
2172
|
+
} catch (e) {
|
|
2173
|
+
return e.code === "EPERM";
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
var registryRow = (file, configDir, j) => {
|
|
2177
|
+
const pid = Number(j.pid ?? path7.basename(file).slice(0, -5));
|
|
2178
|
+
const str = (v) => typeof v === "string" && v ? v : null;
|
|
2179
|
+
let keyFile = null;
|
|
2180
|
+
try {
|
|
2181
|
+
const want = new RegExp(`^${pid}\\.[0-9a-f]{64}\\.key$`);
|
|
2182
|
+
const hit = readdirSync4(path7.dirname(file)).find((n) => want.test(n));
|
|
2183
|
+
if (hit) keyFile = path7.join(path7.dirname(file), hit);
|
|
2184
|
+
} catch {
|
|
2185
|
+
}
|
|
2186
|
+
return {
|
|
2187
|
+
configDir,
|
|
2188
|
+
file,
|
|
2189
|
+
pid,
|
|
2190
|
+
socket: str(j.messagingSocketPath),
|
|
2191
|
+
peerProtocol: typeof j.peerProtocol === "number" ? j.peerProtocol : null,
|
|
2192
|
+
procStart: str(j.procStart),
|
|
2193
|
+
status: str(j.status),
|
|
2194
|
+
kind: str(j.kind),
|
|
2195
|
+
entrypoint: str(j.entrypoint),
|
|
2196
|
+
keyFile
|
|
2197
|
+
};
|
|
2198
|
+
};
|
|
2199
|
+
function liveRecords(session, dirs) {
|
|
2200
|
+
const out = [];
|
|
2201
|
+
for (const dir of typeof dirs === "string" ? [dirs] : dirs) {
|
|
2202
|
+
let names = [];
|
|
2203
|
+
try {
|
|
2204
|
+
names = readdirSync4(path7.join(dir, "sessions")).filter((n) => /^\d+\.json$/.test(n));
|
|
2205
|
+
} catch {
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
for (const n of names) {
|
|
2209
|
+
const file = path7.join(dir, "sessions", n);
|
|
2210
|
+
let j;
|
|
2211
|
+
try {
|
|
2212
|
+
j = JSON.parse(readFileSync5(file, "utf8"));
|
|
2213
|
+
} catch {
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
2216
|
+
if (String(j.sessionId) !== session) continue;
|
|
2217
|
+
const row = registryRow(file, dir, j);
|
|
2218
|
+
if (pidAlive(row.pid)) out.push(row);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
return out;
|
|
2222
|
+
}
|
|
2223
|
+
function liveRecord(session, dirs) {
|
|
2224
|
+
const all = liveRecords(session, dirs);
|
|
2225
|
+
return all.length === 1 ? all[0] : null;
|
|
2226
|
+
}
|
|
2227
|
+
function liveProcess(session, configDir) {
|
|
2228
|
+
return liveRecords(session, configDir).length > 0;
|
|
2229
|
+
}
|
|
2230
|
+
var MODE_RANK = {
|
|
2231
|
+
plan: 0,
|
|
2232
|
+
default: 1,
|
|
2233
|
+
manual: 1,
|
|
2234
|
+
dontAsk: 1,
|
|
2235
|
+
acceptEdits: 2,
|
|
2236
|
+
auto: 3,
|
|
2237
|
+
bypassPermissions: 4
|
|
2238
|
+
};
|
|
2239
|
+
var CLAUDE_MODES = Object.keys(MODE_RANK);
|
|
2240
|
+
var modeRank = (m) => m && m in MODE_RANK ? MODE_RANK[m] : -1;
|
|
2241
|
+
function hookMode(payload) {
|
|
2242
|
+
if (!payload || typeof payload !== "object") return null;
|
|
2243
|
+
const p = payload;
|
|
2244
|
+
const v = typeof p.permission_mode === "string" ? p.permission_mode : p.permissionMode;
|
|
2245
|
+
return typeof v === "string" && CLAUDE_MODES.includes(v) ? v : null;
|
|
2246
|
+
}
|
|
2247
|
+
function peerClass(mode) {
|
|
2248
|
+
if (mode === "bypassPermissions") return "bypass";
|
|
2249
|
+
if (mode === "default" || mode === "manual" || mode === "dontAsk" || mode === "acceptEdits") return "prompting";
|
|
2250
|
+
return null;
|
|
2251
|
+
}
|
|
2252
|
+
var NO_HOME_SETTINGS = { defaultMode: null, disableAutoMode: false, disableBypass: false };
|
|
2253
|
+
function homeSettings(configDir, home = os2.homedir()) {
|
|
2254
|
+
const file = path7.join(configDir ?? path7.join(home, ".claude"), "settings.json");
|
|
2255
|
+
let j;
|
|
2256
|
+
try {
|
|
2257
|
+
j = JSON.parse(readFileSync5(file, "utf8"));
|
|
2258
|
+
} catch {
|
|
2259
|
+
return { ...NO_HOME_SETTINGS };
|
|
2260
|
+
}
|
|
2261
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return { ...NO_HOME_SETTINGS };
|
|
2262
|
+
const p = j.permissions && typeof j.permissions === "object" ? j.permissions : {};
|
|
2263
|
+
const off = (k) => j[k] === "disable" || p[k] === "disable";
|
|
2264
|
+
return {
|
|
2265
|
+
defaultMode: typeof p.defaultMode === "string" && CLAUDE_MODES.includes(p.defaultMode) ? p.defaultMode : null,
|
|
2266
|
+
disableAutoMode: off("disableAutoMode"),
|
|
2267
|
+
disableBypass: off("disableBypassPermissionsMode")
|
|
2268
|
+
};
|
|
2269
|
+
}
|
|
2270
|
+
function relaunchMode(recorded, home) {
|
|
2271
|
+
if (recorded === "plan") return recorded;
|
|
2272
|
+
if ((recorded === "dontAsk" || recorded === "acceptEdits") && modeRank(home.defaultMode) >= modeRank(recorded))
|
|
2273
|
+
return recorded;
|
|
2274
|
+
return null;
|
|
2275
|
+
}
|
|
2276
|
+
function recentlyWritten(file, ourLastEnd, now = Date.now()) {
|
|
2277
|
+
let m;
|
|
2278
|
+
try {
|
|
2279
|
+
m = statSync5(file).mtimeMs;
|
|
2280
|
+
} catch {
|
|
2281
|
+
return false;
|
|
2282
|
+
}
|
|
2283
|
+
if (now - m > LIVE_WRITE_MS) return false;
|
|
2284
|
+
return !(ourLastEnd && m <= ourLastEnd + 2e3);
|
|
2285
|
+
}
|
|
2286
|
+
function runClaude(bin, args, o) {
|
|
2287
|
+
return runHeadless(bin, args, { ...o, keep: o.keep ?? ((line) => line.includes('"type":"result"')) });
|
|
2288
|
+
}
|
|
2289
|
+
function parseResult(stdout) {
|
|
2290
|
+
const s = String(stdout || "").trim();
|
|
2291
|
+
if (!s) return null;
|
|
2292
|
+
const pick = (j) => {
|
|
2293
|
+
if (Array.isArray(j)) {
|
|
2294
|
+
for (let i = j.length - 1; i >= 0; i--) if (j[i]?.type === "result") return j[i];
|
|
2295
|
+
return null;
|
|
2296
|
+
}
|
|
2297
|
+
return j && typeof j === "object" ? j : null;
|
|
2298
|
+
};
|
|
2299
|
+
try {
|
|
2300
|
+
return pick(JSON.parse(s));
|
|
2301
|
+
} catch {
|
|
2302
|
+
}
|
|
2303
|
+
const lines = s.split("\n");
|
|
2304
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2305
|
+
try {
|
|
2306
|
+
const j = pick(JSON.parse(lines[i]));
|
|
2307
|
+
if (j && (j.type === "result" || "result" in j)) return j;
|
|
2308
|
+
} catch {
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
return null;
|
|
2312
|
+
}
|
|
2313
|
+
var isUuid = (s) => typeof s === "string" && UUID_RE.test(s);
|
|
2314
|
+
|
|
1618
2315
|
// ../core/src/adapters/codex.ts
|
|
1619
|
-
import { existsSync as existsSync4,
|
|
1620
|
-
import
|
|
2316
|
+
import { existsSync as existsSync4, readdirSync as readdirSync5, readFileSync as readFileSync6 } from "node:fs";
|
|
2317
|
+
import path8 from "node:path";
|
|
1621
2318
|
|
|
1622
2319
|
// ../core/src/adapters/codex-config.ts
|
|
1623
2320
|
var NOTIFY_LINE = /^[ \t]*notify[ \t]*=[ \t]*(\[.*\])[ \t]*(?:#.*)?(?=\r?$)/m;
|
|
@@ -1763,35 +2460,36 @@ var CODEX_EVENTS = [
|
|
|
1763
2460
|
];
|
|
1764
2461
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1765
2462
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1766
|
-
var codexHome = (env) => env.CODEX_HOME ||
|
|
1767
|
-
var hooksFile2 = (env) =>
|
|
1768
|
-
var configFile2 = (env) =>
|
|
1769
|
-
var sessionsDir = (env) =>
|
|
2463
|
+
var codexHome = (env) => env.CODEX_HOME || path8.join(homeDir(), ".codex");
|
|
2464
|
+
var hooksFile2 = (env) => path8.join(codexHome(env), "hooks.json");
|
|
2465
|
+
var configFile2 = (env) => path8.join(codexHome(env), "config.toml");
|
|
2466
|
+
var sessionsDir = (env) => path8.join(codexHome(env), "sessions");
|
|
1770
2467
|
var validTool3 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1771
2468
|
var endReason2 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
2469
|
+
var UNTIL_APPROVED = "Codex will report nothing until you approve the hooks: start `codex` and accept the hook review";
|
|
1772
2470
|
function installNotify(cmd, env) {
|
|
1773
2471
|
const file = configFile2(env);
|
|
1774
2472
|
let text = "";
|
|
2473
|
+
let existed = true;
|
|
1775
2474
|
try {
|
|
1776
|
-
text =
|
|
1777
|
-
} catch {
|
|
2475
|
+
text = readFileSync6(file, "utf8");
|
|
2476
|
+
} catch (e) {
|
|
2477
|
+
existed = e.code !== "ENOENT";
|
|
1778
2478
|
}
|
|
1779
2479
|
const argv = cmd([NAME3, "notify"]).argv;
|
|
1780
2480
|
const line = `notify = ${JSON.stringify(argv)}`;
|
|
1781
2481
|
const n = readNotify(text);
|
|
1782
|
-
const write = (next) => {
|
|
1783
|
-
|
|
1784
|
-
|
|
2482
|
+
const write = (next, note) => {
|
|
2483
|
+
const bad = writeBack(file, next, existed);
|
|
2484
|
+
return bad ? { ...bad, note: `${bad.note}. ${UNTIL_APPROVED}` } : { file, changed: true, note };
|
|
1785
2485
|
};
|
|
1786
2486
|
if (n && !n.other) {
|
|
1787
2487
|
if (!n.misplaced && JSON.stringify(n.argv) === JSON.stringify(argv))
|
|
1788
2488
|
return { file, changed: false, note: "notify current" };
|
|
1789
|
-
write(
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
note: n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
1794
|
-
};
|
|
2489
|
+
return write(
|
|
2490
|
+
withNotify(text, line),
|
|
2491
|
+
n.misplaced ? "notify moved to the top level (it sat inside a [table], where Codex never reads it)" : "notify updated"
|
|
2492
|
+
);
|
|
1795
2493
|
}
|
|
1796
2494
|
if (n?.other) {
|
|
1797
2495
|
const prev = previousNotify(n.argv);
|
|
@@ -1800,40 +2498,38 @@ function installNotify(cmd, env) {
|
|
|
1800
2498
|
if (JSON.stringify(prev) === JSON.stringify(argv))
|
|
1801
2499
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1802
2500
|
}
|
|
1803
|
-
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
1804
|
-
write(
|
|
1805
|
-
|
|
1806
|
-
|
|
2501
|
+
if (!prev || OURS2.test(JSON.stringify(prev)))
|
|
2502
|
+
return write(
|
|
2503
|
+
replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`),
|
|
2504
|
+
"chained after Codex Computer Use (--previous-notify)"
|
|
2505
|
+
);
|
|
1807
2506
|
}
|
|
1808
2507
|
return {
|
|
1809
2508
|
file,
|
|
1810
2509
|
changed: false,
|
|
1811
2510
|
skipped: true,
|
|
1812
|
-
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone.
|
|
2511
|
+
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. ${UNTIL_APPROVED}`
|
|
1813
2512
|
};
|
|
1814
2513
|
}
|
|
1815
|
-
write(withNotify(text, line));
|
|
1816
|
-
return { file, changed: true, note: "notify set at the top level" };
|
|
2514
|
+
return write(withNotify(text, line), "notify set at the top level");
|
|
1817
2515
|
}
|
|
1818
2516
|
function uninstallNotify(env) {
|
|
1819
2517
|
const file = configFile2(env);
|
|
1820
2518
|
let text;
|
|
1821
2519
|
try {
|
|
1822
|
-
text =
|
|
2520
|
+
text = readFileSync6(file, "utf8");
|
|
1823
2521
|
} catch {
|
|
1824
2522
|
return { file, changed: false };
|
|
1825
2523
|
}
|
|
1826
2524
|
const n = readNotify(text);
|
|
1827
2525
|
if (!n) return { file, changed: false };
|
|
1828
|
-
if (!n.other) {
|
|
1829
|
-
writeFileSync4(file, withoutNotify(text));
|
|
1830
|
-
return { file, changed: true };
|
|
1831
|
-
}
|
|
2526
|
+
if (!n.other) return writeBack(file, withoutNotify(text)) ?? { file, changed: true };
|
|
1832
2527
|
const prev = previousNotify(n.argv);
|
|
1833
|
-
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
2528
|
+
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev)))
|
|
2529
|
+
return writeBack(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`)) ?? {
|
|
2530
|
+
file,
|
|
2531
|
+
changed: true
|
|
2532
|
+
};
|
|
1837
2533
|
return { file, changed: false };
|
|
1838
2534
|
}
|
|
1839
2535
|
function findRollout(threadId, env = process.env) {
|
|
@@ -1842,12 +2538,12 @@ function findRollout(threadId, env = process.env) {
|
|
|
1842
2538
|
const walk = (dir, depth) => {
|
|
1843
2539
|
let names = [];
|
|
1844
2540
|
try {
|
|
1845
|
-
names =
|
|
2541
|
+
names = readdirSync5(dir);
|
|
1846
2542
|
} catch {
|
|
1847
2543
|
return;
|
|
1848
2544
|
}
|
|
1849
2545
|
for (const n of names.sort().reverse()) {
|
|
1850
|
-
const p =
|
|
2546
|
+
const p = path8.join(dir, n);
|
|
1851
2547
|
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1852
2548
|
best = p;
|
|
1853
2549
|
return;
|
|
@@ -1865,8 +2561,12 @@ function rolloutMeta(file) {
|
|
|
1865
2561
|
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1866
2562
|
}
|
|
1867
2563
|
function ofAnotherThread(file, id) {
|
|
2564
|
+
const other = threadOf(file);
|
|
2565
|
+
return !!other && other !== id.toLowerCase();
|
|
2566
|
+
}
|
|
2567
|
+
function threadOf(file) {
|
|
1868
2568
|
const m = /-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl(?:\.zst)?$/i.exec(file);
|
|
1869
|
-
return
|
|
2569
|
+
return m?.[1] ? m[1].toLowerCase() : null;
|
|
1870
2570
|
}
|
|
1871
2571
|
var codex = {
|
|
1872
2572
|
name: NAME3,
|
|
@@ -1891,7 +2591,7 @@ var codex = {
|
|
|
1891
2591
|
];
|
|
1892
2592
|
let text = "";
|
|
1893
2593
|
try {
|
|
1894
|
-
text =
|
|
2594
|
+
text = readFileSync6(configFile2(env), "utf8");
|
|
1895
2595
|
} catch {
|
|
1896
2596
|
}
|
|
1897
2597
|
const n = readNotify(text);
|
|
@@ -1945,6 +2645,8 @@ var codex = {
|
|
|
1945
2645
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
1946
2646
|
const tool = validTool3(s.tool_name) ? s.tool_name : void 0;
|
|
1947
2647
|
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
2648
|
+
const thread = typeof s.transcript_path === "string" ? threadOf(s.transcript_path) : null;
|
|
2649
|
+
const agent_id = typeof s.agent_id === "string" && s.agent_id ? s.agent_id : typeof s.subagent_id === "string" && s.subagent_id ? s.subagent_id : thread && thread !== String(id).toLowerCase() ? thread : void 0;
|
|
1948
2650
|
const ev = (type, data) => ({ type, data: prune3(data), harnessEvent: event });
|
|
1949
2651
|
const events = [];
|
|
1950
2652
|
switch (event) {
|
|
@@ -1964,17 +2666,30 @@ var codex = {
|
|
|
1964
2666
|
);
|
|
1965
2667
|
break;
|
|
1966
2668
|
case "PreToolUse":
|
|
1967
|
-
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
2669
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
1968
2670
|
break;
|
|
1969
2671
|
case "PostToolUse":
|
|
1970
2672
|
if (tool)
|
|
1971
2673
|
events.push(
|
|
1972
|
-
ev("tool.ended", {
|
|
2674
|
+
ev("tool.ended", {
|
|
2675
|
+
tool,
|
|
2676
|
+
call_id,
|
|
2677
|
+
turn_id,
|
|
2678
|
+
agent_id,
|
|
2679
|
+
ok: true,
|
|
2680
|
+
input: s.tool_input,
|
|
2681
|
+
output: s.tool_response
|
|
2682
|
+
})
|
|
1973
2683
|
);
|
|
1974
2684
|
break;
|
|
1975
2685
|
case "PermissionRequest":
|
|
1976
2686
|
events.push(
|
|
1977
|
-
ev("attention.needed", {
|
|
2687
|
+
ev("attention.needed", {
|
|
2688
|
+
attention_id: call_id ?? `perm-${Date.now()}`,
|
|
2689
|
+
kind: "permission",
|
|
2690
|
+
tool,
|
|
2691
|
+
agent_id
|
|
2692
|
+
})
|
|
1978
2693
|
);
|
|
1979
2694
|
break;
|
|
1980
2695
|
case "Stop":
|
|
@@ -2018,7 +2733,7 @@ var codex = {
|
|
|
2018
2733
|
readTranscript(file, fromLine) {
|
|
2019
2734
|
let text = "";
|
|
2020
2735
|
try {
|
|
2021
|
-
text =
|
|
2736
|
+
text = readFileSync6(file, "utf8");
|
|
2022
2737
|
} catch {
|
|
2023
2738
|
return { turns: [], line: fromLine };
|
|
2024
2739
|
}
|
|
@@ -2032,7 +2747,7 @@ var codex = {
|
|
|
2032
2747
|
backfill(sinceMs, env = process.env) {
|
|
2033
2748
|
const rows2 = [];
|
|
2034
2749
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
2035
|
-
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(
|
|
2750
|
+
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path8.basename(f));
|
|
2036
2751
|
if (!m?.[1]) continue;
|
|
2037
2752
|
if (codexSubagent(rolloutMeta(f))) continue;
|
|
2038
2753
|
const facts = codexFacts(f);
|
|
@@ -2119,10 +2834,10 @@ function codexFacts(file) {
|
|
|
2119
2834
|
// ../core/src/adapters/devin.ts
|
|
2120
2835
|
import { createHash as createHash3 } from "node:crypto";
|
|
2121
2836
|
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
2122
|
-
import
|
|
2837
|
+
import path9 from "node:path";
|
|
2123
2838
|
|
|
2124
2839
|
// ../core/src/readers/sqlite.ts
|
|
2125
|
-
import { execFileSync as
|
|
2840
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2126
2841
|
import { existsSync as existsSync5 } from "node:fs";
|
|
2127
2842
|
import { createRequire } from "node:module";
|
|
2128
2843
|
function quiet(fn) {
|
|
@@ -2202,7 +2917,7 @@ db.close();
|
|
|
2202
2917
|
process.stdout.write(JSON.stringify(out));`;
|
|
2203
2918
|
function viaChild(file, sql, params, o) {
|
|
2204
2919
|
try {
|
|
2205
|
-
const out =
|
|
2920
|
+
const out = execFileSync4(
|
|
2206
2921
|
process.execPath,
|
|
2207
2922
|
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
2208
2923
|
{
|
|
@@ -2246,7 +2961,7 @@ function viaCli(file, sql, params, o) {
|
|
|
2246
2961
|
const text = inlineParams(sql, params);
|
|
2247
2962
|
if (!text) return null;
|
|
2248
2963
|
try {
|
|
2249
|
-
const out =
|
|
2964
|
+
const out = execFileSync4("sqlite3", ["-readonly", "-json", file, text], {
|
|
2250
2965
|
encoding: "utf8",
|
|
2251
2966
|
timeout: o.timeoutMs + 3e3,
|
|
2252
2967
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -2303,11 +3018,11 @@ function foreignEvent(file) {
|
|
|
2303
3018
|
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
2304
3019
|
}
|
|
2305
3020
|
var homeOf = (env) => env.HOME || homeDir();
|
|
2306
|
-
var appData = (env) => env.APPDATA ||
|
|
2307
|
-
var devinConfigDir = (env = process.env) => process.platform === "win32" ?
|
|
2308
|
-
var configFile3 = (env) =>
|
|
2309
|
-
var devinDataDir = (env = process.env) => process.platform === "win32" ?
|
|
2310
|
-
var sessionsDb = (env) =>
|
|
3021
|
+
var appData = (env) => env.APPDATA || path9.join(homeOf(env), "AppData", "Roaming");
|
|
3022
|
+
var devinConfigDir = (env = process.env) => process.platform === "win32" ? path9.join(appData(env), "devin") : path9.join(env.XDG_CONFIG_HOME || path9.join(homeOf(env), ".config"), "devin");
|
|
3023
|
+
var configFile3 = (env) => path9.join(devinConfigDir(env), "config.json");
|
|
3024
|
+
var devinDataDir = (env = process.env) => process.platform === "win32" ? path9.join(appData(env), "devin", "cli") : path9.join(env.XDG_DATA_HOME || path9.join(homeOf(env), ".local", "share"), "devin", "cli");
|
|
3025
|
+
var sessionsDb = (env) => path9.join(devinDataDir(env), "sessions.db");
|
|
2311
3026
|
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
2312
3027
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2313
3028
|
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
@@ -2464,7 +3179,7 @@ var CHAIN_LIMIT = 4e3;
|
|
|
2464
3179
|
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2465
3180
|
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2466
3181
|
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2467
|
-
UNION
|
|
3182
|
+
UNION
|
|
2468
3183
|
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2469
3184
|
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2470
3185
|
)
|
|
@@ -2479,7 +3194,7 @@ var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
|
2479
3194
|
function sessionFacts(row) {
|
|
2480
3195
|
const out = {};
|
|
2481
3196
|
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2482
|
-
if (typeof row.working_directory === "string" &&
|
|
3197
|
+
if (typeof row.working_directory === "string" && path9.isAbsolute(row.working_directory))
|
|
2483
3198
|
out.cwd = row.working_directory;
|
|
2484
3199
|
const started = Number(row.created_at);
|
|
2485
3200
|
const last = Number(row.last_activity_at);
|
|
@@ -2524,8 +3239,8 @@ function readDevinChain(db, session, fromLine) {
|
|
|
2524
3239
|
}
|
|
2525
3240
|
function devinVersion(env = process.env) {
|
|
2526
3241
|
try {
|
|
2527
|
-
const target = readlinkSync(
|
|
2528
|
-
const name =
|
|
3242
|
+
const target = readlinkSync(path9.join(devinDataDir(env), "_versions", "current"));
|
|
3243
|
+
const name = path9.basename(target);
|
|
2529
3244
|
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2530
3245
|
} catch {
|
|
2531
3246
|
return void 0;
|
|
@@ -2538,9 +3253,9 @@ function devinAccount(env = process.env) {
|
|
|
2538
3253
|
}
|
|
2539
3254
|
|
|
2540
3255
|
// ../core/src/adapters/gemini-cli.ts
|
|
2541
|
-
import { execFileSync as
|
|
2542
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2543
|
-
import
|
|
3256
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
3257
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
|
|
3258
|
+
import path10 from "node:path";
|
|
2544
3259
|
var NAME5 = "gemini-cli";
|
|
2545
3260
|
var GEMINI_EVENTS = [
|
|
2546
3261
|
"SessionStart",
|
|
@@ -2553,16 +3268,16 @@ var GEMINI_EVENTS = [
|
|
|
2553
3268
|
"SessionEnd"
|
|
2554
3269
|
];
|
|
2555
3270
|
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2556
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
2557
|
-
var settings = (env) =>
|
|
3271
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path10.join(homeDir(), ".gemini");
|
|
3272
|
+
var settings = (env) => path10.join(geminiDir(env), "settings.json");
|
|
2558
3273
|
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2559
3274
|
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
2560
3275
|
function geminiVersion(platform = process.platform, env = process.env) {
|
|
2561
3276
|
const win = platform === "win32";
|
|
2562
|
-
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [
|
|
3277
|
+
const bin = win ? findBinary({ names: ["gemini.exe", "gemini.cmd"], dirs: [path10.dirname(process.execPath)], env }) : "gemini";
|
|
2563
3278
|
if (!bin) throw new Error("no gemini on PATH");
|
|
2564
3279
|
const c = probe(bin, ["--version"]);
|
|
2565
|
-
return
|
|
3280
|
+
return execFileSync5(c.file, c.args, {
|
|
2566
3281
|
encoding: "utf8",
|
|
2567
3282
|
timeout: 4e3,
|
|
2568
3283
|
stdio: ["ignore", "pipe", "ignore"],
|
|
@@ -2595,7 +3310,7 @@ var geminiCli = {
|
|
|
2595
3310
|
name: NAME5,
|
|
2596
3311
|
events: GEMINI_EVENTS,
|
|
2597
3312
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
2598
|
-
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(
|
|
3313
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path10.join(geminiDir(env), "tmp")),
|
|
2599
3314
|
configFiles: (env = process.env) => [settings(env)],
|
|
2600
3315
|
install: (cmd, env = process.env, opts = {}) => {
|
|
2601
3316
|
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
@@ -2693,7 +3408,7 @@ var geminiCli = {
|
|
|
2693
3408
|
readTranscript(file, fromLine) {
|
|
2694
3409
|
let text = "";
|
|
2695
3410
|
try {
|
|
2696
|
-
text =
|
|
3411
|
+
text = readFileSync7(file, "utf8");
|
|
2697
3412
|
} catch {
|
|
2698
3413
|
return { turns: [], line: fromLine };
|
|
2699
3414
|
}
|
|
@@ -2733,12 +3448,12 @@ var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
|
2733
3448
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
2734
3449
|
|
|
2735
3450
|
// ../core/src/config.ts
|
|
2736
|
-
import { chmodSync, existsSync as existsSync8, mkdirSync as
|
|
2737
|
-
import
|
|
3451
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3452
|
+
import path11 from "node:path";
|
|
2738
3453
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
2739
3454
|
function readConfig(file = configFile()) {
|
|
2740
3455
|
try {
|
|
2741
|
-
const j = JSON.parse(
|
|
3456
|
+
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
2742
3457
|
return {
|
|
2743
3458
|
...DEFAULT_CONFIG,
|
|
2744
3459
|
...j,
|
|
@@ -2749,9 +3464,36 @@ function readConfig(file = configFile()) {
|
|
|
2749
3464
|
return { ...DEFAULT_CONFIG, sinks: [], harnesses: {} };
|
|
2750
3465
|
}
|
|
2751
3466
|
}
|
|
3467
|
+
function configProblem(file) {
|
|
3468
|
+
let text;
|
|
3469
|
+
try {
|
|
3470
|
+
text = readFileSync8(file, "utf8");
|
|
3471
|
+
} catch (e) {
|
|
3472
|
+
const code = e.code;
|
|
3473
|
+
return code === "ENOENT" ? void 0 : `could not be read (${code})`;
|
|
3474
|
+
}
|
|
3475
|
+
if (!text.trim()) return void 0;
|
|
3476
|
+
if (text.charCodeAt(0) === 65279) return "starts with a byte-order mark (save it as UTF-8 without one)";
|
|
3477
|
+
try {
|
|
3478
|
+
const j = JSON.parse(text);
|
|
3479
|
+
return j && typeof j === "object" && !Array.isArray(j) ? void 0 : "is not a JSON object";
|
|
3480
|
+
} catch (e) {
|
|
3481
|
+
const m = e.message;
|
|
3482
|
+
const at = (/line \d+ column \d+/.exec(m) ?? /position \d+/.exec(m))?.[0];
|
|
3483
|
+
return `is not valid JSON${at ? ` at ${at}` : ""}`;
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
function assertConfigWritable(file = configFile()) {
|
|
3487
|
+
const problem = configProblem(file);
|
|
3488
|
+
if (problem)
|
|
3489
|
+
throw new Error(
|
|
3490
|
+
`${tilde(file)} ${problem}, so it was not written over: that would lose its machine name and sinks. Fix it, or move it aside to start again, then run this again.`
|
|
3491
|
+
);
|
|
3492
|
+
}
|
|
2752
3493
|
function writeConfig(c, file = configFile()) {
|
|
2753
|
-
|
|
2754
|
-
|
|
3494
|
+
assertConfigWritable(file);
|
|
3495
|
+
mkdirSync2(path11.dirname(file), { recursive: true, mode: 448 });
|
|
3496
|
+
writeFileSync2(file, `${JSON.stringify(c, null, 2)}
|
|
2755
3497
|
`, { mode: 384 });
|
|
2756
3498
|
if (process.platform !== "win32" && existsSync8(file)) {
|
|
2757
3499
|
try {
|
|
@@ -2814,38 +3556,23 @@ function makeEvent(a, ctx) {
|
|
|
2814
3556
|
};
|
|
2815
3557
|
}
|
|
2816
3558
|
|
|
2817
|
-
// ../core/src/launched-by.ts
|
|
2818
|
-
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2819
|
-
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2820
|
-
function launchedBy(env) {
|
|
2821
|
-
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2822
|
-
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2823
|
-
}
|
|
2824
|
-
function readHook(adapter, input) {
|
|
2825
|
-
const r = adapter.fromHook(input);
|
|
2826
|
-
if (r && !r.session.parent_id) {
|
|
2827
|
-
const launcher = launchedBy(input.env);
|
|
2828
|
-
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2829
|
-
}
|
|
2830
|
-
return r;
|
|
2831
|
-
}
|
|
2832
|
-
|
|
2833
3559
|
// ../core/src/outbox.ts
|
|
3560
|
+
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
2834
3561
|
import {
|
|
2835
3562
|
appendFileSync,
|
|
2836
|
-
closeSync as
|
|
3563
|
+
closeSync as closeSync3,
|
|
2837
3564
|
existsSync as existsSync9,
|
|
2838
|
-
mkdirSync as
|
|
2839
|
-
openSync as
|
|
2840
|
-
readdirSync as
|
|
2841
|
-
readFileSync as
|
|
2842
|
-
readSync as
|
|
3565
|
+
mkdirSync as mkdirSync3,
|
|
3566
|
+
openSync as openSync3,
|
|
3567
|
+
readdirSync as readdirSync6,
|
|
3568
|
+
readFileSync as readFileSync9,
|
|
3569
|
+
readSync as readSync3,
|
|
2843
3570
|
renameSync,
|
|
2844
|
-
statSync as
|
|
2845
|
-
unlinkSync as
|
|
2846
|
-
writeFileSync as
|
|
3571
|
+
statSync as statSync6,
|
|
3572
|
+
unlinkSync as unlinkSync2,
|
|
3573
|
+
writeFileSync as writeFileSync3
|
|
2847
3574
|
} from "node:fs";
|
|
2848
|
-
import
|
|
3575
|
+
import path12 from "node:path";
|
|
2849
3576
|
var READ_CHUNK = 1024 * 1024;
|
|
2850
3577
|
var READ_MAX = 8 * 1024 * 1024;
|
|
2851
3578
|
var Outbox = class {
|
|
@@ -2854,13 +3581,13 @@ var Outbox = class {
|
|
|
2854
3581
|
}
|
|
2855
3582
|
root;
|
|
2856
3583
|
dir(harness) {
|
|
2857
|
-
return
|
|
3584
|
+
return path12.join(this.root, "outbox", safeId(harness));
|
|
2858
3585
|
}
|
|
2859
3586
|
file(ref) {
|
|
2860
|
-
return
|
|
3587
|
+
return path12.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2861
3588
|
}
|
|
2862
3589
|
seqFile(ref) {
|
|
2863
|
-
return
|
|
3590
|
+
return path12.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2864
3591
|
}
|
|
2865
3592
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2866
3593
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
@@ -2868,19 +3595,19 @@ var Outbox = class {
|
|
|
2868
3595
|
const f = this.seqFile(ref);
|
|
2869
3596
|
let n = -1;
|
|
2870
3597
|
try {
|
|
2871
|
-
const t =
|
|
3598
|
+
const t = readFileSync9(f, "utf8").trim();
|
|
2872
3599
|
if (/^\d+$/.test(t)) n = Number(t);
|
|
2873
3600
|
} catch {
|
|
2874
3601
|
}
|
|
2875
3602
|
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2876
|
-
|
|
2877
|
-
|
|
3603
|
+
mkdirSync3(path12.dirname(f), { recursive: true, mode: 448 });
|
|
3604
|
+
writeFileSync3(f, String(n + 1), { mode: 384 });
|
|
2878
3605
|
return n;
|
|
2879
3606
|
}
|
|
2880
3607
|
lastSeqInFile(ref) {
|
|
2881
3608
|
const f = this.file(ref);
|
|
2882
3609
|
if (!existsSync9(f)) return -1;
|
|
2883
|
-
const lines =
|
|
3610
|
+
const lines = readFileSync9(f, "utf8").trim().split("\n");
|
|
2884
3611
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2885
3612
|
try {
|
|
2886
3613
|
const j = JSON.parse(lines[i]);
|
|
@@ -2892,26 +3619,26 @@ var Outbox = class {
|
|
|
2892
3619
|
}
|
|
2893
3620
|
append(ref, events) {
|
|
2894
3621
|
if (!events.length) return;
|
|
2895
|
-
|
|
3622
|
+
mkdirSync3(this.dir(ref.harness), { recursive: true, mode: 448 });
|
|
2896
3623
|
appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2897
3624
|
`, { mode: 384 });
|
|
2898
3625
|
}
|
|
2899
3626
|
/** Every session file, newest first. */
|
|
2900
3627
|
sessions() {
|
|
2901
3628
|
const out = [];
|
|
2902
|
-
const base =
|
|
3629
|
+
const base = path12.join(this.root, "outbox");
|
|
2903
3630
|
if (!existsSync9(base)) return [];
|
|
2904
|
-
for (const h of
|
|
2905
|
-
const d =
|
|
3631
|
+
for (const h of readdirSync6(base)) {
|
|
3632
|
+
const d = path12.join(base, h);
|
|
2906
3633
|
let names = [];
|
|
2907
3634
|
try {
|
|
2908
|
-
names =
|
|
3635
|
+
names = readdirSync6(d).filter((n) => n.endsWith(".jsonl"));
|
|
2909
3636
|
} catch {
|
|
2910
3637
|
continue;
|
|
2911
3638
|
}
|
|
2912
3639
|
for (const n of names) {
|
|
2913
3640
|
try {
|
|
2914
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime:
|
|
3641
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync6(path12.join(d, n)).mtimeMs });
|
|
2915
3642
|
} catch {
|
|
2916
3643
|
}
|
|
2917
3644
|
}
|
|
@@ -2927,16 +3654,16 @@ var Outbox = class {
|
|
|
2927
3654
|
read(ref, from, maxEvents = 500) {
|
|
2928
3655
|
const f = this.file(ref);
|
|
2929
3656
|
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2930
|
-
const size =
|
|
3657
|
+
const size = statSync6(f).size;
|
|
2931
3658
|
if (from >= size) return { events: [], offset: from };
|
|
2932
|
-
const fd =
|
|
3659
|
+
const fd = openSync3(f, "r");
|
|
2933
3660
|
try {
|
|
2934
3661
|
const events = [];
|
|
2935
3662
|
let offset = from;
|
|
2936
3663
|
let chunk = READ_CHUNK;
|
|
2937
3664
|
while (events.length < maxEvents && offset < size) {
|
|
2938
3665
|
const buf = Buffer.alloc(Math.min(size - offset, chunk));
|
|
2939
|
-
const n =
|
|
3666
|
+
const n = readSync3(fd, buf, 0, buf.length, offset);
|
|
2940
3667
|
const text = buf.subarray(0, n).toString("utf8");
|
|
2941
3668
|
let pos = 0;
|
|
2942
3669
|
let used = 0;
|
|
@@ -2959,37 +3686,109 @@ var Outbox = class {
|
|
|
2959
3686
|
}
|
|
2960
3687
|
return { events, offset };
|
|
2961
3688
|
} finally {
|
|
2962
|
-
|
|
3689
|
+
closeSync3(fd);
|
|
2963
3690
|
}
|
|
2964
3691
|
}
|
|
2965
|
-
/** Files older than `days` are pruned
|
|
3692
|
+
/** Files older than `days` are pruned, and each sink's cursor into them. The seq
|
|
3693
|
+
* counter stays: a session resumed after the window counts on from where it was,
|
|
3694
|
+
* so a receiver's (harness, session, seq) dedup doesn't take its new events for
|
|
3695
|
+
* ones it already has, and the sinks read the new file from its start.
|
|
3696
|
+
* It all happens under the drain's `flush` lock, since a drain holds the whole
|
|
3697
|
+
* cursor map and writes it back: a file and its cursor keys go together, so a
|
|
3698
|
+
* session resumed in between can't keep a key past its new file's end. While a
|
|
3699
|
+
* drain runs, nothing is pruned (0); the next prune does it. It never throws: it
|
|
3700
|
+
* is a cleanup, and a hook's drain comes after it. */
|
|
2966
3701
|
prune(days, now = Date.now()) {
|
|
3702
|
+
let release = null;
|
|
3703
|
+
try {
|
|
3704
|
+
release = takeNamedLock(this.root, "flush");
|
|
3705
|
+
} catch {
|
|
3706
|
+
}
|
|
3707
|
+
if (!release) return 0;
|
|
2967
3708
|
let n = 0;
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
const
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
3709
|
+
try {
|
|
3710
|
+
const cutoff = now - days * 864e5;
|
|
3711
|
+
const aged = this.sessions().filter((ref) => {
|
|
3712
|
+
try {
|
|
3713
|
+
return statSync6(this.file(ref)).mtimeMs < cutoff;
|
|
3714
|
+
} catch {
|
|
3715
|
+
return false;
|
|
3716
|
+
}
|
|
3717
|
+
});
|
|
3718
|
+
this.dropStaleCursors(new Set(aged.map((ref) => `${ref.harness}/${ref.session}`)));
|
|
3719
|
+
for (const ref of aged) {
|
|
3720
|
+
const f = this.file(ref);
|
|
3721
|
+
try {
|
|
3722
|
+
if (statSync6(f).mtimeMs < cutoff) {
|
|
3723
|
+
unlinkSync2(f);
|
|
3724
|
+
n++;
|
|
2977
3725
|
}
|
|
2978
|
-
|
|
3726
|
+
} catch {
|
|
2979
3727
|
}
|
|
2980
|
-
} catch {
|
|
2981
3728
|
}
|
|
3729
|
+
} catch {
|
|
3730
|
+
} finally {
|
|
3731
|
+
release();
|
|
2982
3732
|
}
|
|
2983
3733
|
return n;
|
|
2984
3734
|
}
|
|
3735
|
+
/** A sink's cursor into a session file about to be pruned (`gone`), or one already
|
|
3736
|
+
* missing (an earlier prune, an older release, by hand) is removed. The caller
|
|
3737
|
+
* holds the `flush` lock. */
|
|
3738
|
+
dropStaleCursors(gone) {
|
|
3739
|
+
let names = [];
|
|
3740
|
+
try {
|
|
3741
|
+
names = readdirSync6(path12.join(this.root, "cursors")).filter((n) => n.endsWith(".json"));
|
|
3742
|
+
} catch {
|
|
3743
|
+
}
|
|
3744
|
+
const stale = (c) => Object.keys(c).filter((k) => {
|
|
3745
|
+
if (gone.has(k)) return true;
|
|
3746
|
+
const i = k.indexOf("/");
|
|
3747
|
+
return !existsSync9(this.file({ harness: k.slice(0, i), session: k.slice(i + 1) }));
|
|
3748
|
+
});
|
|
3749
|
+
for (const n of names) {
|
|
3750
|
+
const s = new Cursors(this.root, n.slice(0, -5));
|
|
3751
|
+
const c = s.read();
|
|
3752
|
+
const keys = stale(c);
|
|
3753
|
+
if (!keys.length) continue;
|
|
3754
|
+
for (const k of keys) delete c[k];
|
|
3755
|
+
s.write(c);
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
2985
3758
|
delete(ref) {
|
|
3759
|
+
this.dropCursors(ref);
|
|
2986
3760
|
for (const f of [this.file(ref), this.seqFile(ref)]) {
|
|
2987
3761
|
try {
|
|
2988
|
-
|
|
3762
|
+
unlinkSync2(f);
|
|
2989
3763
|
} catch {
|
|
2990
3764
|
}
|
|
2991
3765
|
}
|
|
2992
3766
|
}
|
|
3767
|
+
/** The key every sink's cursor holds for a session is removed with the session's
|
|
3768
|
+
* file: the key alone pointed into the old file, and a session that went on (still
|
|
3769
|
+
* running, or resumed later) had its new file's first events unsent until it grew
|
|
3770
|
+
* past that offset. Every file under <state>/cursors is taken (a sink since taken
|
|
3771
|
+
* out of the config still has one; one that doesn't parse reads as empty and is
|
|
3772
|
+
* left), by the key a drain writes (the file's own name, `safeId`) and by the ref
|
|
3773
|
+
* as given. The caller holds the drain's `flush` lock: a drain holds the whole
|
|
3774
|
+
* cursor map and writes it back, so a key dropped under it would come back. */
|
|
3775
|
+
dropCursors(ref) {
|
|
3776
|
+
const keys = /* @__PURE__ */ new Set([`${ref.harness}/${ref.session}`, `${safeId(ref.harness)}/${safeId(ref.session)}`]);
|
|
3777
|
+
let names = [];
|
|
3778
|
+
try {
|
|
3779
|
+
names = readdirSync6(path12.join(this.root, "cursors")).filter((n) => n.endsWith(".json"));
|
|
3780
|
+
} catch {
|
|
3781
|
+
return;
|
|
3782
|
+
}
|
|
3783
|
+
for (const n of names) {
|
|
3784
|
+
const s = new Cursors(this.root, n.slice(0, -5));
|
|
3785
|
+
const c = s.read();
|
|
3786
|
+
const had = Object.keys(c).filter((k) => keys.has(k));
|
|
3787
|
+
if (!had.length) continue;
|
|
3788
|
+
for (const k of had) delete c[k];
|
|
3789
|
+
s.write(c);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
2993
3792
|
};
|
|
2994
3793
|
var Cursors = class {
|
|
2995
3794
|
constructor(root, sink) {
|
|
@@ -2999,20 +3798,23 @@ var Cursors = class {
|
|
|
2999
3798
|
root;
|
|
3000
3799
|
sink;
|
|
3001
3800
|
file() {
|
|
3002
|
-
return
|
|
3801
|
+
return path12.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
3003
3802
|
}
|
|
3803
|
+
/** The map; empty when the file is missing, cut off mid-write, or holds anything
|
|
3804
|
+
* but an object (`null`, a list), so a drain never indexes into those. */
|
|
3004
3805
|
read() {
|
|
3005
3806
|
try {
|
|
3006
|
-
|
|
3807
|
+
const c = JSON.parse(readFileSync9(this.file(), "utf8"));
|
|
3808
|
+
return c && typeof c === "object" && !Array.isArray(c) ? c : {};
|
|
3007
3809
|
} catch {
|
|
3008
3810
|
return {};
|
|
3009
3811
|
}
|
|
3010
3812
|
}
|
|
3011
3813
|
write(c) {
|
|
3012
3814
|
const f = this.file();
|
|
3013
|
-
|
|
3815
|
+
mkdirSync3(path12.dirname(f), { recursive: true, mode: 448 });
|
|
3014
3816
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
3015
|
-
|
|
3817
|
+
writeFileSync3(tmp, JSON.stringify(c), { mode: 384 });
|
|
3016
3818
|
renameSync(tmp, f);
|
|
3017
3819
|
}
|
|
3018
3820
|
key(ref) {
|
|
@@ -3023,22 +3825,44 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
3023
3825
|
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
3024
3826
|
}
|
|
3025
3827
|
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
3026
|
-
const dir =
|
|
3027
|
-
|
|
3028
|
-
const f =
|
|
3828
|
+
const dir = path12.join(root, "locks");
|
|
3829
|
+
mkdirSync3(dir, { recursive: true, mode: 448 });
|
|
3830
|
+
const f = path12.join(dir, `${name}.lock`);
|
|
3831
|
+
const nonce = randomBytes2(8).toString("hex");
|
|
3832
|
+
const mine = `${process.pid} ${nonce}`;
|
|
3833
|
+
let body = null;
|
|
3029
3834
|
try {
|
|
3030
|
-
|
|
3031
|
-
|
|
3835
|
+
body = readFileSync9(f, "utf8");
|
|
3836
|
+
const stale = Date.now() - statSync6(f).mtimeMs > staleMs || !processAlive(Number(body.split(" ")[0]));
|
|
3837
|
+
if (!stale) return null;
|
|
3032
3838
|
} catch {
|
|
3839
|
+
body = null;
|
|
3840
|
+
}
|
|
3841
|
+
if (body !== null) {
|
|
3842
|
+
const aside = `${f}.${process.pid}.${nonce}.stale`;
|
|
3843
|
+
try {
|
|
3844
|
+
renameSync(f, aside);
|
|
3845
|
+
} catch {
|
|
3846
|
+
return null;
|
|
3847
|
+
}
|
|
3848
|
+
const moved = readFileSync9(aside, "utf8");
|
|
3849
|
+
unlinkSync2(aside);
|
|
3850
|
+
if (moved !== body) {
|
|
3851
|
+
try {
|
|
3852
|
+
writeFileSync3(f, moved, { flag: "wx" });
|
|
3853
|
+
} catch {
|
|
3854
|
+
}
|
|
3855
|
+
return null;
|
|
3856
|
+
}
|
|
3033
3857
|
}
|
|
3034
3858
|
try {
|
|
3035
|
-
|
|
3859
|
+
writeFileSync3(f, mine, { flag: "wx" });
|
|
3036
3860
|
} catch {
|
|
3037
3861
|
return null;
|
|
3038
3862
|
}
|
|
3039
3863
|
return () => {
|
|
3040
3864
|
try {
|
|
3041
|
-
|
|
3865
|
+
if (readFileSync9(f, "utf8") === mine) unlinkSync2(f);
|
|
3042
3866
|
} catch {
|
|
3043
3867
|
}
|
|
3044
3868
|
};
|
|
@@ -3057,7 +3881,7 @@ function processAlive(pid) {
|
|
|
3057
3881
|
import { createHash as createHash4 } from "node:crypto";
|
|
3058
3882
|
var RULESET_PII = "pii@1";
|
|
3059
3883
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
3060
|
-
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
3884
|
+
var PHONE = /(?<![\w.])(?:(?<!\)[ .-]?|\+\d{1,3}[ .-]?)(\+?(?:19|20)\d{2}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01]))(?:(?= (?:[01]?\d|2[0-3]):)|(?!\w|[ .-]\d\d))|(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w))/g;
|
|
3061
3885
|
function looksPhone(s) {
|
|
3062
3886
|
const digits = s.replace(/\D/g, "");
|
|
3063
3887
|
if (digits.length < 7 || digits.length > 15) return false;
|
|
@@ -3068,7 +3892,7 @@ function looksPhone(s) {
|
|
|
3068
3892
|
function reducePii(text, home) {
|
|
3069
3893
|
if (!text) return text;
|
|
3070
3894
|
let out = text.replace(EMAIL, "[email]");
|
|
3071
|
-
out = out.replace(PHONE, (m) => looksPhone(m) ? "[phone]" : m);
|
|
3895
|
+
out = out.replace(PHONE, (m, date) => !date && looksPhone(m) ? "[phone]" : m);
|
|
3072
3896
|
if (home) {
|
|
3073
3897
|
const h = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3074
3898
|
out = out.replace(new RegExp(`(?<![A-Za-z0-9_])${h}(?=/|$|\\s|["'\`])`, "g"), "~");
|
|
@@ -3265,6 +4089,8 @@ var TYPE_TIER = {
|
|
|
3265
4089
|
"subagent.ended": 1,
|
|
3266
4090
|
"context.compacted": 1,
|
|
3267
4091
|
"limits.observed": 1,
|
|
4092
|
+
"delegation.started": 0,
|
|
4093
|
+
"delegation.ended": 0,
|
|
3268
4094
|
native: 3
|
|
3269
4095
|
};
|
|
3270
4096
|
var DATA_ALLOW = {
|
|
@@ -3275,17 +4101,19 @@ var DATA_ALLOW = {
|
|
|
3275
4101
|
"session.forgotten": { 0: [] },
|
|
3276
4102
|
"turn.started": { 0: ["turn_id", "submitted_by", "injected"], 1: ["prompt_chars"] },
|
|
3277
4103
|
"turn.ended": { 0: ["turn_id", "reason", "ms", "background"], 1: ["error"] },
|
|
3278
|
-
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
3279
|
-
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
4104
|
+
"turn.transcript": { 2: ["turn", "user", "assistant", "at", "injected"] },
|
|
4105
|
+
"turn.progress": { 2: ["turn", "user", "assistant", "at", "injected"] },
|
|
3280
4106
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
3281
4107
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
3282
4108
|
"files.changed": { 1: ["paths"] },
|
|
3283
|
-
"attention.needed": { 0: ["attention_id", "kind"], 1: ["tool", "message"] },
|
|
4109
|
+
"attention.needed": { 0: ["attention_id", "kind"], 1: ["tool", "message", "agent_id"] },
|
|
3284
4110
|
"attention.cleared": { 0: ["attention_id", "how"] },
|
|
3285
4111
|
"subagent.started": { 1: ["agent_id", "agent_type"] },
|
|
3286
4112
|
"subagent.ended": { 1: ["agent_id", "agent_type"] },
|
|
3287
4113
|
"context.compacted": { 1: ["trigger"] },
|
|
3288
|
-
"limits.observed": { 1: ["windows", "plan", "account_id"] }
|
|
4114
|
+
"limits.observed": { 1: ["windows", "plan", "account_id"] },
|
|
4115
|
+
"delegation.started": { 0: ["ask_id", "via", "harness", "to"], 1: ["text"] },
|
|
4116
|
+
"delegation.ended": { 0: ["ask_id", "to", "ok", "ms", "error", "relayed"], 2: ["reply"] }
|
|
3289
4117
|
};
|
|
3290
4118
|
var SESSION_ALLOW = [
|
|
3291
4119
|
"id",
|
|
@@ -3301,7 +4129,8 @@ var SESSION_ALLOW = [
|
|
|
3301
4129
|
"account_id",
|
|
3302
4130
|
"attended",
|
|
3303
4131
|
"parent_id",
|
|
3304
|
-
"launched_by"
|
|
4132
|
+
"launched_by",
|
|
4133
|
+
"delegated_by"
|
|
3305
4134
|
];
|
|
3306
4135
|
var MAX_SIDE = 2e4;
|
|
3307
4136
|
var MAX_IO = 64 * 1024;
|
|
@@ -3340,6 +4169,8 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
3340
4169
|
if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
|
|
3341
4170
|
if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
|
|
3342
4171
|
if (typeof data.message === "string") data.message = cap(data.message, 200);
|
|
4172
|
+
if (event.type === "delegation.started" && typeof data.text === "string") data.text = cap(data.text, 200);
|
|
4173
|
+
if (event.type === "delegation.ended" && typeof data.reply === "string") data.reply = cap(data.reply, MAX_SIDE);
|
|
3343
4174
|
const rulesets = [];
|
|
3344
4175
|
let out = { ...event, tier, session, data, privacy: { rulesets, pii } };
|
|
3345
4176
|
if (tier > 0) {
|
|
@@ -3366,8 +4197,8 @@ function shortId(harness, id) {
|
|
|
3366
4197
|
}
|
|
3367
4198
|
|
|
3368
4199
|
// ../core/src/sinks/file.ts
|
|
3369
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
3370
|
-
import
|
|
4200
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync4 } from "node:fs";
|
|
4201
|
+
import path13 from "node:path";
|
|
3371
4202
|
var FileSink = class {
|
|
3372
4203
|
constructor(name, file, tier, pii) {
|
|
3373
4204
|
this.name = name;
|
|
@@ -3381,7 +4212,7 @@ var FileSink = class {
|
|
|
3381
4212
|
pii;
|
|
3382
4213
|
async send(events) {
|
|
3383
4214
|
try {
|
|
3384
|
-
|
|
4215
|
+
mkdirSync4(path13.dirname(this.file), { recursive: true });
|
|
3385
4216
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
3386
4217
|
`);
|
|
3387
4218
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -3415,12 +4246,12 @@ var StdoutSink = class {
|
|
|
3415
4246
|
};
|
|
3416
4247
|
|
|
3417
4248
|
// src/control/aliases.ts
|
|
3418
|
-
import { chmodSync as chmodSync2, mkdirSync as
|
|
3419
|
-
import
|
|
4249
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync10, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
4250
|
+
import path14 from "node:path";
|
|
3420
4251
|
|
|
3421
4252
|
// src/secrets.ts
|
|
3422
4253
|
import { spawnSync } from "node:child_process";
|
|
3423
|
-
import { createHash as createHash6, randomBytes as
|
|
4254
|
+
import { createHash as createHash6, randomBytes as randomBytes3 } from "node:crypto";
|
|
3424
4255
|
var SERVICE = "sessionpipe";
|
|
3425
4256
|
var defaultRunner = (cmd, args, input) => {
|
|
3426
4257
|
const r = spawnSync(cmd, args, {
|
|
@@ -3444,9 +4275,9 @@ function secretGet(store, account, run = defaultRunner) {
|
|
|
3444
4275
|
function sinkWithSecrets(s, run = defaultRunner) {
|
|
3445
4276
|
if (!s.token_in) return s;
|
|
3446
4277
|
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
3447
|
-
if (!token) return null;
|
|
3448
4278
|
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
3449
|
-
|
|
4279
|
+
if (!token && !secret) return null;
|
|
4280
|
+
return { ...s, ...token ? { token } : {}, ...secret ? { secret } : {} };
|
|
3450
4281
|
}
|
|
3451
4282
|
|
|
3452
4283
|
// src/control/store.ts
|
|
@@ -3460,10 +4291,10 @@ function startOptionsOf(v) {
|
|
|
3460
4291
|
}
|
|
3461
4292
|
|
|
3462
4293
|
// src/control/aliases.ts
|
|
3463
|
-
var aliasFile = (controlDir) =>
|
|
4294
|
+
var aliasFile = (controlDir) => path14.join(controlDir, "aliases.json");
|
|
3464
4295
|
function readAliases(file) {
|
|
3465
4296
|
try {
|
|
3466
|
-
const j = JSON.parse(
|
|
4297
|
+
const j = JSON.parse(readFileSync10(file, "utf8"));
|
|
3467
4298
|
if (!j || typeof j !== "object" || Array.isArray(j)) return {};
|
|
3468
4299
|
const out = {};
|
|
3469
4300
|
for (const [k, v] of Object.entries(j)) {
|
|
@@ -3484,12 +4315,12 @@ function receiverIdOf(all, harness, id) {
|
|
|
3484
4315
|
|
|
3485
4316
|
// src/control/local.ts
|
|
3486
4317
|
import net from "node:net";
|
|
3487
|
-
import
|
|
3488
|
-
import
|
|
4318
|
+
import os3 from "node:os";
|
|
4319
|
+
import path15 from "node:path";
|
|
3489
4320
|
function socketPath(state, env = process.env) {
|
|
3490
4321
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
3491
|
-
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${
|
|
3492
|
-
return
|
|
4322
|
+
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os3.userInfo().username}`;
|
|
4323
|
+
return path15.join(state, "control.sock");
|
|
3493
4324
|
}
|
|
3494
4325
|
function ask(sock, req, timeoutMs) {
|
|
3495
4326
|
return new Promise((resolve) => {
|
|
@@ -3521,10 +4352,85 @@ function ask(sock, req, timeoutMs) {
|
|
|
3521
4352
|
});
|
|
3522
4353
|
}
|
|
3523
4354
|
|
|
4355
|
+
// src/fold.ts
|
|
4356
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
4357
|
+
import path16 from "node:path";
|
|
4358
|
+
var TOOLS_MODES = ["all", "main", "beat"];
|
|
4359
|
+
var TOOL_FOLD_MS = 6e4;
|
|
4360
|
+
var KEEP_MS = 24 * 60 * 6e4;
|
|
4361
|
+
function isToolsMode(v) {
|
|
4362
|
+
return typeof v === "string" && TOOLS_MODES.includes(v);
|
|
4363
|
+
}
|
|
4364
|
+
function toolsMode(s) {
|
|
4365
|
+
if (isToolsMode(s.tools)) return s.tools;
|
|
4366
|
+
if (isToolsMode(s.receiver_tools)) return s.receiver_tools;
|
|
4367
|
+
return "all";
|
|
4368
|
+
}
|
|
4369
|
+
var agentOf = (e) => typeof e.data.agent_id === "string" ? e.data.agent_id : "";
|
|
4370
|
+
var thins = (mode, agent) => mode === "beat" || mode === "main" && agent !== "";
|
|
4371
|
+
var askKey = (key, agent) => `${key}\0ask\0${agent}`;
|
|
4372
|
+
function sendTool(e, mode, clock, key) {
|
|
4373
|
+
if (mode === "all") return true;
|
|
4374
|
+
const agent = agentOf(e);
|
|
4375
|
+
const t = Date.parse(e.time);
|
|
4376
|
+
if (e.type === "attention.needed") {
|
|
4377
|
+
if (thins(mode, agent)) clock[askKey(key, agent)] = Number.isFinite(t) ? t : 0;
|
|
4378
|
+
return true;
|
|
4379
|
+
}
|
|
4380
|
+
if (e.type !== "tool.started" && e.type !== "tool.ended" || !thins(mode, agent)) return true;
|
|
4381
|
+
if (e.type === "tool.started") return false;
|
|
4382
|
+
if (!Number.isFinite(t)) return true;
|
|
4383
|
+
const ask2 = askKey(key, agent);
|
|
4384
|
+
const asked = clock[ask2];
|
|
4385
|
+
if (asked !== void 0 && t >= asked) {
|
|
4386
|
+
delete clock[ask2];
|
|
4387
|
+
clock[key] = t;
|
|
4388
|
+
return true;
|
|
4389
|
+
}
|
|
4390
|
+
const last = clock[key];
|
|
4391
|
+
if (last !== void 0 && t > last - TOOL_FOLD_MS && t < last + TOOL_FOLD_MS) return false;
|
|
4392
|
+
clock[key] = t;
|
|
4393
|
+
return true;
|
|
4394
|
+
}
|
|
4395
|
+
var ToolClock = class {
|
|
4396
|
+
constructor(root, sink) {
|
|
4397
|
+
this.root = root;
|
|
4398
|
+
this.sink = sink;
|
|
4399
|
+
}
|
|
4400
|
+
root;
|
|
4401
|
+
sink;
|
|
4402
|
+
file() {
|
|
4403
|
+
return path16.join(this.root, "folds", `${safeId(this.sink)}.json`);
|
|
4404
|
+
}
|
|
4405
|
+
read() {
|
|
4406
|
+
try {
|
|
4407
|
+
const j = JSON.parse(readFileSync11(this.file(), "utf8"));
|
|
4408
|
+
return j && typeof j === "object" && !Array.isArray(j) ? j : {};
|
|
4409
|
+
} catch {
|
|
4410
|
+
return {};
|
|
4411
|
+
}
|
|
4412
|
+
}
|
|
4413
|
+
write(c) {
|
|
4414
|
+
const newest = Math.max(0, ...Object.values(c).filter(Number.isFinite));
|
|
4415
|
+
const kept = {};
|
|
4416
|
+
for (const [k, v] of Object.entries(c)) if (Number.isFinite(v) && v >= newest - KEEP_MS) kept[k] = v;
|
|
4417
|
+
const f = this.file();
|
|
4418
|
+
mkdirSync6(path16.dirname(f), { recursive: true, mode: 448 });
|
|
4419
|
+
const tmp = `${f}.${process.pid}.tmp`;
|
|
4420
|
+
writeFileSync5(tmp, JSON.stringify(kept), { mode: 384 });
|
|
4421
|
+
renameSync3(tmp, f);
|
|
4422
|
+
}
|
|
4423
|
+
};
|
|
4424
|
+
|
|
3524
4425
|
// src/run.ts
|
|
3525
4426
|
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
3526
4427
|
const sock = socketPath(state);
|
|
3527
4428
|
if (process.platform !== "win32" && !existsSync10(sock)) return;
|
|
4429
|
+
let mode = null;
|
|
4430
|
+
try {
|
|
4431
|
+
mode = claude_code_control_exports.hookMode(JSON.parse(job.stdin));
|
|
4432
|
+
} catch {
|
|
4433
|
+
}
|
|
3528
4434
|
await ask(
|
|
3529
4435
|
sock,
|
|
3530
4436
|
{
|
|
@@ -3532,31 +4438,32 @@ async function tellDaemon(state, job, session, cwd, transcript) {
|
|
|
3532
4438
|
session: `${job.harness}:${session}`,
|
|
3533
4439
|
event: job.event,
|
|
3534
4440
|
...cwd ? { cwd } : {},
|
|
3535
|
-
...transcript ? { transcript } : {}
|
|
4441
|
+
...transcript ? { transcript } : {},
|
|
4442
|
+
...mode ? { mode } : {}
|
|
3536
4443
|
},
|
|
3537
4444
|
300
|
|
3538
4445
|
);
|
|
3539
4446
|
}
|
|
3540
4447
|
function enqueueJob(state, job) {
|
|
3541
4448
|
const jobs = jobsDir(state);
|
|
3542
|
-
|
|
3543
|
-
const f =
|
|
3544
|
-
|
|
4449
|
+
mkdirSync7(jobs, { recursive: true, mode: 448 });
|
|
4450
|
+
const f = path17.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
4451
|
+
writeFileSync6(f, JSON.stringify(job), { mode: 384 });
|
|
3545
4452
|
return f;
|
|
3546
4453
|
}
|
|
3547
4454
|
function releaseJob(claimed) {
|
|
3548
4455
|
if (!claimed) return;
|
|
3549
4456
|
try {
|
|
3550
|
-
|
|
4457
|
+
unlinkSync3(claimed);
|
|
3551
4458
|
} catch {
|
|
3552
4459
|
}
|
|
3553
4460
|
}
|
|
3554
4461
|
function log(state, line) {
|
|
3555
4462
|
try {
|
|
3556
|
-
|
|
3557
|
-
const f =
|
|
4463
|
+
mkdirSync7(state, { recursive: true, mode: 448 });
|
|
4464
|
+
const f = path17.join(state, "log");
|
|
3558
4465
|
try {
|
|
3559
|
-
if (
|
|
4466
|
+
if (statSync7(f).size > 1024 * 1024) renameSync4(f, `${f}.1`);
|
|
3560
4467
|
} catch {
|
|
3561
4468
|
}
|
|
3562
4469
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -3565,11 +4472,11 @@ function log(state, line) {
|
|
|
3565
4472
|
}
|
|
3566
4473
|
}
|
|
3567
4474
|
function factsState(state) {
|
|
3568
|
-
const file = (h, s) =>
|
|
4475
|
+
const file = (h, s) => path17.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
3569
4476
|
return {
|
|
3570
4477
|
get(h, s) {
|
|
3571
4478
|
try {
|
|
3572
|
-
return JSON.parse(
|
|
4479
|
+
return JSON.parse(readFileSync12(file(h, s), "utf8"));
|
|
3573
4480
|
} catch {
|
|
3574
4481
|
return {};
|
|
3575
4482
|
}
|
|
@@ -3578,8 +4485,8 @@ function factsState(state) {
|
|
|
3578
4485
|
const cur = this.get(h, s);
|
|
3579
4486
|
const next = { ...cur, ...patch };
|
|
3580
4487
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
3581
|
-
|
|
3582
|
-
|
|
4488
|
+
mkdirSync7(path17.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
4489
|
+
writeFileSync6(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
3583
4490
|
}
|
|
3584
4491
|
};
|
|
3585
4492
|
}
|
|
@@ -3592,10 +4499,10 @@ function buildSinks(sinks, o = {}) {
|
|
|
3592
4499
|
o.locked?.(raw.name);
|
|
3593
4500
|
continue;
|
|
3594
4501
|
}
|
|
3595
|
-
const tier = Math.min(s.tier, s.max_tier ?? 3);
|
|
4502
|
+
const tier = Math.trunc(Math.min(Number(s.tier) || 0, s.max_tier ?? 3));
|
|
3596
4503
|
if (s.url === "stdout") out.push(new StdoutSink(s.name, tier, !!s.pii));
|
|
3597
4504
|
else if (s.url.startsWith("file:"))
|
|
3598
|
-
out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/,
|
|
4505
|
+
out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os4.homedir()), tier, !!s.pii));
|
|
3599
4506
|
else if (/^https?:\/\//.test(s.url)) out.push(new HttpSink(s, tier));
|
|
3600
4507
|
}
|
|
3601
4508
|
return out;
|
|
@@ -3603,13 +4510,14 @@ function buildSinks(sinks, o = {}) {
|
|
|
3603
4510
|
var PROGRESS_MS = 2e4;
|
|
3604
4511
|
function openTurn(read, o) {
|
|
3605
4512
|
const last = read.turns[read.turns.length - 1];
|
|
3606
|
-
const fresh = (user, at) => ({
|
|
4513
|
+
const fresh = (user, at, injected) => ({
|
|
3607
4514
|
turn: o.base + read.turns.length,
|
|
3608
4515
|
user: user.slice(0, MAX_TURN_SIDE),
|
|
3609
4516
|
assistant: "",
|
|
3610
|
-
at
|
|
4517
|
+
at,
|
|
4518
|
+
...injected ? { injected: true } : {}
|
|
3611
4519
|
});
|
|
3612
|
-
if (read.open) return fresh(read.open.user, read.open.at);
|
|
4520
|
+
if (read.open) return fresh(read.open.user, read.open.at, read.open.injected);
|
|
3613
4521
|
const prompt = o.prompt?.trim();
|
|
3614
4522
|
if (prompt && !isInjected(prompt) && !(last && last.user.includes(prompt.slice(0, 200)))) return fresh(prompt, o.at);
|
|
3615
4523
|
if (!last) return null;
|
|
@@ -3617,6 +4525,7 @@ function openTurn(read, o) {
|
|
|
3617
4525
|
turn: o.base + read.turns.length - 1,
|
|
3618
4526
|
user: last.user.slice(0, MAX_TURN_SIDE),
|
|
3619
4527
|
assistant: capTail(last.assistant, MAX_TURN_SIDE),
|
|
4528
|
+
...last.injected ? { injected: true } : {},
|
|
3620
4529
|
at: last.at
|
|
3621
4530
|
};
|
|
3622
4531
|
}
|
|
@@ -3624,6 +4533,7 @@ var MAX_TURN_SIDE = 2e4;
|
|
|
3624
4533
|
var JOB_OWN_ENV = [
|
|
3625
4534
|
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
3626
4535
|
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
4536
|
+
"SESSIONPIPE_ASKED_BY",
|
|
3627
4537
|
"SESSIONPIPE_CONTROL_JOB",
|
|
3628
4538
|
"SESSIONPIPE_LAUNCHED_BY"
|
|
3629
4539
|
];
|
|
@@ -3651,7 +4561,7 @@ async function runJob(job, opts = {}) {
|
|
|
3651
4561
|
);
|
|
3652
4562
|
return { events: [], delivered: {} };
|
|
3653
4563
|
}
|
|
3654
|
-
const id = receiverIdOf(readAliases(aliasFile(
|
|
4564
|
+
const id = receiverIdOf(readAliases(aliasFile(path17.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3655
4565
|
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3656
4566
|
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
3657
4567
|
const outbox = new Outbox(state);
|
|
@@ -3688,7 +4598,7 @@ async function runJob(job, opts = {}) {
|
|
|
3688
4598
|
learned = rest;
|
|
3689
4599
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
3690
4600
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
3691
|
-
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd &&
|
|
4601
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path17.isAbsolute(r.session.cwd))
|
|
3692
4602
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
3693
4603
|
}
|
|
3694
4604
|
const known = mem.session ?? {};
|
|
@@ -3701,10 +4611,11 @@ async function runJob(job, opts = {}) {
|
|
|
3701
4611
|
...learned.branch ? { branch: learned.branch } : {},
|
|
3702
4612
|
id,
|
|
3703
4613
|
seq: 0,
|
|
3704
|
-
machine: machineName(cfg,
|
|
4614
|
+
machine: machineName(cfg, os4.hostname()),
|
|
3705
4615
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
3706
4616
|
...r.session.model ? { model: r.session.model } : {},
|
|
3707
|
-
...r.session.launched_by ? { launched_by: r.session.launched_by } : {}
|
|
4617
|
+
...r.session.launched_by ? { launched_by: r.session.launched_by } : {},
|
|
4618
|
+
...r.session.delegated_by ? { delegated_by: r.session.delegated_by } : {}
|
|
3708
4619
|
};
|
|
3709
4620
|
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
3710
4621
|
for (const a of r.events) {
|
|
@@ -3758,7 +4669,13 @@ async function runJob(job, opts = {}) {
|
|
|
3758
4669
|
{
|
|
3759
4670
|
type: "turn.transcript",
|
|
3760
4671
|
harnessEvent: job.event,
|
|
3761
|
-
data: {
|
|
4672
|
+
data: {
|
|
4673
|
+
turn: turnNo++,
|
|
4674
|
+
user: t.user,
|
|
4675
|
+
assistant: t.assistant,
|
|
4676
|
+
at: new Date(t.at).toISOString(),
|
|
4677
|
+
...t.injected ? { injected: true } : {}
|
|
4678
|
+
}
|
|
3762
4679
|
},
|
|
3763
4680
|
{
|
|
3764
4681
|
harness: { name: job.harness, ...version ? { version } : {} },
|
|
@@ -3799,29 +4716,83 @@ async function runJob(job, opts = {}) {
|
|
|
3799
4716
|
} finally {
|
|
3800
4717
|
release();
|
|
3801
4718
|
}
|
|
3802
|
-
const delivered = await drain(state, cfg.sinks, outbox, ref) ?? {};
|
|
3803
4719
|
if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
|
|
4720
|
+
const delivered = opts.drain === false ? {} : await drain(state, cfg.sinks, outbox, ref, {
|
|
4721
|
+
...opts.clock ? { clock: opts.clock } : {},
|
|
4722
|
+
...opts.deadline !== void 0 ? { deadline: opts.deadline } : {},
|
|
4723
|
+
...opts.handoff ? { handoff: opts.handoff } : {}
|
|
4724
|
+
}) ?? {};
|
|
3804
4725
|
return { events, delivered };
|
|
3805
4726
|
}
|
|
3806
|
-
|
|
3807
|
-
|
|
4727
|
+
var REAL_CLOCK = { now: () => Date.now(), sleep: (ms) => new Promise((r) => setTimeout(r, ms)) };
|
|
4728
|
+
var PACE_MS = 1e3;
|
|
4729
|
+
var sentFile = (state) => path17.join(state, "locks", "flush.sent");
|
|
4730
|
+
function lastSent(state) {
|
|
4731
|
+
try {
|
|
4732
|
+
return Number(readFileSync12(sentFile(state), "utf8"));
|
|
4733
|
+
} catch {
|
|
4734
|
+
return 0;
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
function noteSent(state, at) {
|
|
4738
|
+
try {
|
|
4739
|
+
mkdirSync7(path17.dirname(sentFile(state)), { recursive: true, mode: 448 });
|
|
4740
|
+
writeFileSync6(sentFile(state), String(at), { mode: 384 });
|
|
4741
|
+
} catch {
|
|
4742
|
+
}
|
|
4743
|
+
}
|
|
4744
|
+
function waitingForReceiver(state, sinkConfigs, outbox, now) {
|
|
4745
|
+
const receivers = sinkConfigs.filter(
|
|
4746
|
+
(s) => !s.paused && /^https?:\/\//.test(s.url) && !((readBackoff(state, s.name)?.until ?? 0) > now)
|
|
4747
|
+
);
|
|
4748
|
+
if (!receivers.length) return false;
|
|
4749
|
+
const refs = outbox.sessions();
|
|
4750
|
+
for (const s of receivers) {
|
|
4751
|
+
const cursors = new Cursors(state, s.name);
|
|
4752
|
+
const c = cursors.read();
|
|
4753
|
+
for (const ref of refs) {
|
|
4754
|
+
try {
|
|
4755
|
+
if (statSync7(outbox.file(ref)).size > (c[cursors.key(ref)] ?? 0)) return true;
|
|
4756
|
+
} catch {
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
}
|
|
4760
|
+
return false;
|
|
4761
|
+
}
|
|
4762
|
+
var PASS_BUDGET_MS = 3e3;
|
|
4763
|
+
var moreFile = (state) => path17.join(state, "locks", "flush.more");
|
|
4764
|
+
function markMore(state) {
|
|
4765
|
+
try {
|
|
4766
|
+
mkdirSync7(path17.dirname(moreFile(state)), { recursive: true, mode: 448 });
|
|
4767
|
+
writeFileSync6(moreFile(state), "", { mode: 384 });
|
|
4768
|
+
} catch {
|
|
4769
|
+
}
|
|
4770
|
+
}
|
|
4771
|
+
async function drain(state, sinkConfigs, outbox, first, o = {}) {
|
|
4772
|
+
const clock = o.clock ?? REAL_CLOCK;
|
|
4773
|
+
const more = moreFile(state);
|
|
3808
4774
|
let release = takeNamedLock(state, "flush");
|
|
3809
4775
|
if (!release) {
|
|
3810
|
-
|
|
3811
|
-
writeFileSync8(more, "", { mode: 384 });
|
|
3812
|
-
} catch {
|
|
3813
|
-
}
|
|
4776
|
+
markMore(state);
|
|
3814
4777
|
release = takeNamedLock(state, "flush");
|
|
3815
4778
|
if (!release) return null;
|
|
3816
4779
|
}
|
|
3817
4780
|
const delivered = {};
|
|
3818
4781
|
for (; ; ) {
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
4782
|
+
if (o.deadline !== void 0 && clock.now() + PASS_BUDGET_MS > o.deadline) {
|
|
4783
|
+
markMore(state);
|
|
4784
|
+
release();
|
|
4785
|
+
o.handoff?.();
|
|
4786
|
+
return delivered;
|
|
3822
4787
|
}
|
|
3823
4788
|
try {
|
|
3824
|
-
|
|
4789
|
+
const wait = Math.min(PACE_MS, lastSent(state) + PACE_MS - clock.now());
|
|
4790
|
+
if (wait > 0 && waitingForReceiver(state, sinkConfigs, outbox, clock.now())) await clock.sleep(wait);
|
|
4791
|
+
try {
|
|
4792
|
+
unlinkSync3(more);
|
|
4793
|
+
} catch {
|
|
4794
|
+
}
|
|
4795
|
+
for (const [k, v] of Object.entries(await flush(state, sinkConfigs, outbox, first, { clock })))
|
|
3825
4796
|
delivered[k] = (delivered[k] ?? 0) + v;
|
|
3826
4797
|
} finally {
|
|
3827
4798
|
release();
|
|
@@ -3832,10 +4803,10 @@ async function drain(state, sinkConfigs, outbox, first) {
|
|
|
3832
4803
|
}
|
|
3833
4804
|
}
|
|
3834
4805
|
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3835
|
-
var backoffFile = (state, sink) =>
|
|
4806
|
+
var backoffFile = (state, sink) => path17.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3836
4807
|
function readBackoff(state, sink) {
|
|
3837
4808
|
try {
|
|
3838
|
-
const b = JSON.parse(
|
|
4809
|
+
const b = JSON.parse(readFileSync12(backoffFile(state, sink), "utf8"));
|
|
3839
4810
|
return typeof b.until === "number" ? b : null;
|
|
3840
4811
|
} catch {
|
|
3841
4812
|
return null;
|
|
@@ -3848,15 +4819,15 @@ function noteFailure(state, sink, res, prev) {
|
|
|
3848
4819
|
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3849
4820
|
const f = backoffFile(state, sink);
|
|
3850
4821
|
try {
|
|
3851
|
-
|
|
3852
|
-
|
|
4822
|
+
mkdirSync7(path17.dirname(f), { recursive: true, mode: 448 });
|
|
4823
|
+
writeFileSync6(f, JSON.stringify(b), { mode: 384 });
|
|
3853
4824
|
} catch {
|
|
3854
4825
|
}
|
|
3855
4826
|
return b;
|
|
3856
4827
|
}
|
|
3857
4828
|
function clearBackoff(state, sink) {
|
|
3858
4829
|
try {
|
|
3859
|
-
|
|
4830
|
+
unlinkSync3(backoffFile(state, sink));
|
|
3860
4831
|
} catch {
|
|
3861
4832
|
}
|
|
3862
4833
|
}
|
|
@@ -3866,14 +4837,31 @@ var BATCH = 50;
|
|
|
3866
4837
|
var MAX_PER_RUN = 500;
|
|
3867
4838
|
async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
3868
4839
|
const delivered = {};
|
|
4840
|
+
const clock = o.clock ?? REAL_CLOCK;
|
|
3869
4841
|
const sinks = buildSinks(sinkConfigs, {
|
|
3870
4842
|
locked: (name) => log(state, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
3871
4843
|
});
|
|
4844
|
+
const modes = new Map(sinkConfigs.map((s) => [s.name, toolsMode(s)]));
|
|
3872
4845
|
const refs = outbox.sessions();
|
|
3873
4846
|
if (first) {
|
|
3874
4847
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
3875
4848
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
3876
4849
|
}
|
|
4850
|
+
const unreadable = /* @__PURE__ */ new Set();
|
|
4851
|
+
const readOrSkip = (ref, offset, room) => {
|
|
4852
|
+
try {
|
|
4853
|
+
return outbox.read(ref, offset, room);
|
|
4854
|
+
} catch (e) {
|
|
4855
|
+
const code = e.code;
|
|
4856
|
+
if (typeof code !== "string") throw e;
|
|
4857
|
+
const f = outbox.file(ref);
|
|
4858
|
+
if (!unreadable.has(f)) {
|
|
4859
|
+
unreadable.add(f);
|
|
4860
|
+
log(state, `drain: can't read ${f} (${code}); skipped, its cursors stay`);
|
|
4861
|
+
}
|
|
4862
|
+
return null;
|
|
4863
|
+
}
|
|
4864
|
+
};
|
|
3877
4865
|
const drainSink = async (sink) => {
|
|
3878
4866
|
let backoff = readBackoff(state, sink.name);
|
|
3879
4867
|
if (backoff && !o.force && backoff.until > Date.now()) {
|
|
@@ -3882,11 +4870,16 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3882
4870
|
}
|
|
3883
4871
|
const cursors = new Cursors(state, sink.name);
|
|
3884
4872
|
const c = cursors.read();
|
|
4873
|
+
const mode = modes.get(sink.name);
|
|
4874
|
+
const tools = new ToolClock(state, sink.name);
|
|
4875
|
+
let clockNow = mode === "all" ? {} : tools.read();
|
|
4876
|
+
let clockNext = { ...clockNow };
|
|
3885
4877
|
let sent = 0;
|
|
3886
4878
|
let stalled = false;
|
|
3887
4879
|
let batch = [];
|
|
3888
4880
|
let pending = {};
|
|
3889
4881
|
const deliver = async (events) => {
|
|
4882
|
+
if (sink instanceof HttpSink) noteSent(state, clock.now());
|
|
3890
4883
|
const res = await sink.send(events);
|
|
3891
4884
|
if (res.ok || res.action !== "split") return res;
|
|
3892
4885
|
if (events.length === 1) {
|
|
@@ -3894,10 +4887,20 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3894
4887
|
log(state, `sink ${sink.name}: 413 ${res.error ?? ""} \u2192 event ${e.id} (${e.type}) is too large alone, dropped`);
|
|
3895
4888
|
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected: [{ id: e.id, reason: "too_large" }] };
|
|
3896
4889
|
}
|
|
4890
|
+
const part = async (some) => {
|
|
4891
|
+
const r = await deliver(some);
|
|
4892
|
+
if (r.ok || r.action !== "drop") return r;
|
|
4893
|
+
log(
|
|
4894
|
+
state,
|
|
4895
|
+
`sink ${sink.name}: ${r.status ?? ""} ${r.error ?? ""} \u2192 drop, ${some.length} of ${events.length} events`
|
|
4896
|
+
);
|
|
4897
|
+
const rejected = some.map((e) => ({ id: e.id, reason: "dropped" }));
|
|
4898
|
+
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected };
|
|
4899
|
+
};
|
|
3897
4900
|
const half = Math.ceil(events.length / 2);
|
|
3898
|
-
const a = await
|
|
4901
|
+
const a = await part(events.slice(0, half));
|
|
3899
4902
|
if (!a.ok) return a;
|
|
3900
|
-
const b = await
|
|
4903
|
+
const b = await part(events.slice(half));
|
|
3901
4904
|
if (!b.ok) return b;
|
|
3902
4905
|
return {
|
|
3903
4906
|
ok: true,
|
|
@@ -3907,11 +4910,17 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3907
4910
|
rejected: [...a.rejected ?? [], ...b.rejected ?? []]
|
|
3908
4911
|
};
|
|
3909
4912
|
};
|
|
4913
|
+
const commitClock = () => {
|
|
4914
|
+
if (mode === "all" || JSON.stringify(clockNext) === JSON.stringify(clockNow)) return;
|
|
4915
|
+
tools.write(clockNext);
|
|
4916
|
+
clockNow = { ...clockNext };
|
|
4917
|
+
};
|
|
3910
4918
|
const send = async () => {
|
|
3911
4919
|
if (!batch.length) {
|
|
3912
4920
|
Object.assign(c, pending);
|
|
3913
4921
|
pending = {};
|
|
3914
4922
|
cursors.write(c);
|
|
4923
|
+
commitClock();
|
|
3915
4924
|
return true;
|
|
3916
4925
|
}
|
|
3917
4926
|
const res = await deliver(batch);
|
|
@@ -3927,6 +4936,7 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3927
4936
|
if (res.action !== "drop") {
|
|
3928
4937
|
batch = [];
|
|
3929
4938
|
pending = {};
|
|
4939
|
+
clockNext = { ...clockNow };
|
|
3930
4940
|
return false;
|
|
3931
4941
|
}
|
|
3932
4942
|
} else if (backoff) {
|
|
@@ -3936,6 +4946,7 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3936
4946
|
sent += batch.length;
|
|
3937
4947
|
Object.assign(c, pending);
|
|
3938
4948
|
cursors.write(c);
|
|
4949
|
+
commitClock();
|
|
3939
4950
|
batch = [];
|
|
3940
4951
|
pending = {};
|
|
3941
4952
|
return true;
|
|
@@ -3945,11 +4956,15 @@ async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
|
3945
4956
|
let offset = pending[cursors.key(ref)] ?? c[cursors.key(ref)] ?? 0;
|
|
3946
4957
|
for (; ; ) {
|
|
3947
4958
|
const room = BATCH - batch.length;
|
|
3948
|
-
const
|
|
4959
|
+
const read = readOrSkip(ref, offset, room);
|
|
4960
|
+
if (!read) break;
|
|
4961
|
+
const { events, offset: next } = read;
|
|
3949
4962
|
if (!events.length) break;
|
|
3950
4963
|
for (const e of events) {
|
|
3951
|
-
const f = filterEvent(e, sink.tier, sink.pii, { home:
|
|
3952
|
-
if (f)
|
|
4964
|
+
const f = filterEvent(e, sink.tier, sink.pii, { home: os4.homedir() });
|
|
4965
|
+
if (!f) continue;
|
|
4966
|
+
if (sendTool(f, mode, clockNext, cursors.key(ref))) batch.push(f);
|
|
4967
|
+
else if (o.thinned) o.thinned[sink.name] = (o.thinned[sink.name] ?? 0) + 1;
|
|
3953
4968
|
}
|
|
3954
4969
|
offset = next;
|
|
3955
4970
|
pending[cursors.key(ref)] = offset;
|
|
@@ -3981,7 +4996,7 @@ function applySinkVerdict(name, res) {
|
|
|
3981
4996
|
} catch {
|
|
3982
4997
|
}
|
|
3983
4998
|
}
|
|
3984
|
-
var jobsDir = (state) =>
|
|
4999
|
+
var jobsDir = (state) => path17.join(state, "jobs");
|
|
3985
5000
|
export {
|
|
3986
5001
|
HttpSink,
|
|
3987
5002
|
buildSinks,
|