sessionpipe 0.7.0 → 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 +58 -4
- package/dist/cli.d.ts +11 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2055 -691
- package/dist/cli.js.map +4 -4
- package/dist/connect.d.ts.map +1 -1
- package/dist/control/aliases.d.ts.map +1 -1
- 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 +16 -1
- 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 +7 -0
- package/dist/control/pair.d.ts.map +1 -1
- 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 +893 -198
- 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 +983 -249
- 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,7 +98,7 @@ var HttpSink = class {
|
|
|
91
98
|
// src/run.ts
|
|
92
99
|
import {
|
|
93
100
|
appendFileSync as appendFileSync3,
|
|
94
|
-
existsSync as
|
|
101
|
+
existsSync as existsSync10,
|
|
95
102
|
mkdirSync as mkdirSync8,
|
|
96
103
|
readdirSync as readdirSync6,
|
|
97
104
|
readFileSync as readFileSync9,
|
|
@@ -101,7 +108,7 @@ import {
|
|
|
101
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,6 +322,9 @@ 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));
|
|
@@ -294,7 +334,10 @@ function codexInjected(text) {
|
|
|
294
334
|
}
|
|
295
335
|
function readCodex(lines, from) {
|
|
296
336
|
const msgs = [];
|
|
297
|
-
|
|
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++) {
|
|
298
341
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
299
342
|
if (!j || j.type !== "response_item") continue;
|
|
300
343
|
const p = j.payload;
|
|
@@ -332,7 +375,7 @@ function readAntigravity(lines, from) {
|
|
|
332
375
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
333
376
|
if (j.type === "USER_INPUT") {
|
|
334
377
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
335
|
-
const text = (m ? m[1]
|
|
378
|
+
const text = (m ? m[1] : j.content).trim();
|
|
336
379
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
337
380
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
338
381
|
const text = j.content.trim();
|
|
@@ -380,6 +423,10 @@ function formatJson(value, f) {
|
|
|
380
423
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
381
424
|
return f.finalNewline ? text + f.eol : text;
|
|
382
425
|
}
|
|
426
|
+
function readJson(file) {
|
|
427
|
+
const f = readJsonFile(file);
|
|
428
|
+
return f.ok ? f.value : {};
|
|
429
|
+
}
|
|
383
430
|
function entryFor(event, harness, cmd, o) {
|
|
384
431
|
const matcher = o.matcherFor(event);
|
|
385
432
|
return {
|
|
@@ -449,8 +496,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
449
496
|
unlinkSync(file);
|
|
450
497
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
451
498
|
}
|
|
452
|
-
|
|
453
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
499
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
454
500
|
return { file, changed: true };
|
|
455
501
|
}
|
|
456
502
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -477,7 +523,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
477
523
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
478
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';
|
|
479
525
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
480
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(
|
|
526
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
|
|
481
527
|
var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
|
|
482
528
|
var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
483
529
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
@@ -543,7 +589,8 @@ var antigravity = {
|
|
|
543
589
|
const event = input.argv[1] ?? "";
|
|
544
590
|
let s = {};
|
|
545
591
|
try {
|
|
546
|
-
|
|
592
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
593
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
547
594
|
} catch {
|
|
548
595
|
}
|
|
549
596
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -599,19 +646,16 @@ var antigravity = {
|
|
|
599
646
|
}
|
|
600
647
|
return readAntigravity(text.split("\n"), fromLine);
|
|
601
648
|
},
|
|
602
|
-
facts(session, transcript, hints
|
|
649
|
+
facts(session, transcript, hints) {
|
|
603
650
|
if (!transcript) return {};
|
|
604
651
|
return antigravityFacts(transcript, {
|
|
605
652
|
conversation: session.id,
|
|
606
653
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
607
|
-
state,
|
|
608
654
|
env: process.env
|
|
609
655
|
});
|
|
610
656
|
},
|
|
611
657
|
backfill(sinceMs, env = process.env) {
|
|
612
|
-
const
|
|
613
|
-
const noState = { get: () => ({}), save: () => {
|
|
614
|
-
} };
|
|
658
|
+
const rows2 = [];
|
|
615
659
|
for (const data of dataDirs(env)) {
|
|
616
660
|
let ids = [];
|
|
617
661
|
try {
|
|
@@ -625,13 +669,13 @@ var antigravity = {
|
|
|
625
669
|
} catch {
|
|
626
670
|
continue;
|
|
627
671
|
}
|
|
628
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
672
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
629
673
|
if (!facts.started_at || !facts.last_at) continue;
|
|
630
674
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
631
|
-
|
|
675
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
632
676
|
}
|
|
633
677
|
}
|
|
634
|
-
return
|
|
678
|
+
return rows2;
|
|
635
679
|
}
|
|
636
680
|
};
|
|
637
681
|
function prune(o) {
|
|
@@ -681,7 +725,7 @@ function agProjects(dir) {
|
|
|
681
725
|
}
|
|
682
726
|
const folders = [];
|
|
683
727
|
for (const r of j?.projectResources?.resources ?? []) {
|
|
684
|
-
const f = agPath(r?.folderUri);
|
|
728
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
685
729
|
if (f) folders.push(...agVariants(f));
|
|
686
730
|
}
|
|
687
731
|
if (folders.length) out.push({ id, folders });
|
|
@@ -735,7 +779,7 @@ function antigravityFacts(file, opts) {
|
|
|
735
779
|
for (const j of H) {
|
|
736
780
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
737
781
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
738
|
-
const text = (m ? m[1]
|
|
782
|
+
const text = (m ? m[1] : j.content).trim();
|
|
739
783
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
740
784
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
741
785
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -783,10 +827,9 @@ function gitFacts(cwd) {
|
|
|
783
827
|
return out;
|
|
784
828
|
}
|
|
785
829
|
function stripCredentials(url) {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
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$/, "");
|
|
790
833
|
}
|
|
791
834
|
|
|
792
835
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -817,7 +860,7 @@ var OPTS = {
|
|
|
817
860
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
818
861
|
};
|
|
819
862
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
820
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
863
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
821
864
|
const out = [];
|
|
822
865
|
const add = (d) => {
|
|
823
866
|
if (!d) return;
|
|
@@ -858,11 +901,13 @@ var claudeCode = {
|
|
|
858
901
|
const event = input.argv[1] ?? "";
|
|
859
902
|
let s = {};
|
|
860
903
|
try {
|
|
861
|
-
|
|
904
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
905
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
862
906
|
} catch {
|
|
863
907
|
}
|
|
864
908
|
const id = s.session_id ?? s.sessionId;
|
|
865
909
|
if (!id) return null;
|
|
910
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
866
911
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
867
912
|
if (typeof s.model === "string") session.model = s.model;
|
|
868
913
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -981,6 +1026,7 @@ var claudeCode = {
|
|
|
981
1026
|
return null;
|
|
982
1027
|
}
|
|
983
1028
|
const hints = {};
|
|
1029
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
984
1030
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
985
1031
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
986
1032
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
@@ -1006,7 +1052,7 @@ var claudeCode = {
|
|
|
1006
1052
|
return out;
|
|
1007
1053
|
},
|
|
1008
1054
|
backfill(sinceMs, env = process.env) {
|
|
1009
|
-
const
|
|
1055
|
+
const rows2 = [];
|
|
1010
1056
|
const files = [];
|
|
1011
1057
|
for (const d of claudeDirs(env))
|
|
1012
1058
|
files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -1018,19 +1064,23 @@ var claudeCode = {
|
|
|
1018
1064
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1019
1065
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1020
1066
|
const cwd = rest.cwd;
|
|
1021
|
-
|
|
1067
|
+
rows2.push({
|
|
1022
1068
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
1023
1069
|
started_at,
|
|
1024
1070
|
last_at
|
|
1025
1071
|
});
|
|
1026
1072
|
}
|
|
1027
|
-
return
|
|
1073
|
+
return rows2;
|
|
1028
1074
|
}
|
|
1029
1075
|
};
|
|
1030
1076
|
function prune2(o) {
|
|
1031
1077
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1032
1078
|
return o;
|
|
1033
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
|
+
}
|
|
1034
1084
|
function pickSource(v) {
|
|
1035
1085
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1036
1086
|
}
|
|
@@ -1136,23 +1186,27 @@ function parentOf() {
|
|
|
1136
1186
|
}
|
|
1137
1187
|
return (pid) => table.get(pid) || 0;
|
|
1138
1188
|
}
|
|
1139
|
-
var
|
|
1140
|
-
function
|
|
1141
|
-
const hit = accountCache.get(dir);
|
|
1142
|
-
if (hit !== void 0) return hit;
|
|
1189
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1190
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1143
1191
|
const files = [path5.join(dir, ".claude.json")];
|
|
1144
|
-
if (path5.resolve(dir) === path5.join(
|
|
1145
|
-
let acct = null;
|
|
1192
|
+
if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
|
|
1146
1193
|
for (const f of files) {
|
|
1147
1194
|
try {
|
|
1148
1195
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1149
|
-
if (a?.accountUuid) {
|
|
1150
|
-
|
|
1151
|
-
|
|
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 } : {} };
|
|
1152
1199
|
}
|
|
1153
1200
|
} catch {
|
|
1154
1201
|
}
|
|
1155
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;
|
|
1156
1210
|
accountCache.set(dir, acct);
|
|
1157
1211
|
return acct;
|
|
1158
1212
|
}
|
|
@@ -1372,18 +1426,13 @@ function parseArgv(raw) {
|
|
|
1372
1426
|
function readNotify(text) {
|
|
1373
1427
|
const { head, tail, at } = split(text);
|
|
1374
1428
|
const m = NOTIFY_LINE.exec(head);
|
|
1375
|
-
if (m)
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
index: m.index,
|
|
1380
|
-
length: m[0].length,
|
|
1381
|
-
misplaced: false,
|
|
1382
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1383
|
-
};
|
|
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
|
+
}
|
|
1384
1433
|
const k = NOTIFY_KEY.exec(head);
|
|
1385
1434
|
if (k) {
|
|
1386
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1435
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1387
1436
|
return {
|
|
1388
1437
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1389
1438
|
argv: null,
|
|
@@ -1394,15 +1443,11 @@ function readNotify(text) {
|
|
|
1394
1443
|
};
|
|
1395
1444
|
}
|
|
1396
1445
|
const t = NOTIFY_LINE.exec(tail);
|
|
1397
|
-
if (t
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
argv: parseArgv(t[
|
|
1401
|
-
|
|
1402
|
-
length: t[0].length,
|
|
1403
|
-
misplaced: true,
|
|
1404
|
-
other: false
|
|
1405
|
-
};
|
|
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
|
+
}
|
|
1406
1451
|
return null;
|
|
1407
1452
|
}
|
|
1408
1453
|
function dropLine(text, index, length) {
|
|
@@ -1421,9 +1466,7 @@ function withNotify(text, line) {
|
|
|
1421
1466
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1422
1467
|
return insertTopLevel(text, line);
|
|
1423
1468
|
}
|
|
1424
|
-
function replaceNotify(text, line) {
|
|
1425
|
-
const n = readNotify(text);
|
|
1426
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1469
|
+
function replaceNotify(text, n, line) {
|
|
1427
1470
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1428
1471
|
}
|
|
1429
1472
|
function withoutNotify(text) {
|
|
@@ -1464,7 +1507,7 @@ var CODEX_EVENTS = [
|
|
|
1464
1507
|
];
|
|
1465
1508
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1466
1509
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1467
|
-
var codexHome = (env) => env.CODEX_HOME || path6.join(
|
|
1510
|
+
var codexHome = (env) => env.CODEX_HOME || path6.join(homeDir(), ".codex");
|
|
1468
1511
|
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1469
1512
|
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
1470
1513
|
var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
|
|
@@ -1502,11 +1545,8 @@ function installNotify(cmd, env) {
|
|
|
1502
1545
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1503
1546
|
}
|
|
1504
1547
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
write(next);
|
|
1508
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1509
|
-
}
|
|
1548
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1549
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1510
1550
|
}
|
|
1511
1551
|
}
|
|
1512
1552
|
return {
|
|
@@ -1535,8 +1575,7 @@ function uninstallNotify(env) {
|
|
|
1535
1575
|
}
|
|
1536
1576
|
const prev = previousNotify(n.argv);
|
|
1537
1577
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1538
|
-
|
|
1539
|
-
if (next) writeFileSync3(file, next);
|
|
1578
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1540
1579
|
return { file, changed: true };
|
|
1541
1580
|
}
|
|
1542
1581
|
return { file, changed: false };
|
|
@@ -1566,6 +1605,13 @@ function findRollout(threadId, env = process.env) {
|
|
|
1566
1605
|
walk(sessionsDir(env), 0);
|
|
1567
1606
|
return best;
|
|
1568
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
|
+
}
|
|
1569
1615
|
var codex = {
|
|
1570
1616
|
name: NAME3,
|
|
1571
1617
|
events: CODEX_EVENTS,
|
|
@@ -1608,11 +1654,14 @@ var codex = {
|
|
|
1608
1654
|
if (event === "notify") {
|
|
1609
1655
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1610
1656
|
try {
|
|
1611
|
-
|
|
1657
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1658
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1612
1659
|
} catch {
|
|
1613
1660
|
}
|
|
1614
1661
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1615
1662
|
if (!id2) return null;
|
|
1663
|
+
const file = findRollout(String(id2), input.env);
|
|
1664
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1616
1665
|
const r2 = {
|
|
1617
1666
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1618
1667
|
events: [
|
|
@@ -1622,18 +1671,19 @@ var codex = {
|
|
|
1622
1671
|
harnessEvent: "notify"
|
|
1623
1672
|
}
|
|
1624
1673
|
],
|
|
1625
|
-
hints: {}
|
|
1674
|
+
hints: {},
|
|
1675
|
+
transcript: file
|
|
1626
1676
|
};
|
|
1627
|
-
const file = findRollout(String(id2), input.env);
|
|
1628
|
-
if (file) r2.transcript = file;
|
|
1629
1677
|
return r2;
|
|
1630
1678
|
}
|
|
1631
1679
|
try {
|
|
1632
|
-
|
|
1680
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1681
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1633
1682
|
} catch {
|
|
1634
1683
|
}
|
|
1635
1684
|
const id = s.session_id;
|
|
1636
1685
|
if (!id) return null;
|
|
1686
|
+
if (s.transcript_path === null) return null;
|
|
1637
1687
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1638
1688
|
if (typeof s.model === "string") session.model = s.model;
|
|
1639
1689
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1705,7 +1755,8 @@ var codex = {
|
|
|
1705
1755
|
return null;
|
|
1706
1756
|
}
|
|
1707
1757
|
const r = { session, events, hints: {} };
|
|
1708
|
-
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;
|
|
1709
1760
|
return r;
|
|
1710
1761
|
},
|
|
1711
1762
|
readTranscript(file, fromLine) {
|
|
@@ -1723,16 +1774,17 @@ var codex = {
|
|
|
1723
1774
|
return out;
|
|
1724
1775
|
},
|
|
1725
1776
|
backfill(sinceMs, env = process.env) {
|
|
1726
|
-
const
|
|
1777
|
+
const rows2 = [];
|
|
1727
1778
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1728
1779
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
|
|
1729
1780
|
if (!m?.[1]) continue;
|
|
1781
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1730
1782
|
const facts = codexFacts(f);
|
|
1731
1783
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1732
1784
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1733
|
-
|
|
1785
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1734
1786
|
}
|
|
1735
|
-
return
|
|
1787
|
+
return rows2;
|
|
1736
1788
|
}
|
|
1737
1789
|
};
|
|
1738
1790
|
function prune3(o) {
|
|
@@ -1741,10 +1793,11 @@ function prune3(o) {
|
|
|
1741
1793
|
}
|
|
1742
1794
|
function codexFacts(file) {
|
|
1743
1795
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1744
|
-
|
|
1745
|
-
|
|
1796
|
+
let H = parseLines(head);
|
|
1797
|
+
let T = parseLines(tail);
|
|
1746
1798
|
const out = {};
|
|
1747
1799
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1800
|
+
const boundary = codexReplayBoundary(meta);
|
|
1748
1801
|
if (meta?.payload) {
|
|
1749
1802
|
const p = meta.payload;
|
|
1750
1803
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1752,6 +1805,13 @@ function codexFacts(file) {
|
|
|
1752
1805
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1753
1806
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1754
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
|
+
}
|
|
1755
1815
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1756
1816
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1757
1817
|
for (const j of H) {
|
|
@@ -1759,12 +1819,12 @@ function codexFacts(file) {
|
|
|
1759
1819
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1760
1820
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1761
1821
|
if (!text || codexInjected(text)) continue;
|
|
1762
|
-
const line = text.split("\n")
|
|
1822
|
+
const line = text.split("\n")[0];
|
|
1763
1823
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1764
1824
|
out.title_source = "first-ask";
|
|
1765
1825
|
break;
|
|
1766
1826
|
}
|
|
1767
|
-
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);
|
|
1768
1828
|
if (times.length) {
|
|
1769
1829
|
out.started_at = Math.min(...times);
|
|
1770
1830
|
out.last_at = Math.max(...times);
|
|
@@ -1772,11 +1832,432 @@ function codexFacts(file) {
|
|
|
1772
1832
|
return out;
|
|
1773
1833
|
}
|
|
1774
1834
|
|
|
1775
|
-
// ../core/src/adapters/
|
|
1776
|
-
import {
|
|
1777
|
-
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";
|
|
1778
1838
|
import path7 from "node:path";
|
|
1779
|
-
|
|
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";
|
|
1780
2261
|
var GEMINI_EVENTS = [
|
|
1781
2262
|
"SessionStart",
|
|
1782
2263
|
"BeforeAgent",
|
|
@@ -1787,11 +2268,11 @@ var GEMINI_EVENTS = [
|
|
|
1787
2268
|
"PreCompress",
|
|
1788
2269
|
"SessionEnd"
|
|
1789
2270
|
];
|
|
1790
|
-
var
|
|
1791
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1792
|
-
var settings = (env) =>
|
|
1793
|
-
var
|
|
1794
|
-
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];
|
|
1795
2276
|
function geminiHooksActive(settingsFile) {
|
|
1796
2277
|
let tools;
|
|
1797
2278
|
try {
|
|
@@ -1803,7 +2284,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1803
2284
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1804
2285
|
let version = "";
|
|
1805
2286
|
try {
|
|
1806
|
-
version =
|
|
2287
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1807
2288
|
encoding: "utf8",
|
|
1808
2289
|
timeout: 4e3,
|
|
1809
2290
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1822,13 +2303,13 @@ function geminiHooksActive(settingsFile) {
|
|
|
1822
2303
|
};
|
|
1823
2304
|
}
|
|
1824
2305
|
var geminiCli = {
|
|
1825
|
-
name:
|
|
2306
|
+
name: NAME5,
|
|
1826
2307
|
events: GEMINI_EVENTS,
|
|
1827
2308
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1828
|
-
detect: (env = process.env) =>
|
|
2309
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path8.join(geminiDir(env), "tmp")),
|
|
1829
2310
|
configFiles: (env = process.env) => [settings(env)],
|
|
1830
2311
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1831
|
-
const r = installClaudeShaped(settings(env),
|
|
2312
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1832
2313
|
const a = geminiHooksActive(settings(env));
|
|
1833
2314
|
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1834
2315
|
},
|
|
@@ -1836,7 +2317,7 @@ var geminiCli = {
|
|
|
1836
2317
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1837
2318
|
],
|
|
1838
2319
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1839
|
-
const state = installedClaudeShaped(settings(env),
|
|
2320
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1840
2321
|
const a = geminiHooksActive(settings(env));
|
|
1841
2322
|
return [
|
|
1842
2323
|
{
|
|
@@ -1850,14 +2331,15 @@ var geminiCli = {
|
|
|
1850
2331
|
const event = input.argv[1] ?? "";
|
|
1851
2332
|
let s = {};
|
|
1852
2333
|
try {
|
|
1853
|
-
|
|
2334
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2335
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1854
2336
|
} catch {
|
|
1855
2337
|
}
|
|
1856
2338
|
const id = s.session_id;
|
|
1857
2339
|
if (!id) return null;
|
|
1858
2340
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1859
|
-
const tool =
|
|
1860
|
-
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 });
|
|
1861
2343
|
const events = [];
|
|
1862
2344
|
switch (event) {
|
|
1863
2345
|
case "SessionStart":
|
|
@@ -1938,7 +2420,7 @@ var geminiCli = {
|
|
|
1938
2420
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1939
2421
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1940
2422
|
if (text) {
|
|
1941
|
-
const line = text.split("\n")
|
|
2423
|
+
const line = text.split("\n")[0];
|
|
1942
2424
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1943
2425
|
out.title_source = "first-ask";
|
|
1944
2426
|
}
|
|
@@ -1952,18 +2434,18 @@ var geminiCli = {
|
|
|
1952
2434
|
backfill: () => []
|
|
1953
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
|
|
1954
2436
|
};
|
|
1955
|
-
function
|
|
2437
|
+
function prune5(o) {
|
|
1956
2438
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1957
2439
|
return o;
|
|
1958
2440
|
}
|
|
1959
2441
|
|
|
1960
2442
|
// ../core/src/adapters/index.ts
|
|
1961
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2443
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1962
2444
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1963
2445
|
|
|
1964
2446
|
// ../core/src/config.ts
|
|
1965
|
-
import { chmodSync, existsSync as
|
|
1966
|
-
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";
|
|
1967
2449
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
1968
2450
|
function readConfig(file = configFile()) {
|
|
1969
2451
|
try {
|
|
@@ -1979,10 +2461,10 @@ function readConfig(file = configFile()) {
|
|
|
1979
2461
|
}
|
|
1980
2462
|
}
|
|
1981
2463
|
function writeConfig(c, file = configFile()) {
|
|
1982
|
-
mkdirSync4(
|
|
2464
|
+
mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
|
|
1983
2465
|
writeFileSync4(file, `${JSON.stringify(c, null, 2)}
|
|
1984
2466
|
`, { mode: 384 });
|
|
1985
|
-
if (process.platform !== "win32" &&
|
|
2467
|
+
if (process.platform !== "win32" && existsSync8(file)) {
|
|
1986
2468
|
try {
|
|
1987
2469
|
chmodSync(file, 384);
|
|
1988
2470
|
} catch {
|
|
@@ -2011,7 +2493,7 @@ function encodeTime(t) {
|
|
|
2011
2493
|
function ulid(now = Date.now()) {
|
|
2012
2494
|
if (now === lastTime) {
|
|
2013
2495
|
for (let i = lastRand.length - 1; i >= 0; i--) {
|
|
2014
|
-
const v =
|
|
2496
|
+
const v = lastRand[i] + 1;
|
|
2015
2497
|
if (v < 32) {
|
|
2016
2498
|
lastRand[i] = v;
|
|
2017
2499
|
break;
|
|
@@ -2020,8 +2502,7 @@ function ulid(now = Date.now()) {
|
|
|
2020
2502
|
}
|
|
2021
2503
|
} else {
|
|
2022
2504
|
lastTime = now;
|
|
2023
|
-
|
|
2024
|
-
lastRand = Array.from({ length: 16 }, (_, i) => (b[i] ?? 0) % 32);
|
|
2505
|
+
lastRand = Array.from(randomBytes(16), (b) => b % 32);
|
|
2025
2506
|
}
|
|
2026
2507
|
return encodeTime(now) + lastRand.map((v) => ALPHABET[v]).join("");
|
|
2027
2508
|
}
|
|
@@ -2048,7 +2529,7 @@ function makeEvent(a, ctx) {
|
|
|
2048
2529
|
import {
|
|
2049
2530
|
appendFileSync,
|
|
2050
2531
|
closeSync as closeSync2,
|
|
2051
|
-
existsSync as
|
|
2532
|
+
existsSync as existsSync9,
|
|
2052
2533
|
mkdirSync as mkdirSync5,
|
|
2053
2534
|
openSync as openSync2,
|
|
2054
2535
|
readdirSync as readdirSync5,
|
|
@@ -2059,42 +2540,45 @@ import {
|
|
|
2059
2540
|
unlinkSync as unlinkSync3,
|
|
2060
2541
|
writeFileSync as writeFileSync5
|
|
2061
2542
|
} from "node:fs";
|
|
2062
|
-
import
|
|
2543
|
+
import path10 from "node:path";
|
|
2544
|
+
var READ_CHUNK = 1024 * 1024;
|
|
2545
|
+
var READ_MAX = 8 * 1024 * 1024;
|
|
2063
2546
|
var Outbox = class {
|
|
2064
2547
|
constructor(root) {
|
|
2065
2548
|
this.root = root;
|
|
2066
2549
|
}
|
|
2067
2550
|
root;
|
|
2068
2551
|
dir(harness) {
|
|
2069
|
-
return
|
|
2552
|
+
return path10.join(this.root, "outbox", safeId(harness));
|
|
2070
2553
|
}
|
|
2071
2554
|
file(ref) {
|
|
2072
|
-
return
|
|
2555
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2073
2556
|
}
|
|
2074
2557
|
seqFile(ref) {
|
|
2075
|
-
return
|
|
2558
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2076
2559
|
}
|
|
2077
2560
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2078
2561
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
2079
2562
|
nextSeq(ref) {
|
|
2080
2563
|
const f = this.seqFile(ref);
|
|
2081
|
-
let n =
|
|
2564
|
+
let n = -1;
|
|
2082
2565
|
try {
|
|
2083
|
-
|
|
2566
|
+
const t = readFileSync7(f, "utf8").trim();
|
|
2567
|
+
if (/^\d+$/.test(t)) n = Number(t);
|
|
2084
2568
|
} catch {
|
|
2085
|
-
n = this.lastSeqInFile(ref) + 1;
|
|
2086
2569
|
}
|
|
2087
|
-
|
|
2570
|
+
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2571
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
2088
2572
|
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
2089
2573
|
return n;
|
|
2090
2574
|
}
|
|
2091
2575
|
lastSeqInFile(ref) {
|
|
2092
2576
|
const f = this.file(ref);
|
|
2093
|
-
if (!
|
|
2577
|
+
if (!existsSync9(f)) return -1;
|
|
2094
2578
|
const lines = readFileSync7(f, "utf8").trim().split("\n");
|
|
2095
2579
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2096
2580
|
try {
|
|
2097
|
-
const j = JSON.parse(lines[i]
|
|
2581
|
+
const j = JSON.parse(lines[i]);
|
|
2098
2582
|
if (typeof j.session?.seq === "number") return j.session.seq;
|
|
2099
2583
|
} catch {
|
|
2100
2584
|
}
|
|
@@ -2110,10 +2594,10 @@ var Outbox = class {
|
|
|
2110
2594
|
/** Every session file, newest first. */
|
|
2111
2595
|
sessions() {
|
|
2112
2596
|
const out = [];
|
|
2113
|
-
const base =
|
|
2114
|
-
if (!
|
|
2597
|
+
const base = path10.join(this.root, "outbox");
|
|
2598
|
+
if (!existsSync9(base)) return [];
|
|
2115
2599
|
for (const h of readdirSync5(base)) {
|
|
2116
|
-
const d =
|
|
2600
|
+
const d = path10.join(base, h);
|
|
2117
2601
|
let names = [];
|
|
2118
2602
|
try {
|
|
2119
2603
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2122,7 +2606,7 @@ var Outbox = class {
|
|
|
2122
2606
|
}
|
|
2123
2607
|
for (const n of names) {
|
|
2124
2608
|
try {
|
|
2125
|
-
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 });
|
|
2126
2610
|
} catch {
|
|
2127
2611
|
}
|
|
2128
2612
|
}
|
|
@@ -2130,31 +2614,43 @@ var Outbox = class {
|
|
|
2130
2614
|
return out.sort((a, b) => b.mtime - a.mtime).map((x) => x.ref);
|
|
2131
2615
|
}
|
|
2132
2616
|
/** Read events after byte offset `from`; returns them and the new offset. Lines
|
|
2133
|
-
* 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. */
|
|
2134
2622
|
read(ref, from, maxEvents = 500) {
|
|
2135
2623
|
const f = this.file(ref);
|
|
2136
|
-
if (!
|
|
2624
|
+
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2137
2625
|
const size = statSync4(f).size;
|
|
2138
2626
|
if (from >= size) return { events: [], offset: from };
|
|
2139
2627
|
const fd = openSync2(f, "r");
|
|
2140
2628
|
try {
|
|
2141
|
-
const buf = Buffer.alloc(Math.min(size - from, 8 * 1024 * 1024));
|
|
2142
|
-
const n = readSync2(fd, buf, 0, buf.length, from);
|
|
2143
|
-
const text = buf.subarray(0, n).toString("utf8");
|
|
2144
2629
|
const events = [];
|
|
2145
2630
|
let offset = from;
|
|
2146
|
-
let
|
|
2147
|
-
while (events.length < maxEvents) {
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2150
|
-
const
|
|
2151
|
-
pos =
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
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
|
+
}
|
|
2157
2649
|
}
|
|
2650
|
+
offset += used;
|
|
2651
|
+
if (used) continue;
|
|
2652
|
+
if (offset + n >= size || chunk >= READ_MAX) break;
|
|
2653
|
+
chunk *= 2;
|
|
2158
2654
|
}
|
|
2159
2655
|
return { events, offset };
|
|
2160
2656
|
} finally {
|
|
@@ -2198,7 +2694,7 @@ var Cursors = class {
|
|
|
2198
2694
|
root;
|
|
2199
2695
|
sink;
|
|
2200
2696
|
file() {
|
|
2201
|
-
return
|
|
2697
|
+
return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2202
2698
|
}
|
|
2203
2699
|
read() {
|
|
2204
2700
|
try {
|
|
@@ -2209,7 +2705,7 @@ var Cursors = class {
|
|
|
2209
2705
|
}
|
|
2210
2706
|
write(c) {
|
|
2211
2707
|
const f = this.file();
|
|
2212
|
-
mkdirSync5(
|
|
2708
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
2213
2709
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2214
2710
|
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
2215
2711
|
renameSync(tmp, f);
|
|
@@ -2219,11 +2715,14 @@ var Cursors = class {
|
|
|
2219
2715
|
}
|
|
2220
2716
|
};
|
|
2221
2717
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
2222
|
-
|
|
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");
|
|
2223
2722
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2224
|
-
const f =
|
|
2723
|
+
const f = path10.join(dir, `${name}.lock`);
|
|
2225
2724
|
try {
|
|
2226
|
-
if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
2725
|
+
if (Date.now() - statSync4(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
|
|
2227
2726
|
else return null;
|
|
2228
2727
|
} catch {
|
|
2229
2728
|
}
|
|
@@ -2239,9 +2738,18 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2239
2738
|
}
|
|
2240
2739
|
};
|
|
2241
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
|
+
}
|
|
2242
2750
|
|
|
2243
2751
|
// ../core/src/privacy/pii.ts
|
|
2244
|
-
import { createHash as
|
|
2752
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2245
2753
|
var RULESET_PII = "pii@1";
|
|
2246
2754
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2247
2755
|
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
@@ -2263,7 +2771,7 @@ function reducePii(text, home) {
|
|
|
2263
2771
|
return out;
|
|
2264
2772
|
}
|
|
2265
2773
|
function hashMachine(name) {
|
|
2266
|
-
return
|
|
2774
|
+
return createHash4("sha256").update(name).digest("hex").slice(0, 8);
|
|
2267
2775
|
}
|
|
2268
2776
|
function reducePiiDeep(v, home) {
|
|
2269
2777
|
if (typeof v === "string") return reducePii(v, home);
|
|
@@ -2352,12 +2860,12 @@ function redactAssignments(text) {
|
|
|
2352
2860
|
let last = 0;
|
|
2353
2861
|
NAME_SEP.lastIndex = 0;
|
|
2354
2862
|
for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
|
|
2355
|
-
const name = m[1]
|
|
2863
|
+
const name = m[1];
|
|
2356
2864
|
if (!isSecretName(name)) continue;
|
|
2357
2865
|
VALUE.lastIndex = NAME_SEP.lastIndex;
|
|
2358
2866
|
const v = VALUE.exec(text);
|
|
2359
2867
|
if (!v) continue;
|
|
2360
|
-
const value = v[1] ?? v[2] ?? v[3] ?? v[4]
|
|
2868
|
+
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
2361
2869
|
NAME_SEP.lastIndex = VALUE.lastIndex;
|
|
2362
2870
|
if (isPlaceholder(value) || value.includes(REDACTED)) continue;
|
|
2363
2871
|
if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
|
|
@@ -2413,7 +2921,7 @@ function redactSecrets(text) {
|
|
|
2413
2921
|
let out = text;
|
|
2414
2922
|
for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
|
|
2415
2923
|
out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
|
|
2416
|
-
out = out.replace(AUTH_HEADER, (
|
|
2924
|
+
out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
|
|
2417
2925
|
out = redactAssignments(out);
|
|
2418
2926
|
out = out.replace(
|
|
2419
2927
|
PROSE,
|
|
@@ -2442,6 +2950,7 @@ var TYPE_TIER = {
|
|
|
2442
2950
|
"turn.started": 0,
|
|
2443
2951
|
"turn.ended": 0,
|
|
2444
2952
|
"turn.transcript": 2,
|
|
2953
|
+
"turn.progress": 2,
|
|
2445
2954
|
"tool.started": 1,
|
|
2446
2955
|
"tool.ended": 1,
|
|
2447
2956
|
"files.changed": 1,
|
|
@@ -2461,6 +2970,7 @@ var DATA_ALLOW = {
|
|
|
2461
2970
|
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2462
2971
|
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
2463
2972
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2973
|
+
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
2464
2974
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2465
2975
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
2466
2976
|
"files.changed": { 1: ["paths"] },
|
|
@@ -2501,10 +3011,12 @@ function cap(v, max) {
|
|
|
2501
3011
|
}
|
|
2502
3012
|
return v;
|
|
2503
3013
|
}
|
|
3014
|
+
function capTail(v, max) {
|
|
3015
|
+
return v.length > max ? `\u2026${v.slice(v.length - max + 1)}` : v;
|
|
3016
|
+
}
|
|
2504
3017
|
function filterEvent(event, tier, pii, opts = {}) {
|
|
2505
3018
|
const minTier = TYPE_TIER[event.type];
|
|
2506
3019
|
if (minTier !== void 0 && minTier > tier) return null;
|
|
2507
|
-
if (event.type === "native" && tier < 3) return null;
|
|
2508
3020
|
const session = {};
|
|
2509
3021
|
for (const k of SESSION_ALLOW)
|
|
2510
3022
|
if (event.session[k] !== void 0)
|
|
@@ -2514,7 +3026,8 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2514
3026
|
if (keys === null) data = { ...event.data };
|
|
2515
3027
|
else for (const k of keys) if (event.data[k] !== void 0) data[k] = event.data[k];
|
|
2516
3028
|
if (typeof data.user === "string") data.user = cap(data.user, MAX_SIDE);
|
|
2517
|
-
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);
|
|
2518
3031
|
if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
|
|
2519
3032
|
if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
|
|
2520
3033
|
if (typeof data.message === "string") data.message = cap(data.message, 200);
|
|
@@ -2535,9 +3048,15 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2535
3048
|
return out;
|
|
2536
3049
|
}
|
|
2537
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
|
+
|
|
2538
3057
|
// ../core/src/sinks/file.ts
|
|
2539
3058
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2540
|
-
import
|
|
3059
|
+
import path11 from "node:path";
|
|
2541
3060
|
var FileSink = class {
|
|
2542
3061
|
constructor(name, file, tier, pii) {
|
|
2543
3062
|
this.name = name;
|
|
@@ -2551,7 +3070,7 @@ var FileSink = class {
|
|
|
2551
3070
|
pii;
|
|
2552
3071
|
async send(events) {
|
|
2553
3072
|
try {
|
|
2554
|
-
mkdirSync6(
|
|
3073
|
+
mkdirSync6(path11.dirname(this.file), { recursive: true });
|
|
2555
3074
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2556
3075
|
`);
|
|
2557
3076
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -2575,15 +3094,19 @@ var StdoutSink = class {
|
|
|
2575
3094
|
pii;
|
|
2576
3095
|
write;
|
|
2577
3096
|
async send(events) {
|
|
2578
|
-
|
|
2579
|
-
|
|
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
|
+
}
|
|
2580
3103
|
}
|
|
2581
3104
|
};
|
|
2582
3105
|
|
|
2583
3106
|
// src/control/aliases.ts
|
|
2584
3107
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2585
|
-
import
|
|
2586
|
-
var aliasFile = (controlDir) =>
|
|
3108
|
+
import path12 from "node:path";
|
|
3109
|
+
var aliasFile = (controlDir) => path12.join(controlDir, "aliases.json");
|
|
2587
3110
|
function readAliases(file) {
|
|
2588
3111
|
try {
|
|
2589
3112
|
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
@@ -2608,11 +3131,11 @@ function receiverIdOf(all, harness, id) {
|
|
|
2608
3131
|
// src/control/local.ts
|
|
2609
3132
|
import net from "node:net";
|
|
2610
3133
|
import os2 from "node:os";
|
|
2611
|
-
import
|
|
3134
|
+
import path13 from "node:path";
|
|
2612
3135
|
function socketPath(state, env = process.env) {
|
|
2613
3136
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
2614
3137
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
2615
|
-
return
|
|
3138
|
+
return path13.join(state, "control.sock");
|
|
2616
3139
|
}
|
|
2617
3140
|
function ask(sock, req, timeoutMs) {
|
|
2618
3141
|
return new Promise((resolve) => {
|
|
@@ -2646,7 +3169,7 @@ function ask(sock, req, timeoutMs) {
|
|
|
2646
3169
|
|
|
2647
3170
|
// src/secrets.ts
|
|
2648
3171
|
import { spawnSync } from "node:child_process";
|
|
2649
|
-
import { createHash as
|
|
3172
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
2650
3173
|
var SERVICE = "sessionpipe";
|
|
2651
3174
|
var defaultRunner = (cmd, args, input) => {
|
|
2652
3175
|
const r = spawnSync(cmd, args, {
|
|
@@ -2658,7 +3181,7 @@ var defaultRunner = (cmd, args, input) => {
|
|
|
2658
3181
|
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
2659
3182
|
};
|
|
2660
3183
|
var sinkAccount = (name, what = "token") => {
|
|
2661
|
-
const scope = process.env.SESSIONPIPE_CONFIG ? `@${
|
|
3184
|
+
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
2662
3185
|
return `sink:${name}:${what}${scope}`;
|
|
2663
3186
|
};
|
|
2664
3187
|
function secretGet(store, account, run = defaultRunner) {
|
|
@@ -2678,7 +3201,7 @@ function sinkWithSecrets(s, run = defaultRunner) {
|
|
|
2678
3201
|
// src/run.ts
|
|
2679
3202
|
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
2680
3203
|
const sock = socketPath(state);
|
|
2681
|
-
if (process.platform !== "win32" && !
|
|
3204
|
+
if (process.platform !== "win32" && !existsSync10(sock)) return;
|
|
2682
3205
|
await ask(
|
|
2683
3206
|
sock,
|
|
2684
3207
|
{
|
|
@@ -2694,14 +3217,21 @@ async function tellDaemon(state, job, session, cwd, transcript) {
|
|
|
2694
3217
|
function enqueueJob(state, job) {
|
|
2695
3218
|
const jobs = jobsDir(state);
|
|
2696
3219
|
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
2697
|
-
const f =
|
|
3220
|
+
const f = path14.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
2698
3221
|
writeFileSync7(f, JSON.stringify(job), { mode: 384 });
|
|
2699
3222
|
return f;
|
|
2700
3223
|
}
|
|
3224
|
+
function releaseJob(claimed) {
|
|
3225
|
+
if (!claimed) return;
|
|
3226
|
+
try {
|
|
3227
|
+
unlinkSync4(claimed);
|
|
3228
|
+
} catch {
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
2701
3231
|
function log(state, line) {
|
|
2702
3232
|
try {
|
|
2703
3233
|
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
2704
|
-
const f =
|
|
3234
|
+
const f = path14.join(state, "log");
|
|
2705
3235
|
try {
|
|
2706
3236
|
if (statSync5(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
2707
3237
|
} catch {
|
|
@@ -2712,7 +3242,7 @@ function log(state, line) {
|
|
|
2712
3242
|
}
|
|
2713
3243
|
}
|
|
2714
3244
|
function factsState(state) {
|
|
2715
|
-
const file = (h, s) =>
|
|
3245
|
+
const file = (h, s) => path14.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
2716
3246
|
return {
|
|
2717
3247
|
get(h, s) {
|
|
2718
3248
|
try {
|
|
@@ -2725,7 +3255,7 @@ function factsState(state) {
|
|
|
2725
3255
|
const cur = this.get(h, s);
|
|
2726
3256
|
const next = { ...cur, ...patch };
|
|
2727
3257
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
2728
|
-
mkdirSync8(
|
|
3258
|
+
mkdirSync8(path14.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
2729
3259
|
writeFileSync7(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
2730
3260
|
}
|
|
2731
3261
|
};
|
|
@@ -2747,39 +3277,75 @@ function buildSinks(sinks, o = {}) {
|
|
|
2747
3277
|
}
|
|
2748
3278
|
return out;
|
|
2749
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;
|
|
2750
3301
|
async function runJob(job, opts = {}) {
|
|
2751
3302
|
const state = opts.state ?? stateDir();
|
|
2752
3303
|
const cfg = readConfig();
|
|
2753
3304
|
const adapter = adapterByName(job.harness);
|
|
2754
|
-
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
|
+
}
|
|
2755
3309
|
const r = adapter.fromHook({ argv: job.argv, stdin: job.stdin, env: { ...process.env, ...job.env }, cwd: job.cwd });
|
|
2756
|
-
if (!r || !r.session.id)
|
|
2757
|
-
|
|
2758
|
-
|
|
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);
|
|
2759
3317
|
const outbox = new Outbox(state);
|
|
2760
3318
|
const ref = { harness: job.harness, session: id };
|
|
2761
3319
|
let release = takeLock(state, ref);
|
|
2762
|
-
|
|
2763
|
-
|
|
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));
|
|
2764
3323
|
release = takeLock(state, ref);
|
|
2765
3324
|
}
|
|
2766
3325
|
if (!release) {
|
|
2767
|
-
log(state, `${job.harness} ${job.event}: session ${id} locked for
|
|
2768
|
-
enqueueJob(state, job);
|
|
2769
|
-
|
|
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 };
|
|
2770
3330
|
}
|
|
3331
|
+
const events = [];
|
|
2771
3332
|
try {
|
|
2772
3333
|
const facts = factsState(state);
|
|
2773
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);
|
|
2774
3340
|
const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
|
|
2775
3341
|
let learned = {};
|
|
2776
3342
|
if (heavy) {
|
|
2777
|
-
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 });
|
|
2778
3344
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
2779
3345
|
learned = rest;
|
|
2780
3346
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
2781
3347
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
2782
|
-
if (job.harness
|
|
3348
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path14.isAbsolute(r.session.cwd))
|
|
2783
3349
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
2784
3350
|
}
|
|
2785
3351
|
const known = mem.session ?? {};
|
|
@@ -2797,7 +3363,6 @@ async function runJob(job, opts = {}) {
|
|
|
2797
3363
|
...r.session.model ? { model: r.session.model } : {}
|
|
2798
3364
|
};
|
|
2799
3365
|
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
2800
|
-
const events = [];
|
|
2801
3366
|
for (const a of r.events) {
|
|
2802
3367
|
const e = makeEvent(a, {
|
|
2803
3368
|
harness: { name: job.harness, ...version ? { version } : {} },
|
|
@@ -2807,6 +3372,37 @@ async function runJob(job, opts = {}) {
|
|
|
2807
3372
|
events.push(e);
|
|
2808
3373
|
}
|
|
2809
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 });
|
|
2810
3406
|
if (wantsTranscript && r.transcript && r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended")) {
|
|
2811
3407
|
const cur = facts.get(job.harness, id);
|
|
2812
3408
|
const fromLine = Number(cur.transcript_line) || 0;
|
|
@@ -2834,17 +3430,80 @@ async function runJob(job, opts = {}) {
|
|
|
2834
3430
|
ref,
|
|
2835
3431
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
2836
3432
|
);
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
3433
|
+
releaseJob(opts.claimed);
|
|
3434
|
+
log(
|
|
3435
|
+
state,
|
|
3436
|
+
`${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${shortId(job.harness, id)})`
|
|
3437
|
+
);
|
|
2841
3438
|
} finally {
|
|
2842
3439
|
release();
|
|
2843
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
|
+
}
|
|
2844
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
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
var LOCK_WAIT_MS = 6e3;
|
|
3503
|
+
var CWD_FROM_HOOKS = /* @__PURE__ */ new Set(["antigravity", "devin"]);
|
|
2845
3504
|
var BATCH = 50;
|
|
2846
3505
|
var MAX_PER_RUN = 500;
|
|
2847
|
-
async function flush(state, sinkConfigs, outbox, first) {
|
|
3506
|
+
async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
2848
3507
|
const delivered = {};
|
|
2849
3508
|
const sinks = buildSinks(sinkConfigs, {
|
|
2850
3509
|
locked: (name) => log(state, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
@@ -2854,13 +3513,39 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2854
3513
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
2855
3514
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
2856
3515
|
}
|
|
2857
|
-
|
|
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
|
+
}
|
|
2858
3522
|
const cursors = new Cursors(state, sink.name);
|
|
2859
3523
|
const c = cursors.read();
|
|
2860
3524
|
let sent = 0;
|
|
2861
3525
|
let stalled = false;
|
|
2862
3526
|
let batch = [];
|
|
2863
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
|
+
};
|
|
2864
3549
|
const send = async () => {
|
|
2865
3550
|
if (!batch.length) {
|
|
2866
3551
|
Object.assign(c, pending);
|
|
@@ -2868,15 +3553,24 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2868
3553
|
cursors.write(c);
|
|
2869
3554
|
return true;
|
|
2870
3555
|
}
|
|
2871
|
-
const res = await
|
|
3556
|
+
const res = await deliver(batch);
|
|
2872
3557
|
if (!res.ok) {
|
|
2873
|
-
|
|
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}`);
|
|
2874
3565
|
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2875
3566
|
if (res.action !== "drop") {
|
|
2876
3567
|
batch = [];
|
|
2877
3568
|
pending = {};
|
|
2878
3569
|
return false;
|
|
2879
3570
|
}
|
|
3571
|
+
} else if (backoff) {
|
|
3572
|
+
clearBackoff(state, sink.name);
|
|
3573
|
+
backoff = null;
|
|
2880
3574
|
}
|
|
2881
3575
|
sent += batch.length;
|
|
2882
3576
|
Object.assign(c, pending);
|
|
@@ -2911,7 +3605,8 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2911
3605
|
if (!await send()) stalled = true;
|
|
2912
3606
|
}
|
|
2913
3607
|
delivered[sink.name] = sent;
|
|
2914
|
-
}
|
|
3608
|
+
};
|
|
3609
|
+
await Promise.all(sinks.map(drainSink));
|
|
2915
3610
|
return delivered;
|
|
2916
3611
|
}
|
|
2917
3612
|
function applySinkVerdict(name, res) {
|
|
@@ -2925,7 +3620,7 @@ function applySinkVerdict(name, res) {
|
|
|
2925
3620
|
} catch {
|
|
2926
3621
|
}
|
|
2927
3622
|
}
|
|
2928
|
-
var jobsDir = (state) =>
|
|
3623
|
+
var jobsDir = (state) => path14.join(state, "jobs");
|
|
2929
3624
|
export {
|
|
2930
3625
|
HttpSink,
|
|
2931
3626
|
buildSinks,
|