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/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
// sessionpipe 0.
|
|
1
|
+
// sessionpipe 0.2.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
|
|
3
3
|
// src/http-sink.ts
|
|
4
|
-
import {
|
|
4
|
+
import { createHash, createHmac } from "node:crypto";
|
|
5
5
|
var TIMEOUT_MS = 15e3;
|
|
6
|
+
function webhookId(events) {
|
|
7
|
+
return `msg_${createHash("sha256").update(events.map((e) => e.id).join(",")).digest("base64url").slice(0, 32)}`;
|
|
8
|
+
}
|
|
6
9
|
var HttpSink = class {
|
|
7
10
|
constructor(cfg, tier) {
|
|
8
11
|
this.cfg = cfg;
|
|
@@ -25,7 +28,7 @@ var HttpSink = class {
|
|
|
25
28
|
const headers = { "content-type": "application/json", "sessionpipe-protocol": "1" };
|
|
26
29
|
if (this.cfg.token) headers.authorization = `Bearer ${this.cfg.token}`;
|
|
27
30
|
if (this.cfg.secret?.startsWith("whsec_")) {
|
|
28
|
-
const id =
|
|
31
|
+
const id = webhookId(events);
|
|
29
32
|
const ts = Math.floor(Date.now() / 1e3);
|
|
30
33
|
const key = Buffer.from(this.cfg.secret.slice(6), "base64");
|
|
31
34
|
const sig = createHmac("sha256", key).update(`${id}.${ts}.${body}`).digest("base64");
|
|
@@ -86,12 +89,31 @@ var HttpSink = class {
|
|
|
86
89
|
};
|
|
87
90
|
|
|
88
91
|
// src/run.ts
|
|
89
|
-
import {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
import {
|
|
93
|
+
appendFileSync as appendFileSync3,
|
|
94
|
+
existsSync as existsSync8,
|
|
95
|
+
mkdirSync as mkdirSync7,
|
|
96
|
+
readdirSync as readdirSync6,
|
|
97
|
+
readFileSync as readFileSync8,
|
|
98
|
+
renameSync as renameSync2,
|
|
99
|
+
statSync as statSync5,
|
|
100
|
+
unlinkSync as unlinkSync4,
|
|
101
|
+
writeFileSync as writeFileSync6
|
|
102
|
+
} from "node:fs";
|
|
103
|
+
import os3 from "node:os";
|
|
104
|
+
import path12 from "node:path";
|
|
92
105
|
|
|
93
106
|
// ../core/src/adapters/antigravity.ts
|
|
94
|
-
import {
|
|
107
|
+
import {
|
|
108
|
+
existsSync as existsSync2,
|
|
109
|
+
mkdirSync as mkdirSync2,
|
|
110
|
+
readdirSync as readdirSync2,
|
|
111
|
+
readFileSync as readFileSync2,
|
|
112
|
+
realpathSync,
|
|
113
|
+
statSync as statSync2,
|
|
114
|
+
unlinkSync as unlinkSync2,
|
|
115
|
+
writeFileSync as writeFileSync2
|
|
116
|
+
} from "node:fs";
|
|
95
117
|
import path4 from "node:path";
|
|
96
118
|
|
|
97
119
|
// ../core/src/paths.ts
|
|
@@ -318,28 +340,67 @@ function readAntigravity(lines, from) {
|
|
|
318
340
|
}
|
|
319
341
|
|
|
320
342
|
// ../core/src/adapters/claude-shaped.ts
|
|
321
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
343
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
322
344
|
import path3 from "node:path";
|
|
323
345
|
var OURS = /sessionpipe[^"\n]*[\\/]hook\.js"?\s+[a-z][a-z-]*\s+[A-Za-z]+\s*$/;
|
|
324
346
|
var isOurs = (h) => Array.isArray(h?.hooks) && h.hooks.some((x) => typeof x.command === "string" && OURS.test(x.command));
|
|
325
|
-
function
|
|
347
|
+
function readJsonFile(file) {
|
|
348
|
+
let raw;
|
|
326
349
|
try {
|
|
327
|
-
|
|
350
|
+
raw = readFileSync(file, "utf8");
|
|
328
351
|
} catch {
|
|
329
|
-
return {};
|
|
352
|
+
return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
|
|
330
353
|
}
|
|
354
|
+
let value;
|
|
355
|
+
try {
|
|
356
|
+
value = JSON.parse(raw);
|
|
357
|
+
} catch {
|
|
358
|
+
const hint = /^\s*\/\/|\/\*/m.test(raw) ? "not plain JSON (it has comments)" : "not plain JSON";
|
|
359
|
+
return { ok: false, existed: true, reason: hint };
|
|
360
|
+
}
|
|
361
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
362
|
+
return { ok: false, existed: true, reason: "not a JSON object" };
|
|
363
|
+
const eol = raw.includes("\r\n") ? "\r\n" : "\n";
|
|
364
|
+
const m = /^( +|\t+)\S/m.exec(raw);
|
|
365
|
+
const indent = m?.[1] ?? " ";
|
|
366
|
+
return {
|
|
367
|
+
ok: true,
|
|
368
|
+
existed: true,
|
|
369
|
+
value,
|
|
370
|
+
raw,
|
|
371
|
+
indent,
|
|
372
|
+
eol,
|
|
373
|
+
finalNewline: /\r?\n$/.test(raw)
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function formatJson(value, f) {
|
|
377
|
+
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
378
|
+
return f.finalNewline ? text + f.eol : text;
|
|
331
379
|
}
|
|
332
380
|
function entryFor(event, harness, cmd, o) {
|
|
333
381
|
const matcher = o.matcherFor(event);
|
|
334
382
|
return {
|
|
335
383
|
...matcher !== void 0 ? { matcher } : {},
|
|
336
|
-
hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeout }]
|
|
384
|
+
hooks: [{ type: "command", command: cmd([harness, event]).command, timeout: o.timeoutFor?.(event) ?? o.timeout }]
|
|
337
385
|
};
|
|
338
386
|
}
|
|
339
387
|
function installClaudeShaped(file, harness, events, cmd, o) {
|
|
340
|
-
const
|
|
388
|
+
const f = readJsonFile(file);
|
|
389
|
+
if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
|
|
390
|
+
const settings2 = f.value;
|
|
341
391
|
const hooks = settings2.hooks && typeof settings2.hooks === "object" ? settings2.hooks : {};
|
|
342
392
|
let changed = false;
|
|
393
|
+
const want = new Set(events);
|
|
394
|
+
for (const ev of Object.keys(hooks)) {
|
|
395
|
+
if (want.has(ev)) continue;
|
|
396
|
+
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
397
|
+
const kept = list.filter((h) => !isOurs(h));
|
|
398
|
+
if (kept.length !== list.length) {
|
|
399
|
+
changed = true;
|
|
400
|
+
if (kept.length) hooks[ev] = kept;
|
|
401
|
+
else delete hooks[ev];
|
|
402
|
+
}
|
|
403
|
+
}
|
|
343
404
|
for (const ev of events) {
|
|
344
405
|
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
345
406
|
const entry2 = entryFor(ev, harness, cmd, o);
|
|
@@ -358,19 +419,19 @@ function installClaudeShaped(file, harness, events, cmd, o) {
|
|
|
358
419
|
if (changed) {
|
|
359
420
|
settings2.hooks = hooks;
|
|
360
421
|
mkdirSync(path3.dirname(file), { recursive: true });
|
|
361
|
-
writeFileSync(file,
|
|
362
|
-
`);
|
|
422
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
363
423
|
}
|
|
364
|
-
return { file, changed };
|
|
424
|
+
return { file, changed, created: !f.existed && changed };
|
|
365
425
|
}
|
|
366
|
-
function uninstallClaudeShaped(file, events) {
|
|
426
|
+
function uninstallClaudeShaped(file, events, opts = {}) {
|
|
367
427
|
if (!existsSync(file)) return { file, changed: false };
|
|
368
|
-
const
|
|
369
|
-
|
|
428
|
+
const f = readJsonFile(file);
|
|
429
|
+
if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
|
|
430
|
+
const settings2 = f.value;
|
|
370
431
|
const hooks = settings2.hooks;
|
|
371
432
|
if (!hooks || typeof hooks !== "object") return { file, changed: false };
|
|
372
433
|
let changed = false;
|
|
373
|
-
for (const ev of events) {
|
|
434
|
+
for (const ev of [...events, ...Object.keys(hooks)]) {
|
|
374
435
|
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
375
436
|
const kept = list.filter((h) => !isOurs(h));
|
|
376
437
|
if (kept.length !== list.length) {
|
|
@@ -381,13 +442,17 @@ function uninstallClaudeShaped(file, events) {
|
|
|
381
442
|
}
|
|
382
443
|
if (!changed) return { file, changed: false };
|
|
383
444
|
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
445
|
+
if (opts.created && !Object.keys(settings2).length) {
|
|
446
|
+
unlinkSync(file);
|
|
447
|
+
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
448
|
+
}
|
|
449
|
+
const next = formatJson(settings2, f);
|
|
450
|
+
if (next !== f.raw) writeFileSync(file, next);
|
|
387
451
|
return { file, changed: true };
|
|
388
452
|
}
|
|
389
453
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
390
|
-
const
|
|
454
|
+
const f = readJsonFile(file);
|
|
455
|
+
const hooks = f.ok ? f.value.hooks ?? {} : {};
|
|
391
456
|
let present = 0;
|
|
392
457
|
let current = 0;
|
|
393
458
|
for (const ev of events) {
|
|
@@ -396,6 +461,9 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
|
396
461
|
if (mine.length) present++;
|
|
397
462
|
if (mine.length === 1 && JSON.stringify(mine[0]) === JSON.stringify(entryFor(ev, harness, cmd, o))) current++;
|
|
398
463
|
}
|
|
464
|
+
for (const ev of Object.keys(hooks))
|
|
465
|
+
if (!events.includes(ev) && (Array.isArray(hooks[ev]) ? hooks[ev] : []).some((h) => isOurs(h)))
|
|
466
|
+
return "stale";
|
|
399
467
|
if (!present) return "missing";
|
|
400
468
|
return current === events.length ? "current" : "stale";
|
|
401
469
|
}
|
|
@@ -410,10 +478,13 @@ var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
|
410
478
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
411
479
|
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
412
480
|
var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
413
|
-
function entry(cmd) {
|
|
481
|
+
function entry(cmd, lean) {
|
|
414
482
|
const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
|
|
415
483
|
const out = { enabled: true };
|
|
416
|
-
for (const e of AG_EVENTS)
|
|
484
|
+
for (const e of AG_EVENTS) {
|
|
485
|
+
if (lean && e === "PreToolUse") continue;
|
|
486
|
+
out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
487
|
+
}
|
|
417
488
|
return out;
|
|
418
489
|
}
|
|
419
490
|
var antigravity = {
|
|
@@ -421,34 +492,49 @@ var antigravity = {
|
|
|
421
492
|
events: AG_EVENTS,
|
|
422
493
|
detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
|
|
423
494
|
configFiles: (env = process.env) => [hooksFile(env)],
|
|
424
|
-
install(cmd, env = process.env) {
|
|
495
|
+
install(cmd, env = process.env, opts = {}) {
|
|
425
496
|
const file = hooksFile(env);
|
|
426
|
-
const
|
|
427
|
-
|
|
497
|
+
const f = readJsonFile(file);
|
|
498
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
499
|
+
const hooks = f.value;
|
|
500
|
+
const e = entry(cmd, opts.lean);
|
|
428
501
|
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
429
502
|
hooks[HOOK_NAME] = e;
|
|
430
503
|
mkdirSync2(path4.dirname(file), { recursive: true });
|
|
431
|
-
writeFileSync2(file,
|
|
432
|
-
`);
|
|
504
|
+
writeFileSync2(file, formatJson(hooks, f));
|
|
433
505
|
return [
|
|
434
|
-
{
|
|
506
|
+
{
|
|
507
|
+
file,
|
|
508
|
+
changed: true,
|
|
509
|
+
created: !f.existed,
|
|
510
|
+
note: "hooks.json is read when a conversation starts; open ones report after a restart"
|
|
511
|
+
}
|
|
435
512
|
];
|
|
436
513
|
},
|
|
437
|
-
uninstall(env = process.env) {
|
|
514
|
+
uninstall(env = process.env, opts = {}) {
|
|
438
515
|
const file = hooksFile(env);
|
|
439
516
|
if (!existsSync2(file)) return [{ file, changed: false }];
|
|
440
|
-
const
|
|
517
|
+
const f = readJsonFile(file);
|
|
518
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
519
|
+
const hooks = f.value;
|
|
441
520
|
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
442
521
|
delete hooks[HOOK_NAME];
|
|
443
|
-
|
|
444
|
-
|
|
522
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
523
|
+
unlinkSync2(file);
|
|
524
|
+
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
525
|
+
}
|
|
526
|
+
writeFileSync2(file, formatJson(hooks, f));
|
|
445
527
|
return [{ file, changed: true }];
|
|
446
528
|
},
|
|
447
|
-
installed(cmd, env = process.env) {
|
|
529
|
+
installed(cmd, env = process.env, opts = {}) {
|
|
448
530
|
const file = hooksFile(env);
|
|
449
|
-
const
|
|
531
|
+
const f = readJsonFile(file);
|
|
532
|
+
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
450
533
|
return [
|
|
451
|
-
{
|
|
534
|
+
{
|
|
535
|
+
file,
|
|
536
|
+
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd, opts.lean)) ? "current" : "stale"
|
|
537
|
+
}
|
|
452
538
|
];
|
|
453
539
|
},
|
|
454
540
|
fromHook(input) {
|
|
@@ -662,7 +748,7 @@ function antigravityFacts(file, opts) {
|
|
|
662
748
|
|
|
663
749
|
// ../core/src/adapters/claude-code.ts
|
|
664
750
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
665
|
-
import { createHash } from "node:crypto";
|
|
751
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
666
752
|
import { existsSync as existsSync3, readdirSync as readdirSync3, readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
|
|
667
753
|
import path5 from "node:path";
|
|
668
754
|
|
|
@@ -714,9 +800,18 @@ var CLAUDE_EVENTS = [
|
|
|
714
800
|
"SubagentStop",
|
|
715
801
|
"PreCompact",
|
|
716
802
|
"PostCompact",
|
|
717
|
-
"SessionEnd"
|
|
803
|
+
"SessionEnd",
|
|
804
|
+
"StopFailure"
|
|
718
805
|
];
|
|
719
|
-
var
|
|
806
|
+
var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
|
|
807
|
+
var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
|
|
808
|
+
var OPTS = {
|
|
809
|
+
matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0,
|
|
810
|
+
timeout: 5,
|
|
811
|
+
// The hook waits up to 125 s for a verified control answer, and only when control
|
|
812
|
+
// is paired on this machine; everywhere else it exits in milliseconds as before.
|
|
813
|
+
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
814
|
+
};
|
|
720
815
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
721
816
|
function claudeDirs(env = process.env, home = HOME) {
|
|
722
817
|
const out = [];
|
|
@@ -747,11 +842,13 @@ var claudeCode = {
|
|
|
747
842
|
events: CLAUDE_EVENTS,
|
|
748
843
|
detect: (env = process.env) => claudeDirs(env).some((d) => existsSync3(d)),
|
|
749
844
|
configFiles: (env = process.env) => claudeDirs(env).map(settingsIn),
|
|
750
|
-
install: (cmd, env = process.env) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2,
|
|
751
|
-
uninstall: (env = process.env) => claudeDirs(env).map(
|
|
752
|
-
|
|
845
|
+
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)),
|
|
846
|
+
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map(
|
|
847
|
+
(d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, { created: opts.created?.includes(settingsIn(d)) })
|
|
848
|
+
),
|
|
849
|
+
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
753
850
|
file: settingsIn(d),
|
|
754
|
-
state: installedClaudeShaped(settingsIn(d), NAME2,
|
|
851
|
+
state: installedClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)
|
|
755
852
|
})),
|
|
756
853
|
fromHook(input) {
|
|
757
854
|
const event = input.argv[1] ?? "";
|
|
@@ -771,7 +868,7 @@ var claudeCode = {
|
|
|
771
868
|
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
772
869
|
const ms = typeof s.duration_ms === "number" ? Math.round(s.duration_ms) : void 0;
|
|
773
870
|
const agent_id = typeof s.agent_id === "string" ? s.agent_id : void 0;
|
|
774
|
-
const permissionId = () => `perm-${
|
|
871
|
+
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
775
872
|
const ev = (type, data) => ({ type, data: prune2(data), harnessEvent: event });
|
|
776
873
|
const events = [];
|
|
777
874
|
switch (event) {
|
|
@@ -842,6 +939,15 @@ var claudeCode = {
|
|
|
842
939
|
case "Stop":
|
|
843
940
|
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
844
941
|
break;
|
|
942
|
+
case "StopFailure":
|
|
943
|
+
events.push(
|
|
944
|
+
ev("turn.ended", {
|
|
945
|
+
turn_id,
|
|
946
|
+
reason: "error",
|
|
947
|
+
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
948
|
+
})
|
|
949
|
+
);
|
|
950
|
+
break;
|
|
845
951
|
case "SubagentStart":
|
|
846
952
|
events.push(
|
|
847
953
|
ev("subagent.started", {
|
|
@@ -1149,9 +1255,9 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1149
1255
|
}
|
|
1150
1256
|
const name = live?.name ? { title: live.name, source: live.name_source } : typeof mem.name === "string" ? { title: mem.name, source: mem.name_source } : null;
|
|
1151
1257
|
const pick = name && name.source === "custom" && name || custom && { title: custom, source: "custom" } || name || ai && { title: ai, source: "harness" };
|
|
1152
|
-
let
|
|
1153
|
-
if (!
|
|
1154
|
-
|
|
1258
|
+
let ask2 = typeof mem.first_ask === "string" ? mem.first_ask : firstAsk(H);
|
|
1259
|
+
if (!ask2 && size > 64 * 1024 && !mem.first_ask_none) {
|
|
1260
|
+
ask2 = scanLines(file, (l) => {
|
|
1155
1261
|
try {
|
|
1156
1262
|
const t = askText(JSON.parse(l));
|
|
1157
1263
|
return t ? appTitle(t) : null;
|
|
@@ -1159,17 +1265,17 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1159
1265
|
return null;
|
|
1160
1266
|
}
|
|
1161
1267
|
});
|
|
1162
|
-
if (!
|
|
1268
|
+
if (!ask2 && size > 16 * 1024 * 1024) state.save(NAME2, id, { first_ask_none: true });
|
|
1163
1269
|
}
|
|
1164
|
-
if (
|
|
1165
|
-
out.first_ask =
|
|
1166
|
-
if (!mem.first_ask) state.save(NAME2, id, { first_ask:
|
|
1270
|
+
if (ask2) {
|
|
1271
|
+
out.first_ask = ask2;
|
|
1272
|
+
if (!mem.first_ask) state.save(NAME2, id, { first_ask: ask2 });
|
|
1167
1273
|
}
|
|
1168
1274
|
if (pick) {
|
|
1169
1275
|
out.title = String(pick.title).slice(0, 120);
|
|
1170
1276
|
out.title_source = pick.source;
|
|
1171
|
-
} else if (
|
|
1172
|
-
out.title =
|
|
1277
|
+
} else if (ask2) {
|
|
1278
|
+
out.title = ask2;
|
|
1173
1279
|
out.title_source = "first-ask";
|
|
1174
1280
|
}
|
|
1175
1281
|
const acct = claudeAccount(dir);
|
|
@@ -1353,6 +1459,7 @@ var CODEX_EVENTS = [
|
|
|
1353
1459
|
"SessionEnd"
|
|
1354
1460
|
];
|
|
1355
1461
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1462
|
+
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1356
1463
|
var codexHome = (env) => env.CODEX_HOME || path6.join(HOME, ".codex");
|
|
1357
1464
|
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1358
1465
|
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
@@ -1401,7 +1508,8 @@ function installNotify(cmd, env) {
|
|
|
1401
1508
|
return {
|
|
1402
1509
|
file,
|
|
1403
1510
|
changed: false,
|
|
1404
|
-
|
|
1511
|
+
skipped: true,
|
|
1512
|
+
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`
|
|
1405
1513
|
};
|
|
1406
1514
|
}
|
|
1407
1515
|
write(withNotify(text, line));
|
|
@@ -1458,14 +1566,21 @@ var codex = {
|
|
|
1458
1566
|
events: CODEX_EVENTS,
|
|
1459
1567
|
detect: (env = process.env) => existsSync4(codexHome(env)),
|
|
1460
1568
|
configFiles: (env = process.env) => [hooksFile2(env), configFile2(env)],
|
|
1461
|
-
install: (cmd, env = process.env) => [
|
|
1462
|
-
installClaudeShaped(hooksFile2(env), NAME3,
|
|
1569
|
+
install: (cmd, env = process.env, opts = {}) => [
|
|
1570
|
+
installClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
1463
1571
|
installNotify(cmd, env)
|
|
1464
1572
|
],
|
|
1465
|
-
uninstall: (env = process.env) => [
|
|
1466
|
-
|
|
1573
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1574
|
+
uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
|
|
1575
|
+
uninstallNotify(env)
|
|
1576
|
+
],
|
|
1577
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1467
1578
|
const out = [
|
|
1468
|
-
{
|
|
1579
|
+
{
|
|
1580
|
+
file: hooksFile2(env),
|
|
1581
|
+
state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
1582
|
+
note: "Codex runs non-managed hooks only after the review in `codex` (/hooks); until then nothing is reported unless the notify fallback is ours"
|
|
1583
|
+
}
|
|
1469
1584
|
];
|
|
1470
1585
|
let text = "";
|
|
1471
1586
|
try {
|
|
@@ -1653,6 +1768,7 @@ function codexFacts(file) {
|
|
|
1653
1768
|
}
|
|
1654
1769
|
|
|
1655
1770
|
// ../core/src/adapters/gemini-cli.ts
|
|
1771
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1656
1772
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1657
1773
|
import path7 from "node:path";
|
|
1658
1774
|
var NAME4 = "gemini-cli";
|
|
@@ -1670,17 +1786,61 @@ var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
|
1670
1786
|
var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(HOME, ".gemini");
|
|
1671
1787
|
var settings = (env) => path7.join(geminiDir(env), "settings.json");
|
|
1672
1788
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1789
|
+
var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1790
|
+
function geminiHooksActive(settingsFile) {
|
|
1791
|
+
let tools;
|
|
1792
|
+
try {
|
|
1793
|
+
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
1794
|
+
} catch {
|
|
1795
|
+
}
|
|
1796
|
+
if (tools?.enableHooks === true) return { active: true };
|
|
1797
|
+
if (tools?.enableHooks === false)
|
|
1798
|
+
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1799
|
+
let version = "";
|
|
1800
|
+
try {
|
|
1801
|
+
version = execFileSync3("gemini", ["--version"], {
|
|
1802
|
+
encoding: "utf8",
|
|
1803
|
+
timeout: 4e3,
|
|
1804
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1805
|
+
}).trim();
|
|
1806
|
+
} catch {
|
|
1807
|
+
return {
|
|
1808
|
+
active: true,
|
|
1809
|
+
note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
|
|
1810
|
+
};
|
|
1811
|
+
}
|
|
1812
|
+
const m = /(\d+)\.(\d+)/.exec(version);
|
|
1813
|
+
if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
|
|
1814
|
+
return {
|
|
1815
|
+
active: false,
|
|
1816
|
+
note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1673
1819
|
var geminiCli = {
|
|
1674
1820
|
name: NAME4,
|
|
1675
1821
|
events: GEMINI_EVENTS,
|
|
1676
1822
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1677
1823
|
detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path7.join(geminiDir(env), "tmp")),
|
|
1678
1824
|
configFiles: (env = process.env) => [settings(env)],
|
|
1679
|
-
install: (cmd, env = process.env
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1825
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
1826
|
+
const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1827
|
+
const a = geminiHooksActive(settings(env));
|
|
1828
|
+
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1829
|
+
},
|
|
1830
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1831
|
+
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1683
1832
|
],
|
|
1833
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1834
|
+
const state = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1835
|
+
const a = geminiHooksActive(settings(env));
|
|
1836
|
+
return [
|
|
1837
|
+
{
|
|
1838
|
+
file: settings(env),
|
|
1839
|
+
state: state === "current" && !a.active ? "inactive" : state,
|
|
1840
|
+
...a.note ? { note: a.note } : {}
|
|
1841
|
+
}
|
|
1842
|
+
];
|
|
1843
|
+
},
|
|
1684
1844
|
fromHook(input) {
|
|
1685
1845
|
const event = input.argv[1] ?? "";
|
|
1686
1846
|
let s = {};
|
|
@@ -1891,7 +2051,7 @@ import {
|
|
|
1891
2051
|
readSync as readSync2,
|
|
1892
2052
|
renameSync,
|
|
1893
2053
|
statSync as statSync4,
|
|
1894
|
-
unlinkSync,
|
|
2054
|
+
unlinkSync as unlinkSync3,
|
|
1895
2055
|
writeFileSync as writeFileSync5
|
|
1896
2056
|
} from "node:fs";
|
|
1897
2057
|
import path9 from "node:path";
|
|
@@ -1919,8 +2079,8 @@ var Outbox = class {
|
|
|
1919
2079
|
} catch {
|
|
1920
2080
|
n = this.lastSeqInFile(ref) + 1;
|
|
1921
2081
|
}
|
|
1922
|
-
mkdirSync5(path9.dirname(f), { recursive: true });
|
|
1923
|
-
writeFileSync5(f, String(n + 1));
|
|
2082
|
+
mkdirSync5(path9.dirname(f), { recursive: true, mode: 448 });
|
|
2083
|
+
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
1924
2084
|
return n;
|
|
1925
2085
|
}
|
|
1926
2086
|
lastSeqInFile(ref) {
|
|
@@ -1938,9 +2098,9 @@ var Outbox = class {
|
|
|
1938
2098
|
}
|
|
1939
2099
|
append(ref, events) {
|
|
1940
2100
|
if (!events.length) return;
|
|
1941
|
-
mkdirSync5(this.dir(ref.harness), { recursive: true });
|
|
2101
|
+
mkdirSync5(this.dir(ref.harness), { recursive: true, mode: 448 });
|
|
1942
2102
|
appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
1943
|
-
|
|
2103
|
+
`, { mode: 384 });
|
|
1944
2104
|
}
|
|
1945
2105
|
/** Every session file, newest first. */
|
|
1946
2106
|
sessions() {
|
|
@@ -2004,9 +2164,9 @@ var Outbox = class {
|
|
|
2004
2164
|
const f = this.file(ref);
|
|
2005
2165
|
try {
|
|
2006
2166
|
if (statSync4(f).mtimeMs < cutoff) {
|
|
2007
|
-
|
|
2167
|
+
unlinkSync3(f);
|
|
2008
2168
|
try {
|
|
2009
|
-
|
|
2169
|
+
unlinkSync3(this.seqFile(ref));
|
|
2010
2170
|
} catch {
|
|
2011
2171
|
}
|
|
2012
2172
|
n++;
|
|
@@ -2019,7 +2179,7 @@ var Outbox = class {
|
|
|
2019
2179
|
delete(ref) {
|
|
2020
2180
|
for (const f of [this.file(ref), this.seqFile(ref)]) {
|
|
2021
2181
|
try {
|
|
2022
|
-
|
|
2182
|
+
unlinkSync3(f);
|
|
2023
2183
|
} catch {
|
|
2024
2184
|
}
|
|
2025
2185
|
}
|
|
@@ -2044,9 +2204,9 @@ var Cursors = class {
|
|
|
2044
2204
|
}
|
|
2045
2205
|
write(c) {
|
|
2046
2206
|
const f = this.file();
|
|
2047
|
-
mkdirSync5(path9.dirname(f), { recursive: true });
|
|
2207
|
+
mkdirSync5(path9.dirname(f), { recursive: true, mode: 448 });
|
|
2048
2208
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2049
|
-
writeFileSync5(tmp, JSON.stringify(c));
|
|
2209
|
+
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
2050
2210
|
renameSync(tmp, f);
|
|
2051
2211
|
}
|
|
2052
2212
|
key(ref) {
|
|
@@ -2055,10 +2215,10 @@ var Cursors = class {
|
|
|
2055
2215
|
};
|
|
2056
2216
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
2057
2217
|
const dir = path9.join(root, "locks");
|
|
2058
|
-
mkdirSync5(dir, { recursive: true });
|
|
2218
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2059
2219
|
const f = path9.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
|
|
2060
2220
|
try {
|
|
2061
|
-
if (Date.now() - statSync4(f).mtimeMs > staleMs)
|
|
2221
|
+
if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
2062
2222
|
else return null;
|
|
2063
2223
|
} catch {
|
|
2064
2224
|
}
|
|
@@ -2069,14 +2229,14 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2069
2229
|
}
|
|
2070
2230
|
return () => {
|
|
2071
2231
|
try {
|
|
2072
|
-
|
|
2232
|
+
unlinkSync3(f);
|
|
2073
2233
|
} catch {
|
|
2074
2234
|
}
|
|
2075
2235
|
};
|
|
2076
2236
|
}
|
|
2077
2237
|
|
|
2078
2238
|
// ../core/src/privacy/pii.ts
|
|
2079
|
-
import { createHash as
|
|
2239
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2080
2240
|
var RULESET_PII = "pii@1";
|
|
2081
2241
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2082
2242
|
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
@@ -2098,7 +2258,7 @@ function reducePii(text, home) {
|
|
|
2098
2258
|
return out;
|
|
2099
2259
|
}
|
|
2100
2260
|
function hashMachine(name) {
|
|
2101
|
-
return
|
|
2261
|
+
return createHash3("sha256").update(name).digest("hex").slice(0, 8);
|
|
2102
2262
|
}
|
|
2103
2263
|
function reducePiiDeep(v, home) {
|
|
2104
2264
|
if (typeof v === "string") return reducePii(v, home);
|
|
@@ -2205,7 +2365,7 @@ function redactAssignments(text) {
|
|
|
2205
2365
|
function isSecretName(name) {
|
|
2206
2366
|
if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
|
|
2207
2367
|
const flat = name.toLowerCase().replace(/[_-]/g, "");
|
|
2208
|
-
return /(?:pass(?:word|wd|phrase|code)
|
|
2368
|
+
return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
|
|
2209
2369
|
flat
|
|
2210
2370
|
);
|
|
2211
2371
|
}
|
|
@@ -2294,7 +2454,7 @@ var DATA_ALLOW = {
|
|
|
2294
2454
|
"session.backfill": { 0: ["started_at", "last_at", "turns"] },
|
|
2295
2455
|
"session.forgotten": { 0: [] },
|
|
2296
2456
|
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2297
|
-
"turn.ended": { 0: ["turn_id", "reason", "ms"] },
|
|
2457
|
+
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
2298
2458
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2299
2459
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2300
2460
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
@@ -2415,11 +2575,72 @@ var StdoutSink = class {
|
|
|
2415
2575
|
}
|
|
2416
2576
|
};
|
|
2417
2577
|
|
|
2578
|
+
// src/control/local.ts
|
|
2579
|
+
import net from "node:net";
|
|
2580
|
+
import os2 from "node:os";
|
|
2581
|
+
import path11 from "node:path";
|
|
2582
|
+
function socketPath(state, env = process.env) {
|
|
2583
|
+
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
2584
|
+
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
2585
|
+
return path11.join(state, "control.sock");
|
|
2586
|
+
}
|
|
2587
|
+
function ask(sock, req, timeoutMs) {
|
|
2588
|
+
return new Promise((resolve) => {
|
|
2589
|
+
let buf = "";
|
|
2590
|
+
let done = false;
|
|
2591
|
+
const s = net.connect(sock);
|
|
2592
|
+
const finish = (r) => {
|
|
2593
|
+
if (done) return;
|
|
2594
|
+
done = true;
|
|
2595
|
+
clearTimeout(t);
|
|
2596
|
+
s.destroy();
|
|
2597
|
+
resolve(r);
|
|
2598
|
+
};
|
|
2599
|
+
const t = setTimeout(() => finish(null), timeoutMs);
|
|
2600
|
+
s.on("connect", () => s.write(`${JSON.stringify(req)}
|
|
2601
|
+
`));
|
|
2602
|
+
s.on("data", (d) => {
|
|
2603
|
+
buf += d.toString("utf8");
|
|
2604
|
+
const i = buf.indexOf("\n");
|
|
2605
|
+
if (i < 0) return;
|
|
2606
|
+
try {
|
|
2607
|
+
finish(JSON.parse(buf.slice(0, i)));
|
|
2608
|
+
} catch {
|
|
2609
|
+
finish(null);
|
|
2610
|
+
}
|
|
2611
|
+
});
|
|
2612
|
+
s.on("error", () => finish(null));
|
|
2613
|
+
s.on("close", () => finish(null));
|
|
2614
|
+
});
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2418
2617
|
// src/run.ts
|
|
2618
|
+
async function tellDaemon(state, job, session, transcript) {
|
|
2619
|
+
const sock = socketPath(state);
|
|
2620
|
+
if (process.platform !== "win32" && !existsSync8(sock)) return;
|
|
2621
|
+
await ask(
|
|
2622
|
+
sock,
|
|
2623
|
+
{
|
|
2624
|
+
op: "event",
|
|
2625
|
+
session: `claude-code:${session}`,
|
|
2626
|
+
event: job.event,
|
|
2627
|
+
cwd: job.cwd,
|
|
2628
|
+
...transcript ? { transcript } : {}
|
|
2629
|
+
},
|
|
2630
|
+
300
|
|
2631
|
+
);
|
|
2632
|
+
}
|
|
2633
|
+
function enqueueJob(state, job) {
|
|
2634
|
+
const jobs = jobsDir(state);
|
|
2635
|
+
mkdirSync7(jobs, { recursive: true, mode: 448 });
|
|
2636
|
+
const f = path12.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
2637
|
+
writeFileSync6(f, JSON.stringify(job), { mode: 384 });
|
|
2638
|
+
return f;
|
|
2639
|
+
}
|
|
2419
2640
|
function log(state, line) {
|
|
2420
2641
|
try {
|
|
2421
|
-
mkdirSync7(state, { recursive: true });
|
|
2422
|
-
const f =
|
|
2642
|
+
mkdirSync7(state, { recursive: true, mode: 448 });
|
|
2643
|
+
const f = path12.join(state, "log");
|
|
2423
2644
|
try {
|
|
2424
2645
|
if (statSync5(f).size > 1024 * 1024) renameSync2(f, `${f}.1`);
|
|
2425
2646
|
} catch {
|
|
@@ -2430,7 +2651,7 @@ function log(state, line) {
|
|
|
2430
2651
|
}
|
|
2431
2652
|
}
|
|
2432
2653
|
function factsState(state) {
|
|
2433
|
-
const file = (h, s) =>
|
|
2654
|
+
const file = (h, s) => path12.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
2434
2655
|
return {
|
|
2435
2656
|
get(h, s) {
|
|
2436
2657
|
try {
|
|
@@ -2443,8 +2664,8 @@ function factsState(state) {
|
|
|
2443
2664
|
const cur = this.get(h, s);
|
|
2444
2665
|
const next = { ...cur, ...patch };
|
|
2445
2666
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
2446
|
-
mkdirSync7(
|
|
2447
|
-
writeFileSync6(file(h, s), JSON.stringify(next));
|
|
2667
|
+
mkdirSync7(path12.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
2668
|
+
writeFileSync6(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
2448
2669
|
}
|
|
2449
2670
|
};
|
|
2450
2671
|
}
|
|
@@ -2455,7 +2676,7 @@ function buildSinks(sinks) {
|
|
|
2455
2676
|
const tier = Math.min(s.tier, s.max_tier ?? 3);
|
|
2456
2677
|
if (s.url === "stdout") out.push(new StdoutSink(s.name, tier, !!s.pii));
|
|
2457
2678
|
else if (s.url.startsWith("file:"))
|
|
2458
|
-
out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/,
|
|
2679
|
+
out.push(new FileSink(s.name, s.url.slice(5).replace(/^~(?=\/|$)/, os3.homedir()), tier, !!s.pii));
|
|
2459
2680
|
else if (/^https?:\/\//.test(s.url)) out.push(new HttpSink(s, tier));
|
|
2460
2681
|
}
|
|
2461
2682
|
return out;
|
|
@@ -2467,14 +2688,17 @@ async function runJob(job, opts = {}) {
|
|
|
2467
2688
|
if (!adapter) return { events: [], delivered: {} };
|
|
2468
2689
|
const r = adapter.fromHook({ argv: job.argv, stdin: job.stdin, env: { ...process.env, ...job.env }, cwd: job.cwd });
|
|
2469
2690
|
if (!r || !r.session.id) return { events: [], delivered: {} };
|
|
2691
|
+
if (job.harness === "claude-code") await tellDaemon(state, job, r.session.id, r.transcript);
|
|
2470
2692
|
const outbox = new Outbox(state);
|
|
2471
2693
|
const ref = { harness: job.harness, session: r.session.id };
|
|
2472
|
-
|
|
2694
|
+
let release = takeLock(state, ref);
|
|
2695
|
+
for (let wait = 50; !release && wait <= 3200; wait *= 2) {
|
|
2696
|
+
await new Promise((res) => setTimeout(res, wait));
|
|
2697
|
+
release = takeLock(state, ref);
|
|
2698
|
+
}
|
|
2473
2699
|
if (!release) {
|
|
2474
|
-
log(state, `${job.harness} ${job.event}: session ${r.session.id} locked, re-queued`);
|
|
2475
|
-
|
|
2476
|
-
mkdirSync7(jobs, { recursive: true });
|
|
2477
|
-
writeFileSync6(path11.join(jobs, `${Date.now().toString(36)}-requeue.json`), JSON.stringify(job));
|
|
2700
|
+
log(state, `${job.harness} ${job.event}: session ${r.session.id} locked for 6 s, re-queued`);
|
|
2701
|
+
enqueueJob(state, job);
|
|
2478
2702
|
return { events: [], delivered: {} };
|
|
2479
2703
|
}
|
|
2480
2704
|
try {
|
|
@@ -2499,7 +2723,7 @@ async function runJob(job, opts = {}) {
|
|
|
2499
2723
|
...learned.branch ? { branch: learned.branch } : {},
|
|
2500
2724
|
id: r.session.id,
|
|
2501
2725
|
seq: 0,
|
|
2502
|
-
machine: machineName(cfg,
|
|
2726
|
+
machine: machineName(cfg, os3.hostname()),
|
|
2503
2727
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
2504
2728
|
...r.session.model ? { model: r.session.model } : {}
|
|
2505
2729
|
};
|
|
@@ -2538,7 +2762,10 @@ async function runJob(job, opts = {}) {
|
|
|
2538
2762
|
if (read.turns.length)
|
|
2539
2763
|
facts.save(job.harness, r.session.id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
2540
2764
|
}
|
|
2541
|
-
outbox.append(
|
|
2765
|
+
outbox.append(
|
|
2766
|
+
ref,
|
|
2767
|
+
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
2768
|
+
);
|
|
2542
2769
|
log(
|
|
2543
2770
|
state,
|
|
2544
2771
|
`${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${r.session.id.slice(0, 8)})`
|
|
@@ -2564,34 +2791,57 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2564
2791
|
const cursors = new Cursors(state, sink.name);
|
|
2565
2792
|
const c = cursors.read();
|
|
2566
2793
|
let sent = 0;
|
|
2794
|
+
let stalled = false;
|
|
2795
|
+
let batch = [];
|
|
2796
|
+
let pending = {};
|
|
2797
|
+
const send = async () => {
|
|
2798
|
+
if (!batch.length) {
|
|
2799
|
+
Object.assign(c, pending);
|
|
2800
|
+
pending = {};
|
|
2801
|
+
cursors.write(c);
|
|
2802
|
+
return true;
|
|
2803
|
+
}
|
|
2804
|
+
const res = await sink.send(batch);
|
|
2805
|
+
if (!res.ok) {
|
|
2806
|
+
log(state, `sink ${sink.name}: ${res.status ?? ""} ${res.error ?? ""} \u2192 ${res.action ?? "retry"}`);
|
|
2807
|
+
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2808
|
+
if (res.action !== "drop") {
|
|
2809
|
+
batch = [];
|
|
2810
|
+
pending = {};
|
|
2811
|
+
return false;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
sent += batch.length;
|
|
2815
|
+
Object.assign(c, pending);
|
|
2816
|
+
cursors.write(c);
|
|
2817
|
+
batch = [];
|
|
2818
|
+
pending = {};
|
|
2819
|
+
return true;
|
|
2820
|
+
};
|
|
2567
2821
|
for (const ref of refs) {
|
|
2568
|
-
if (sent >= MAX_PER_RUN) break;
|
|
2569
|
-
let offset = c[cursors.key(ref)] ?? 0;
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
const { events, offset: next } = outbox.read(ref, offset,
|
|
2822
|
+
if (stalled || sent >= MAX_PER_RUN) break;
|
|
2823
|
+
let offset = pending[cursors.key(ref)] ?? c[cursors.key(ref)] ?? 0;
|
|
2824
|
+
for (; ; ) {
|
|
2825
|
+
const room = BATCH - batch.length;
|
|
2826
|
+
const { events, offset: next } = outbox.read(ref, offset, room);
|
|
2573
2827
|
if (!events.length) break;
|
|
2574
|
-
const
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
const res = await sink.send(filtered);
|
|
2578
|
-
ok = res.ok;
|
|
2579
|
-
if (!ok) {
|
|
2580
|
-
log(state, `sink ${sink.name}: ${res.status ?? ""} ${res.error ?? ""} \u2192 ${res.action ?? "retry"}`);
|
|
2581
|
-
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2582
|
-
if (res.action === "drop") ok = true;
|
|
2583
|
-
}
|
|
2584
|
-
if (ok) sent += filtered.length;
|
|
2585
|
-
}
|
|
2586
|
-
if (!ok) {
|
|
2587
|
-
stalled = true;
|
|
2588
|
-
break;
|
|
2828
|
+
for (const e of events) {
|
|
2829
|
+
const f = filterEvent(e, sink.tier, sink.pii, { home: os3.homedir() });
|
|
2830
|
+
if (f) batch.push(f);
|
|
2589
2831
|
}
|
|
2590
2832
|
offset = next;
|
|
2591
|
-
|
|
2592
|
-
|
|
2833
|
+
pending[cursors.key(ref)] = offset;
|
|
2834
|
+
if (batch.length >= BATCH) {
|
|
2835
|
+
if (!await send()) {
|
|
2836
|
+
stalled = true;
|
|
2837
|
+
break;
|
|
2838
|
+
}
|
|
2839
|
+
if (sent >= MAX_PER_RUN) break;
|
|
2840
|
+
}
|
|
2593
2841
|
}
|
|
2594
|
-
|
|
2842
|
+
}
|
|
2843
|
+
if (!stalled && (batch.length || Object.keys(pending).length)) {
|
|
2844
|
+
if (!await send()) stalled = true;
|
|
2595
2845
|
}
|
|
2596
2846
|
delivered[sink.name] = sent;
|
|
2597
2847
|
}
|
|
@@ -2608,6 +2858,7 @@ function applySinkVerdict(name, res) {
|
|
|
2608
2858
|
} catch {
|
|
2609
2859
|
}
|
|
2610
2860
|
}
|
|
2861
|
+
var jobsDir = (state) => path12.join(state, "jobs");
|
|
2611
2862
|
export {
|
|
2612
2863
|
HttpSink,
|
|
2613
2864
|
buildSinks,
|