sessionpipe 0.1.0 → 0.1.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/dist/cli.js +417 -140
- package/dist/cli.js.map +3 -3
- package/dist/hook.js +5 -5
- package/dist/hook.js.map +2 -2
- package/dist/index.js +283 -96
- package/dist/index.js.map +3 -3
- package/dist/run.d.ts +13 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/worker.js +315 -102
- package/dist/worker.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe 0.1.
|
|
1
|
+
// sessionpipe 0.1.1 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
|
|
3
3
|
// src/http-sink.ts
|
|
4
4
|
import { createHmac, randomUUID } from "node:crypto";
|
|
@@ -86,12 +86,31 @@ var HttpSink = class {
|
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
// src/run.ts
|
|
89
|
-
import {
|
|
89
|
+
import {
|
|
90
|
+
appendFileSync as appendFileSync3,
|
|
91
|
+
existsSync as existsSync8,
|
|
92
|
+
mkdirSync as mkdirSync7,
|
|
93
|
+
readdirSync as readdirSync6,
|
|
94
|
+
readFileSync as readFileSync8,
|
|
95
|
+
renameSync as renameSync2,
|
|
96
|
+
statSync as statSync5,
|
|
97
|
+
unlinkSync as unlinkSync4,
|
|
98
|
+
writeFileSync as writeFileSync6
|
|
99
|
+
} from "node:fs";
|
|
90
100
|
import os2 from "node:os";
|
|
91
101
|
import path11 from "node:path";
|
|
92
102
|
|
|
93
103
|
// ../core/src/adapters/antigravity.ts
|
|
94
|
-
import {
|
|
104
|
+
import {
|
|
105
|
+
existsSync as existsSync2,
|
|
106
|
+
mkdirSync as mkdirSync2,
|
|
107
|
+
readdirSync as readdirSync2,
|
|
108
|
+
readFileSync as readFileSync2,
|
|
109
|
+
realpathSync,
|
|
110
|
+
statSync as statSync2,
|
|
111
|
+
unlinkSync as unlinkSync2,
|
|
112
|
+
writeFileSync as writeFileSync2
|
|
113
|
+
} from "node:fs";
|
|
95
114
|
import path4 from "node:path";
|
|
96
115
|
|
|
97
116
|
// ../core/src/paths.ts
|
|
@@ -318,16 +337,42 @@ function readAntigravity(lines, from) {
|
|
|
318
337
|
}
|
|
319
338
|
|
|
320
339
|
// ../core/src/adapters/claude-shaped.ts
|
|
321
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
340
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
322
341
|
import path3 from "node:path";
|
|
323
342
|
var OURS = /sessionpipe[^"\n]*[\\/]hook\.js"?\s+[a-z][a-z-]*\s+[A-Za-z]+\s*$/;
|
|
324
343
|
var isOurs = (h) => Array.isArray(h?.hooks) && h.hooks.some((x) => typeof x.command === "string" && OURS.test(x.command));
|
|
325
|
-
function
|
|
344
|
+
function readJsonFile(file) {
|
|
345
|
+
let raw;
|
|
326
346
|
try {
|
|
327
|
-
|
|
347
|
+
raw = readFileSync(file, "utf8");
|
|
328
348
|
} catch {
|
|
329
|
-
return {};
|
|
349
|
+
return { ok: true, existed: false, value: {}, raw: "", indent: " ", eol: "\n", finalNewline: true };
|
|
330
350
|
}
|
|
351
|
+
let value;
|
|
352
|
+
try {
|
|
353
|
+
value = JSON.parse(raw);
|
|
354
|
+
} catch {
|
|
355
|
+
const hint = /^\s*\/\/|\/\*/m.test(raw) ? "not plain JSON (it has comments)" : "not plain JSON";
|
|
356
|
+
return { ok: false, existed: true, reason: hint };
|
|
357
|
+
}
|
|
358
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
359
|
+
return { ok: false, existed: true, reason: "not a JSON object" };
|
|
360
|
+
const eol = raw.includes("\r\n") ? "\r\n" : "\n";
|
|
361
|
+
const m = /^( +|\t+)\S/m.exec(raw);
|
|
362
|
+
const indent = m?.[1] ?? " ";
|
|
363
|
+
return {
|
|
364
|
+
ok: true,
|
|
365
|
+
existed: true,
|
|
366
|
+
value,
|
|
367
|
+
raw,
|
|
368
|
+
indent,
|
|
369
|
+
eol,
|
|
370
|
+
finalNewline: /\r?\n$/.test(raw)
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
function formatJson(value, f) {
|
|
374
|
+
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
375
|
+
return f.finalNewline ? text + f.eol : text;
|
|
331
376
|
}
|
|
332
377
|
function entryFor(event, harness, cmd, o) {
|
|
333
378
|
const matcher = o.matcherFor(event);
|
|
@@ -337,9 +382,22 @@ function entryFor(event, harness, cmd, o) {
|
|
|
337
382
|
};
|
|
338
383
|
}
|
|
339
384
|
function installClaudeShaped(file, harness, events, cmd, o) {
|
|
340
|
-
const
|
|
385
|
+
const f = readJsonFile(file);
|
|
386
|
+
if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
|
|
387
|
+
const settings2 = f.value;
|
|
341
388
|
const hooks = settings2.hooks && typeof settings2.hooks === "object" ? settings2.hooks : {};
|
|
342
389
|
let changed = false;
|
|
390
|
+
const want = new Set(events);
|
|
391
|
+
for (const ev of Object.keys(hooks)) {
|
|
392
|
+
if (want.has(ev)) continue;
|
|
393
|
+
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
394
|
+
const kept = list.filter((h) => !isOurs(h));
|
|
395
|
+
if (kept.length !== list.length) {
|
|
396
|
+
changed = true;
|
|
397
|
+
if (kept.length) hooks[ev] = kept;
|
|
398
|
+
else delete hooks[ev];
|
|
399
|
+
}
|
|
400
|
+
}
|
|
343
401
|
for (const ev of events) {
|
|
344
402
|
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
345
403
|
const entry2 = entryFor(ev, harness, cmd, o);
|
|
@@ -358,19 +416,19 @@ function installClaudeShaped(file, harness, events, cmd, o) {
|
|
|
358
416
|
if (changed) {
|
|
359
417
|
settings2.hooks = hooks;
|
|
360
418
|
mkdirSync(path3.dirname(file), { recursive: true });
|
|
361
|
-
writeFileSync(file,
|
|
362
|
-
`);
|
|
419
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
363
420
|
}
|
|
364
|
-
return { file, changed };
|
|
421
|
+
return { file, changed, created: !f.existed && changed };
|
|
365
422
|
}
|
|
366
|
-
function uninstallClaudeShaped(file, events) {
|
|
423
|
+
function uninstallClaudeShaped(file, events, opts = {}) {
|
|
367
424
|
if (!existsSync(file)) return { file, changed: false };
|
|
368
|
-
const
|
|
369
|
-
|
|
425
|
+
const f = readJsonFile(file);
|
|
426
|
+
if (!f.ok) return { file, changed: false, skipped: true, note: `${f.reason}; not touched` };
|
|
427
|
+
const settings2 = f.value;
|
|
370
428
|
const hooks = settings2.hooks;
|
|
371
429
|
if (!hooks || typeof hooks !== "object") return { file, changed: false };
|
|
372
430
|
let changed = false;
|
|
373
|
-
for (const ev of events) {
|
|
431
|
+
for (const ev of [...events, ...Object.keys(hooks)]) {
|
|
374
432
|
const list = Array.isArray(hooks[ev]) ? hooks[ev] : [];
|
|
375
433
|
const kept = list.filter((h) => !isOurs(h));
|
|
376
434
|
if (kept.length !== list.length) {
|
|
@@ -381,13 +439,17 @@ function uninstallClaudeShaped(file, events) {
|
|
|
381
439
|
}
|
|
382
440
|
if (!changed) return { file, changed: false };
|
|
383
441
|
if (!Object.keys(hooks).length) delete settings2.hooks;
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
442
|
+
if (opts.created && !Object.keys(settings2).length) {
|
|
443
|
+
unlinkSync(file);
|
|
444
|
+
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
445
|
+
}
|
|
446
|
+
const next = formatJson(settings2, f);
|
|
447
|
+
if (next !== f.raw) writeFileSync(file, next);
|
|
387
448
|
return { file, changed: true };
|
|
388
449
|
}
|
|
389
450
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
390
|
-
const
|
|
451
|
+
const f = readJsonFile(file);
|
|
452
|
+
const hooks = f.ok ? f.value.hooks ?? {} : {};
|
|
391
453
|
let present = 0;
|
|
392
454
|
let current = 0;
|
|
393
455
|
for (const ev of events) {
|
|
@@ -396,6 +458,9 @@ function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
|
396
458
|
if (mine.length) present++;
|
|
397
459
|
if (mine.length === 1 && JSON.stringify(mine[0]) === JSON.stringify(entryFor(ev, harness, cmd, o))) current++;
|
|
398
460
|
}
|
|
461
|
+
for (const ev of Object.keys(hooks))
|
|
462
|
+
if (!events.includes(ev) && (Array.isArray(hooks[ev]) ? hooks[ev] : []).some((h) => isOurs(h)))
|
|
463
|
+
return "stale";
|
|
399
464
|
if (!present) return "missing";
|
|
400
465
|
return current === events.length ? "current" : "stale";
|
|
401
466
|
}
|
|
@@ -410,10 +475,13 @@ var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
|
410
475
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
411
476
|
var ID_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
412
477
|
var validTool = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
413
|
-
function entry(cmd) {
|
|
478
|
+
function entry(cmd, lean) {
|
|
414
479
|
const handler = (e) => ({ type: "command", command: cmd([NAME, e]).command, timeout: 5 });
|
|
415
480
|
const out = { enabled: true };
|
|
416
|
-
for (const e of AG_EVENTS)
|
|
481
|
+
for (const e of AG_EVENTS) {
|
|
482
|
+
if (lean && e === "PreToolUse") continue;
|
|
483
|
+
out[e] = /Tool/.test(e) ? [{ matcher: "*", hooks: [handler(e)] }] : [handler(e)];
|
|
484
|
+
}
|
|
417
485
|
return out;
|
|
418
486
|
}
|
|
419
487
|
var antigravity = {
|
|
@@ -421,34 +489,49 @@ var antigravity = {
|
|
|
421
489
|
events: AG_EVENTS,
|
|
422
490
|
detect: (env = process.env) => existsSync2(path4.join(gemini(env), "config")) || dataDirs(env).some((d) => existsSync2(d)),
|
|
423
491
|
configFiles: (env = process.env) => [hooksFile(env)],
|
|
424
|
-
install(cmd, env = process.env) {
|
|
492
|
+
install(cmd, env = process.env, opts = {}) {
|
|
425
493
|
const file = hooksFile(env);
|
|
426
|
-
const
|
|
427
|
-
|
|
494
|
+
const f = readJsonFile(file);
|
|
495
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
496
|
+
const hooks = f.value;
|
|
497
|
+
const e = entry(cmd, opts.lean);
|
|
428
498
|
if (JSON.stringify(hooks[HOOK_NAME]) === JSON.stringify(e)) return [{ file, changed: false }];
|
|
429
499
|
hooks[HOOK_NAME] = e;
|
|
430
500
|
mkdirSync2(path4.dirname(file), { recursive: true });
|
|
431
|
-
writeFileSync2(file,
|
|
432
|
-
`);
|
|
501
|
+
writeFileSync2(file, formatJson(hooks, f));
|
|
433
502
|
return [
|
|
434
|
-
{
|
|
503
|
+
{
|
|
504
|
+
file,
|
|
505
|
+
changed: true,
|
|
506
|
+
created: !f.existed,
|
|
507
|
+
note: "hooks.json is read when a conversation starts; open ones report after a restart"
|
|
508
|
+
}
|
|
435
509
|
];
|
|
436
510
|
},
|
|
437
|
-
uninstall(env = process.env) {
|
|
511
|
+
uninstall(env = process.env, opts = {}) {
|
|
438
512
|
const file = hooksFile(env);
|
|
439
513
|
if (!existsSync2(file)) return [{ file, changed: false }];
|
|
440
|
-
const
|
|
514
|
+
const f = readJsonFile(file);
|
|
515
|
+
if (!f.ok) return [{ file, changed: false, skipped: true, note: `${f.reason}; not touched` }];
|
|
516
|
+
const hooks = f.value;
|
|
441
517
|
if (!(HOOK_NAME in hooks)) return [{ file, changed: false }];
|
|
442
518
|
delete hooks[HOOK_NAME];
|
|
443
|
-
|
|
444
|
-
|
|
519
|
+
if (opts.created?.includes(file) && !Object.keys(hooks).length) {
|
|
520
|
+
unlinkSync2(file);
|
|
521
|
+
return [{ file, changed: true, note: "removed (sessionpipe created it)" }];
|
|
522
|
+
}
|
|
523
|
+
writeFileSync2(file, formatJson(hooks, f));
|
|
445
524
|
return [{ file, changed: true }];
|
|
446
525
|
},
|
|
447
|
-
installed(cmd, env = process.env) {
|
|
526
|
+
installed(cmd, env = process.env, opts = {}) {
|
|
448
527
|
const file = hooksFile(env);
|
|
449
|
-
const
|
|
528
|
+
const f = readJsonFile(file);
|
|
529
|
+
const have = f.ok ? f.value[HOOK_NAME] : void 0;
|
|
450
530
|
return [
|
|
451
|
-
{
|
|
531
|
+
{
|
|
532
|
+
file,
|
|
533
|
+
state: !have ? "missing" : JSON.stringify(have) === JSON.stringify(entry(cmd, opts.lean)) ? "current" : "stale"
|
|
534
|
+
}
|
|
452
535
|
];
|
|
453
536
|
},
|
|
454
537
|
fromHook(input) {
|
|
@@ -714,8 +797,11 @@ var CLAUDE_EVENTS = [
|
|
|
714
797
|
"SubagentStop",
|
|
715
798
|
"PreCompact",
|
|
716
799
|
"PostCompact",
|
|
717
|
-
"SessionEnd"
|
|
800
|
+
"SessionEnd",
|
|
801
|
+
"StopFailure"
|
|
718
802
|
];
|
|
803
|
+
var LEAN_SKIP = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUseFailure"]);
|
|
804
|
+
var eventsFor = (lean) => lean ? CLAUDE_EVENTS.filter((e) => !LEAN_SKIP.has(e)) : [...CLAUDE_EVENTS];
|
|
719
805
|
var OPTS = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
720
806
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
721
807
|
function claudeDirs(env = process.env, home = HOME) {
|
|
@@ -747,11 +833,13 @@ var claudeCode = {
|
|
|
747
833
|
events: CLAUDE_EVENTS,
|
|
748
834
|
detect: (env = process.env) => claudeDirs(env).some((d) => existsSync3(d)),
|
|
749
835
|
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
|
-
|
|
836
|
+
install: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => installClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)),
|
|
837
|
+
uninstall: (env = process.env, opts = {}) => claudeDirs(env).map(
|
|
838
|
+
(d) => uninstallClaudeShaped(settingsIn(d), CLAUDE_EVENTS, { created: opts.created?.includes(settingsIn(d)) })
|
|
839
|
+
),
|
|
840
|
+
installed: (cmd, env = process.env, opts = {}) => claudeDirs(env).map((d) => ({
|
|
753
841
|
file: settingsIn(d),
|
|
754
|
-
state: installedClaudeShaped(settingsIn(d), NAME2,
|
|
842
|
+
state: installedClaudeShaped(settingsIn(d), NAME2, eventsFor(opts.lean), cmd, OPTS)
|
|
755
843
|
})),
|
|
756
844
|
fromHook(input) {
|
|
757
845
|
const event = input.argv[1] ?? "";
|
|
@@ -842,6 +930,15 @@ var claudeCode = {
|
|
|
842
930
|
case "Stop":
|
|
843
931
|
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
844
932
|
break;
|
|
933
|
+
case "StopFailure":
|
|
934
|
+
events.push(
|
|
935
|
+
ev("turn.ended", {
|
|
936
|
+
turn_id,
|
|
937
|
+
reason: "error",
|
|
938
|
+
error: typeof s.error === "string" ? s.error.slice(0, 200) : void 0
|
|
939
|
+
})
|
|
940
|
+
);
|
|
941
|
+
break;
|
|
845
942
|
case "SubagentStart":
|
|
846
943
|
events.push(
|
|
847
944
|
ev("subagent.started", {
|
|
@@ -1353,6 +1450,7 @@ var CODEX_EVENTS = [
|
|
|
1353
1450
|
"SessionEnd"
|
|
1354
1451
|
];
|
|
1355
1452
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1453
|
+
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1356
1454
|
var codexHome = (env) => env.CODEX_HOME || path6.join(HOME, ".codex");
|
|
1357
1455
|
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1358
1456
|
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
@@ -1401,7 +1499,8 @@ function installNotify(cmd, env) {
|
|
|
1401
1499
|
return {
|
|
1402
1500
|
file,
|
|
1403
1501
|
changed: false,
|
|
1404
|
-
|
|
1502
|
+
skipped: true,
|
|
1503
|
+
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
1504
|
};
|
|
1406
1505
|
}
|
|
1407
1506
|
write(withNotify(text, line));
|
|
@@ -1458,14 +1557,21 @@ var codex = {
|
|
|
1458
1557
|
events: CODEX_EVENTS,
|
|
1459
1558
|
detect: (env = process.env) => existsSync4(codexHome(env)),
|
|
1460
1559
|
configFiles: (env = process.env) => [hooksFile2(env), configFile2(env)],
|
|
1461
|
-
install: (cmd, env = process.env) => [
|
|
1462
|
-
installClaudeShaped(hooksFile2(env), NAME3,
|
|
1560
|
+
install: (cmd, env = process.env, opts = {}) => [
|
|
1561
|
+
installClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
1463
1562
|
installNotify(cmd, env)
|
|
1464
1563
|
],
|
|
1465
|
-
uninstall: (env = process.env) => [
|
|
1466
|
-
|
|
1564
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1565
|
+
uninstallClaudeShaped(hooksFile2(env), CODEX_EVENTS, { created: opts.created?.includes(hooksFile2(env)) }),
|
|
1566
|
+
uninstallNotify(env)
|
|
1567
|
+
],
|
|
1568
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1467
1569
|
const out = [
|
|
1468
|
-
{
|
|
1570
|
+
{
|
|
1571
|
+
file: hooksFile2(env),
|
|
1572
|
+
state: installedClaudeShaped(hooksFile2(env), NAME3, eventsFor2(opts.lean), cmd, OPTS2),
|
|
1573
|
+
note: "Codex runs non-managed hooks only after the review in `codex` (/hooks); until then nothing is reported unless the notify fallback is ours"
|
|
1574
|
+
}
|
|
1469
1575
|
];
|
|
1470
1576
|
let text = "";
|
|
1471
1577
|
try {
|
|
@@ -1653,6 +1759,7 @@ function codexFacts(file) {
|
|
|
1653
1759
|
}
|
|
1654
1760
|
|
|
1655
1761
|
// ../core/src/adapters/gemini-cli.ts
|
|
1762
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1656
1763
|
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
1657
1764
|
import path7 from "node:path";
|
|
1658
1765
|
var NAME4 = "gemini-cli";
|
|
@@ -1670,17 +1777,61 @@ var OPTS3 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
|
1670
1777
|
var geminiDir = (env) => env.GEMINI_CLI_HOME || path7.join(HOME, ".gemini");
|
|
1671
1778
|
var settings = (env) => path7.join(geminiDir(env), "settings.json");
|
|
1672
1779
|
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1780
|
+
var eventsFor3 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1781
|
+
function geminiHooksActive(settingsFile) {
|
|
1782
|
+
let tools;
|
|
1783
|
+
try {
|
|
1784
|
+
tools = JSON.parse(readFileSync5(settingsFile, "utf8")).tools;
|
|
1785
|
+
} catch {
|
|
1786
|
+
}
|
|
1787
|
+
if (tools?.enableHooks === true) return { active: true };
|
|
1788
|
+
if (tools?.enableHooks === false)
|
|
1789
|
+
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1790
|
+
let version = "";
|
|
1791
|
+
try {
|
|
1792
|
+
version = execFileSync3("gemini", ["--version"], {
|
|
1793
|
+
encoding: "utf8",
|
|
1794
|
+
timeout: 4e3,
|
|
1795
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1796
|
+
}).trim();
|
|
1797
|
+
} catch {
|
|
1798
|
+
return {
|
|
1799
|
+
active: true,
|
|
1800
|
+
note: "could not run `gemini --version`; hooks need tools.enableHooks on versions before 0.46"
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
const m = /(\d+)\.(\d+)/.exec(version);
|
|
1804
|
+
if (m && (Number(m[1]) > 0 || Number(m[2]) >= 46)) return { active: true };
|
|
1805
|
+
return {
|
|
1806
|
+
active: false,
|
|
1807
|
+
note: `inactive: Gemini CLI ${version || "?"} runs hooks only with "tools": {"enableHooks": true} in settings.json`
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1673
1810
|
var geminiCli = {
|
|
1674
1811
|
name: NAME4,
|
|
1675
1812
|
events: GEMINI_EVENTS,
|
|
1676
1813
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1677
1814
|
detect: (env = process.env) => existsSync5(settings(env)) || existsSync5(path7.join(geminiDir(env), "tmp")),
|
|
1678
1815
|
configFiles: (env = process.env) => [settings(env)],
|
|
1679
|
-
install: (cmd, env = process.env
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1816
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
1817
|
+
const r = installClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1818
|
+
const a = geminiHooksActive(settings(env));
|
|
1819
|
+
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1820
|
+
},
|
|
1821
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1822
|
+
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1683
1823
|
],
|
|
1824
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1825
|
+
const state = installedClaudeShaped(settings(env), NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1826
|
+
const a = geminiHooksActive(settings(env));
|
|
1827
|
+
return [
|
|
1828
|
+
{
|
|
1829
|
+
file: settings(env),
|
|
1830
|
+
state: state === "current" && !a.active ? "inactive" : state,
|
|
1831
|
+
...a.note ? { note: a.note } : {}
|
|
1832
|
+
}
|
|
1833
|
+
];
|
|
1834
|
+
},
|
|
1684
1835
|
fromHook(input) {
|
|
1685
1836
|
const event = input.argv[1] ?? "";
|
|
1686
1837
|
let s = {};
|
|
@@ -1891,7 +2042,7 @@ import {
|
|
|
1891
2042
|
readSync as readSync2,
|
|
1892
2043
|
renameSync,
|
|
1893
2044
|
statSync as statSync4,
|
|
1894
|
-
unlinkSync,
|
|
2045
|
+
unlinkSync as unlinkSync3,
|
|
1895
2046
|
writeFileSync as writeFileSync5
|
|
1896
2047
|
} from "node:fs";
|
|
1897
2048
|
import path9 from "node:path";
|
|
@@ -1919,8 +2070,8 @@ var Outbox = class {
|
|
|
1919
2070
|
} catch {
|
|
1920
2071
|
n = this.lastSeqInFile(ref) + 1;
|
|
1921
2072
|
}
|
|
1922
|
-
mkdirSync5(path9.dirname(f), { recursive: true });
|
|
1923
|
-
writeFileSync5(f, String(n + 1));
|
|
2073
|
+
mkdirSync5(path9.dirname(f), { recursive: true, mode: 448 });
|
|
2074
|
+
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
1924
2075
|
return n;
|
|
1925
2076
|
}
|
|
1926
2077
|
lastSeqInFile(ref) {
|
|
@@ -1938,9 +2089,9 @@ var Outbox = class {
|
|
|
1938
2089
|
}
|
|
1939
2090
|
append(ref, events) {
|
|
1940
2091
|
if (!events.length) return;
|
|
1941
|
-
mkdirSync5(this.dir(ref.harness), { recursive: true });
|
|
2092
|
+
mkdirSync5(this.dir(ref.harness), { recursive: true, mode: 448 });
|
|
1942
2093
|
appendFileSync(this.file(ref), `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
1943
|
-
|
|
2094
|
+
`, { mode: 384 });
|
|
1944
2095
|
}
|
|
1945
2096
|
/** Every session file, newest first. */
|
|
1946
2097
|
sessions() {
|
|
@@ -2004,9 +2155,9 @@ var Outbox = class {
|
|
|
2004
2155
|
const f = this.file(ref);
|
|
2005
2156
|
try {
|
|
2006
2157
|
if (statSync4(f).mtimeMs < cutoff) {
|
|
2007
|
-
|
|
2158
|
+
unlinkSync3(f);
|
|
2008
2159
|
try {
|
|
2009
|
-
|
|
2160
|
+
unlinkSync3(this.seqFile(ref));
|
|
2010
2161
|
} catch {
|
|
2011
2162
|
}
|
|
2012
2163
|
n++;
|
|
@@ -2019,7 +2170,7 @@ var Outbox = class {
|
|
|
2019
2170
|
delete(ref) {
|
|
2020
2171
|
for (const f of [this.file(ref), this.seqFile(ref)]) {
|
|
2021
2172
|
try {
|
|
2022
|
-
|
|
2173
|
+
unlinkSync3(f);
|
|
2023
2174
|
} catch {
|
|
2024
2175
|
}
|
|
2025
2176
|
}
|
|
@@ -2044,9 +2195,9 @@ var Cursors = class {
|
|
|
2044
2195
|
}
|
|
2045
2196
|
write(c) {
|
|
2046
2197
|
const f = this.file();
|
|
2047
|
-
mkdirSync5(path9.dirname(f), { recursive: true });
|
|
2198
|
+
mkdirSync5(path9.dirname(f), { recursive: true, mode: 448 });
|
|
2048
2199
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2049
|
-
writeFileSync5(tmp, JSON.stringify(c));
|
|
2200
|
+
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
2050
2201
|
renameSync(tmp, f);
|
|
2051
2202
|
}
|
|
2052
2203
|
key(ref) {
|
|
@@ -2055,10 +2206,10 @@ var Cursors = class {
|
|
|
2055
2206
|
};
|
|
2056
2207
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
2057
2208
|
const dir = path9.join(root, "locks");
|
|
2058
|
-
mkdirSync5(dir, { recursive: true });
|
|
2209
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2059
2210
|
const f = path9.join(dir, `${safeId(ref.harness)}-${safeId(ref.session)}.lock`);
|
|
2060
2211
|
try {
|
|
2061
|
-
if (Date.now() - statSync4(f).mtimeMs > staleMs)
|
|
2212
|
+
if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
2062
2213
|
else return null;
|
|
2063
2214
|
} catch {
|
|
2064
2215
|
}
|
|
@@ -2069,7 +2220,7 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2069
2220
|
}
|
|
2070
2221
|
return () => {
|
|
2071
2222
|
try {
|
|
2072
|
-
|
|
2223
|
+
unlinkSync3(f);
|
|
2073
2224
|
} catch {
|
|
2074
2225
|
}
|
|
2075
2226
|
};
|
|
@@ -2205,7 +2356,7 @@ function redactAssignments(text) {
|
|
|
2205
2356
|
function isSecretName(name) {
|
|
2206
2357
|
if (/^[A-Z0-9_]+_KEY$/.test(name)) return true;
|
|
2207
2358
|
const flat = name.toLowerCase().replace(/[_-]/g, "");
|
|
2208
|
-
return /(?:pass(?:word|wd|phrase|code)
|
|
2359
|
+
return /(?:pass(?:word|wd|phrase|code)|pwd|secret|secretkey|secretaccesskey|clientsecret|apikey|accesskey|accesstoken|authtoken|refreshtoken|privatekey|token|credential|creds)s?$/.test(
|
|
2209
2360
|
flat
|
|
2210
2361
|
);
|
|
2211
2362
|
}
|
|
@@ -2294,7 +2445,7 @@ var DATA_ALLOW = {
|
|
|
2294
2445
|
"session.backfill": { 0: ["started_at", "last_at", "turns"] },
|
|
2295
2446
|
"session.forgotten": { 0: [] },
|
|
2296
2447
|
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2297
|
-
"turn.ended": { 0: ["turn_id", "reason", "ms"] },
|
|
2448
|
+
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
2298
2449
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2299
2450
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2300
2451
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
@@ -2416,9 +2567,16 @@ var StdoutSink = class {
|
|
|
2416
2567
|
};
|
|
2417
2568
|
|
|
2418
2569
|
// src/run.ts
|
|
2570
|
+
function enqueueJob(state, job) {
|
|
2571
|
+
const jobs = jobsDir(state);
|
|
2572
|
+
mkdirSync7(jobs, { recursive: true, mode: 448 });
|
|
2573
|
+
const f = path11.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
2574
|
+
writeFileSync6(f, JSON.stringify(job), { mode: 384 });
|
|
2575
|
+
return f;
|
|
2576
|
+
}
|
|
2419
2577
|
function log(state, line) {
|
|
2420
2578
|
try {
|
|
2421
|
-
mkdirSync7(state, { recursive: true });
|
|
2579
|
+
mkdirSync7(state, { recursive: true, mode: 448 });
|
|
2422
2580
|
const f = path11.join(state, "log");
|
|
2423
2581
|
try {
|
|
2424
2582
|
if (statSync5(f).size > 1024 * 1024) renameSync2(f, `${f}.1`);
|
|
@@ -2443,8 +2601,8 @@ function factsState(state) {
|
|
|
2443
2601
|
const cur = this.get(h, s);
|
|
2444
2602
|
const next = { ...cur, ...patch };
|
|
2445
2603
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
2446
|
-
mkdirSync7(path11.dirname(file(h, s)), { recursive: true });
|
|
2447
|
-
writeFileSync6(file(h, s), JSON.stringify(next));
|
|
2604
|
+
mkdirSync7(path11.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
2605
|
+
writeFileSync6(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
2448
2606
|
}
|
|
2449
2607
|
};
|
|
2450
2608
|
}
|
|
@@ -2469,12 +2627,14 @@ async function runJob(job, opts = {}) {
|
|
|
2469
2627
|
if (!r || !r.session.id) return { events: [], delivered: {} };
|
|
2470
2628
|
const outbox = new Outbox(state);
|
|
2471
2629
|
const ref = { harness: job.harness, session: r.session.id };
|
|
2472
|
-
|
|
2630
|
+
let release = takeLock(state, ref);
|
|
2631
|
+
for (let wait = 50; !release && wait <= 3200; wait *= 2) {
|
|
2632
|
+
await new Promise((res) => setTimeout(res, wait));
|
|
2633
|
+
release = takeLock(state, ref);
|
|
2634
|
+
}
|
|
2473
2635
|
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));
|
|
2636
|
+
log(state, `${job.harness} ${job.event}: session ${r.session.id} locked for 6 s, re-queued`);
|
|
2637
|
+
enqueueJob(state, job);
|
|
2478
2638
|
return { events: [], delivered: {} };
|
|
2479
2639
|
}
|
|
2480
2640
|
try {
|
|
@@ -2538,7 +2698,10 @@ async function runJob(job, opts = {}) {
|
|
|
2538
2698
|
if (read.turns.length)
|
|
2539
2699
|
facts.save(job.harness, r.session.id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
2540
2700
|
}
|
|
2541
|
-
outbox.append(
|
|
2701
|
+
outbox.append(
|
|
2702
|
+
ref,
|
|
2703
|
+
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
2704
|
+
);
|
|
2542
2705
|
log(
|
|
2543
2706
|
state,
|
|
2544
2707
|
`${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${r.session.id.slice(0, 8)})`
|
|
@@ -2564,34 +2727,57 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2564
2727
|
const cursors = new Cursors(state, sink.name);
|
|
2565
2728
|
const c = cursors.read();
|
|
2566
2729
|
let sent = 0;
|
|
2730
|
+
let stalled = false;
|
|
2731
|
+
let batch = [];
|
|
2732
|
+
let pending = {};
|
|
2733
|
+
const send = async () => {
|
|
2734
|
+
if (!batch.length) {
|
|
2735
|
+
Object.assign(c, pending);
|
|
2736
|
+
pending = {};
|
|
2737
|
+
cursors.write(c);
|
|
2738
|
+
return true;
|
|
2739
|
+
}
|
|
2740
|
+
const res = await sink.send(batch);
|
|
2741
|
+
if (!res.ok) {
|
|
2742
|
+
log(state, `sink ${sink.name}: ${res.status ?? ""} ${res.error ?? ""} \u2192 ${res.action ?? "retry"}`);
|
|
2743
|
+
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2744
|
+
if (res.action !== "drop") {
|
|
2745
|
+
batch = [];
|
|
2746
|
+
pending = {};
|
|
2747
|
+
return false;
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2750
|
+
sent += batch.length;
|
|
2751
|
+
Object.assign(c, pending);
|
|
2752
|
+
cursors.write(c);
|
|
2753
|
+
batch = [];
|
|
2754
|
+
pending = {};
|
|
2755
|
+
return true;
|
|
2756
|
+
};
|
|
2567
2757
|
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,
|
|
2758
|
+
if (stalled || sent >= MAX_PER_RUN) break;
|
|
2759
|
+
let offset = pending[cursors.key(ref)] ?? c[cursors.key(ref)] ?? 0;
|
|
2760
|
+
for (; ; ) {
|
|
2761
|
+
const room = BATCH - batch.length;
|
|
2762
|
+
const { events, offset: next } = outbox.read(ref, offset, room);
|
|
2573
2763
|
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;
|
|
2764
|
+
for (const e of events) {
|
|
2765
|
+
const f = filterEvent(e, sink.tier, sink.pii, { home: os2.homedir() });
|
|
2766
|
+
if (f) batch.push(f);
|
|
2589
2767
|
}
|
|
2590
2768
|
offset = next;
|
|
2591
|
-
|
|
2592
|
-
|
|
2769
|
+
pending[cursors.key(ref)] = offset;
|
|
2770
|
+
if (batch.length >= BATCH) {
|
|
2771
|
+
if (!await send()) {
|
|
2772
|
+
stalled = true;
|
|
2773
|
+
break;
|
|
2774
|
+
}
|
|
2775
|
+
if (sent >= MAX_PER_RUN) break;
|
|
2776
|
+
}
|
|
2593
2777
|
}
|
|
2594
|
-
|
|
2778
|
+
}
|
|
2779
|
+
if (!stalled && (batch.length || Object.keys(pending).length)) {
|
|
2780
|
+
if (!await send()) stalled = true;
|
|
2595
2781
|
}
|
|
2596
2782
|
delivered[sink.name] = sent;
|
|
2597
2783
|
}
|
|
@@ -2608,6 +2794,7 @@ function applySinkVerdict(name, res) {
|
|
|
2608
2794
|
} catch {
|
|
2609
2795
|
}
|
|
2610
2796
|
}
|
|
2797
|
+
var jobsDir = (state) => path11.join(state, "jobs");
|
|
2611
2798
|
export {
|
|
2612
2799
|
HttpSink,
|
|
2613
2800
|
buildSinks,
|