sessionpipe 0.1.0 → 0.2.0
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 +35 -2
- package/dist/cli.js +2749 -266
- package/dist/cli.js.map +4 -4
- package/dist/control/cli.d.ts +4 -0
- package/dist/control/cli.d.ts.map +1 -0
- package/dist/control/daemon.d.ts +172 -0
- package/dist/control/daemon.d.ts.map +1 -0
- package/dist/control/local.d.ts +63 -0
- package/dist/control/local.d.ts.map +1 -0
- package/dist/control/pair.d.ts +50 -0
- package/dist/control/pair.d.ts.map +1 -0
- package/dist/control/reply.d.ts +19 -0
- package/dist/control/reply.d.ts.map +1 -0
- package/dist/control/route.d.ts +41 -0
- package/dist/control/route.d.ts.map +1 -0
- package/dist/control/sdk.d.ts +7 -0
- package/dist/control/sdk.d.ts.map +1 -0
- package/dist/control/store.d.ts +76 -0
- package/dist/control/store.d.ts.map +1 -0
- package/dist/control/wait.d.ts +8 -0
- package/dist/control/wait.d.ts.map +1 -0
- package/dist/hook.js +116 -21
- package/dist/hook.js.map +4 -4
- package/dist/http-sink.d.ts +4 -0
- package/dist/http-sink.d.ts.map +1 -1
- package/dist/index.js +370 -119
- package/dist/index.js.map +4 -4
- package/dist/run.d.ts +13 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/worker.js +396 -119
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/worker.js
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe 0.
|
|
2
|
+
// sessionpipe 0.2.0 · Apache-2.0 · https://sessionpipe.org
|
|
3
3
|
|
|
4
4
|
// src/worker.ts
|
|
5
|
-
import { readFileSync as readFileSync9, unlinkSync as
|
|
6
|
-
|
|
7
|
-
// src/run.ts
|
|
8
|
-
import { appendFileSync as appendFileSync3, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, statSync as statSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9
|
-
import os2 from "node:os";
|
|
10
|
-
import path11 from "node:path";
|
|
5
|
+
import { readFileSync as readFileSync9, unlinkSync as unlinkSync5 } from "node:fs";
|
|
11
6
|
|
|
12
7
|
// ../core/src/adapters/antigravity.ts
|
|
13
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
existsSync as existsSync2,
|
|
10
|
+
mkdirSync as mkdirSync2,
|
|
11
|
+
readdirSync as readdirSync2,
|
|
12
|
+
readFileSync as readFileSync2,
|
|
13
|
+
realpathSync,
|
|
14
|
+
statSync as statSync2,
|
|
15
|
+
unlinkSync as unlinkSync2,
|
|
16
|
+
writeFileSync as writeFileSync2
|
|
17
|
+
} from "node:fs";
|
|
14
18
|
import path4 from "node:path";
|
|
15
19
|
|
|
16
20
|
// ../core/src/paths.ts
|
|
@@ -237,28 +241,67 @@ function readAntigravity(lines, from) {
|
|
|
237
241
|
}
|
|
238
242
|
|
|
239
243
|
// ../core/src/adapters/claude-shaped.ts
|
|
240
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
244
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
241
245
|
import path3 from "node:path";
|
|
242
246
|
var OURS = /sessionpipe[^"\n]*[\\/]hook\.js"?\s+[a-z][a-z-]*\s+[A-Za-z]+\s*$/;
|
|
243
247
|
var isOurs = (h) => Array.isArray(h?.hooks) && h.hooks.some((x) => typeof x.command === "string" && OURS.test(x.command));
|
|
244
|
-
function
|
|
248
|
+
function readJsonFile(file) {
|
|
249
|
+
let raw;
|
|
245
250
|
try {
|
|
246
|
-
|
|
251
|
+
raw = readFileSync(file, "utf8");
|
|
247
252
|
} catch {
|
|
248
|
-
return {};
|
|
253
|
+
return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
|
|
249
254
|
}
|
|
255
|
+
let value;
|
|
256
|
+
try {
|
|
257
|
+
value = JSON.parse(raw);
|
|
258
|
+
} catch {
|
|
259
|
+
const hint = /^\s*\/\/|\/\*/m.test(raw) ? "not plain JSON (it has comments)" : "not plain JSON";
|
|
260
|
+
return { ok: false, existed: true, reason: hint };
|
|
261
|
+
}
|
|
262
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
263
|
+
return { ok: false, existed: true, reason: "not a JSON object" };
|
|
264
|
+
const eol = raw.includes("\r\n") ? "\r\n" : "\n";
|
|
265
|
+
const m = /^( +|\t+)\S/m.exec(raw);
|
|
266
|
+
const indent = m?.[1] ?? " ";
|
|
267
|
+
return {
|
|
268
|
+
ok: true,
|
|
269
|
+
existed: true,
|
|
270
|
+
value,
|
|
271
|
+
raw,
|
|
272
|
+
indent,
|
|
273
|
+
eol,
|
|
274
|
+
finalNewline: /\r?\n$/.test(raw)
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function formatJson(value, f) {
|
|
278
|
+
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
279
|
+
return f.finalNewline ? text + f.eol : text;
|
|
250
280
|
}
|
|
251
281
|
function entryFor(event, harness, cmd, o) {
|
|
252
282
|
const matcher = o.matcherFor(event);
|
|
253
283
|
return {
|
|
254
284
|
...matcher !== void 0 ? { matcher } : {},
|
|
255
|
-
hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeout }]
|
|
285
|
+
hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeoutFor?.(event) ?? o.timeout }]
|
|
256
286
|
};
|
|
257
287
|
}
|
|
258
288
|
function installClaudeShaped(file, harness, events, cmd, o) {
|
|
259
|
-
const
|
|
289
|
+
const f = readJsonFile(file);
|
|
290
|
+
if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
|
|
291
|
+
const settings2 = f.value;
|
|
260
292
|
const hooks = settings2.hooks && typeof settings2.hooks === "object" ? settings2.hooks : {};
|
|
261
293
|
let changed = false;
|
|
294
|
+
const want = new Set(events);
|
|
295
|
+
for (const ev of Object.keys(hooks)) {
|
|
296
|
+
if (want.has(ev)) continue;
|
|
297
|
+
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
298
|
+
const kept = list.filter((h) => !isOurs(h));
|
|
299
|
+
if (kept.length !== list.length) {
|
|
300
|
+
changed = true;
|
|
301
|
+
if (kept.length) hooks[ev] = kept;
|
|
302
|
+
else delete hooks[ev];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
262
305
|
for (const ev of events) {
|
|
263
306
|
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
264
307
|
const entry2 = entryFor(ev, harness, cmd, o);
|
|
@@ -277,19 +320,19 @@ function installClaudeShaped(file, harness, events, cmd, o) {
|
|
|
277
320
|
if (changed) {
|
|
278
321
|
settings2.hooks = hooks;
|
|
279
322
|
mkdirSync(path3.dirname(file), { recursive: true });
|
|
280
|
-
writeFileSync(file,
|
|
281
|
-
`);
|
|
323
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
282
324
|
}
|
|
283
|
-
return { file, changed };
|
|
325
|
+
return { file, changed, created: !f.existed && changed };
|
|
284
326
|
}
|
|
285
|
-
function uninstallClaudeShaped(file, events) {
|
|
327
|
+
function uninstallClaudeShaped(file, events, opts = {}) {
|
|
286
328
|
if (!existsSync(file)) return { file, changed: false };
|
|
287
|
-
const
|
|
288
|
-
|
|
329
|
+
const f = readJsonFile(file);
|
|
330
|
+
if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
|
|
331
|
+
const settings2 = f.value;
|
|
289
332
|
const hooks = settings2.hooks;
|
|
290
333
|
if (!hooks || typeof hooks !== "object") return { file, changed: false };
|
|
291
334
|
let changed = false;
|
|
292
|
-
for (const ev of events) {
|
|
335
|
+
for (const ev of [...events, ...Object.keys(hooks)]) {
|
|
293
336
|
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
294
337
|
const kept = list.filter((h) => !isOurs(h));
|
|
295
338
|
if (kept.length !== list.length) {
|
|
@@ -300,13 +343,17 @@ function uninstallClaudeShaped(file, events) {
|
|
|
300
343
|
}
|
|
301
344
|
if (!changed) return { file, changed: false };
|
|
302
345
|
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
346
|
+
if (opts.created && !Object.keys(settings2).length) {
|
|
347
|
+
unlinkSync(file);
|
|
348
|
+
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
349
|
+
}
|
|
350
|
+
const next = formatJson(settings2, f);
|
|
351
|
+
if (next !== f.raw) writeFileSync(file, next);
|
|
306
352
|
return { file, changed: true };
|
|
307
353
|
}
|
|
308
354
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
309
|
-
const
|
|
355
|
+
const f = readJsonFile(file);
|
|
356
|
+
const hooks = f.ok ? f.value.hooks ?? {} : {};
|
|
310
357
|
let present = 0;
|
|
311
358
|
let current = 0;
|
|
312
359
|
for (const ev of events) {
|
|
@@ -315,6 +362,9 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
|
315
362
|
if (mine.length) present++;
|
|
316
363
|
if (mine.length === 1 && JSON.stringify(mine[0]) === JSON.stringify(entryFor(ev, harness, cmd, o))) current++;
|
|
317
364
|
}
|
|
365
|
+
for (const ev of Object.keys(hooks))
|
|
366
|
+
if (!events.includes(ev) && (Array.isArray(hooks[ev]) ? hooks[ev] : []).some((h) => isOurs(h)))
|
|
367
|
+
return "stale";
|
|
318
368
|
if (!present) return "missing";
|
|
319
369
|
return current === events.length ? "current" : "stale";
|
|
320
370
|
}
|
|
@@ -329,10 +379,13 @@ var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
|
329
379
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
330
380
|
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
331
381
|
var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
332
|
-
function entry(cmd) {
|
|
382
|
+
function entry(cmd, lean) {
|
|
333
383
|
const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
|
|
334
384
|
const out = { enabled: true };
|
|
335
|
-
for (const e of AG_EVENTS)
|
|
385
|
+
for (const e of AG_EVENTS) {
|
|
386
|
+
if (lean && e === "PreToolUse") continue;
|
|
387
|
+
out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
388
|
+
}
|
|
336
389
|
return out;
|
|
337
390
|
}
|
|
338
391
|
var antigravity = {
|
|
@@ -340,34 +393,49 @@ var antigravity = {
|
|
|
340
393
|
events: AG_EVENTS,
|
|
341
394
|
detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
|
|
342
395
|
configFiles: (env = process.env) => [hooksFile(env)],
|
|
343
|
-
install(cmd, env = process.env) {
|
|
396
|
+
install(cmd, env = process.env, opts = {}) {
|
|
344
397
|
const file = hooksFile(env);
|
|
345
|
-
const
|
|
346
|
-
|
|
398
|
+
const f = readJsonFile(file);
|
|
399
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
400
|
+
const hooks = f.value;
|
|
401
|
+
const e = entry(cmd, opts.lean);
|
|
347
402
|
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
348
403
|
hooks[HOOK_NAME] = e;
|
|
349
404
|
mkdirSync2(path4.dirname(file), { recursive: true });
|
|
350
|
-
writeFileSync2(file,
|
|
351
|
-
`);
|
|
405
|
+
writeFileSync2(file, formatJson(hooks, f));
|
|
352
406
|
return [
|
|
353
|
-
{
|
|
407
|
+
{
|
|
408
|
+
file,
|
|
409
|
+
changed: true,
|
|
410
|
+
created: !f.existed,
|
|
411
|
+
note: "hooks.json is read when a conversation starts; open ones report after a restart"
|
|
412
|
+
}
|
|
354
413
|
];
|
|
355
414
|
},
|
|
356
|
-
uninstall(env = process.env) {
|
|
415
|
+
uninstall(env = process.env, opts = {}) {
|
|
357
416
|
const file = hooksFile(env);
|
|
358
417
|
if (!existsSync2(file)) return [{ file, changed: false }];
|
|
359
|
-
const
|
|
418
|
+
const f = readJsonFile(file);
|
|
419
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
420
|
+
const hooks = f.value;
|
|
360
421
|
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
361
422
|
delete hooks[HOOK_NAME];
|
|
362
|
-
|
|
363
|
-
|
|
423
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
424
|
+
unlinkSync2(file);
|
|
425
|
+
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
426
|
+
}
|
|
427
|
+
writeFileSync2(file, formatJson(hooks, f));
|
|
364
428
|
return [{ file, changed: true }];
|
|
365
429
|
},
|
|
366
|
-
installed(cmd, env = process.env) {
|
|
430
|
+
installed(cmd, env = process.env, opts = {}) {
|
|
367
431
|
const file = hooksFile(env);
|
|
368
|
-
const
|
|
432
|
+
const f = readJsonFile(file);
|
|
433
|
+
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
369
434
|
return [
|
|
370
|
-
{
|
|
435
|
+
{
|
|
436
|
+
file,
|
|
437
|
+
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd, opts.lean)) ? "current" : "stale"
|
|
438
|
+
}
|
|
371
439
|
];
|
|
372
440
|
},
|
|
373
441
|
fromHook(input) {
|
|
@@ -633,9 +701,18 @@ var CLAUDE_EVENTS = [
|
|
|
633
701
|
"SubagentStop",
|
|
634
702
|
"PreCompact",
|
|
635
703
|
"PostCompact",
|
|
636
|
-
"SessionEnd"
|
|
704
|
+
"SessionEnd",
|
|
705
|
+
"StopFailure"
|
|
637
706
|
];
|
|
638
|
-
var
|
|
707
|
+
var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
|
|
708
|
+
var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
|
|
709
|
+
var OPTS = {
|
|
710
|
+
matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
|
|
711
|
+
timeout: 5,
|
|
712
|
+
// The hook waits up to 125 s for a verified control answer, and only when control
|
|
713
|
+
// is paired on this machine; everywhere else it exits in milliseconds as before.
|
|
714
|
+
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
715
|
+
};
|
|
639
716
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
640
717
|
function claudeDirs(env = process.env, home = HOME) {
|
|
641
718
|
const out = [];
|
|
@@ -666,11 +743,13 @@ var claudeCode = {
|
|
|
666
743
|
events: CLAUDE_EVENTS,
|
|
667
744
|
detect: (env = process.env) => claudeDirs(env).some((d) => existsSync3(d)),
|
|
668
745
|
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
669
|
-
install: (cmd, env = process.env) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2,
|
|
670
|
-
uninstall: (env = process.env) => claudeDirs(env).map(
|
|
671
|
-
|
|
746
|
+
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)),
|
|
747
|
+
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map(
|
|
748
|
+
(d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, { created: opts.created?.includes(settingsIn(d)) })
|
|
749
|
+
),
|
|
750
|
+
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
672
751
|
file: settingsIn(d),
|
|
673
|
-
state: installedClaudeShaped(settingsIn(d), NAME2,
|
|
752
|
+
state: installedClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)
|
|
674
753
|
})),
|
|
675
754
|
fromHook(input) {
|
|
676
755
|
const event = input.argv[1] ?? "";
|
|
@@ -761,6 +840,15 @@ var claudeCode = {
|
|
|
761
840
|
case "Stop":
|
|
762
841
|
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
763
842
|
break;
|
|
843
|
+
case "StopFailure":
|
|
844
|
+
events.push(
|
|
845
|
+
ev("turn.ended", {
|
|
846
|
+
turn_id,
|
|
847
|
+
reason: "error",
|
|
848
|
+
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
849
|
+
})
|
|
850
|
+
);
|
|
851
|
+
break;
|
|
764
852
|
case "SubagentStart":
|
|
765
853
|
events.push(
|
|
766
854
|
ev("subagent.started", {
|
|
@@ -1068,9 +1156,9 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1068
1156
|
}
|
|
1069
1157
|
const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
|
|
1070
1158
|
const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
|
|
1071
|
-
let
|
|
1072
|
-
if (!
|
|
1073
|
-
|
|
1159
|
+
let ask2 = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
|
|
1160
|
+
if (!ask2 && size > 64 * 1024 && !mem.first_ask_none) {
|
|
1161
|
+
ask2 = scanLines(file, (l) => {
|
|
1074
1162
|
try {
|
|
1075
1163
|
const t = askText(JSON.parse(l));
|
|
1076
1164
|
return t ? appTitle(t) : null;
|
|
@@ -1078,17 +1166,17 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1078
1166
|
return null;
|
|
1079
1167
|
}
|
|
1080
1168
|
});
|
|
1081
|
-
if (!
|
|
1169
|
+
if (!ask2 && size > 16 * 1024 * 1024) state.save(NAME2, id, { first_ask_none: true });
|
|
1082
1170
|
}
|
|
1083
|
-
if (
|
|
1084
|
-
out.first_ask =
|
|
1085
|
-
if (!mem.first_ask) state.save(NAME2, id, { first_ask:
|
|
1171
|
+
if (ask2) {
|
|
1172
|
+
out.first_ask = ask2;
|
|
1173
|
+
if (!mem.first_ask) state.save(NAME2, id, { first_ask: ask2 });
|
|
1086
1174
|
}
|
|
1087
1175
|
if (pick) {
|
|
1088
1176
|
out.title = String(pick.title).slice(0, 120);
|
|
1089
1177
|
out.title_source = pick.source;
|
|
1090
|
-
} else if (
|
|
1091
|
-
out.title =
|
|
1178
|
+
} else if (ask2) {
|
|
1179
|
+
out.title = ask2;
|
|
1092
1180
|
out.title_source = "first-ask";
|
|
1093
1181
|
}
|
|
1094
1182
|
const acct = claudeAccount(dir);
|
|
@@ -1272,6 +1360,7 @@ var CODEX_EVENTS = [
|
|
|
1272
1360
|
"SessionEnd"
|
|
1273
1361
|
];
|
|
1274
1362
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1363
|
+
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1275
1364
|
var codexHome = (env) => env.CODEX_HOME || path6.join(HOME, ".codex");
|
|
1276
1365
|
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1277
1366
|
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
@@ -1320,7 +1409,8 @@ function installNotify(cmd, env) {
|
|
|
1320
1409
|
return {
|
|
1321
1410
|
file,
|
|
1322
1411
|
changed: false,
|
|
1323
|
-
|
|
1412
|
+
skipped: true,
|
|
1413
|
+
note: `notify belongs to ${(n.argv?.[0] ?? n.raw).replace(/^.*\//, "").slice(0, 40)}; left alone. Codex will report nothing until you approve the hooks: start \`codex\` and accept the hook review`
|
|
1324
1414
|
};
|
|
1325
1415
|
}
|
|
1326
1416
|
write(withNotify(text, line));
|
|
@@ -1377,14 +1467,21 @@ var codex = {
|
|
|
1377
1467
|
events: CODEX_EVENTS,
|
|
1378
1468
|
detect: (env = process.env) => existsSync4(codexHome(env)),
|
|
1379
1469
|
configFiles: (env = process.env) => [hooksFile2(env), configFile2(env)],
|
|
1380
|
-
install: (cmd, env = process.env) => [
|
|
1381
|
-
installClaudeShaped(hooksFile2(env), NAME3,
|
|
1470
|
+
install: (cmd, env = process.env, opts = {}) => [
|
|
1471
|
+
installClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
1382
1472
|
installNotify(cmd, env)
|
|
1383
1473
|
],
|
|
1384
|
-
uninstall: (env = process.env) => [
|
|
1385
|
-
|
|
1474
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1475
|
+
uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
|
|
1476
|
+
uninstallNotify(env)
|
|
1477
|
+
],
|
|
1478
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1386
1479
|
const out = [
|
|
1387
|
-
{
|
|
1480
|
+
{
|
|
1481
|
+
file: hooksFile2(env),
|
|
1482
|
+
state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
1483
|
+
note: "Codex runs non-managed hooks only after the review in `codex` (/hooks); until then nothing is reported unless the notify fallback is ours"
|
|
1484
|
+
}
|
|
1388
1485
|
];
|
|
1389
1486
|
let text = "";
|
|
1390
1487
|
try {
|
|
@@ -1572,6 +1669,7 @@ function codexFacts(file) {
|
|
|
1572
1669
|
}
|
|
1573
1670
|
|
|
1574
1671
|
// ../core/src/adapters/gemini-cli.ts
|
|
1672
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1575
1673
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1576
1674
|
import path7 from "node:path";
|
|
1577
1675
|
var NAME4 = "gemini-cli";
|
|
@@ -1589,17 +1687,61 @@ var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
|
1589
1687
|
var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(HOME, ".gemini");
|
|
1590
1688
|
var settings = (env) => path7.join(geminiDir(env), "settings.json");
|
|
1591
1689
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1690
|
+
var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1691
|
+
function geminiHooksActive(settingsFile) {
|
|
1692
|
+
let tools;
|
|
1693
|
+
try {
|
|
1694
|
+
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
1695
|
+
} catch {
|
|
1696
|
+
}
|
|
1697
|
+
if (tools?.enableHooks === true) return { active: true };
|
|
1698
|
+
if (tools?.enableHooks === false)
|
|
1699
|
+
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1700
|
+
let version = "";
|
|
1701
|
+
try {
|
|
1702
|
+
version = execFileSync3("gemini", ["--version"], {
|
|
1703
|
+
encoding: "utf8",
|
|
1704
|
+
timeout: 4e3,
|
|
1705
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1706
|
+
}).trim();
|
|
1707
|
+
} catch {
|
|
1708
|
+
return {
|
|
1709
|
+
active: true,
|
|
1710
|
+
note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
|
|
1711
|
+
};
|
|
1712
|
+
}
|
|
1713
|
+
const m = /(\d+)\.(\d+)/.exec(version);
|
|
1714
|
+
if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
|
|
1715
|
+
return {
|
|
1716
|
+
active: false,
|
|
1717
|
+
note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1592
1720
|
var geminiCli = {
|
|
1593
1721
|
name: NAME4,
|
|
1594
1722
|
events: GEMINI_EVENTS,
|
|
1595
1723
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1596
1724
|
detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path7.join(geminiDir(env), "tmp")),
|
|
1597
1725
|
configFiles: (env = process.env) => [settings(env)],
|
|
1598
|
-
install: (cmd, env = process.env
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1726
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
1727
|
+
const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1728
|
+
const a = geminiHooksActive(settings(env));
|
|
1729
|
+
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1730
|
+
},
|
|
1731
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1732
|
+
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1602
1733
|
],
|
|
1734
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1735
|
+
const state = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1736
|
+
const a = geminiHooksActive(settings(env));
|
|
1737
|
+
return [
|
|
1738
|
+
{
|
|
1739
|
+
file: settings(env),
|
|
1740
|
+
state: state === "current" && !a.active ? "inactive" : state,
|
|
1741
|
+
...a.note ? { note: a.note } : {}
|
|
1742
|
+
}
|
|
1743
|
+
];
|
|
1744
|
+
},
|
|
1603
1745
|
fromHook(input) {
|
|
1604
1746
|
const event = input.argv[1] ?? "";
|
|
1605
1747
|
let s = {};
|
|
@@ -1810,7 +1952,7 @@ import {
|
|
|
1810
1952
|
readSync as readSync2,
|
|
1811
1953
|
renameSync,
|
|
1812
1954
|
statSync as statSync4,
|
|
1813
|
-
unlinkSync,
|
|
1955
|
+
unlinkSync as unlinkSync3,
|
|
1814
1956
|
writeFileSync as writeFileSync5
|
|
1815
1957
|
} from "node:fs";
|
|
1816
1958
|
import path9 from "node:path";
|
|
@@ -1838,8 +1980,8 @@ var Outbox = class {
|
|
|
1838
1980
|
} catch {
|
|
1839
1981
|
n = this.lastSeqInFile(ref) + 1;
|
|
1840
1982
|
}
|
|
1841
|
-
mkdirSync5(path9.dirname(f), { recursive: true });
|
|
1842
|
-
writeFileSync5(f, String(n + 1));
|
|
1983
|
+
mkdirSync5(path9.dirname(f), { recursive: true, mode: 448 });
|
|
1984
|
+
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
1843
1985
|
return n;
|
|
1844
1986
|
}
|
|
1845
1987
|
lastSeqInFile(ref) {
|
|
@@ -1857,9 +1999,9 @@ var Outbox = class {
|
|
|
1857
1999
|
}
|
|
1858
2000
|
append(ref, events) {
|
|
1859
2001
|
if (!events.length) return;
|
|
1860
|
-
mkdirSync5(this.dir(ref.harness), { recursive: true });
|
|
2002
|
+
mkdirSync5(this.dir(ref.harness), { recursive: true, mode: 448 });
|
|
1861
2003
|
appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
1862
|
-
|
|
2004
|
+
`, { mode: 384 });
|
|
1863
2005
|
}
|
|
1864
2006
|
/** Every session file, newest first. */
|
|
1865
2007
|
sessions() {
|
|
@@ -1923,9 +2065,9 @@ var Outbox = class {
|
|
|
1923
2065
|
const f = this.file(ref);
|
|
1924
2066
|
try {
|
|
1925
2067
|
if (statSync4(f).mtimeMs < cutoff) {
|
|
1926
|
-
|
|
2068
|
+
unlinkSync3(f);
|
|
1927
2069
|
try {
|
|
1928
|
-
|
|
2070
|
+
unlinkSync3(this.seqFile(ref));
|
|
1929
2071
|
} catch {
|
|
1930
2072
|
}
|
|
1931
2073
|
n++;
|
|
@@ -1938,7 +2080,7 @@ var Outbox = class {
|
|
|
1938
2080
|
delete(ref) {
|
|
1939
2081
|
for (const f of [this.file(ref), this.seqFile(ref)]) {
|
|
1940
2082
|
try {
|
|
1941
|
-
|
|
2083
|
+
unlinkSync3(f);
|
|
1942
2084
|
} catch {
|
|
1943
2085
|
}
|
|
1944
2086
|
}
|
|
@@ -1963,9 +2105,9 @@ var Cursors = class {
|
|
|
1963
2105
|
}
|
|
1964
2106
|
write(c) {
|
|
1965
2107
|
const f = this.file();
|
|
1966
|
-
mkdirSync5(path9.dirname(f), { recursive: true });
|
|
2108
|
+
mkdirSync5(path9.dirname(f), { recursive: true, mode: 448 });
|
|
1967
2109
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
1968
|
-
writeFileSync5(tmp, JSON.stringify(c));
|
|
2110
|
+
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
1969
2111
|
renameSync(tmp, f);
|
|
1970
2112
|
}
|
|
1971
2113
|
key(ref) {
|
|
@@ -1974,10 +2116,10 @@ var Cursors = class {
|
|
|
1974
2116
|
};
|
|
1975
2117
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
1976
2118
|
const dir = path9.join(root, "locks");
|
|
1977
|
-
mkdirSync5(dir, { recursive: true });
|
|
2119
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
1978
2120
|
const f = path9.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
|
|
1979
2121
|
try {
|
|
1980
|
-
if (Date.now() - statSync4(f).mtimeMs > staleMs)
|
|
2122
|
+
if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
1981
2123
|
else return null;
|
|
1982
2124
|
} catch {
|
|
1983
2125
|
}
|
|
@@ -1988,7 +2130,7 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
1988
2130
|
}
|
|
1989
2131
|
return () => {
|
|
1990
2132
|
try {
|
|
1991
|
-
|
|
2133
|
+
unlinkSync3(f);
|
|
1992
2134
|
} catch {
|
|
1993
2135
|
}
|
|
1994
2136
|
};
|
|
@@ -2124,7 +2266,7 @@ function redactAssignments(text) {
|
|
|
2124
2266
|
function isSecretName(name) {
|
|
2125
2267
|
if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
|
|
2126
2268
|
const flat = name.toLowerCase().replace(/[_-]/g, "");
|
|
2127
|
-
return /(?:pass(?:word|wd|phrase|code)
|
|
2269
|
+
return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
|
|
2128
2270
|
flat
|
|
2129
2271
|
);
|
|
2130
2272
|
}
|
|
@@ -2213,7 +2355,7 @@ var DATA_ALLOW = {
|
|
|
2213
2355
|
"session.backfill": { 0: ["started_at", "last_at", "turns"] },
|
|
2214
2356
|
"session.forgotten": { 0: [] },
|
|
2215
2357
|
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2216
|
-
"turn.ended": { 0: ["turn_id", "reason", "ms"] },
|
|
2358
|
+
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
2217
2359
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2218
2360
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2219
2361
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
@@ -2334,9 +2476,66 @@ var StdoutSink = class {
|
|
|
2334
2476
|
}
|
|
2335
2477
|
};
|
|
2336
2478
|
|
|
2479
|
+
// src/run.ts
|
|
2480
|
+
import {
|
|
2481
|
+
appendFileSync as appendFileSync3,
|
|
2482
|
+
existsSync as existsSync8,
|
|
2483
|
+
mkdirSync as mkdirSync7,
|
|
2484
|
+
readdirSync as readdirSync6,
|
|
2485
|
+
readFileSync as readFileSync8,
|
|
2486
|
+
renameSync as renameSync2,
|
|
2487
|
+
statSync as statSync5,
|
|
2488
|
+
unlinkSync as unlinkSync4,
|
|
2489
|
+
writeFileSync as writeFileSync6
|
|
2490
|
+
} from "node:fs";
|
|
2491
|
+
import os3 from "node:os";
|
|
2492
|
+
import path12 from "node:path";
|
|
2493
|
+
|
|
2494
|
+
// src/control/local.ts
|
|
2495
|
+
import net from "node:net";
|
|
2496
|
+
import os2 from "node:os";
|
|
2497
|
+
import path11 from "node:path";
|
|
2498
|
+
function socketPath(state, env = process.env) {
|
|
2499
|
+
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
2500
|
+
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
2501
|
+
return path11.join(state, "control.sock");
|
|
2502
|
+
}
|
|
2503
|
+
function ask(sock, req, timeoutMs) {
|
|
2504
|
+
return new Promise((resolve) => {
|
|
2505
|
+
let buf = "";
|
|
2506
|
+
let done = false;
|
|
2507
|
+
const s = net.connect(sock);
|
|
2508
|
+
const finish = (r) => {
|
|
2509
|
+
if (done) return;
|
|
2510
|
+
done = true;
|
|
2511
|
+
clearTimeout(t);
|
|
2512
|
+
s.destroy();
|
|
2513
|
+
resolve(r);
|
|
2514
|
+
};
|
|
2515
|
+
const t = setTimeout(() => finish(null), timeoutMs);
|
|
2516
|
+
s.on("connect", () => s.write(`${JSON.stringify(req)}
|
|
2517
|
+
`));
|
|
2518
|
+
s.on("data", (d) => {
|
|
2519
|
+
buf += d.toString("utf8");
|
|
2520
|
+
const i = buf.indexOf("\n");
|
|
2521
|
+
if (i < 0) return;
|
|
2522
|
+
try {
|
|
2523
|
+
finish(JSON.parse(buf.slice(0, i)));
|
|
2524
|
+
} catch {
|
|
2525
|
+
finish(null);
|
|
2526
|
+
}
|
|
2527
|
+
});
|
|
2528
|
+
s.on("error", () => finish(null));
|
|
2529
|
+
s.on("close", () => finish(null));
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2337
2533
|
// src/http-sink.ts
|
|
2338
|
-
import {
|
|
2534
|
+
import { createHash as createHash3, createHmac } from "node:crypto";
|
|
2339
2535
|
var TIMEOUT_MS = 15e3;
|
|
2536
|
+
function webhookId(events) {
|
|
2537
|
+
return `msg_${createHash3("sha256").update(events.map((e) => e.id).join(",")).digest("base64url").slice(0, 32)}`;
|
|
2538
|
+
}
|
|
2340
2539
|
var HttpSink = class {
|
|
2341
2540
|
constructor(cfg, tier) {
|
|
2342
2541
|
this.cfg = cfg;
|
|
@@ -2359,7 +2558,7 @@ var HttpSink = class {
|
|
|
2359
2558
|
const headers = { "content-type": "application/json", "sessionpipe-protocol": "1" };
|
|
2360
2559
|
if (this.cfg.token) headers.authorization = `Bearer ${this.cfg.token}`;
|
|
2361
2560
|
if (this.cfg.secret?.startsWith("whsec_")) {
|
|
2362
|
-
const id =
|
|
2561
|
+
const id = webhookId(events);
|
|
2363
2562
|
const ts = Math.floor(Date.now() / 1e3);
|
|
2364
2563
|
const key = Buffer.from(this.cfg.secret.slice(6), "base64");
|
|
2365
2564
|
const sig = createHmac("sha256", key).update(`${id}.${ts}.${body}`).digest("base64");
|
|
@@ -2420,10 +2619,57 @@ var HttpSink = class {
|
|
|
2420
2619
|
};
|
|
2421
2620
|
|
|
2422
2621
|
// src/run.ts
|
|
2622
|
+
async function tellDaemon(state, job2, session, transcript) {
|
|
2623
|
+
const sock = socketPath(state);
|
|
2624
|
+
if (process.platform !== "win32" && !existsSync8(sock)) return;
|
|
2625
|
+
await ask(
|
|
2626
|
+
sock,
|
|
2627
|
+
{
|
|
2628
|
+
op: "event",
|
|
2629
|
+
session: `claude-code:${session}`,
|
|
2630
|
+
event: job2.event,
|
|
2631
|
+
cwd: job2.cwd,
|
|
2632
|
+
...transcript ? { transcript } : {}
|
|
2633
|
+
},
|
|
2634
|
+
300
|
|
2635
|
+
);
|
|
2636
|
+
}
|
|
2637
|
+
function enqueueJob(state, job2) {
|
|
2638
|
+
const jobs = jobsDir(state);
|
|
2639
|
+
mkdirSync7(jobs, { recursive: true, mode: 448 });
|
|
2640
|
+
const f = path12.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
2641
|
+
writeFileSync6(f, JSON.stringify(job2), { mode: 384 });
|
|
2642
|
+
return f;
|
|
2643
|
+
}
|
|
2644
|
+
async function sweepJobs(state, olderThanMs = 3e3, limit = 20) {
|
|
2645
|
+
const jobs = jobsDir(state);
|
|
2646
|
+
let names = [];
|
|
2647
|
+
try {
|
|
2648
|
+
names = readdirSync6(jobs).filter((n) => n.endsWith(".json"));
|
|
2649
|
+
} catch {
|
|
2650
|
+
return 0;
|
|
2651
|
+
}
|
|
2652
|
+
let ran = 0;
|
|
2653
|
+
for (const n of names.sort().slice(0, limit)) {
|
|
2654
|
+
const f = path12.join(jobs, n);
|
|
2655
|
+
let job2 = null;
|
|
2656
|
+
try {
|
|
2657
|
+
if (Date.now() - statSync5(f).mtimeMs < olderThanMs) continue;
|
|
2658
|
+
job2 = JSON.parse(readFileSync8(f, "utf8"));
|
|
2659
|
+
unlinkSync4(f);
|
|
2660
|
+
} catch {
|
|
2661
|
+
continue;
|
|
2662
|
+
}
|
|
2663
|
+
await runJob(job2, { state }).catch(() => {
|
|
2664
|
+
});
|
|
2665
|
+
ran++;
|
|
2666
|
+
}
|
|
2667
|
+
return ran;
|
|
2668
|
+
}
|
|
2423
2669
|
function log(state, line) {
|
|
2424
2670
|
try {
|
|
2425
|
-
mkdirSync7(state, { recursive: true });
|
|
2426
|
-
const f =
|
|
2671
|
+
mkdirSync7(state, { recursive: true, mode: 448 });
|
|
2672
|
+
const f = path12.join(state, "log");
|
|
2427
2673
|
try {
|
|
2428
2674
|
if (statSync5(f).size > 1024 * 1024) renameSync2(f, `${f}.1`);
|
|
2429
2675
|
} catch {
|
|
@@ -2434,7 +2680,7 @@ function log(state, line) {
|
|
|
2434
2680
|
}
|
|
2435
2681
|
}
|
|
2436
2682
|
function factsState(state) {
|
|
2437
|
-
const file = (h, s) =>
|
|
2683
|
+
const file = (h, s) => path12.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
2438
2684
|
return {
|
|
2439
2685
|
get(h, s) {
|
|
2440
2686
|
try {
|
|
@@ -2447,8 +2693,8 @@ function factsState(state) {
|
|
|
2447
2693
|
const cur = this.get(h, s);
|
|
2448
2694
|
const next = { ...cur, ...patch };
|
|
2449
2695
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
2450
|
-
mkdirSync7(
|
|
2451
|
-
writeFileSync6(file(h, s), JSON.stringify(next));
|
|
2696
|
+
mkdirSync7(path12.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
2697
|
+
writeFileSync6(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
2452
2698
|
}
|
|
2453
2699
|
};
|
|
2454
2700
|
}
|
|
@@ -2459,7 +2705,7 @@ function buildSinks(sinks) {
|
|
|
2459
2705
|
const tier = Math.min(s.tier, s.max_tier ?? 3);
|
|
2460
2706
|
if (s.url === "stdout") out.push(new StdoutSink(s.name, tier, !!s.pii));
|
|
2461
2707
|
else if (s.url.startsWith("file:"))
|
|
2462
|
-
out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/,
|
|
2708
|
+
out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os3.homedir()), tier, !!s.pii));
|
|
2463
2709
|
else if (/^https?:\/\//.test(s.url)) out.push(new HttpSink(s, tier));
|
|
2464
2710
|
}
|
|
2465
2711
|
return out;
|
|
@@ -2471,14 +2717,17 @@ async function runJob(job2, opts = {}) {
|
|
|
2471
2717
|
if (!adapter) return { events: [], delivered: {} };
|
|
2472
2718
|
const r = adapter.fromHook({ argv: job2.argv, stdin: job2.stdin, env: { ...process.env, ...job2.env }, cwd: job2.cwd });
|
|
2473
2719
|
if (!r || !r.session.id) return { events: [], delivered: {} };
|
|
2720
|
+
if (job2.harness === "claude-code") await tellDaemon(state, job2, r.session.id, r.transcript);
|
|
2474
2721
|
const outbox = new Outbox(state);
|
|
2475
2722
|
const ref = { harness: job2.harness, session: r.session.id };
|
|
2476
|
-
|
|
2723
|
+
let release = takeLock(state, ref);
|
|
2724
|
+
for (let wait = 50; !release && wait <= 3200; wait *= 2) {
|
|
2725
|
+
await new Promise((res) => setTimeout(res, wait));
|
|
2726
|
+
release = takeLock(state, ref);
|
|
2727
|
+
}
|
|
2477
2728
|
if (!release) {
|
|
2478
|
-
log(state, `${job2.harness} ${job2.event}: session ${r.session.id} locked, re-queued`);
|
|
2479
|
-
|
|
2480
|
-
mkdirSync7(jobs, { recursive: true });
|
|
2481
|
-
writeFileSync6(path11.join(jobs, `${Date.now().toString(36)}-requeue.json`), JSON.stringify(job2));
|
|
2729
|
+
log(state, `${job2.harness} ${job2.event}: session ${r.session.id} locked for 6 s, re-queued`);
|
|
2730
|
+
enqueueJob(state, job2);
|
|
2482
2731
|
return { events: [], delivered: {} };
|
|
2483
2732
|
}
|
|
2484
2733
|
try {
|
|
@@ -2503,7 +2752,7 @@ async function runJob(job2, opts = {}) {
|
|
|
2503
2752
|
...learned.branch ? { branch: learned.branch } : {},
|
|
2504
2753
|
id: r.session.id,
|
|
2505
2754
|
seq: 0,
|
|
2506
|
-
machine: machineName(cfg,
|
|
2755
|
+
machine: machineName(cfg, os3.hostname()),
|
|
2507
2756
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
2508
2757
|
...r.session.model ? { model: r.session.model } : {}
|
|
2509
2758
|
};
|
|
@@ -2542,7 +2791,10 @@ async function runJob(job2, opts = {}) {
|
|
|
2542
2791
|
if (read.turns.length)
|
|
2543
2792
|
facts.save(job2.harness, r.session.id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
2544
2793
|
}
|
|
2545
|
-
outbox.append(
|
|
2794
|
+
outbox.append(
|
|
2795
|
+
ref,
|
|
2796
|
+
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
2797
|
+
);
|
|
2546
2798
|
log(
|
|
2547
2799
|
state,
|
|
2548
2800
|
`${job2.harness} ${job2.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${r.session.id.slice(0, 8)})`
|
|
@@ -2568,34 +2820,57 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2568
2820
|
const cursors = new Cursors(state, sink.name);
|
|
2569
2821
|
const c = cursors.read();
|
|
2570
2822
|
let sent = 0;
|
|
2823
|
+
let stalled = false;
|
|
2824
|
+
let batch = [];
|
|
2825
|
+
let pending = {};
|
|
2826
|
+
const send = async () => {
|
|
2827
|
+
if (!batch.length) {
|
|
2828
|
+
Object.assign(c, pending);
|
|
2829
|
+
pending = {};
|
|
2830
|
+
cursors.write(c);
|
|
2831
|
+
return true;
|
|
2832
|
+
}
|
|
2833
|
+
const res = await sink.send(batch);
|
|
2834
|
+
if (!res.ok) {
|
|
2835
|
+
log(state, `sink ${sink.name}: ${res.status ?? ""} ${res.error ?? ""} \u2192 ${res.action ?? "retry"}`);
|
|
2836
|
+
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2837
|
+
if (res.action !== "drop") {
|
|
2838
|
+
batch = [];
|
|
2839
|
+
pending = {};
|
|
2840
|
+
return false;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
sent += batch.length;
|
|
2844
|
+
Object.assign(c, pending);
|
|
2845
|
+
cursors.write(c);
|
|
2846
|
+
batch = [];
|
|
2847
|
+
pending = {};
|
|
2848
|
+
return true;
|
|
2849
|
+
};
|
|
2571
2850
|
for (const ref of refs) {
|
|
2572
|
-
if (sent >= MAX_PER_RUN) break;
|
|
2573
|
-
let offset = c[cursors.key(ref)] ?? 0;
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
const { events, offset: next } = outbox.read(ref, offset,
|
|
2851
|
+
if (stalled || sent >= MAX_PER_RUN) break;
|
|
2852
|
+
let offset = pending[cursors.key(ref)] ?? c[cursors.key(ref)] ?? 0;
|
|
2853
|
+
for (; ; ) {
|
|
2854
|
+
const room = BATCH - batch.length;
|
|
2855
|
+
const { events, offset: next } = outbox.read(ref, offset, room);
|
|
2577
2856
|
if (!events.length) break;
|
|
2578
|
-
const
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
const res = await sink.send(filtered);
|
|
2582
|
-
ok = res.ok;
|
|
2583
|
-
if (!ok) {
|
|
2584
|
-
log(state, `sink ${sink.name}: ${res.status ?? ""} ${res.error ?? ""} \u2192 ${res.action ?? "retry"}`);
|
|
2585
|
-
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2586
|
-
if (res.action === "drop") ok = true;
|
|
2587
|
-
}
|
|
2588
|
-
if (ok) sent += filtered.length;
|
|
2589
|
-
}
|
|
2590
|
-
if (!ok) {
|
|
2591
|
-
stalled = true;
|
|
2592
|
-
break;
|
|
2857
|
+
for (const e of events) {
|
|
2858
|
+
const f = filterEvent(e, sink.tier, sink.pii, { home: os3.homedir() });
|
|
2859
|
+
if (f) batch.push(f);
|
|
2593
2860
|
}
|
|
2594
2861
|
offset = next;
|
|
2595
|
-
|
|
2596
|
-
|
|
2862
|
+
pending[cursors.key(ref)] = offset;
|
|
2863
|
+
if (batch.length >= BATCH) {
|
|
2864
|
+
if (!await send()) {
|
|
2865
|
+
stalled = true;
|
|
2866
|
+
break;
|
|
2867
|
+
}
|
|
2868
|
+
if (sent >= MAX_PER_RUN) break;
|
|
2869
|
+
}
|
|
2597
2870
|
}
|
|
2598
|
-
|
|
2871
|
+
}
|
|
2872
|
+
if (!stalled && (batch.length || Object.keys(pending).length)) {
|
|
2873
|
+
if (!await send()) stalled = true;
|
|
2599
2874
|
}
|
|
2600
2875
|
delivered[sink.name] = sent;
|
|
2601
2876
|
}
|
|
@@ -2612,6 +2887,7 @@ function applySinkVerdict(name, res) {
|
|
|
2612
2887
|
} catch {
|
|
2613
2888
|
}
|
|
2614
2889
|
}
|
|
2890
|
+
var jobsDir = (state) => path12.join(state, "jobs");
|
|
2615
2891
|
|
|
2616
2892
|
// src/worker.ts
|
|
2617
2893
|
var jobFile = process.argv[2];
|
|
@@ -2622,12 +2898,13 @@ try {
|
|
|
2622
2898
|
} catch {
|
|
2623
2899
|
}
|
|
2624
2900
|
try {
|
|
2625
|
-
|
|
2901
|
+
unlinkSync5(jobFile);
|
|
2626
2902
|
} catch {
|
|
2627
2903
|
}
|
|
2628
2904
|
if (!job) process.exit(0);
|
|
2629
2905
|
var deadline = setTimeout(() => process.exit(0), 15e3);
|
|
2630
2906
|
deadline.unref();
|
|
2631
2907
|
runJob(job).catch(() => {
|
|
2908
|
+
}).then(() => sweepJobs(stateDir())).catch(() => {
|
|
2632
2909
|
}).finally(() => process.exit(0));
|
|
2633
2910
|
//# sourceMappingURL=worker.js.map
|