sessionpipe 0.6.1 → 0.9.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 +74 -5
- package/dist/cli.d.ts +11 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3331 -1371
- package/dist/cli.js.map +4 -4
- package/dist/connect.d.ts.map +1 -1
- package/dist/control/aliases.d.ts +15 -0
- package/dist/control/aliases.d.ts.map +1 -0
- package/dist/control/autoupdate.d.ts.map +1 -1
- package/dist/control/cli.d.ts +8 -1
- package/dist/control/cli.d.ts.map +1 -1
- package/dist/control/daemon.d.ts +65 -10
- package/dist/control/daemon.d.ts.map +1 -1
- package/dist/control/files.d.ts +33 -0
- package/dist/control/files.d.ts.map +1 -0
- package/dist/control/pair.d.ts +9 -0
- package/dist/control/pair.d.ts.map +1 -1
- package/dist/control/route.d.ts +5 -2
- package/dist/control/route.d.ts.map +1 -1
- package/dist/control/store.d.ts.map +1 -1
- package/dist/control/stream.d.ts.map +1 -1
- package/dist/help.d.ts +3 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/hook.d.ts +9 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +184 -158
- package/dist/hook.js.map +3 -3
- package/dist/http-sink.d.ts +3 -0
- package/dist/http-sink.d.ts.map +1 -1
- package/dist/index.js +943 -220
- package/dist/index.js.map +4 -4
- package/dist/launcher.d.ts.map +1 -1
- package/dist/run.d.ts +70 -8
- package/dist/run.d.ts.map +1 -1
- package/dist/runtime.d.ts +5 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/secrets.d.ts.map +1 -1
- package/dist/update.d.ts +3 -2
- package/dist/update.d.ts.map +1 -1
- package/dist/worker.d.ts +3 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +1021 -259
- package/dist/worker.js.map +4 -4
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe 0.
|
|
1
|
+
// sessionpipe 0.9.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
|
|
3
3
|
// src/http-sink.ts
|
|
4
4
|
import { createHash, createHmac } from "node:crypto";
|
|
@@ -6,6 +6,13 @@ var TIMEOUT_MS = 15e3;
|
|
|
6
6
|
function webhookId(events) {
|
|
7
7
|
return `msg_${createHash("sha256").update(events.map((e) => e.id).join(",")).digest("base64url").slice(0, 32)}`;
|
|
8
8
|
}
|
|
9
|
+
function retryAfterMs(header, now = Date.now()) {
|
|
10
|
+
if (header === null || header.trim() === "") return 6e4;
|
|
11
|
+
const secs = Number(header);
|
|
12
|
+
if (Number.isFinite(secs)) return Math.max(0, secs) * 1e3;
|
|
13
|
+
const at = Date.parse(header);
|
|
14
|
+
return Number.isFinite(at) ? Math.max(0, at - now) : 6e4;
|
|
15
|
+
}
|
|
9
16
|
var HttpSink = class {
|
|
10
17
|
constructor(cfg, tier) {
|
|
11
18
|
this.cfg = cfg;
|
|
@@ -77,7 +84,7 @@ var HttpSink = class {
|
|
|
77
84
|
status: 429,
|
|
78
85
|
error: err,
|
|
79
86
|
action: "retry",
|
|
80
|
-
retryAfterMs:
|
|
87
|
+
retryAfterMs: retryAfterMs(r.headers.get("retry-after"))
|
|
81
88
|
};
|
|
82
89
|
return { ok: false, acknowledged: 0, status: r.status, error: err, action: "retry" };
|
|
83
90
|
} catch (e) {
|
|
@@ -91,17 +98,17 @@ var HttpSink = class {
|
|
|
91
98
|
// src/run.ts
|
|
92
99
|
import {
|
|
93
100
|
appendFileSync as appendFileSync3,
|
|
94
|
-
existsSync as
|
|
95
|
-
mkdirSync as
|
|
101
|
+
existsSync as existsSync10,
|
|
102
|
+
mkdirSync as mkdirSync8,
|
|
96
103
|
readdirSync as readdirSync6,
|
|
97
|
-
readFileSync as
|
|
98
|
-
renameSync as
|
|
104
|
+
readFileSync as readFileSync9,
|
|
105
|
+
renameSync as renameSync3,
|
|
99
106
|
statSync as statSync5,
|
|
100
107
|
unlinkSync as unlinkSync4,
|
|
101
|
-
writeFileSync as
|
|
108
|
+
writeFileSync as writeFileSync7
|
|
102
109
|
} from "node:fs";
|
|
103
110
|
import os3 from "node:os";
|
|
104
|
-
import
|
|
111
|
+
import path14 from "node:path";
|
|
105
112
|
|
|
106
113
|
// ../core/src/adapters/antigravity.ts
|
|
107
114
|
import {
|
|
@@ -119,20 +126,20 @@ import path4 from "node:path";
|
|
|
119
126
|
// ../core/src/paths.ts
|
|
120
127
|
import os from "node:os";
|
|
121
128
|
import path from "node:path";
|
|
122
|
-
var
|
|
129
|
+
var homeDir = () => os.homedir();
|
|
123
130
|
function configFile(env = process.env) {
|
|
124
131
|
if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);
|
|
125
132
|
if (process.platform === "win32")
|
|
126
|
-
return path.join(env.APPDATA || path.join(
|
|
127
|
-
return path.join(env.XDG_CONFIG_HOME || path.join(
|
|
133
|
+
return path.join(env.APPDATA || path.join(homeDir(), "AppData", "Roaming"), "sessionpipe", "config.json");
|
|
134
|
+
return path.join(env.XDG_CONFIG_HOME || path.join(homeDir(), ".config"), "sessionpipe", "config.json");
|
|
128
135
|
}
|
|
129
136
|
function stateDir(env = process.env) {
|
|
130
137
|
if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);
|
|
131
138
|
if (process.platform === "win32")
|
|
132
|
-
return path.join(env.LOCALAPPDATA || path.join(
|
|
133
|
-
return path.join(env.XDG_STATE_HOME || path.join(
|
|
139
|
+
return path.join(env.LOCALAPPDATA || path.join(homeDir(), "AppData", "Local"), "sessionpipe");
|
|
140
|
+
return path.join(env.XDG_STATE_HOME || path.join(homeDir(), ".local", "state"), "sessionpipe");
|
|
134
141
|
}
|
|
135
|
-
function tilde(p, home =
|
|
142
|
+
function tilde(p, home = homeDir()) {
|
|
136
143
|
if (!p) return p;
|
|
137
144
|
if (p === home) return "~";
|
|
138
145
|
return p.startsWith(home + path.sep) || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
|
|
@@ -229,6 +236,19 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
229
236
|
return out;
|
|
230
237
|
}
|
|
231
238
|
|
|
239
|
+
// ../core/src/adapters/codex-rollout.ts
|
|
240
|
+
function codexSubagent(meta) {
|
|
241
|
+
if (meta?.type !== "session_meta") return false;
|
|
242
|
+
const p = meta.payload;
|
|
243
|
+
return !!p?.source?.subagent;
|
|
244
|
+
}
|
|
245
|
+
function codexReplayBoundary(meta) {
|
|
246
|
+
if (meta?.type !== "session_meta") return void 0;
|
|
247
|
+
const p = meta.payload;
|
|
248
|
+
if (!p || !p.forked_from_id && !p.source?.subagent) return void 0;
|
|
249
|
+
return (record) => typeof p.id === "string" && !!p.id && record?.type === "event_msg" && record.payload?.thread_id === p.id;
|
|
250
|
+
}
|
|
251
|
+
|
|
232
252
|
// ../core/src/readers/transcripts.ts
|
|
233
253
|
function pairTurns(msgs, endLine) {
|
|
234
254
|
const turns = [];
|
|
@@ -251,10 +271,14 @@ ${m.text}`;
|
|
|
251
271
|
}
|
|
252
272
|
if (cur?.assistant) turns.push(cur);
|
|
253
273
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
254
|
-
|
|
274
|
+
const out = {
|
|
275
|
+
turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
|
|
276
|
+
line
|
|
277
|
+
};
|
|
278
|
+
if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
|
|
279
|
+
return out;
|
|
255
280
|
}
|
|
256
|
-
var isInjected = (text) => /^<[a-z_-]
|
|
257
|
-
var endOf = (lines) => lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
281
|
+
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
258
282
|
var parse = (raw) => {
|
|
259
283
|
try {
|
|
260
284
|
return JSON.parse(raw);
|
|
@@ -262,6 +286,11 @@ var parse = (raw) => {
|
|
|
262
286
|
return null;
|
|
263
287
|
}
|
|
264
288
|
};
|
|
289
|
+
var endOf = (lines) => {
|
|
290
|
+
const last = lines[lines.length - 1];
|
|
291
|
+
if (last === void 0) return 0;
|
|
292
|
+
return last === "" || parse(last) === null ? lines.length - 1 : lines.length;
|
|
293
|
+
};
|
|
265
294
|
var textParts = (c, kinds) => {
|
|
266
295
|
if (typeof c === "string") return c;
|
|
267
296
|
if (!Array.isArray(c)) return "";
|
|
@@ -269,13 +298,21 @@ var textParts = (c, kinds) => {
|
|
|
269
298
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
270
299
|
).map((b) => b.text).join("\n");
|
|
271
300
|
};
|
|
301
|
+
var queuedPrompt = (a) => {
|
|
302
|
+
if (!a || typeof a !== "object") return "";
|
|
303
|
+
const q = a;
|
|
304
|
+
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
305
|
+
if (q.origin?.kind && q.origin.kind !== "human") return "";
|
|
306
|
+
return textParts(q.prompt, ["text"]).trim();
|
|
307
|
+
};
|
|
272
308
|
function readClaude(lines, from) {
|
|
273
309
|
const msgs = [];
|
|
274
310
|
for (let i = from; i < lines.length; i++) {
|
|
275
311
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
276
312
|
if (!j || j.isSidechain) continue;
|
|
277
313
|
const origin = j.origin;
|
|
278
|
-
if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human"))
|
|
314
|
+
if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
|
|
315
|
+
continue;
|
|
279
316
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
280
317
|
const c = j.message?.content;
|
|
281
318
|
if (j.type === "user") {
|
|
@@ -285,13 +322,22 @@ function readClaude(lines, from) {
|
|
|
285
322
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
286
323
|
const text = textParts(c, ["text"]).trim();
|
|
287
324
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
325
|
+
} else if (j.type === "attachment") {
|
|
326
|
+
const text = queuedPrompt(j.attachment);
|
|
327
|
+
if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
|
|
288
328
|
}
|
|
289
329
|
}
|
|
290
330
|
return pairTurns(msgs, endOf(lines));
|
|
291
331
|
}
|
|
332
|
+
function codexInjected(text) {
|
|
333
|
+
return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
|
|
334
|
+
}
|
|
292
335
|
function readCodex(lines, from) {
|
|
293
336
|
const msgs = [];
|
|
294
|
-
|
|
337
|
+
const boundary = codexReplayBoundary(parse(lines.find((line) => line.trim()) ?? ""));
|
|
338
|
+
const start = boundary ? lines.findIndex((line) => boundary(parse(line))) : 0;
|
|
339
|
+
if (start < 0) return pairTurns(msgs, endOf(lines));
|
|
340
|
+
for (let i = Math.max(from, start); i < lines.length; i++) {
|
|
295
341
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
296
342
|
if (!j || j.type !== "response_item") continue;
|
|
297
343
|
const p = j.payload;
|
|
@@ -300,7 +346,7 @@ function readCodex(lines, from) {
|
|
|
300
346
|
const text = textParts(p.content, ["input_text", "output_text"]).trim();
|
|
301
347
|
if (!text) continue;
|
|
302
348
|
if (p.role === "user") {
|
|
303
|
-
if (
|
|
349
|
+
if (codexInjected(text)) continue;
|
|
304
350
|
msgs.push({ role: "user", text, at, line: i });
|
|
305
351
|
} else if (p.role === "assistant") msgs.push({ role: "assistant", text, at, line: i });
|
|
306
352
|
}
|
|
@@ -329,7 +375,7 @@ function readAntigravity(lines, from) {
|
|
|
329
375
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
330
376
|
if (j.type === "USER_INPUT") {
|
|
331
377
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
332
|
-
const text = (m ? m[1]
|
|
378
|
+
const text = (m ? m[1] : j.content).trim();
|
|
333
379
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
334
380
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
335
381
|
const text = j.content.trim();
|
|
@@ -377,6 +423,10 @@ function formatJson(value, f) {
|
|
|
377
423
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
378
424
|
return f.finalNewline ? text + f.eol : text;
|
|
379
425
|
}
|
|
426
|
+
function readJson(file) {
|
|
427
|
+
const f = readJsonFile(file);
|
|
428
|
+
return f.ok ? f.value : {};
|
|
429
|
+
}
|
|
380
430
|
function entryFor(event, harness, cmd, o) {
|
|
381
431
|
const matcher = o.matcherFor(event);
|
|
382
432
|
return {
|
|
@@ -446,8 +496,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
446
496
|
unlinkSync(file);
|
|
447
497
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
448
498
|
}
|
|
449
|
-
|
|
450
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
499
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
451
500
|
return { file, changed: true };
|
|
452
501
|
}
|
|
453
502
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -474,7 +523,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
474
523
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
475
524
|
var AG_STALE_PRETOOL = '{"decision":"ask","reason":"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it."}\n';
|
|
476
525
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
477
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(
|
|
526
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
|
|
478
527
|
var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
|
|
479
528
|
var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
480
529
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
@@ -540,7 +589,8 @@ var antigravity = {
|
|
|
540
589
|
const event = input.argv[1] ?? "";
|
|
541
590
|
let s = {};
|
|
542
591
|
try {
|
|
543
|
-
|
|
592
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
593
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
544
594
|
} catch {
|
|
545
595
|
}
|
|
546
596
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -596,19 +646,16 @@ var antigravity = {
|
|
|
596
646
|
}
|
|
597
647
|
return readAntigravity(text.split("\n"), fromLine);
|
|
598
648
|
},
|
|
599
|
-
facts(session, transcript, hints
|
|
649
|
+
facts(session, transcript, hints) {
|
|
600
650
|
if (!transcript) return {};
|
|
601
651
|
return antigravityFacts(transcript, {
|
|
602
652
|
conversation: session.id,
|
|
603
653
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
604
|
-
state,
|
|
605
654
|
env: process.env
|
|
606
655
|
});
|
|
607
656
|
},
|
|
608
657
|
backfill(sinceMs, env = process.env) {
|
|
609
|
-
const
|
|
610
|
-
const noState = { get: () => ({}), save: () => {
|
|
611
|
-
} };
|
|
658
|
+
const rows2 = [];
|
|
612
659
|
for (const data of dataDirs(env)) {
|
|
613
660
|
let ids = [];
|
|
614
661
|
try {
|
|
@@ -622,13 +669,13 @@ var antigravity = {
|
|
|
622
669
|
} catch {
|
|
623
670
|
continue;
|
|
624
671
|
}
|
|
625
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
672
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
626
673
|
if (!facts.started_at || !facts.last_at) continue;
|
|
627
674
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
628
|
-
|
|
675
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
629
676
|
}
|
|
630
677
|
}
|
|
631
|
-
return
|
|
678
|
+
return rows2;
|
|
632
679
|
}
|
|
633
680
|
};
|
|
634
681
|
function prune(o) {
|
|
@@ -678,7 +725,7 @@ function agProjects(dir) {
|
|
|
678
725
|
}
|
|
679
726
|
const folders = [];
|
|
680
727
|
for (const r of j?.projectResources?.resources ?? []) {
|
|
681
|
-
const f = agPath(r?.folderUri);
|
|
728
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
682
729
|
if (f) folders.push(...agVariants(f));
|
|
683
730
|
}
|
|
684
731
|
if (folders.length) out.push({ id, folders });
|
|
@@ -732,7 +779,7 @@ function antigravityFacts(file, opts) {
|
|
|
732
779
|
for (const j of H) {
|
|
733
780
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
734
781
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
735
|
-
const text = (m ? m[1]
|
|
782
|
+
const text = (m ? m[1] : j.content).trim();
|
|
736
783
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
737
784
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
738
785
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -780,10 +827,9 @@ function gitFacts(cwd) {
|
|
|
780
827
|
return out;
|
|
781
828
|
}
|
|
782
829
|
function stripCredentials(url) {
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
return u.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
830
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
831
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
832
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
787
833
|
}
|
|
788
834
|
|
|
789
835
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -814,7 +860,7 @@ var OPTS = {
|
|
|
814
860
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
815
861
|
};
|
|
816
862
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
817
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
863
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
818
864
|
const out = [];
|
|
819
865
|
const add = (d) => {
|
|
820
866
|
if (!d) return;
|
|
@@ -855,11 +901,13 @@ var claudeCode = {
|
|
|
855
901
|
const event = input.argv[1] ?? "";
|
|
856
902
|
let s = {};
|
|
857
903
|
try {
|
|
858
|
-
|
|
904
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
905
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
859
906
|
} catch {
|
|
860
907
|
}
|
|
861
908
|
const id = s.session_id ?? s.sessionId;
|
|
862
909
|
if (!id) return null;
|
|
910
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
863
911
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
864
912
|
if (typeof s.model === "string") session.model = s.model;
|
|
865
913
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -978,6 +1026,7 @@ var claudeCode = {
|
|
|
978
1026
|
return null;
|
|
979
1027
|
}
|
|
980
1028
|
const hints = {};
|
|
1029
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
981
1030
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
982
1031
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
983
1032
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
@@ -1003,7 +1052,7 @@ var claudeCode = {
|
|
|
1003
1052
|
return out;
|
|
1004
1053
|
},
|
|
1005
1054
|
backfill(sinceMs, env = process.env) {
|
|
1006
|
-
const
|
|
1055
|
+
const rows2 = [];
|
|
1007
1056
|
const files = [];
|
|
1008
1057
|
for (const d of claudeDirs(env))
|
|
1009
1058
|
files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -1015,19 +1064,23 @@ var claudeCode = {
|
|
|
1015
1064
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1016
1065
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1017
1066
|
const cwd = rest.cwd;
|
|
1018
|
-
|
|
1067
|
+
rows2.push({
|
|
1019
1068
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
1020
1069
|
started_at,
|
|
1021
1070
|
last_at
|
|
1022
1071
|
});
|
|
1023
1072
|
}
|
|
1024
|
-
return
|
|
1073
|
+
return rows2;
|
|
1025
1074
|
}
|
|
1026
1075
|
};
|
|
1027
1076
|
function prune2(o) {
|
|
1028
1077
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1029
1078
|
return o;
|
|
1030
1079
|
}
|
|
1080
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1081
|
+
function notClaudeCode(id, s) {
|
|
1082
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
1083
|
+
}
|
|
1031
1084
|
function pickSource(v) {
|
|
1032
1085
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1033
1086
|
}
|
|
@@ -1133,23 +1186,27 @@ function parentOf() {
|
|
|
1133
1186
|
}
|
|
1134
1187
|
return (pid) => table.get(pid) || 0;
|
|
1135
1188
|
}
|
|
1136
|
-
var
|
|
1137
|
-
function
|
|
1138
|
-
const hit = accountCache.get(dir);
|
|
1139
|
-
if (hit !== void 0) return hit;
|
|
1189
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1190
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1140
1191
|
const files = [path5.join(dir, ".claude.json")];
|
|
1141
|
-
if (path5.resolve(dir) === path5.join(
|
|
1142
|
-
let acct = null;
|
|
1192
|
+
if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
|
|
1143
1193
|
for (const f of files) {
|
|
1144
1194
|
try {
|
|
1145
1195
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1146
|
-
if (a?.accountUuid) {
|
|
1147
|
-
|
|
1148
|
-
|
|
1196
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1197
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1198
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1149
1199
|
}
|
|
1150
1200
|
} catch {
|
|
1151
1201
|
}
|
|
1152
1202
|
}
|
|
1203
|
+
return null;
|
|
1204
|
+
}
|
|
1205
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1206
|
+
function claudeAccount(dir) {
|
|
1207
|
+
const hit = accountCache.get(dir);
|
|
1208
|
+
if (hit !== void 0) return hit;
|
|
1209
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1153
1210
|
accountCache.set(dir, acct);
|
|
1154
1211
|
return acct;
|
|
1155
1212
|
}
|
|
@@ -1369,18 +1426,13 @@ function parseArgv(raw) {
|
|
|
1369
1426
|
function readNotify(text) {
|
|
1370
1427
|
const { head, tail, at } = split(text);
|
|
1371
1428
|
const m = NOTIFY_LINE.exec(head);
|
|
1372
|
-
if (m)
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
index: m.index,
|
|
1377
|
-
length: m[0].length,
|
|
1378
|
-
misplaced: false,
|
|
1379
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1380
|
-
};
|
|
1429
|
+
if (m) {
|
|
1430
|
+
const raw = m[1];
|
|
1431
|
+
return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
|
|
1432
|
+
}
|
|
1381
1433
|
const k = NOTIFY_KEY.exec(head);
|
|
1382
1434
|
if (k) {
|
|
1383
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1435
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1384
1436
|
return {
|
|
1385
1437
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1386
1438
|
argv: null,
|
|
@@ -1391,15 +1443,11 @@ function readNotify(text) {
|
|
|
1391
1443
|
};
|
|
1392
1444
|
}
|
|
1393
1445
|
const t = NOTIFY_LINE.exec(tail);
|
|
1394
|
-
if (t
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
argv: parseArgv(t[
|
|
1398
|
-
|
|
1399
|
-
length: t[0].length,
|
|
1400
|
-
misplaced: true,
|
|
1401
|
-
other: false
|
|
1402
|
-
};
|
|
1446
|
+
if (t) {
|
|
1447
|
+
const raw = t[1];
|
|
1448
|
+
if (OURS2.test(raw))
|
|
1449
|
+
return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
|
|
1450
|
+
}
|
|
1403
1451
|
return null;
|
|
1404
1452
|
}
|
|
1405
1453
|
function dropLine(text, index, length) {
|
|
@@ -1418,9 +1466,7 @@ function withNotify(text, line) {
|
|
|
1418
1466
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1419
1467
|
return insertTopLevel(text, line);
|
|
1420
1468
|
}
|
|
1421
|
-
function replaceNotify(text, line) {
|
|
1422
|
-
const n = readNotify(text);
|
|
1423
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1469
|
+
function replaceNotify(text, n, line) {
|
|
1424
1470
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1425
1471
|
}
|
|
1426
1472
|
function withoutNotify(text) {
|
|
@@ -1461,7 +1507,7 @@ var CODEX_EVENTS = [
|
|
|
1461
1507
|
];
|
|
1462
1508
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1463
1509
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1464
|
-
var codexHome = (env) => env.CODEX_HOME || path6.join(
|
|
1510
|
+
var codexHome = (env) => env.CODEX_HOME || path6.join(homeDir(), ".codex");
|
|
1465
1511
|
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1466
1512
|
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
1467
1513
|
var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
|
|
@@ -1499,11 +1545,8 @@ function installNotify(cmd, env) {
|
|
|
1499
1545
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1500
1546
|
}
|
|
1501
1547
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
write(next);
|
|
1505
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1506
|
-
}
|
|
1548
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1549
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1507
1550
|
}
|
|
1508
1551
|
}
|
|
1509
1552
|
return {
|
|
@@ -1532,13 +1575,13 @@ function uninstallNotify(env) {
|
|
|
1532
1575
|
}
|
|
1533
1576
|
const prev = previousNotify(n.argv);
|
|
1534
1577
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1535
|
-
|
|
1536
|
-
if (next) writeFileSync3(file, next);
|
|
1578
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1537
1579
|
return { file, changed: true };
|
|
1538
1580
|
}
|
|
1539
1581
|
return { file, changed: false };
|
|
1540
1582
|
}
|
|
1541
|
-
function findRollout(threadId, env) {
|
|
1583
|
+
function findRollout(threadId, env = process.env) {
|
|
1584
|
+
if (!/^[A-Za-z0-9-]{8,128}$/.test(threadId)) return null;
|
|
1542
1585
|
let best = null;
|
|
1543
1586
|
const walk = (dir, depth) => {
|
|
1544
1587
|
let names = [];
|
|
@@ -1549,7 +1592,7 @@ function findRollout(threadId, env) {
|
|
|
1549
1592
|
}
|
|
1550
1593
|
for (const n of names.sort().reverse()) {
|
|
1551
1594
|
const p = path6.join(dir, n);
|
|
1552
|
-
if (n.endsWith(
|
|
1595
|
+
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1553
1596
|
best = p;
|
|
1554
1597
|
return;
|
|
1555
1598
|
}
|
|
@@ -1562,6 +1605,13 @@ function findRollout(threadId, env) {
|
|
|
1562
1605
|
walk(sessionsDir(env), 0);
|
|
1563
1606
|
return best;
|
|
1564
1607
|
}
|
|
1608
|
+
function rolloutMeta(file) {
|
|
1609
|
+
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1610
|
+
}
|
|
1611
|
+
function ofAnotherThread(file, id) {
|
|
1612
|
+
const m = /-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl(?:\.zst)?$/i.exec(file);
|
|
1613
|
+
return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
|
|
1614
|
+
}
|
|
1565
1615
|
var codex = {
|
|
1566
1616
|
name: NAME3,
|
|
1567
1617
|
events: CODEX_EVENTS,
|
|
@@ -1604,11 +1654,14 @@ var codex = {
|
|
|
1604
1654
|
if (event === "notify") {
|
|
1605
1655
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1606
1656
|
try {
|
|
1607
|
-
|
|
1657
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1658
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1608
1659
|
} catch {
|
|
1609
1660
|
}
|
|
1610
1661
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1611
1662
|
if (!id2) return null;
|
|
1663
|
+
const file = findRollout(String(id2), input.env);
|
|
1664
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1612
1665
|
const r2 = {
|
|
1613
1666
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1614
1667
|
events: [
|
|
@@ -1618,18 +1671,19 @@ var codex = {
|
|
|
1618
1671
|
harnessEvent: "notify"
|
|
1619
1672
|
}
|
|
1620
1673
|
],
|
|
1621
|
-
hints: {}
|
|
1674
|
+
hints: {},
|
|
1675
|
+
transcript: file
|
|
1622
1676
|
};
|
|
1623
|
-
const file = findRollout(String(id2), input.env);
|
|
1624
|
-
if (file) r2.transcript = file;
|
|
1625
1677
|
return r2;
|
|
1626
1678
|
}
|
|
1627
1679
|
try {
|
|
1628
|
-
|
|
1680
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1681
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1629
1682
|
} catch {
|
|
1630
1683
|
}
|
|
1631
1684
|
const id = s.session_id;
|
|
1632
1685
|
if (!id) return null;
|
|
1686
|
+
if (s.transcript_path === null) return null;
|
|
1633
1687
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1634
1688
|
if (typeof s.model === "string") session.model = s.model;
|
|
1635
1689
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1701,7 +1755,8 @@ var codex = {
|
|
|
1701
1755
|
return null;
|
|
1702
1756
|
}
|
|
1703
1757
|
const r = { session, events, hints: {} };
|
|
1704
|
-
if (typeof s.transcript_path === "string"
|
|
1758
|
+
if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
|
|
1759
|
+
r.transcript = s.transcript_path;
|
|
1705
1760
|
return r;
|
|
1706
1761
|
},
|
|
1707
1762
|
readTranscript(file, fromLine) {
|
|
@@ -1719,16 +1774,17 @@ var codex = {
|
|
|
1719
1774
|
return out;
|
|
1720
1775
|
},
|
|
1721
1776
|
backfill(sinceMs, env = process.env) {
|
|
1722
|
-
const
|
|
1777
|
+
const rows2 = [];
|
|
1723
1778
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1724
1779
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
|
|
1725
1780
|
if (!m?.[1]) continue;
|
|
1781
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1726
1782
|
const facts = codexFacts(f);
|
|
1727
1783
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1728
1784
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1729
|
-
|
|
1785
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1730
1786
|
}
|
|
1731
|
-
return
|
|
1787
|
+
return rows2;
|
|
1732
1788
|
}
|
|
1733
1789
|
};
|
|
1734
1790
|
function prune3(o) {
|
|
@@ -1737,10 +1793,11 @@ function prune3(o) {
|
|
|
1737
1793
|
}
|
|
1738
1794
|
function codexFacts(file) {
|
|
1739
1795
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1740
|
-
|
|
1741
|
-
|
|
1796
|
+
let H = parseLines(head);
|
|
1797
|
+
let T = parseLines(tail);
|
|
1742
1798
|
const out = {};
|
|
1743
1799
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1800
|
+
const boundary = codexReplayBoundary(meta);
|
|
1744
1801
|
if (meta?.payload) {
|
|
1745
1802
|
const p = meta.payload;
|
|
1746
1803
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1748,19 +1805,26 @@ function codexFacts(file) {
|
|
|
1748
1805
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1749
1806
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1750
1807
|
}
|
|
1808
|
+
if (boundary) {
|
|
1809
|
+
const h = H.findIndex(boundary);
|
|
1810
|
+
const t = T.findIndex(boundary);
|
|
1811
|
+
H = h < 0 ? [] : H.slice(h);
|
|
1812
|
+
const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
|
|
1813
|
+
T = t >= 0 ? T.slice(t) : found ? T : [];
|
|
1814
|
+
}
|
|
1751
1815
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1752
1816
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1753
1817
|
for (const j of H) {
|
|
1754
1818
|
const p = j.payload;
|
|
1755
1819
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1756
1820
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1757
|
-
if (!text ||
|
|
1758
|
-
const line = text.split("\n")
|
|
1821
|
+
if (!text || codexInjected(text)) continue;
|
|
1822
|
+
const line = text.split("\n")[0];
|
|
1759
1823
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1760
1824
|
out.title_source = "first-ask";
|
|
1761
1825
|
break;
|
|
1762
1826
|
}
|
|
1763
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1827
|
+
const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1764
1828
|
if (times.length) {
|
|
1765
1829
|
out.started_at = Math.min(...times);
|
|
1766
1830
|
out.last_at = Math.max(...times);
|
|
@@ -1768,11 +1832,432 @@ function codexFacts(file) {
|
|
|
1768
1832
|
return out;
|
|
1769
1833
|
}
|
|
1770
1834
|
|
|
1771
|
-
// ../core/src/adapters/
|
|
1772
|
-
import {
|
|
1773
|
-
import { existsSync as
|
|
1835
|
+
// ../core/src/adapters/devin.ts
|
|
1836
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1837
|
+
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1774
1838
|
import path7 from "node:path";
|
|
1775
|
-
|
|
1839
|
+
|
|
1840
|
+
// ../core/src/readers/sqlite.ts
|
|
1841
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1842
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1843
|
+
import { createRequire } from "node:module";
|
|
1844
|
+
function quiet(fn) {
|
|
1845
|
+
const original = process.emitWarning;
|
|
1846
|
+
try {
|
|
1847
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
1848
|
+
const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
|
|
1849
|
+
if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
|
|
1850
|
+
original.call(process, warning, ...rest);
|
|
1851
|
+
});
|
|
1852
|
+
return fn();
|
|
1853
|
+
} finally {
|
|
1854
|
+
process.emitWarning = original;
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
var ctorCache;
|
|
1858
|
+
function dbCtor() {
|
|
1859
|
+
if (ctorCache !== void 0) return ctorCache;
|
|
1860
|
+
ctorCache = null;
|
|
1861
|
+
try {
|
|
1862
|
+
const req = createRequire(import.meta.url);
|
|
1863
|
+
const m = quiet(() => req("node:sqlite"));
|
|
1864
|
+
if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
|
|
1865
|
+
} catch {
|
|
1866
|
+
}
|
|
1867
|
+
return ctorCache;
|
|
1868
|
+
}
|
|
1869
|
+
function plain(row) {
|
|
1870
|
+
if (!row || typeof row !== "object") return null;
|
|
1871
|
+
const out = {};
|
|
1872
|
+
for (const [k, v] of Object.entries(row)) {
|
|
1873
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
1874
|
+
else if (v instanceof Uint8Array) out[k] = null;
|
|
1875
|
+
else out[k] = v;
|
|
1876
|
+
}
|
|
1877
|
+
return out;
|
|
1878
|
+
}
|
|
1879
|
+
var rows = (v) => {
|
|
1880
|
+
if (!Array.isArray(v)) return null;
|
|
1881
|
+
const out = [];
|
|
1882
|
+
for (const r of v) {
|
|
1883
|
+
const p = plain(r);
|
|
1884
|
+
if (p) out.push(p);
|
|
1885
|
+
}
|
|
1886
|
+
return out;
|
|
1887
|
+
};
|
|
1888
|
+
function viaNode(file, sql, params, o) {
|
|
1889
|
+
const C = dbCtor();
|
|
1890
|
+
if (!C) return null;
|
|
1891
|
+
let db;
|
|
1892
|
+
try {
|
|
1893
|
+
db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
|
|
1894
|
+
} catch {
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1897
|
+
try {
|
|
1898
|
+
try {
|
|
1899
|
+
db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
|
|
1900
|
+
} catch {
|
|
1901
|
+
}
|
|
1902
|
+
return quiet(() => rows(db.prepare(sql).all(...params)));
|
|
1903
|
+
} catch {
|
|
1904
|
+
return null;
|
|
1905
|
+
} finally {
|
|
1906
|
+
try {
|
|
1907
|
+
db.close();
|
|
1908
|
+
} catch {
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
var CHILD = `const { DatabaseSync } = require("node:sqlite");
|
|
1913
|
+
const [, file, sql, params, ms] = process.argv;
|
|
1914
|
+
const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
|
|
1915
|
+
try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
|
|
1916
|
+
const out = db.prepare(sql).all(...JSON.parse(params));
|
|
1917
|
+
db.close();
|
|
1918
|
+
process.stdout.write(JSON.stringify(out));`;
|
|
1919
|
+
function viaChild(file, sql, params, o) {
|
|
1920
|
+
try {
|
|
1921
|
+
const out = execFileSync3(
|
|
1922
|
+
process.execPath,
|
|
1923
|
+
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
1924
|
+
{
|
|
1925
|
+
encoding: "utf8",
|
|
1926
|
+
timeout: o.timeoutMs + 3e3,
|
|
1927
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1928
|
+
maxBuffer: o.maxBuffer,
|
|
1929
|
+
windowsHide: true
|
|
1930
|
+
}
|
|
1931
|
+
);
|
|
1932
|
+
return rows(JSON.parse(out || "null"));
|
|
1933
|
+
} catch {
|
|
1934
|
+
return null;
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
|
|
1938
|
+
function inlineParams(sql, params) {
|
|
1939
|
+
if (sql.includes("'")) return null;
|
|
1940
|
+
const holes = sql.split("?").length - 1;
|
|
1941
|
+
if (holes !== params.length) return null;
|
|
1942
|
+
let i = 0;
|
|
1943
|
+
let bad = false;
|
|
1944
|
+
const out = sql.replace(/\?/g, () => {
|
|
1945
|
+
const p = params[i++];
|
|
1946
|
+
if (typeof p === "number") {
|
|
1947
|
+
if (!Number.isFinite(p)) {
|
|
1948
|
+
bad = true;
|
|
1949
|
+
return "NULL";
|
|
1950
|
+
}
|
|
1951
|
+
return String(p);
|
|
1952
|
+
}
|
|
1953
|
+
if (typeof p !== "string" || !SAFE.test(p)) {
|
|
1954
|
+
bad = true;
|
|
1955
|
+
return "NULL";
|
|
1956
|
+
}
|
|
1957
|
+
return `'${p}'`;
|
|
1958
|
+
});
|
|
1959
|
+
return bad ? null : out;
|
|
1960
|
+
}
|
|
1961
|
+
function viaCli(file, sql, params, o) {
|
|
1962
|
+
const text = inlineParams(sql, params);
|
|
1963
|
+
if (!text) return null;
|
|
1964
|
+
try {
|
|
1965
|
+
const out = execFileSync3("sqlite3", ["-readonly", "-json", file, text], {
|
|
1966
|
+
encoding: "utf8",
|
|
1967
|
+
timeout: o.timeoutMs + 3e3,
|
|
1968
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1969
|
+
maxBuffer: o.maxBuffer,
|
|
1970
|
+
windowsHide: true
|
|
1971
|
+
}).trim();
|
|
1972
|
+
if (!out) return [];
|
|
1973
|
+
return rows(JSON.parse(out));
|
|
1974
|
+
} catch {
|
|
1975
|
+
return null;
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
function querySqlite(file, sql, params = [], opts = {}) {
|
|
1979
|
+
const o = {
|
|
1980
|
+
timeoutMs: opts.timeoutMs ?? 2e3,
|
|
1981
|
+
engine: opts.engine ?? "auto",
|
|
1982
|
+
maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
|
|
1983
|
+
};
|
|
1984
|
+
if (!file) return null;
|
|
1985
|
+
try {
|
|
1986
|
+
if (!existsSync5(file)) return null;
|
|
1987
|
+
} catch {
|
|
1988
|
+
return null;
|
|
1989
|
+
}
|
|
1990
|
+
const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
|
|
1991
|
+
for (const way of ways) {
|
|
1992
|
+
const r = way(file, sql, params, o);
|
|
1993
|
+
if (r) return r;
|
|
1994
|
+
}
|
|
1995
|
+
return null;
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// ../core/src/adapters/devin.ts
|
|
1999
|
+
var NAME4 = "devin";
|
|
2000
|
+
var DEVIN_EVENTS = [
|
|
2001
|
+
"SessionStart",
|
|
2002
|
+
"UserPromptSubmit",
|
|
2003
|
+
"PreToolUse",
|
|
2004
|
+
"PostToolUse",
|
|
2005
|
+
"PermissionRequest",
|
|
2006
|
+
"Stop",
|
|
2007
|
+
"PostCompaction",
|
|
2008
|
+
"SessionEnd"
|
|
2009
|
+
];
|
|
2010
|
+
var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
2011
|
+
var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
|
|
2012
|
+
var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
|
|
2013
|
+
var CLAUDE_NOTE = "Devin also reads ~/.claude/settings.json hooks (read_config_from.claude); a Claude-only event name there makes it ignore that file whole";
|
|
2014
|
+
var deadFileNote = (bad) => `Devin loads NO hooks from this file: "${bad}" is not one of its event names and it rejects the file whole \u2014 remove that key (\`/hooks\` in \`devin\` lists what loaded)`;
|
|
2015
|
+
function foreignEvent(file) {
|
|
2016
|
+
const hooks = readJson(file).hooks;
|
|
2017
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
|
|
2018
|
+
const ok = DEVIN_EVENTS;
|
|
2019
|
+
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
2020
|
+
}
|
|
2021
|
+
var homeOf = (env) => env.HOME || homeDir();
|
|
2022
|
+
var appData = (env) => env.APPDATA || path7.join(homeOf(env), "AppData", "Roaming");
|
|
2023
|
+
var devinConfigDir = (env = process.env) => process.platform === "win32" ? path7.join(appData(env), "devin") : path7.join(env.XDG_CONFIG_HOME || path7.join(homeOf(env), ".config"), "devin");
|
|
2024
|
+
var configFile3 = (env) => path7.join(devinConfigDir(env), "config.json");
|
|
2025
|
+
var devinDataDir = (env = process.env) => process.platform === "win32" ? path7.join(appData(env), "devin", "cli") : path7.join(env.XDG_DATA_HOME || path7.join(homeOf(env), ".local", "share"), "devin", "cli");
|
|
2026
|
+
var sessionsDb = (env) => path7.join(devinDataDir(env), "sessions.db");
|
|
2027
|
+
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
2028
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2029
|
+
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
2030
|
+
var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
2031
|
+
function errText2(v) {
|
|
2032
|
+
if (typeof v === "string" && v) return v.slice(0, 2e3);
|
|
2033
|
+
if (v && typeof v === "object") {
|
|
2034
|
+
const e = v;
|
|
2035
|
+
if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
|
|
2036
|
+
if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
|
|
2037
|
+
}
|
|
2038
|
+
return void 0;
|
|
2039
|
+
}
|
|
2040
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
|
|
2041
|
+
var devin = {
|
|
2042
|
+
name: NAME4,
|
|
2043
|
+
events: DEVIN_EVENTS,
|
|
2044
|
+
detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
|
|
2045
|
+
configFiles: (env = process.env) => [configFile3(env)],
|
|
2046
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
2047
|
+
const file = configFile3(env);
|
|
2048
|
+
const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
2049
|
+
if (r.skipped) return [r];
|
|
2050
|
+
const bad = foreignEvent(file);
|
|
2051
|
+
return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
|
|
2052
|
+
},
|
|
2053
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
2054
|
+
uninstallClaudeShaped(configFile3(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile3(env)) })
|
|
2055
|
+
],
|
|
2056
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
2057
|
+
const file = configFile3(env);
|
|
2058
|
+
const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
2059
|
+
const bad = state === "missing" ? void 0 : foreignEvent(file);
|
|
2060
|
+
return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
|
|
2061
|
+
},
|
|
2062
|
+
fromHook(input) {
|
|
2063
|
+
const event = input.argv[1] ?? "";
|
|
2064
|
+
let s = {};
|
|
2065
|
+
try {
|
|
2066
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2067
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
2068
|
+
} catch {
|
|
2069
|
+
}
|
|
2070
|
+
const id = typeof s.session_id === "string" ? s.session_id : "";
|
|
2071
|
+
if (!id) return null;
|
|
2072
|
+
const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
|
|
2073
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
2074
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
2075
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
2076
|
+
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
2077
|
+
const permissionId = () => `perm-${createHash3("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
2078
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
2079
|
+
const events = [];
|
|
2080
|
+
switch (event) {
|
|
2081
|
+
case "SessionStart":
|
|
2082
|
+
events.push(ev("session.started", { source: pickSource2(s.source) }));
|
|
2083
|
+
break;
|
|
2084
|
+
case "UserPromptSubmit":
|
|
2085
|
+
events.push(
|
|
2086
|
+
ev("turn.started", {
|
|
2087
|
+
turn_id,
|
|
2088
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
2089
|
+
})
|
|
2090
|
+
);
|
|
2091
|
+
break;
|
|
2092
|
+
case "PreToolUse":
|
|
2093
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
2094
|
+
break;
|
|
2095
|
+
case "PostToolUse":
|
|
2096
|
+
if (tool)
|
|
2097
|
+
events.push(
|
|
2098
|
+
ev("tool.ended", {
|
|
2099
|
+
tool,
|
|
2100
|
+
call_id,
|
|
2101
|
+
turn_id,
|
|
2102
|
+
// `ok` is required on tool.ended (PROTOCOL.md §events), so a response
|
|
2103
|
+
// that carries no success flag at all reads as "nothing said it failed".
|
|
2104
|
+
ok: response?.success !== false,
|
|
2105
|
+
error: errText2(response?.error),
|
|
2106
|
+
input: s.tool_input,
|
|
2107
|
+
output: response ? response.output : s.tool_response
|
|
2108
|
+
})
|
|
2109
|
+
);
|
|
2110
|
+
break;
|
|
2111
|
+
case "PermissionRequest":
|
|
2112
|
+
events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
|
|
2113
|
+
break;
|
|
2114
|
+
case "Stop":
|
|
2115
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
2116
|
+
break;
|
|
2117
|
+
case "PostCompaction":
|
|
2118
|
+
events.push(ev("context.compacted", { trigger: "auto" }));
|
|
2119
|
+
break;
|
|
2120
|
+
case "SessionEnd":
|
|
2121
|
+
events.push(ev("session.ended", { reason: endReason3(s.reason) }));
|
|
2122
|
+
break;
|
|
2123
|
+
default:
|
|
2124
|
+
return null;
|
|
2125
|
+
}
|
|
2126
|
+
return {
|
|
2127
|
+
session,
|
|
2128
|
+
events,
|
|
2129
|
+
hints: {},
|
|
2130
|
+
transcript: transcriptRef(sessionsDb(input.env), id)
|
|
2131
|
+
};
|
|
2132
|
+
},
|
|
2133
|
+
readTranscript(file, fromLine) {
|
|
2134
|
+
const ref = splitRef(file);
|
|
2135
|
+
if (!ref) return { turns: [], line: fromLine };
|
|
2136
|
+
return readDevinChain(ref.db, ref.session, fromLine);
|
|
2137
|
+
},
|
|
2138
|
+
facts(session, transcript, _hints, _state, env = process.env) {
|
|
2139
|
+
const out = {};
|
|
2140
|
+
const version = devinVersion(env);
|
|
2141
|
+
if (version) out.harness_version = version;
|
|
2142
|
+
const account = devinAccount(env);
|
|
2143
|
+
if (account) out.account_id = account;
|
|
2144
|
+
const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
|
|
2145
|
+
if (!SESSION_ID.test(ref.session)) return out;
|
|
2146
|
+
const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
|
|
2147
|
+
const row = rows2?.[0];
|
|
2148
|
+
if (!row) return out;
|
|
2149
|
+
Object.assign(out, sessionFacts(row));
|
|
2150
|
+
return out;
|
|
2151
|
+
},
|
|
2152
|
+
backfill(sinceMs, env = process.env) {
|
|
2153
|
+
const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
|
|
2154
|
+
if (!rows2) return [];
|
|
2155
|
+
const out = [];
|
|
2156
|
+
for (const row of rows2) {
|
|
2157
|
+
const id = String(row.id);
|
|
2158
|
+
if (!SESSION_ID.test(id)) continue;
|
|
2159
|
+
const { started_at, last_at, ...rest } = sessionFacts(row);
|
|
2160
|
+
if (!started_at || !last_at) continue;
|
|
2161
|
+
out.push({ session: { id, ...rest }, started_at, last_at });
|
|
2162
|
+
}
|
|
2163
|
+
return out;
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2166
|
+
function prune4(o) {
|
|
2167
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2168
|
+
return o;
|
|
2169
|
+
}
|
|
2170
|
+
var transcriptRef = (db, session) => `${db}#${session}`;
|
|
2171
|
+
function splitRef(ref) {
|
|
2172
|
+
if (!ref) return null;
|
|
2173
|
+
const at = ref.lastIndexOf("#");
|
|
2174
|
+
if (at <= 0) return null;
|
|
2175
|
+
const db = ref.slice(0, at);
|
|
2176
|
+
const session = ref.slice(at + 1);
|
|
2177
|
+
return db && SESSION_ID.test(session) ? { db, session } : null;
|
|
2178
|
+
}
|
|
2179
|
+
var CHAIN_LIMIT = 4e3;
|
|
2180
|
+
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2181
|
+
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2182
|
+
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2183
|
+
UNION ALL
|
|
2184
|
+
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2185
|
+
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2186
|
+
)
|
|
2187
|
+
SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
|
|
2188
|
+
var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
|
|
2189
|
+
(SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
|
|
2190
|
+
ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
|
|
2191
|
+
var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
|
|
2192
|
+
var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
2193
|
+
WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
|
|
2194
|
+
ORDER BY s.last_activity_at DESC LIMIT 500`;
|
|
2195
|
+
function sessionFacts(row) {
|
|
2196
|
+
const out = {};
|
|
2197
|
+
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2198
|
+
if (typeof row.working_directory === "string" && path7.isAbsolute(row.working_directory))
|
|
2199
|
+
out.cwd = row.working_directory;
|
|
2200
|
+
const started = Number(row.created_at);
|
|
2201
|
+
const last = Number(row.last_activity_at);
|
|
2202
|
+
if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
|
|
2203
|
+
if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
|
|
2204
|
+
if (typeof row.first_ask === "string") {
|
|
2205
|
+
const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
|
|
2206
|
+
if (line.trim()) {
|
|
2207
|
+
out.title = cut(line.trim(), 80);
|
|
2208
|
+
out.title_source = "first-ask";
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
return out;
|
|
2212
|
+
}
|
|
2213
|
+
function readDevinChain(db, session, fromLine) {
|
|
2214
|
+
if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
|
|
2215
|
+
const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
|
|
2216
|
+
const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
|
|
2217
|
+
if (!rows2?.length) return { turns: [], line: fromLine };
|
|
2218
|
+
const msgs = [];
|
|
2219
|
+
let end = fromLine;
|
|
2220
|
+
for (const row of rows2) {
|
|
2221
|
+
const node = Number(row.node_id);
|
|
2222
|
+
if (!Number.isFinite(node)) continue;
|
|
2223
|
+
if (node + 1 > end) end = node + 1;
|
|
2224
|
+
if (node < fromLine) continue;
|
|
2225
|
+
let j;
|
|
2226
|
+
try {
|
|
2227
|
+
j = JSON.parse(String(row.chat_message ?? ""));
|
|
2228
|
+
} catch {
|
|
2229
|
+
continue;
|
|
2230
|
+
}
|
|
2231
|
+
const text = typeof j.content === "string" ? j.content.trim() : "";
|
|
2232
|
+
if (!text) continue;
|
|
2233
|
+
const secs = Number(row.created_at);
|
|
2234
|
+
const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
|
|
2235
|
+
if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
|
|
2236
|
+
else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
|
|
2237
|
+
}
|
|
2238
|
+
msgs.sort((a, b) => a.line - b.line);
|
|
2239
|
+
return pairTurns(msgs, end);
|
|
2240
|
+
}
|
|
2241
|
+
function devinVersion(env = process.env) {
|
|
2242
|
+
try {
|
|
2243
|
+
const target = readlinkSync(path7.join(devinDataDir(env), "_versions", "current"));
|
|
2244
|
+
const name = path7.basename(target);
|
|
2245
|
+
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2246
|
+
} catch {
|
|
2247
|
+
return void 0;
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
function devinAccount(env = process.env) {
|
|
2251
|
+
const d = readJson(configFile3(env)).devin;
|
|
2252
|
+
const id = d && typeof d === "object" ? d.org_id : void 0;
|
|
2253
|
+
return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
// ../core/src/adapters/gemini-cli.ts
|
|
2257
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2258
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2259
|
+
import path8 from "node:path";
|
|
2260
|
+
var NAME5 = "gemini-cli";
|
|
1776
2261
|
var GEMINI_EVENTS = [
|
|
1777
2262
|
"SessionStart",
|
|
1778
2263
|
"BeforeAgent",
|
|
@@ -1783,11 +2268,11 @@ var GEMINI_EVENTS = [
|
|
|
1783
2268
|
"PreCompress",
|
|
1784
2269
|
"SessionEnd"
|
|
1785
2270
|
];
|
|
1786
|
-
var
|
|
1787
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1788
|
-
var settings = (env) =>
|
|
1789
|
-
var
|
|
1790
|
-
var
|
|
2271
|
+
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2272
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path8.join(homeDir(), ".gemini");
|
|
2273
|
+
var settings = (env) => path8.join(geminiDir(env), "settings.json");
|
|
2274
|
+
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2275
|
+
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1791
2276
|
function geminiHooksActive(settingsFile) {
|
|
1792
2277
|
let tools;
|
|
1793
2278
|
try {
|
|
@@ -1799,7 +2284,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1799
2284
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1800
2285
|
let version = "";
|
|
1801
2286
|
try {
|
|
1802
|
-
version =
|
|
2287
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1803
2288
|
encoding: "utf8",
|
|
1804
2289
|
timeout: 4e3,
|
|
1805
2290
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1818,13 +2303,13 @@ function geminiHooksActive(settingsFile) {
|
|
|
1818
2303
|
};
|
|
1819
2304
|
}
|
|
1820
2305
|
var geminiCli = {
|
|
1821
|
-
name:
|
|
2306
|
+
name: NAME5,
|
|
1822
2307
|
events: GEMINI_EVENTS,
|
|
1823
2308
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1824
|
-
detect: (env = process.env) =>
|
|
2309
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path8.join(geminiDir(env), "tmp")),
|
|
1825
2310
|
configFiles: (env = process.env) => [settings(env)],
|
|
1826
2311
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1827
|
-
const r = installClaudeShaped(settings(env),
|
|
2312
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1828
2313
|
const a = geminiHooksActive(settings(env));
|
|
1829
2314
|
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1830
2315
|
},
|
|
@@ -1832,7 +2317,7 @@ var geminiCli = {
|
|
|
1832
2317
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1833
2318
|
],
|
|
1834
2319
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1835
|
-
const state = installedClaudeShaped(settings(env),
|
|
2320
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1836
2321
|
const a = geminiHooksActive(settings(env));
|
|
1837
2322
|
return [
|
|
1838
2323
|
{
|
|
@@ -1846,14 +2331,15 @@ var geminiCli = {
|
|
|
1846
2331
|
const event = input.argv[1] ?? "";
|
|
1847
2332
|
let s = {};
|
|
1848
2333
|
try {
|
|
1849
|
-
|
|
2334
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2335
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1850
2336
|
} catch {
|
|
1851
2337
|
}
|
|
1852
2338
|
const id = s.session_id;
|
|
1853
2339
|
if (!id) return null;
|
|
1854
2340
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1855
|
-
const tool =
|
|
1856
|
-
const ev = (type, data) => ({ type, data:
|
|
2341
|
+
const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
|
|
2342
|
+
const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
|
|
1857
2343
|
const events = [];
|
|
1858
2344
|
switch (event) {
|
|
1859
2345
|
case "SessionStart":
|
|
@@ -1934,7 +2420,7 @@ var geminiCli = {
|
|
|
1934
2420
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1935
2421
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1936
2422
|
if (text) {
|
|
1937
|
-
const line = text.split("\n")
|
|
2423
|
+
const line = text.split("\n")[0];
|
|
1938
2424
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1939
2425
|
out.title_source = "first-ask";
|
|
1940
2426
|
}
|
|
@@ -1948,18 +2434,18 @@ var geminiCli = {
|
|
|
1948
2434
|
backfill: () => []
|
|
1949
2435
|
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
1950
2436
|
};
|
|
1951
|
-
function
|
|
2437
|
+
function prune5(o) {
|
|
1952
2438
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1953
2439
|
return o;
|
|
1954
2440
|
}
|
|
1955
2441
|
|
|
1956
2442
|
// ../core/src/adapters/index.ts
|
|
1957
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2443
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1958
2444
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1959
2445
|
|
|
1960
2446
|
// ../core/src/config.ts
|
|
1961
|
-
import { chmodSync, existsSync as
|
|
1962
|
-
import
|
|
2447
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2448
|
+
import path9 from "node:path";
|
|
1963
2449
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
1964
2450
|
function readConfig(file = configFile()) {
|
|
1965
2451
|
try {
|
|
@@ -1975,10 +2461,10 @@ function readConfig(file = configFile()) {
|
|
|
1975
2461
|
}
|
|
1976
2462
|
}
|
|
1977
2463
|
function writeConfig(c, file = configFile()) {
|
|
1978
|
-
mkdirSync4(
|
|
2464
|
+
mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
|
|
1979
2465
|
writeFileSync4(file, `${JSON.stringify(c, null, 2)}
|
|
1980
2466
|
`, { mode: 384 });
|
|
1981
|
-
if (process.platform !== "win32" &&
|
|
2467
|
+
if (process.platform !== "win32" && existsSync8(file)) {
|
|
1982
2468
|
try {
|
|
1983
2469
|
chmodSync(file, 384);
|
|
1984
2470
|
} catch {
|
|
@@ -2007,7 +2493,7 @@ function encodeTime(t) {
|
|
|
2007
2493
|
function ulid(now = Date.now()) {
|
|
2008
2494
|
if (now === lastTime) {
|
|
2009
2495
|
for (let i = lastRand.length - 1; i >= 0; i--) {
|
|
2010
|
-
const v =
|
|
2496
|
+
const v = lastRand[i] + 1;
|
|
2011
2497
|
if (v < 32) {
|
|
2012
2498
|
lastRand[i] = v;
|
|
2013
2499
|
break;
|
|
@@ -2016,8 +2502,7 @@ function ulid(now = Date.now()) {
|
|
|
2016
2502
|
}
|
|
2017
2503
|
} else {
|
|
2018
2504
|
lastTime = now;
|
|
2019
|
-
|
|
2020
|
-
lastRand = Array.from({ length: 16 }, (_, i) => (b[i] ?? 0) % 32);
|
|
2505
|
+
lastRand = Array.from(randomBytes(16), (b) => b % 32);
|
|
2021
2506
|
}
|
|
2022
2507
|
return encodeTime(now) + lastRand.map((v) => ALPHABET[v]).join("");
|
|
2023
2508
|
}
|
|
@@ -2044,7 +2529,7 @@ function makeEvent(a, ctx) {
|
|
|
2044
2529
|
import {
|
|
2045
2530
|
appendFileSync,
|
|
2046
2531
|
closeSync as closeSync2,
|
|
2047
|
-
existsSync as
|
|
2532
|
+
existsSync as existsSync9,
|
|
2048
2533
|
mkdirSync as mkdirSync5,
|
|
2049
2534
|
openSync as openSync2,
|
|
2050
2535
|
readdirSync as readdirSync5,
|
|
@@ -2055,42 +2540,45 @@ import {
|
|
|
2055
2540
|
unlinkSync as unlinkSync3,
|
|
2056
2541
|
writeFileSync as writeFileSync5
|
|
2057
2542
|
} from "node:fs";
|
|
2058
|
-
import
|
|
2543
|
+
import path10 from "node:path";
|
|
2544
|
+
var READ_CHUNK = 1024 * 1024;
|
|
2545
|
+
var READ_MAX = 8 * 1024 * 1024;
|
|
2059
2546
|
var Outbox = class {
|
|
2060
2547
|
constructor(root) {
|
|
2061
2548
|
this.root = root;
|
|
2062
2549
|
}
|
|
2063
2550
|
root;
|
|
2064
2551
|
dir(harness) {
|
|
2065
|
-
return
|
|
2552
|
+
return path10.join(this.root, "outbox", safeId(harness));
|
|
2066
2553
|
}
|
|
2067
2554
|
file(ref) {
|
|
2068
|
-
return
|
|
2555
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2069
2556
|
}
|
|
2070
2557
|
seqFile(ref) {
|
|
2071
|
-
return
|
|
2558
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2072
2559
|
}
|
|
2073
2560
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2074
2561
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
2075
2562
|
nextSeq(ref) {
|
|
2076
2563
|
const f = this.seqFile(ref);
|
|
2077
|
-
let n =
|
|
2564
|
+
let n = -1;
|
|
2078
2565
|
try {
|
|
2079
|
-
|
|
2566
|
+
const t = readFileSync7(f, "utf8").trim();
|
|
2567
|
+
if (/^\d+$/.test(t)) n = Number(t);
|
|
2080
2568
|
} catch {
|
|
2081
|
-
n = this.lastSeqInFile(ref) + 1;
|
|
2082
2569
|
}
|
|
2083
|
-
|
|
2570
|
+
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2571
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
2084
2572
|
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
2085
2573
|
return n;
|
|
2086
2574
|
}
|
|
2087
2575
|
lastSeqInFile(ref) {
|
|
2088
2576
|
const f = this.file(ref);
|
|
2089
|
-
if (!
|
|
2577
|
+
if (!existsSync9(f)) return -1;
|
|
2090
2578
|
const lines = readFileSync7(f, "utf8").trim().split("\n");
|
|
2091
2579
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2092
2580
|
try {
|
|
2093
|
-
const j = JSON.parse(lines[i]
|
|
2581
|
+
const j = JSON.parse(lines[i]);
|
|
2094
2582
|
if (typeof j.session?.seq === "number") return j.session.seq;
|
|
2095
2583
|
} catch {
|
|
2096
2584
|
}
|
|
@@ -2106,10 +2594,10 @@ var Outbox = class {
|
|
|
2106
2594
|
/** Every session file, newest first. */
|
|
2107
2595
|
sessions() {
|
|
2108
2596
|
const out = [];
|
|
2109
|
-
const base =
|
|
2110
|
-
if (!
|
|
2597
|
+
const base = path10.join(this.root, "outbox");
|
|
2598
|
+
if (!existsSync9(base)) return [];
|
|
2111
2599
|
for (const h of readdirSync5(base)) {
|
|
2112
|
-
const d =
|
|
2600
|
+
const d = path10.join(base, h);
|
|
2113
2601
|
let names = [];
|
|
2114
2602
|
try {
|
|
2115
2603
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2118,7 +2606,7 @@ var Outbox = class {
|
|
|
2118
2606
|
}
|
|
2119
2607
|
for (const n of names) {
|
|
2120
2608
|
try {
|
|
2121
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync4(
|
|
2609
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync4(path10.join(d, n)).mtimeMs });
|
|
2122
2610
|
} catch {
|
|
2123
2611
|
}
|
|
2124
2612
|
}
|
|
@@ -2126,31 +2614,43 @@ var Outbox = class {
|
|
|
2126
2614
|
return out.sort((a, b) => b.mtime - a.mtime).map((x) => x.ref);
|
|
2127
2615
|
}
|
|
2128
2616
|
/** Read events after byte offset `from`; returns them and the new offset. Lines
|
|
2129
|
-
* are read whole; a partial trailing line (a write in progress) is left.
|
|
2617
|
+
* are read whole; a partial trailing line (a write in progress) is left. A read
|
|
2618
|
+
* takes READ_CHUNK bytes at a time, not the rest of the file: a session file grows
|
|
2619
|
+
* to tens of MB and a drain takes it in batches of 50. It reads on until the batch
|
|
2620
|
+
* is full or the file ends, so lines that parse to nothing never stop a drain; one
|
|
2621
|
+
* line longer than the chunk gets a larger one, up to READ_MAX. */
|
|
2130
2622
|
read(ref, from, maxEvents = 500) {
|
|
2131
2623
|
const f = this.file(ref);
|
|
2132
|
-
if (!
|
|
2624
|
+
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2133
2625
|
const size = statSync4(f).size;
|
|
2134
2626
|
if (from >= size) return { events: [], offset: from };
|
|
2135
2627
|
const fd = openSync2(f, "r");
|
|
2136
2628
|
try {
|
|
2137
|
-
const buf = Buffer.alloc(Math.min(size - from, 8 * 1024 * 1024));
|
|
2138
|
-
const n = readSync2(fd, buf, 0, buf.length, from);
|
|
2139
|
-
const text = buf.subarray(0, n).toString("utf8");
|
|
2140
2629
|
const events = [];
|
|
2141
2630
|
let offset = from;
|
|
2142
|
-
let
|
|
2143
|
-
while (events.length < maxEvents) {
|
|
2144
|
-
const
|
|
2145
|
-
|
|
2146
|
-
const
|
|
2147
|
-
pos =
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2631
|
+
let chunk = READ_CHUNK;
|
|
2632
|
+
while (events.length < maxEvents && offset < size) {
|
|
2633
|
+
const buf = Buffer.alloc(Math.min(size - offset, chunk));
|
|
2634
|
+
const n = readSync2(fd, buf, 0, buf.length, offset);
|
|
2635
|
+
const text = buf.subarray(0, n).toString("utf8");
|
|
2636
|
+
let pos = 0;
|
|
2637
|
+
let used = 0;
|
|
2638
|
+
while (events.length < maxEvents) {
|
|
2639
|
+
const nl = text.indexOf("\n", pos);
|
|
2640
|
+
if (nl < 0) break;
|
|
2641
|
+
const line = text.slice(pos, nl);
|
|
2642
|
+
pos = nl + 1;
|
|
2643
|
+
used += Buffer.byteLength(line, "utf8") + 1;
|
|
2644
|
+
if (!line.trim()) continue;
|
|
2645
|
+
try {
|
|
2646
|
+
events.push(JSON.parse(line));
|
|
2647
|
+
} catch {
|
|
2648
|
+
}
|
|
2153
2649
|
}
|
|
2650
|
+
offset += used;
|
|
2651
|
+
if (used) continue;
|
|
2652
|
+
if (offset + n >= size || chunk >= READ_MAX) break;
|
|
2653
|
+
chunk *= 2;
|
|
2154
2654
|
}
|
|
2155
2655
|
return { events, offset };
|
|
2156
2656
|
} finally {
|
|
@@ -2194,7 +2694,7 @@ var Cursors = class {
|
|
|
2194
2694
|
root;
|
|
2195
2695
|
sink;
|
|
2196
2696
|
file() {
|
|
2197
|
-
return
|
|
2697
|
+
return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2198
2698
|
}
|
|
2199
2699
|
read() {
|
|
2200
2700
|
try {
|
|
@@ -2205,7 +2705,7 @@ var Cursors = class {
|
|
|
2205
2705
|
}
|
|
2206
2706
|
write(c) {
|
|
2207
2707
|
const f = this.file();
|
|
2208
|
-
mkdirSync5(
|
|
2708
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
2209
2709
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2210
2710
|
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
2211
2711
|
renameSync(tmp, f);
|
|
@@ -2215,11 +2715,14 @@ var Cursors = class {
|
|
|
2215
2715
|
}
|
|
2216
2716
|
};
|
|
2217
2717
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
2218
|
-
|
|
2718
|
+
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
2719
|
+
}
|
|
2720
|
+
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
2721
|
+
const dir = path10.join(root, "locks");
|
|
2219
2722
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2220
|
-
const f =
|
|
2723
|
+
const f = path10.join(dir, `${name}.lock`);
|
|
2221
2724
|
try {
|
|
2222
|
-
if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
2725
|
+
if (Date.now() - statSync4(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
|
|
2223
2726
|
else return null;
|
|
2224
2727
|
} catch {
|
|
2225
2728
|
}
|
|
@@ -2235,9 +2738,18 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2235
2738
|
}
|
|
2236
2739
|
};
|
|
2237
2740
|
}
|
|
2741
|
+
function processAlive(pid) {
|
|
2742
|
+
if (!Number.isInteger(pid) || pid <= 0) return true;
|
|
2743
|
+
try {
|
|
2744
|
+
process.kill(pid, 0);
|
|
2745
|
+
return true;
|
|
2746
|
+
} catch (e) {
|
|
2747
|
+
return e.code === "EPERM";
|
|
2748
|
+
}
|
|
2749
|
+
}
|
|
2238
2750
|
|
|
2239
2751
|
// ../core/src/privacy/pii.ts
|
|
2240
|
-
import { createHash as
|
|
2752
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2241
2753
|
var RULESET_PII = "pii@1";
|
|
2242
2754
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2243
2755
|
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
@@ -2259,7 +2771,7 @@ function reducePii(text, home) {
|
|
|
2259
2771
|
return out;
|
|
2260
2772
|
}
|
|
2261
2773
|
function hashMachine(name) {
|
|
2262
|
-
return
|
|
2774
|
+
return createHash4("sha256").update(name).digest("hex").slice(0, 8);
|
|
2263
2775
|
}
|
|
2264
2776
|
function reducePiiDeep(v, home) {
|
|
2265
2777
|
if (typeof v === "string") return reducePii(v, home);
|
|
@@ -2348,12 +2860,12 @@ function redactAssignments(text) {
|
|
|
2348
2860
|
let last = 0;
|
|
2349
2861
|
NAME_SEP.lastIndex = 0;
|
|
2350
2862
|
for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
|
|
2351
|
-
const name = m[1]
|
|
2863
|
+
const name = m[1];
|
|
2352
2864
|
if (!isSecretName(name)) continue;
|
|
2353
2865
|
VALUE.lastIndex = NAME_SEP.lastIndex;
|
|
2354
2866
|
const v = VALUE.exec(text);
|
|
2355
2867
|
if (!v) continue;
|
|
2356
|
-
const value = v[1] ?? v[2] ?? v[3] ?? v[4]
|
|
2868
|
+
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
2357
2869
|
NAME_SEP.lastIndex = VALUE.lastIndex;
|
|
2358
2870
|
if (isPlaceholder(value) || value.includes(REDACTED)) continue;
|
|
2359
2871
|
if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
|
|
@@ -2409,7 +2921,7 @@ function redactSecrets(text) {
|
|
|
2409
2921
|
let out = text;
|
|
2410
2922
|
for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
|
|
2411
2923
|
out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
|
|
2412
|
-
out = out.replace(AUTH_HEADER, (
|
|
2924
|
+
out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
|
|
2413
2925
|
out = redactAssignments(out);
|
|
2414
2926
|
out = out.replace(
|
|
2415
2927
|
PROSE,
|
|
@@ -2438,6 +2950,7 @@ var TYPE_TIER = {
|
|
|
2438
2950
|
"turn.started": 0,
|
|
2439
2951
|
"turn.ended": 0,
|
|
2440
2952
|
"turn.transcript": 2,
|
|
2953
|
+
"turn.progress": 2,
|
|
2441
2954
|
"tool.started": 1,
|
|
2442
2955
|
"tool.ended": 1,
|
|
2443
2956
|
"files.changed": 1,
|
|
@@ -2457,6 +2970,7 @@ var DATA_ALLOW = {
|
|
|
2457
2970
|
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2458
2971
|
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
2459
2972
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2973
|
+
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
2460
2974
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2461
2975
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
2462
2976
|
"files.changed": { 1: ["paths"] },
|
|
@@ -2497,10 +3011,12 @@ function cap(v, max) {
|
|
|
2497
3011
|
}
|
|
2498
3012
|
return v;
|
|
2499
3013
|
}
|
|
3014
|
+
function capTail(v, max) {
|
|
3015
|
+
return v.length > max ? `\u2026${v.slice(v.length - max + 1)}` : v;
|
|
3016
|
+
}
|
|
2500
3017
|
function filterEvent(event, tier, pii, opts = {}) {
|
|
2501
3018
|
const minTier = TYPE_TIER[event.type];
|
|
2502
3019
|
if (minTier !== void 0 && minTier > tier) return null;
|
|
2503
|
-
if (event.type === "native" && tier < 3) return null;
|
|
2504
3020
|
const session = {};
|
|
2505
3021
|
for (const k of SESSION_ALLOW)
|
|
2506
3022
|
if (event.session[k] !== void 0)
|
|
@@ -2510,7 +3026,8 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2510
3026
|
if (keys === null) data = { ...event.data };
|
|
2511
3027
|
else for (const k of keys) if (event.data[k] !== void 0) data[k] = event.data[k];
|
|
2512
3028
|
if (typeof data.user === "string") data.user = cap(data.user, MAX_SIDE);
|
|
2513
|
-
if (typeof data.assistant === "string")
|
|
3029
|
+
if (typeof data.assistant === "string")
|
|
3030
|
+
data.assistant = event.type === "turn.progress" ? capTail(data.assistant, MAX_SIDE) : cap(data.assistant, MAX_SIDE);
|
|
2514
3031
|
if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
|
|
2515
3032
|
if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
|
|
2516
3033
|
if (typeof data.message === "string") data.message = cap(data.message, 200);
|
|
@@ -2531,9 +3048,15 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2531
3048
|
return out;
|
|
2532
3049
|
}
|
|
2533
3050
|
|
|
3051
|
+
// ../core/src/short-id.ts
|
|
3052
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
3053
|
+
function shortId(harness, id) {
|
|
3054
|
+
return createHash5("sha256").update(`sessionpipe.session:${harness}:${id}`).digest("hex").slice(0, 8);
|
|
3055
|
+
}
|
|
3056
|
+
|
|
2534
3057
|
// ../core/src/sinks/file.ts
|
|
2535
3058
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2536
|
-
import
|
|
3059
|
+
import path11 from "node:path";
|
|
2537
3060
|
var FileSink = class {
|
|
2538
3061
|
constructor(name, file, tier, pii) {
|
|
2539
3062
|
this.name = name;
|
|
@@ -2547,7 +3070,7 @@ var FileSink = class {
|
|
|
2547
3070
|
pii;
|
|
2548
3071
|
async send(events) {
|
|
2549
3072
|
try {
|
|
2550
|
-
mkdirSync6(
|
|
3073
|
+
mkdirSync6(path11.dirname(this.file), { recursive: true });
|
|
2551
3074
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2552
3075
|
`);
|
|
2553
3076
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -2571,19 +3094,48 @@ var StdoutSink = class {
|
|
|
2571
3094
|
pii;
|
|
2572
3095
|
write;
|
|
2573
3096
|
async send(events) {
|
|
2574
|
-
|
|
2575
|
-
|
|
3097
|
+
try {
|
|
3098
|
+
for (const e of events) this.write(JSON.stringify(e));
|
|
3099
|
+
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
3100
|
+
} catch (e) {
|
|
3101
|
+
return { ok: false, acknowledged: 0, error: String(e.message ?? e), action: "retry" };
|
|
3102
|
+
}
|
|
2576
3103
|
}
|
|
2577
3104
|
};
|
|
2578
3105
|
|
|
3106
|
+
// src/control/aliases.ts
|
|
3107
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
3108
|
+
import path12 from "node:path";
|
|
3109
|
+
var aliasFile = (controlDir) => path12.join(controlDir, "aliases.json");
|
|
3110
|
+
function readAliases(file) {
|
|
3111
|
+
try {
|
|
3112
|
+
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
3113
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return {};
|
|
3114
|
+
const out = {};
|
|
3115
|
+
for (const [k, v] of Object.entries(j)) {
|
|
3116
|
+
const a = v;
|
|
3117
|
+
if (a && typeof a.id === "string" && a.id && typeof a.cwd === "string")
|
|
3118
|
+
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "" };
|
|
3119
|
+
}
|
|
3120
|
+
return out;
|
|
3121
|
+
} catch {
|
|
3122
|
+
return {};
|
|
3123
|
+
}
|
|
3124
|
+
}
|
|
3125
|
+
function receiverIdOf(all, harness, id) {
|
|
3126
|
+
const prefix = `${harness}:`;
|
|
3127
|
+
for (const [k, a] of Object.entries(all)) if (a.id === id && k.startsWith(prefix)) return k.slice(prefix.length);
|
|
3128
|
+
return null;
|
|
3129
|
+
}
|
|
3130
|
+
|
|
2579
3131
|
// src/control/local.ts
|
|
2580
3132
|
import net from "node:net";
|
|
2581
3133
|
import os2 from "node:os";
|
|
2582
|
-
import
|
|
3134
|
+
import path13 from "node:path";
|
|
2583
3135
|
function socketPath(state, env = process.env) {
|
|
2584
3136
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
2585
3137
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
2586
|
-
return
|
|
3138
|
+
return path13.join(state, "control.sock");
|
|
2587
3139
|
}
|
|
2588
3140
|
function ask(sock, req, timeoutMs) {
|
|
2589
3141
|
return new Promise((resolve) => {
|
|
@@ -2617,7 +3169,7 @@ function ask(sock, req, timeoutMs) {
|
|
|
2617
3169
|
|
|
2618
3170
|
// src/secrets.ts
|
|
2619
3171
|
import { spawnSync } from "node:child_process";
|
|
2620
|
-
import { createHash as
|
|
3172
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
2621
3173
|
var SERVICE = "sessionpipe";
|
|
2622
3174
|
var defaultRunner = (cmd, args, input) => {
|
|
2623
3175
|
const r = spawnSync(cmd, args, {
|
|
@@ -2629,7 +3181,7 @@ var defaultRunner = (cmd, args, input) => {
|
|
|
2629
3181
|
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
2630
3182
|
};
|
|
2631
3183
|
var sinkAccount = (name, what = "token") => {
|
|
2632
|
-
const scope = process.env.SESSIONPIPE_CONFIG ? `@${
|
|
3184
|
+
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
2633
3185
|
return `sink:${name}:${what}${scope}`;
|
|
2634
3186
|
};
|
|
2635
3187
|
function secretGet(store, account, run = defaultRunner) {
|
|
@@ -2647,16 +3199,16 @@ function sinkWithSecrets(s, run = defaultRunner) {
|
|
|
2647
3199
|
}
|
|
2648
3200
|
|
|
2649
3201
|
// src/run.ts
|
|
2650
|
-
async function tellDaemon(state, job, session, transcript) {
|
|
3202
|
+
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
2651
3203
|
const sock = socketPath(state);
|
|
2652
|
-
if (process.platform !== "win32" && !
|
|
3204
|
+
if (process.platform !== "win32" && !existsSync10(sock)) return;
|
|
2653
3205
|
await ask(
|
|
2654
3206
|
sock,
|
|
2655
3207
|
{
|
|
2656
3208
|
op: "event",
|
|
2657
|
-
session:
|
|
3209
|
+
session: `${job.harness}:${session}`,
|
|
2658
3210
|
event: job.event,
|
|
2659
|
-
cwd:
|
|
3211
|
+
...cwd ? { cwd } : {},
|
|
2660
3212
|
...transcript ? { transcript } : {}
|
|
2661
3213
|
},
|
|
2662
3214
|
300
|
|
@@ -2664,17 +3216,24 @@ async function tellDaemon(state, job, session, transcript) {
|
|
|
2664
3216
|
}
|
|
2665
3217
|
function enqueueJob(state, job) {
|
|
2666
3218
|
const jobs = jobsDir(state);
|
|
2667
|
-
|
|
2668
|
-
const f =
|
|
2669
|
-
|
|
3219
|
+
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
3220
|
+
const f = path14.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
3221
|
+
writeFileSync7(f, JSON.stringify(job), { mode: 384 });
|
|
2670
3222
|
return f;
|
|
2671
3223
|
}
|
|
3224
|
+
function releaseJob(claimed) {
|
|
3225
|
+
if (!claimed) return;
|
|
3226
|
+
try {
|
|
3227
|
+
unlinkSync4(claimed);
|
|
3228
|
+
} catch {
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
2672
3231
|
function log(state, line) {
|
|
2673
3232
|
try {
|
|
2674
|
-
|
|
2675
|
-
const f =
|
|
3233
|
+
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
3234
|
+
const f = path14.join(state, "log");
|
|
2676
3235
|
try {
|
|
2677
|
-
if (statSync5(f).size > 1024 * 1024)
|
|
3236
|
+
if (statSync5(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
2678
3237
|
} catch {
|
|
2679
3238
|
}
|
|
2680
3239
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -2683,11 +3242,11 @@ function log(state, line) {
|
|
|
2683
3242
|
}
|
|
2684
3243
|
}
|
|
2685
3244
|
function factsState(state) {
|
|
2686
|
-
const file = (h, s) =>
|
|
3245
|
+
const file = (h, s) => path14.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
2687
3246
|
return {
|
|
2688
3247
|
get(h, s) {
|
|
2689
3248
|
try {
|
|
2690
|
-
return JSON.parse(
|
|
3249
|
+
return JSON.parse(readFileSync9(file(h, s), "utf8"));
|
|
2691
3250
|
} catch {
|
|
2692
3251
|
return {};
|
|
2693
3252
|
}
|
|
@@ -2696,8 +3255,8 @@ function factsState(state) {
|
|
|
2696
3255
|
const cur = this.get(h, s);
|
|
2697
3256
|
const next = { ...cur, ...patch };
|
|
2698
3257
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
2699
|
-
|
|
2700
|
-
|
|
3258
|
+
mkdirSync8(path14.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
3259
|
+
writeFileSync7(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
2701
3260
|
}
|
|
2702
3261
|
};
|
|
2703
3262
|
}
|
|
@@ -2718,37 +3277,76 @@ function buildSinks(sinks, o = {}) {
|
|
|
2718
3277
|
}
|
|
2719
3278
|
return out;
|
|
2720
3279
|
}
|
|
3280
|
+
var PROGRESS_MS = 2e4;
|
|
3281
|
+
function openTurn(read, o) {
|
|
3282
|
+
const last = read.turns[read.turns.length - 1];
|
|
3283
|
+
const fresh = (user, at) => ({
|
|
3284
|
+
turn: o.base + read.turns.length,
|
|
3285
|
+
user: user.slice(0, MAX_TURN_SIDE),
|
|
3286
|
+
assistant: "",
|
|
3287
|
+
at
|
|
3288
|
+
});
|
|
3289
|
+
if (read.open) return fresh(read.open.user, read.open.at);
|
|
3290
|
+
const prompt = o.prompt?.trim();
|
|
3291
|
+
if (prompt && !isInjected(prompt) && !(last && last.user.includes(prompt.slice(0, 200)))) return fresh(prompt, o.at);
|
|
3292
|
+
if (!last) return null;
|
|
3293
|
+
return {
|
|
3294
|
+
turn: o.base + read.turns.length - 1,
|
|
3295
|
+
user: last.user.slice(0, MAX_TURN_SIDE),
|
|
3296
|
+
assistant: capTail(last.assistant, MAX_TURN_SIDE),
|
|
3297
|
+
at: last.at
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
var MAX_TURN_SIDE = 2e4;
|
|
2721
3301
|
async function runJob(job, opts = {}) {
|
|
2722
3302
|
const state = opts.state ?? stateDir();
|
|
2723
3303
|
const cfg = readConfig();
|
|
2724
3304
|
const adapter = adapterByName(job.harness);
|
|
2725
|
-
if (!adapter)
|
|
3305
|
+
if (!adapter) {
|
|
3306
|
+
log(state, `${job.harness} ${job.event}: no adapter for harness "${job.harness}"; event dropped`);
|
|
3307
|
+
return { events: [], delivered: {} };
|
|
3308
|
+
}
|
|
2726
3309
|
const r = adapter.fromHook({ argv: job.argv, stdin: job.stdin, env: { ...process.env, ...job.env }, cwd: job.cwd });
|
|
2727
|
-
if (!r || !r.session.id)
|
|
2728
|
-
|
|
3310
|
+
if (!r || !r.session.id) {
|
|
3311
|
+
log(state, `${job.harness} ${job.event}: the ${job.harness} adapter did not claim this payload; event dropped`);
|
|
3312
|
+
return { events: [], delivered: {} };
|
|
3313
|
+
}
|
|
3314
|
+
const id = receiverIdOf(readAliases(aliasFile(path14.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3315
|
+
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3316
|
+
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
2729
3317
|
const outbox = new Outbox(state);
|
|
2730
|
-
const ref = { harness: job.harness, session:
|
|
3318
|
+
const ref = { harness: job.harness, session: id };
|
|
2731
3319
|
let release = takeLock(state, ref);
|
|
2732
|
-
|
|
2733
|
-
|
|
3320
|
+
const lockWait = opts.lockWaitMs ?? LOCK_WAIT_MS;
|
|
3321
|
+
for (const until = Date.now() + lockWait; !release && Date.now() < until; ) {
|
|
3322
|
+
await new Promise((res) => setTimeout(res, 50));
|
|
2734
3323
|
release = takeLock(state, ref);
|
|
2735
3324
|
}
|
|
2736
3325
|
if (!release) {
|
|
2737
|
-
log(state, `${job.harness} ${job.event}: session ${
|
|
2738
|
-
enqueueJob(state, job);
|
|
2739
|
-
|
|
3326
|
+
log(state, `${job.harness} ${job.event}: session ${id} locked for ${lockWait / 1e3} s, re-queued`);
|
|
3327
|
+
const requeued = enqueueJob(state, { ...job, tries: (job.tries ?? 0) + 1 });
|
|
3328
|
+
releaseJob(opts.claimed);
|
|
3329
|
+
return { events: [], delivered: {}, requeued };
|
|
2740
3330
|
}
|
|
3331
|
+
const events = [];
|
|
2741
3332
|
try {
|
|
2742
3333
|
const facts = factsState(state);
|
|
2743
|
-
const mem = facts.get(job.harness,
|
|
3334
|
+
const mem = facts.get(job.harness, id);
|
|
3335
|
+
if (claudeEnd && !existsSync10(outbox.file(ref)) && Object.keys(mem).length === 0 && !(r.transcript && existsSync10(r.transcript))) {
|
|
3336
|
+
releaseJob(opts.claimed);
|
|
3337
|
+
return { events: [], delivered: {} };
|
|
3338
|
+
}
|
|
3339
|
+
if (claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
2744
3340
|
const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
|
|
2745
3341
|
let learned = {};
|
|
2746
3342
|
if (heavy) {
|
|
2747
|
-
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts);
|
|
3343
|
+
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts, { ...process.env, ...job.env });
|
|
2748
3344
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
2749
3345
|
learned = rest;
|
|
2750
|
-
if (harness_version) facts.save(job.harness,
|
|
2751
|
-
facts.save(job.harness,
|
|
3346
|
+
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
3347
|
+
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
3348
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path14.isAbsolute(r.session.cwd))
|
|
3349
|
+
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
2752
3350
|
}
|
|
2753
3351
|
const known = mem.session ?? {};
|
|
2754
3352
|
const cwdRaw = learned.cwd ?? r.session.cwd ?? known.cwd;
|
|
@@ -2758,14 +3356,13 @@ async function runJob(job, opts = {}) {
|
|
|
2758
3356
|
...learned,
|
|
2759
3357
|
...git,
|
|
2760
3358
|
...learned.branch ? { branch: learned.branch } : {},
|
|
2761
|
-
id
|
|
3359
|
+
id,
|
|
2762
3360
|
seq: 0,
|
|
2763
3361
|
machine: machineName(cfg, os3.hostname()),
|
|
2764
3362
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
2765
3363
|
...r.session.model ? { model: r.session.model } : {}
|
|
2766
3364
|
};
|
|
2767
|
-
const version = facts.get(job.harness,
|
|
2768
|
-
const events = [];
|
|
3365
|
+
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
2769
3366
|
for (const a of r.events) {
|
|
2770
3367
|
const e = makeEvent(a, {
|
|
2771
3368
|
harness: { name: job.harness, ...version ? { version } : {} },
|
|
@@ -2775,8 +3372,39 @@ async function runJob(job, opts = {}) {
|
|
|
2775
3372
|
events.push(e);
|
|
2776
3373
|
}
|
|
2777
3374
|
const wantsTranscript = cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 2);
|
|
3375
|
+
const ending = r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended");
|
|
3376
|
+
const starting = r.events.some((e) => e.type === "turn.started");
|
|
3377
|
+
const startedId = r.events.find((e) => e.type === "turn.started")?.data.turn_id;
|
|
3378
|
+
const absorbed = starting && typeof startedId === "string" && facts.get(job.harness, id).open_turn === startedId;
|
|
3379
|
+
if (wantsTranscript && r.transcript && !ending && (starting || r.events.some((e) => e.type === "tool.ended"))) {
|
|
3380
|
+
const cur = facts.get(job.harness, id);
|
|
3381
|
+
const at = opts.now ?? job.at;
|
|
3382
|
+
if (starting || at - (Number(cur.progress_at) || 0) >= PROGRESS_MS) {
|
|
3383
|
+
const p = openTurn(adapter.readTranscript(r.transcript, Number(cur.transcript_line) || 0), {
|
|
3384
|
+
base: Number(cur.transcript_turns) || 0,
|
|
3385
|
+
prompt: !absorbed && typeof r.hints?.prompt === "string" ? r.hints.prompt : void 0,
|
|
3386
|
+
at
|
|
3387
|
+
});
|
|
3388
|
+
const sig = p && `${p.turn}:${p.user.length}:${p.assistant.length}`;
|
|
3389
|
+
if (p && (starting || sig !== cur.progress_sig)) {
|
|
3390
|
+
events.push(
|
|
3391
|
+
makeEvent(
|
|
3392
|
+
{ type: "turn.progress", harnessEvent: job.event, data: { ...p, at: new Date(p.at).toISOString() } },
|
|
3393
|
+
{
|
|
3394
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
3395
|
+
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
3396
|
+
now: at
|
|
3397
|
+
}
|
|
3398
|
+
)
|
|
3399
|
+
);
|
|
3400
|
+
facts.save(job.harness, id, { progress_at: at, progress_sig: sig });
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
if (starting && typeof startedId === "string" && !absorbed) facts.save(job.harness, id, { open_turn: startedId });
|
|
3405
|
+
if (ending) facts.save(job.harness, id, { open_turn: null });
|
|
2778
3406
|
if (wantsTranscript && r.transcript && r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended")) {
|
|
2779
|
-
const cur = facts.get(job.harness,
|
|
3407
|
+
const cur = facts.get(job.harness, id);
|
|
2780
3408
|
const fromLine = Number(cur.transcript_line) || 0;
|
|
2781
3409
|
let turnNo = Number(cur.transcript_turns) || 0;
|
|
2782
3410
|
const read = adapter.readTranscript(r.transcript, fromLine);
|
|
@@ -2796,27 +3424,86 @@ async function runJob(job, opts = {}) {
|
|
|
2796
3424
|
)
|
|
2797
3425
|
);
|
|
2798
3426
|
}
|
|
2799
|
-
if (read.turns.length)
|
|
2800
|
-
facts.save(job.harness, r.session.id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
3427
|
+
if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
2801
3428
|
}
|
|
2802
3429
|
outbox.append(
|
|
2803
3430
|
ref,
|
|
2804
3431
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
2805
3432
|
);
|
|
3433
|
+
releaseJob(opts.claimed);
|
|
2806
3434
|
log(
|
|
2807
3435
|
state,
|
|
2808
|
-
`${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${
|
|
3436
|
+
`${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${shortId(job.harness, id)})`
|
|
2809
3437
|
);
|
|
2810
|
-
const delivered = await flush(state, cfg.sinks, outbox, ref);
|
|
2811
|
-
if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
|
|
2812
|
-
return { events, delivered };
|
|
2813
3438
|
} finally {
|
|
2814
3439
|
release();
|
|
2815
3440
|
}
|
|
3441
|
+
const delivered = await drain(state, cfg.sinks, outbox, ref) ?? {};
|
|
3442
|
+
if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
|
|
3443
|
+
return { events, delivered };
|
|
3444
|
+
}
|
|
3445
|
+
async function drain(state, sinkConfigs, outbox, first) {
|
|
3446
|
+
const more = path14.join(state, "locks", "flush.more");
|
|
3447
|
+
let release = takeNamedLock(state, "flush");
|
|
3448
|
+
if (!release) {
|
|
3449
|
+
try {
|
|
3450
|
+
writeFileSync7(more, "", { mode: 384 });
|
|
3451
|
+
} catch {
|
|
3452
|
+
}
|
|
3453
|
+
release = takeNamedLock(state, "flush");
|
|
3454
|
+
if (!release) return null;
|
|
3455
|
+
}
|
|
3456
|
+
const delivered = {};
|
|
3457
|
+
for (; ; ) {
|
|
3458
|
+
try {
|
|
3459
|
+
unlinkSync4(more);
|
|
3460
|
+
} catch {
|
|
3461
|
+
}
|
|
3462
|
+
try {
|
|
3463
|
+
for (const [k, v] of Object.entries(await flush(state, sinkConfigs, outbox, first)))
|
|
3464
|
+
delivered[k] = (delivered[k] ?? 0) + v;
|
|
3465
|
+
} finally {
|
|
3466
|
+
release();
|
|
3467
|
+
}
|
|
3468
|
+
if (!existsSync10(more)) return delivered;
|
|
3469
|
+
release = takeNamedLock(state, "flush");
|
|
3470
|
+
if (!release) return delivered;
|
|
3471
|
+
}
|
|
3472
|
+
}
|
|
3473
|
+
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3474
|
+
var backoffFile = (state, sink) => path14.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3475
|
+
function readBackoff(state, sink) {
|
|
3476
|
+
try {
|
|
3477
|
+
const b = JSON.parse(readFileSync9(backoffFile(state, sink), "utf8"));
|
|
3478
|
+
return typeof b.until === "number" ? b : null;
|
|
3479
|
+
} catch {
|
|
3480
|
+
return null;
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
function noteFailure(state, sink, res, prev) {
|
|
3484
|
+
const failures = (prev?.failures ?? 0) + 1;
|
|
3485
|
+
const ladder = BACKOFF_MS[Math.min(failures, BACKOFF_MS.length) - 1];
|
|
3486
|
+
const wait = Number.isFinite(res.retryAfterMs) ? res.retryAfterMs : ladder;
|
|
3487
|
+
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3488
|
+
const f = backoffFile(state, sink);
|
|
3489
|
+
try {
|
|
3490
|
+
mkdirSync8(path14.dirname(f), { recursive: true, mode: 448 });
|
|
3491
|
+
writeFileSync7(f, JSON.stringify(b), { mode: 384 });
|
|
3492
|
+
} catch {
|
|
3493
|
+
}
|
|
3494
|
+
return b;
|
|
3495
|
+
}
|
|
3496
|
+
function clearBackoff(state, sink) {
|
|
3497
|
+
try {
|
|
3498
|
+
unlinkSync4(backoffFile(state, sink));
|
|
3499
|
+
} catch {
|
|
3500
|
+
}
|
|
2816
3501
|
}
|
|
3502
|
+
var LOCK_WAIT_MS = 6e3;
|
|
3503
|
+
var CWD_FROM_HOOKS = /* @__PURE__ */ new Set(["antigravity", "devin"]);
|
|
2817
3504
|
var BATCH = 50;
|
|
2818
3505
|
var MAX_PER_RUN = 500;
|
|
2819
|
-
async function flush(state, sinkConfigs, outbox, first) {
|
|
3506
|
+
async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
2820
3507
|
const delivered = {};
|
|
2821
3508
|
const sinks = buildSinks(sinkConfigs, {
|
|
2822
3509
|
locked: (name) => log(state, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
@@ -2826,13 +3513,39 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2826
3513
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
2827
3514
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
2828
3515
|
}
|
|
2829
|
-
|
|
3516
|
+
const drainSink = async (sink) => {
|
|
3517
|
+
let backoff = readBackoff(state, sink.name);
|
|
3518
|
+
if (backoff && !o.force && backoff.until > Date.now()) {
|
|
3519
|
+
delivered[sink.name] = 0;
|
|
3520
|
+
return;
|
|
3521
|
+
}
|
|
2830
3522
|
const cursors = new Cursors(state, sink.name);
|
|
2831
3523
|
const c = cursors.read();
|
|
2832
3524
|
let sent = 0;
|
|
2833
3525
|
let stalled = false;
|
|
2834
3526
|
let batch = [];
|
|
2835
3527
|
let pending = {};
|
|
3528
|
+
const deliver = async (events) => {
|
|
3529
|
+
const res = await sink.send(events);
|
|
3530
|
+
if (res.ok || res.action !== "split") return res;
|
|
3531
|
+
if (events.length === 1) {
|
|
3532
|
+
const e = events[0];
|
|
3533
|
+
log(state, `sink ${sink.name}: 413 ${res.error ?? ""} \u2192 event ${e.id} (${e.type}) is too large alone, dropped`);
|
|
3534
|
+
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected: [{ id: e.id, reason: "too_large" }] };
|
|
3535
|
+
}
|
|
3536
|
+
const half = Math.ceil(events.length / 2);
|
|
3537
|
+
const a = await deliver(events.slice(0, half));
|
|
3538
|
+
if (!a.ok) return a;
|
|
3539
|
+
const b = await deliver(events.slice(half));
|
|
3540
|
+
if (!b.ok) return b;
|
|
3541
|
+
return {
|
|
3542
|
+
ok: true,
|
|
3543
|
+
acknowledged: a.acknowledged + b.acknowledged,
|
|
3544
|
+
accepted: (a.accepted ?? 0) + (b.accepted ?? 0),
|
|
3545
|
+
duplicates: (a.duplicates ?? 0) + (b.duplicates ?? 0),
|
|
3546
|
+
rejected: [...a.rejected ?? [], ...b.rejected ?? []]
|
|
3547
|
+
};
|
|
3548
|
+
};
|
|
2836
3549
|
const send = async () => {
|
|
2837
3550
|
if (!batch.length) {
|
|
2838
3551
|
Object.assign(c, pending);
|
|
@@ -2840,15 +3553,24 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2840
3553
|
cursors.write(c);
|
|
2841
3554
|
return true;
|
|
2842
3555
|
}
|
|
2843
|
-
const res = await
|
|
3556
|
+
const res = await deliver(batch);
|
|
2844
3557
|
if (!res.ok) {
|
|
2845
|
-
|
|
3558
|
+
const action = res.action ?? "retry";
|
|
3559
|
+
let wait = "";
|
|
3560
|
+
if (action === "retry") {
|
|
3561
|
+
backoff = noteFailure(state, sink.name, res, backoff);
|
|
3562
|
+
wait = `, waits ${Math.round((backoff.until - Date.now()) / 1e3)} s`;
|
|
3563
|
+
}
|
|
3564
|
+
log(state, `sink ${sink.name}: ${res.status ?? ""} ${res.error ?? ""} \u2192 ${action}${wait}`);
|
|
2846
3565
|
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2847
3566
|
if (res.action !== "drop") {
|
|
2848
3567
|
batch = [];
|
|
2849
3568
|
pending = {};
|
|
2850
3569
|
return false;
|
|
2851
3570
|
}
|
|
3571
|
+
} else if (backoff) {
|
|
3572
|
+
clearBackoff(state, sink.name);
|
|
3573
|
+
backoff = null;
|
|
2852
3574
|
}
|
|
2853
3575
|
sent += batch.length;
|
|
2854
3576
|
Object.assign(c, pending);
|
|
@@ -2883,7 +3605,8 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2883
3605
|
if (!await send()) stalled = true;
|
|
2884
3606
|
}
|
|
2885
3607
|
delivered[sink.name] = sent;
|
|
2886
|
-
}
|
|
3608
|
+
};
|
|
3609
|
+
await Promise.all(sinks.map(drainSink));
|
|
2887
3610
|
return delivered;
|
|
2888
3611
|
}
|
|
2889
3612
|
function applySinkVerdict(name, res) {
|
|
@@ -2897,7 +3620,7 @@ function applySinkVerdict(name, res) {
|
|
|
2897
3620
|
} catch {
|
|
2898
3621
|
}
|
|
2899
3622
|
}
|
|
2900
|
-
var jobsDir = (state) =>
|
|
3623
|
+
var jobsDir = (state) => path14.join(state, "jobs");
|
|
2901
3624
|
export {
|
|
2902
3625
|
HttpSink,
|
|
2903
3626
|
buildSinks,
|