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/worker.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe 0.
|
|
2
|
+
// sessionpipe 0.9.0 · Apache-2.0 · https://sessionpipe.org
|
|
3
3
|
|
|
4
4
|
// src/worker.ts
|
|
5
|
-
import {
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
6
8
|
|
|
7
9
|
// ../core/src/adapters/antigravity.ts
|
|
8
10
|
import {
|
|
@@ -20,20 +22,20 @@ import path4 from "node:path";
|
|
|
20
22
|
// ../core/src/paths.ts
|
|
21
23
|
import os from "node:os";
|
|
22
24
|
import path from "node:path";
|
|
23
|
-
var
|
|
25
|
+
var homeDir = () => os.homedir();
|
|
24
26
|
function configFile(env = process.env) {
|
|
25
27
|
if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);
|
|
26
28
|
if (process.platform === "win32")
|
|
27
|
-
return path.join(env.APPDATA || path.join(
|
|
28
|
-
return path.join(env.XDG_CONFIG_HOME || path.join(
|
|
29
|
+
return path.join(env.APPDATA || path.join(homeDir(), "AppData", "Roaming"), "sessionpipe", "config.json");
|
|
30
|
+
return path.join(env.XDG_CONFIG_HOME || path.join(homeDir(), ".config"), "sessionpipe", "config.json");
|
|
29
31
|
}
|
|
30
32
|
function stateDir(env = process.env) {
|
|
31
33
|
if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);
|
|
32
34
|
if (process.platform === "win32")
|
|
33
|
-
return path.join(env.LOCALAPPDATA || path.join(
|
|
34
|
-
return path.join(env.XDG_STATE_HOME || path.join(
|
|
35
|
+
return path.join(env.LOCALAPPDATA || path.join(homeDir(), "AppData", "Local"), "sessionpipe");
|
|
36
|
+
return path.join(env.XDG_STATE_HOME || path.join(homeDir(), ".local", "state"), "sessionpipe");
|
|
35
37
|
}
|
|
36
|
-
function tilde(p, home =
|
|
38
|
+
function tilde(p, home = homeDir()) {
|
|
37
39
|
if (!p) return p;
|
|
38
40
|
if (p === home) return "~";
|
|
39
41
|
return p.startsWith(home + path.sep) || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
|
|
@@ -130,6 +132,19 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
130
132
|
return out;
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
// ../core/src/adapters/codex-rollout.ts
|
|
136
|
+
function codexSubagent(meta) {
|
|
137
|
+
if (meta?.type !== "session_meta") return false;
|
|
138
|
+
const p = meta.payload;
|
|
139
|
+
return !!p?.source?.subagent;
|
|
140
|
+
}
|
|
141
|
+
function codexReplayBoundary(meta) {
|
|
142
|
+
if (meta?.type !== "session_meta") return void 0;
|
|
143
|
+
const p = meta.payload;
|
|
144
|
+
if (!p || !p.forked_from_id && !p.source?.subagent) return void 0;
|
|
145
|
+
return (record) => typeof p.id === "string" && !!p.id && record?.type === "event_msg" && record.payload?.thread_id === p.id;
|
|
146
|
+
}
|
|
147
|
+
|
|
133
148
|
// ../core/src/readers/transcripts.ts
|
|
134
149
|
function pairTurns(msgs, endLine) {
|
|
135
150
|
const turns = [];
|
|
@@ -152,10 +167,14 @@ ${m.text}`;
|
|
|
152
167
|
}
|
|
153
168
|
if (cur?.assistant) turns.push(cur);
|
|
154
169
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
155
|
-
|
|
170
|
+
const out = {
|
|
171
|
+
turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
|
|
172
|
+
line
|
|
173
|
+
};
|
|
174
|
+
if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
|
|
175
|
+
return out;
|
|
156
176
|
}
|
|
157
|
-
var isInjected = (text) => /^<[a-z_-]
|
|
158
|
-
var endOf = (lines) => lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
177
|
+
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
159
178
|
var parse = (raw) => {
|
|
160
179
|
try {
|
|
161
180
|
return JSON.parse(raw);
|
|
@@ -163,6 +182,11 @@ var parse = (raw) => {
|
|
|
163
182
|
return null;
|
|
164
183
|
}
|
|
165
184
|
};
|
|
185
|
+
var endOf = (lines) => {
|
|
186
|
+
const last = lines[lines.length - 1];
|
|
187
|
+
if (last === void 0) return 0;
|
|
188
|
+
return last === "" || parse(last) === null ? lines.length - 1 : lines.length;
|
|
189
|
+
};
|
|
166
190
|
var textParts = (c, kinds) => {
|
|
167
191
|
if (typeof c === "string") return c;
|
|
168
192
|
if (!Array.isArray(c)) return "";
|
|
@@ -170,13 +194,21 @@ var textParts = (c, kinds) => {
|
|
|
170
194
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
171
195
|
).map((b) => b.text).join("\n");
|
|
172
196
|
};
|
|
197
|
+
var queuedPrompt = (a) => {
|
|
198
|
+
if (!a || typeof a !== "object") return "";
|
|
199
|
+
const q = a;
|
|
200
|
+
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
201
|
+
if (q.origin?.kind && q.origin.kind !== "human") return "";
|
|
202
|
+
return textParts(q.prompt, ["text"]).trim();
|
|
203
|
+
};
|
|
173
204
|
function readClaude(lines, from) {
|
|
174
205
|
const msgs = [];
|
|
175
206
|
for (let i = from; i < lines.length; i++) {
|
|
176
207
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
177
208
|
if (!j || j.isSidechain) continue;
|
|
178
209
|
const origin = j.origin;
|
|
179
|
-
if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human"))
|
|
210
|
+
if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
|
|
211
|
+
continue;
|
|
180
212
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
181
213
|
const c = j.message?.content;
|
|
182
214
|
if (j.type === "user") {
|
|
@@ -186,6 +218,9 @@ function readClaude(lines, from) {
|
|
|
186
218
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
187
219
|
const text = textParts(c, ["text"]).trim();
|
|
188
220
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
221
|
+
} else if (j.type === "attachment") {
|
|
222
|
+
const text = queuedPrompt(j.attachment);
|
|
223
|
+
if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
|
|
189
224
|
}
|
|
190
225
|
}
|
|
191
226
|
return pairTurns(msgs, endOf(lines));
|
|
@@ -195,7 +230,10 @@ function codexInjected(text) {
|
|
|
195
230
|
}
|
|
196
231
|
function readCodex(lines, from) {
|
|
197
232
|
const msgs = [];
|
|
198
|
-
|
|
233
|
+
const boundary = codexReplayBoundary(parse(lines.find((line) => line.trim()) ?? ""));
|
|
234
|
+
const start = boundary ? lines.findIndex((line) => boundary(parse(line))) : 0;
|
|
235
|
+
if (start < 0) return pairTurns(msgs, endOf(lines));
|
|
236
|
+
for (let i = Math.max(from, start); i < lines.length; i++) {
|
|
199
237
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
200
238
|
if (!j || j.type !== "response_item") continue;
|
|
201
239
|
const p = j.payload;
|
|
@@ -233,7 +271,7 @@ function readAntigravity(lines, from) {
|
|
|
233
271
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
234
272
|
if (j.type === "USER_INPUT") {
|
|
235
273
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
236
|
-
const text = (m ? m[1]
|
|
274
|
+
const text = (m ? m[1] : j.content).trim();
|
|
237
275
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
238
276
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
239
277
|
const text = j.content.trim();
|
|
@@ -281,6 +319,10 @@ function formatJson(value, f) {
|
|
|
281
319
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
282
320
|
return f.finalNewline ? text + f.eol : text;
|
|
283
321
|
}
|
|
322
|
+
function readJson(file) {
|
|
323
|
+
const f = readJsonFile(file);
|
|
324
|
+
return f.ok ? f.value : {};
|
|
325
|
+
}
|
|
284
326
|
function entryFor(event, harness, cmd, o) {
|
|
285
327
|
const matcher = o.matcherFor(event);
|
|
286
328
|
return {
|
|
@@ -350,8 +392,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
350
392
|
unlinkSync(file);
|
|
351
393
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
352
394
|
}
|
|
353
|
-
|
|
354
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
395
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
355
396
|
return { file, changed: true };
|
|
356
397
|
}
|
|
357
398
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -378,7 +419,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
378
419
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
379
420
|
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';
|
|
380
421
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
381
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(
|
|
422
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
|
|
382
423
|
var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
|
|
383
424
|
var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
384
425
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
@@ -444,7 +485,8 @@ var antigravity = {
|
|
|
444
485
|
const event = input.argv[1] ?? "";
|
|
445
486
|
let s = {};
|
|
446
487
|
try {
|
|
447
|
-
|
|
488
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
489
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
448
490
|
} catch {
|
|
449
491
|
}
|
|
450
492
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -500,19 +542,16 @@ var antigravity = {
|
|
|
500
542
|
}
|
|
501
543
|
return readAntigravity(text.split("\n"), fromLine);
|
|
502
544
|
},
|
|
503
|
-
facts(session, transcript, hints
|
|
545
|
+
facts(session, transcript, hints) {
|
|
504
546
|
if (!transcript) return {};
|
|
505
547
|
return antigravityFacts(transcript, {
|
|
506
548
|
conversation: session.id,
|
|
507
549
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
508
|
-
state,
|
|
509
550
|
env: process.env
|
|
510
551
|
});
|
|
511
552
|
},
|
|
512
553
|
backfill(sinceMs, env = process.env) {
|
|
513
|
-
const
|
|
514
|
-
const noState = { get: () => ({}), save: () => {
|
|
515
|
-
} };
|
|
554
|
+
const rows2 = [];
|
|
516
555
|
for (const data of dataDirs(env)) {
|
|
517
556
|
let ids = [];
|
|
518
557
|
try {
|
|
@@ -526,13 +565,13 @@ var antigravity = {
|
|
|
526
565
|
} catch {
|
|
527
566
|
continue;
|
|
528
567
|
}
|
|
529
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
568
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
530
569
|
if (!facts.started_at || !facts.last_at) continue;
|
|
531
570
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
532
|
-
|
|
571
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
533
572
|
}
|
|
534
573
|
}
|
|
535
|
-
return
|
|
574
|
+
return rows2;
|
|
536
575
|
}
|
|
537
576
|
};
|
|
538
577
|
function prune(o) {
|
|
@@ -582,7 +621,7 @@ function agProjects(dir) {
|
|
|
582
621
|
}
|
|
583
622
|
const folders = [];
|
|
584
623
|
for (const r of j?.projectResources?.resources ?? []) {
|
|
585
|
-
const f = agPath(r?.folderUri);
|
|
624
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
586
625
|
if (f) folders.push(...agVariants(f));
|
|
587
626
|
}
|
|
588
627
|
if (folders.length) out.push({ id, folders });
|
|
@@ -636,7 +675,7 @@ function antigravityFacts(file, opts) {
|
|
|
636
675
|
for (const j of H) {
|
|
637
676
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
638
677
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
639
|
-
const text = (m ? m[1]
|
|
678
|
+
const text = (m ? m[1] : j.content).trim();
|
|
640
679
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
641
680
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
642
681
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -684,10 +723,9 @@ function gitFacts(cwd) {
|
|
|
684
723
|
return out;
|
|
685
724
|
}
|
|
686
725
|
function stripCredentials(url) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
return u.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
726
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
727
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
728
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
691
729
|
}
|
|
692
730
|
|
|
693
731
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -718,7 +756,7 @@ var OPTS = {
|
|
|
718
756
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
719
757
|
};
|
|
720
758
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
721
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
759
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
722
760
|
const out = [];
|
|
723
761
|
const add = (d) => {
|
|
724
762
|
if (!d) return;
|
|
@@ -759,11 +797,13 @@ var claudeCode = {
|
|
|
759
797
|
const event = input.argv[1] ?? "";
|
|
760
798
|
let s = {};
|
|
761
799
|
try {
|
|
762
|
-
|
|
800
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
801
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
763
802
|
} catch {
|
|
764
803
|
}
|
|
765
804
|
const id = s.session_id ?? s.sessionId;
|
|
766
805
|
if (!id) return null;
|
|
806
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
767
807
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
768
808
|
if (typeof s.model === "string") session.model = s.model;
|
|
769
809
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -882,6 +922,7 @@ var claudeCode = {
|
|
|
882
922
|
return null;
|
|
883
923
|
}
|
|
884
924
|
const hints = {};
|
|
925
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
885
926
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
886
927
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
887
928
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
@@ -907,7 +948,7 @@ var claudeCode = {
|
|
|
907
948
|
return out;
|
|
908
949
|
},
|
|
909
950
|
backfill(sinceMs, env = process.env) {
|
|
910
|
-
const
|
|
951
|
+
const rows2 = [];
|
|
911
952
|
const files = [];
|
|
912
953
|
for (const d of claudeDirs(env))
|
|
913
954
|
files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -919,19 +960,23 @@ var claudeCode = {
|
|
|
919
960
|
if (!facts.started_at || !facts.last_at) continue;
|
|
920
961
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
921
962
|
const cwd = rest.cwd;
|
|
922
|
-
|
|
963
|
+
rows2.push({
|
|
923
964
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
924
965
|
started_at,
|
|
925
966
|
last_at
|
|
926
967
|
});
|
|
927
968
|
}
|
|
928
|
-
return
|
|
969
|
+
return rows2;
|
|
929
970
|
}
|
|
930
971
|
};
|
|
931
972
|
function prune2(o) {
|
|
932
973
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
933
974
|
return o;
|
|
934
975
|
}
|
|
976
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
977
|
+
function notClaudeCode(id, s) {
|
|
978
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
979
|
+
}
|
|
935
980
|
function pickSource(v) {
|
|
936
981
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
937
982
|
}
|
|
@@ -1037,23 +1082,27 @@ function parentOf() {
|
|
|
1037
1082
|
}
|
|
1038
1083
|
return (pid) => table.get(pid) || 0;
|
|
1039
1084
|
}
|
|
1040
|
-
var
|
|
1041
|
-
function
|
|
1042
|
-
const hit = accountCache.get(dir);
|
|
1043
|
-
if (hit !== void 0) return hit;
|
|
1085
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1086
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1044
1087
|
const files = [path5.join(dir, ".claude.json")];
|
|
1045
|
-
if (path5.resolve(dir) === path5.join(
|
|
1046
|
-
let acct = null;
|
|
1088
|
+
if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
|
|
1047
1089
|
for (const f of files) {
|
|
1048
1090
|
try {
|
|
1049
1091
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1050
|
-
if (a?.accountUuid) {
|
|
1051
|
-
|
|
1052
|
-
|
|
1092
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1093
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1094
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1053
1095
|
}
|
|
1054
1096
|
} catch {
|
|
1055
1097
|
}
|
|
1056
1098
|
}
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1102
|
+
function claudeAccount(dir) {
|
|
1103
|
+
const hit = accountCache.get(dir);
|
|
1104
|
+
if (hit !== void 0) return hit;
|
|
1105
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1057
1106
|
accountCache.set(dir, acct);
|
|
1058
1107
|
return acct;
|
|
1059
1108
|
}
|
|
@@ -1273,18 +1322,13 @@ function parseArgv(raw) {
|
|
|
1273
1322
|
function readNotify(text) {
|
|
1274
1323
|
const { head, tail, at } = split(text);
|
|
1275
1324
|
const m = NOTIFY_LINE.exec(head);
|
|
1276
|
-
if (m)
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
index: m.index,
|
|
1281
|
-
length: m[0].length,
|
|
1282
|
-
misplaced: false,
|
|
1283
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1284
|
-
};
|
|
1325
|
+
if (m) {
|
|
1326
|
+
const raw = m[1];
|
|
1327
|
+
return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
|
|
1328
|
+
}
|
|
1285
1329
|
const k = NOTIFY_KEY.exec(head);
|
|
1286
1330
|
if (k) {
|
|
1287
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1331
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1288
1332
|
return {
|
|
1289
1333
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1290
1334
|
argv: null,
|
|
@@ -1295,15 +1339,11 @@ function readNotify(text) {
|
|
|
1295
1339
|
};
|
|
1296
1340
|
}
|
|
1297
1341
|
const t = NOTIFY_LINE.exec(tail);
|
|
1298
|
-
if (t
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
argv: parseArgv(t[
|
|
1302
|
-
|
|
1303
|
-
length: t[0].length,
|
|
1304
|
-
misplaced: true,
|
|
1305
|
-
other: false
|
|
1306
|
-
};
|
|
1342
|
+
if (t) {
|
|
1343
|
+
const raw = t[1];
|
|
1344
|
+
if (OURS2.test(raw))
|
|
1345
|
+
return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
|
|
1346
|
+
}
|
|
1307
1347
|
return null;
|
|
1308
1348
|
}
|
|
1309
1349
|
function dropLine(text, index, length) {
|
|
@@ -1322,9 +1362,7 @@ function withNotify(text, line) {
|
|
|
1322
1362
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1323
1363
|
return insertTopLevel(text, line);
|
|
1324
1364
|
}
|
|
1325
|
-
function replaceNotify(text, line) {
|
|
1326
|
-
const n = readNotify(text);
|
|
1327
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1365
|
+
function replaceNotify(text, n, line) {
|
|
1328
1366
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1329
1367
|
}
|
|
1330
1368
|
function withoutNotify(text) {
|
|
@@ -1365,7 +1403,7 @@ var CODEX_EVENTS = [
|
|
|
1365
1403
|
];
|
|
1366
1404
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1367
1405
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1368
|
-
var codexHome = (env) => env.CODEX_HOME || path6.join(
|
|
1406
|
+
var codexHome = (env) => env.CODEX_HOME || path6.join(homeDir(), ".codex");
|
|
1369
1407
|
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1370
1408
|
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
1371
1409
|
var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
|
|
@@ -1403,11 +1441,8 @@ function installNotify(cmd, env) {
|
|
|
1403
1441
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1404
1442
|
}
|
|
1405
1443
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
write(next);
|
|
1409
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1410
|
-
}
|
|
1444
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1445
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1411
1446
|
}
|
|
1412
1447
|
}
|
|
1413
1448
|
return {
|
|
@@ -1436,8 +1471,7 @@ function uninstallNotify(env) {
|
|
|
1436
1471
|
}
|
|
1437
1472
|
const prev = previousNotify(n.argv);
|
|
1438
1473
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1439
|
-
|
|
1440
|
-
if (next) writeFileSync3(file, next);
|
|
1474
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1441
1475
|
return { file, changed: true };
|
|
1442
1476
|
}
|
|
1443
1477
|
return { file, changed: false };
|
|
@@ -1467,6 +1501,13 @@ function findRollout(threadId, env = process.env) {
|
|
|
1467
1501
|
walk(sessionsDir(env), 0);
|
|
1468
1502
|
return best;
|
|
1469
1503
|
}
|
|
1504
|
+
function rolloutMeta(file) {
|
|
1505
|
+
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1506
|
+
}
|
|
1507
|
+
function ofAnotherThread(file, id) {
|
|
1508
|
+
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);
|
|
1509
|
+
return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
|
|
1510
|
+
}
|
|
1470
1511
|
var codex = {
|
|
1471
1512
|
name: NAME3,
|
|
1472
1513
|
events: CODEX_EVENTS,
|
|
@@ -1509,11 +1550,14 @@ var codex = {
|
|
|
1509
1550
|
if (event === "notify") {
|
|
1510
1551
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1511
1552
|
try {
|
|
1512
|
-
|
|
1553
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1554
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1513
1555
|
} catch {
|
|
1514
1556
|
}
|
|
1515
1557
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1516
1558
|
if (!id2) return null;
|
|
1559
|
+
const file = findRollout(String(id2), input.env);
|
|
1560
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1517
1561
|
const r2 = {
|
|
1518
1562
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1519
1563
|
events: [
|
|
@@ -1523,18 +1567,19 @@ var codex = {
|
|
|
1523
1567
|
harnessEvent: "notify"
|
|
1524
1568
|
}
|
|
1525
1569
|
],
|
|
1526
|
-
hints: {}
|
|
1570
|
+
hints: {},
|
|
1571
|
+
transcript: file
|
|
1527
1572
|
};
|
|
1528
|
-
const file = findRollout(String(id2), input.env);
|
|
1529
|
-
if (file) r2.transcript = file;
|
|
1530
1573
|
return r2;
|
|
1531
1574
|
}
|
|
1532
1575
|
try {
|
|
1533
|
-
|
|
1576
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1577
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1534
1578
|
} catch {
|
|
1535
1579
|
}
|
|
1536
1580
|
const id = s.session_id;
|
|
1537
1581
|
if (!id) return null;
|
|
1582
|
+
if (s.transcript_path === null) return null;
|
|
1538
1583
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1539
1584
|
if (typeof s.model === "string") session.model = s.model;
|
|
1540
1585
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1606,7 +1651,8 @@ var codex = {
|
|
|
1606
1651
|
return null;
|
|
1607
1652
|
}
|
|
1608
1653
|
const r = { session, events, hints: {} };
|
|
1609
|
-
if (typeof s.transcript_path === "string"
|
|
1654
|
+
if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
|
|
1655
|
+
r.transcript = s.transcript_path;
|
|
1610
1656
|
return r;
|
|
1611
1657
|
},
|
|
1612
1658
|
readTranscript(file, fromLine) {
|
|
@@ -1624,16 +1670,17 @@ var codex = {
|
|
|
1624
1670
|
return out;
|
|
1625
1671
|
},
|
|
1626
1672
|
backfill(sinceMs, env = process.env) {
|
|
1627
|
-
const
|
|
1673
|
+
const rows2 = [];
|
|
1628
1674
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1629
1675
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
|
|
1630
1676
|
if (!m?.[1]) continue;
|
|
1677
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1631
1678
|
const facts = codexFacts(f);
|
|
1632
1679
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1633
1680
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1634
|
-
|
|
1681
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1635
1682
|
}
|
|
1636
|
-
return
|
|
1683
|
+
return rows2;
|
|
1637
1684
|
}
|
|
1638
1685
|
};
|
|
1639
1686
|
function prune3(o) {
|
|
@@ -1642,10 +1689,11 @@ function prune3(o) {
|
|
|
1642
1689
|
}
|
|
1643
1690
|
function codexFacts(file) {
|
|
1644
1691
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1645
|
-
|
|
1646
|
-
|
|
1692
|
+
let H = parseLines(head);
|
|
1693
|
+
let T = parseLines(tail);
|
|
1647
1694
|
const out = {};
|
|
1648
1695
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1696
|
+
const boundary = codexReplayBoundary(meta);
|
|
1649
1697
|
if (meta?.payload) {
|
|
1650
1698
|
const p = meta.payload;
|
|
1651
1699
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1653,6 +1701,13 @@ function codexFacts(file) {
|
|
|
1653
1701
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1654
1702
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1655
1703
|
}
|
|
1704
|
+
if (boundary) {
|
|
1705
|
+
const h = H.findIndex(boundary);
|
|
1706
|
+
const t = T.findIndex(boundary);
|
|
1707
|
+
H = h < 0 ? [] : H.slice(h);
|
|
1708
|
+
const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
|
|
1709
|
+
T = t >= 0 ? T.slice(t) : found ? T : [];
|
|
1710
|
+
}
|
|
1656
1711
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1657
1712
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1658
1713
|
for (const j of H) {
|
|
@@ -1660,12 +1715,12 @@ function codexFacts(file) {
|
|
|
1660
1715
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1661
1716
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1662
1717
|
if (!text || codexInjected(text)) continue;
|
|
1663
|
-
const line = text.split("\n")
|
|
1718
|
+
const line = text.split("\n")[0];
|
|
1664
1719
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1665
1720
|
out.title_source = "first-ask";
|
|
1666
1721
|
break;
|
|
1667
1722
|
}
|
|
1668
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1723
|
+
const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1669
1724
|
if (times.length) {
|
|
1670
1725
|
out.started_at = Math.min(...times);
|
|
1671
1726
|
out.last_at = Math.max(...times);
|
|
@@ -1673,11 +1728,432 @@ function codexFacts(file) {
|
|
|
1673
1728
|
return out;
|
|
1674
1729
|
}
|
|
1675
1730
|
|
|
1676
|
-
// ../core/src/adapters/
|
|
1677
|
-
import {
|
|
1678
|
-
import { existsSync as
|
|
1731
|
+
// ../core/src/adapters/devin.ts
|
|
1732
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1733
|
+
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1679
1734
|
import path7 from "node:path";
|
|
1680
|
-
|
|
1735
|
+
|
|
1736
|
+
// ../core/src/readers/sqlite.ts
|
|
1737
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1738
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1739
|
+
import { createRequire } from "node:module";
|
|
1740
|
+
function quiet(fn) {
|
|
1741
|
+
const original = process.emitWarning;
|
|
1742
|
+
try {
|
|
1743
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
1744
|
+
const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
|
|
1745
|
+
if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
|
|
1746
|
+
original.call(process, warning, ...rest);
|
|
1747
|
+
});
|
|
1748
|
+
return fn();
|
|
1749
|
+
} finally {
|
|
1750
|
+
process.emitWarning = original;
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
var ctorCache;
|
|
1754
|
+
function dbCtor() {
|
|
1755
|
+
if (ctorCache !== void 0) return ctorCache;
|
|
1756
|
+
ctorCache = null;
|
|
1757
|
+
try {
|
|
1758
|
+
const req = createRequire(import.meta.url);
|
|
1759
|
+
const m = quiet(() => req("node:sqlite"));
|
|
1760
|
+
if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
|
|
1761
|
+
} catch {
|
|
1762
|
+
}
|
|
1763
|
+
return ctorCache;
|
|
1764
|
+
}
|
|
1765
|
+
function plain(row) {
|
|
1766
|
+
if (!row || typeof row !== "object") return null;
|
|
1767
|
+
const out = {};
|
|
1768
|
+
for (const [k, v] of Object.entries(row)) {
|
|
1769
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
1770
|
+
else if (v instanceof Uint8Array) out[k] = null;
|
|
1771
|
+
else out[k] = v;
|
|
1772
|
+
}
|
|
1773
|
+
return out;
|
|
1774
|
+
}
|
|
1775
|
+
var rows = (v) => {
|
|
1776
|
+
if (!Array.isArray(v)) return null;
|
|
1777
|
+
const out = [];
|
|
1778
|
+
for (const r of v) {
|
|
1779
|
+
const p = plain(r);
|
|
1780
|
+
if (p) out.push(p);
|
|
1781
|
+
}
|
|
1782
|
+
return out;
|
|
1783
|
+
};
|
|
1784
|
+
function viaNode(file, sql, params, o) {
|
|
1785
|
+
const C = dbCtor();
|
|
1786
|
+
if (!C) return null;
|
|
1787
|
+
let db;
|
|
1788
|
+
try {
|
|
1789
|
+
db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
|
|
1790
|
+
} catch {
|
|
1791
|
+
return null;
|
|
1792
|
+
}
|
|
1793
|
+
try {
|
|
1794
|
+
try {
|
|
1795
|
+
db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
|
|
1796
|
+
} catch {
|
|
1797
|
+
}
|
|
1798
|
+
return quiet(() => rows(db.prepare(sql).all(...params)));
|
|
1799
|
+
} catch {
|
|
1800
|
+
return null;
|
|
1801
|
+
} finally {
|
|
1802
|
+
try {
|
|
1803
|
+
db.close();
|
|
1804
|
+
} catch {
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
var CHILD = `const { DatabaseSync } = require("node:sqlite");
|
|
1809
|
+
const [, file, sql, params, ms] = process.argv;
|
|
1810
|
+
const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
|
|
1811
|
+
try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
|
|
1812
|
+
const out = db.prepare(sql).all(...JSON.parse(params));
|
|
1813
|
+
db.close();
|
|
1814
|
+
process.stdout.write(JSON.stringify(out));`;
|
|
1815
|
+
function viaChild(file, sql, params, o) {
|
|
1816
|
+
try {
|
|
1817
|
+
const out = execFileSync3(
|
|
1818
|
+
process.execPath,
|
|
1819
|
+
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
1820
|
+
{
|
|
1821
|
+
encoding: "utf8",
|
|
1822
|
+
timeout: o.timeoutMs + 3e3,
|
|
1823
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1824
|
+
maxBuffer: o.maxBuffer,
|
|
1825
|
+
windowsHide: true
|
|
1826
|
+
}
|
|
1827
|
+
);
|
|
1828
|
+
return rows(JSON.parse(out || "null"));
|
|
1829
|
+
} catch {
|
|
1830
|
+
return null;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
|
|
1834
|
+
function inlineParams(sql, params) {
|
|
1835
|
+
if (sql.includes("'")) return null;
|
|
1836
|
+
const holes = sql.split("?").length - 1;
|
|
1837
|
+
if (holes !== params.length) return null;
|
|
1838
|
+
let i = 0;
|
|
1839
|
+
let bad = false;
|
|
1840
|
+
const out = sql.replace(/\?/g, () => {
|
|
1841
|
+
const p = params[i++];
|
|
1842
|
+
if (typeof p === "number") {
|
|
1843
|
+
if (!Number.isFinite(p)) {
|
|
1844
|
+
bad = true;
|
|
1845
|
+
return "NULL";
|
|
1846
|
+
}
|
|
1847
|
+
return String(p);
|
|
1848
|
+
}
|
|
1849
|
+
if (typeof p !== "string" || !SAFE.test(p)) {
|
|
1850
|
+
bad = true;
|
|
1851
|
+
return "NULL";
|
|
1852
|
+
}
|
|
1853
|
+
return `'${p}'`;
|
|
1854
|
+
});
|
|
1855
|
+
return bad ? null : out;
|
|
1856
|
+
}
|
|
1857
|
+
function viaCli(file, sql, params, o) {
|
|
1858
|
+
const text = inlineParams(sql, params);
|
|
1859
|
+
if (!text) return null;
|
|
1860
|
+
try {
|
|
1861
|
+
const out = execFileSync3("sqlite3", ["-readonly", "-json", file, text], {
|
|
1862
|
+
encoding: "utf8",
|
|
1863
|
+
timeout: o.timeoutMs + 3e3,
|
|
1864
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1865
|
+
maxBuffer: o.maxBuffer,
|
|
1866
|
+
windowsHide: true
|
|
1867
|
+
}).trim();
|
|
1868
|
+
if (!out) return [];
|
|
1869
|
+
return rows(JSON.parse(out));
|
|
1870
|
+
} catch {
|
|
1871
|
+
return null;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
function querySqlite(file, sql, params = [], opts = {}) {
|
|
1875
|
+
const o = {
|
|
1876
|
+
timeoutMs: opts.timeoutMs ?? 2e3,
|
|
1877
|
+
engine: opts.engine ?? "auto",
|
|
1878
|
+
maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
|
|
1879
|
+
};
|
|
1880
|
+
if (!file) return null;
|
|
1881
|
+
try {
|
|
1882
|
+
if (!existsSync5(file)) return null;
|
|
1883
|
+
} catch {
|
|
1884
|
+
return null;
|
|
1885
|
+
}
|
|
1886
|
+
const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
|
|
1887
|
+
for (const way of ways) {
|
|
1888
|
+
const r = way(file, sql, params, o);
|
|
1889
|
+
if (r) return r;
|
|
1890
|
+
}
|
|
1891
|
+
return null;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
// ../core/src/adapters/devin.ts
|
|
1895
|
+
var NAME4 = "devin";
|
|
1896
|
+
var DEVIN_EVENTS = [
|
|
1897
|
+
"SessionStart",
|
|
1898
|
+
"UserPromptSubmit",
|
|
1899
|
+
"PreToolUse",
|
|
1900
|
+
"PostToolUse",
|
|
1901
|
+
"PermissionRequest",
|
|
1902
|
+
"Stop",
|
|
1903
|
+
"PostCompaction",
|
|
1904
|
+
"SessionEnd"
|
|
1905
|
+
];
|
|
1906
|
+
var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1907
|
+
var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
|
|
1908
|
+
var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
|
|
1909
|
+
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";
|
|
1910
|
+
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)`;
|
|
1911
|
+
function foreignEvent(file) {
|
|
1912
|
+
const hooks = readJson(file).hooks;
|
|
1913
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
|
|
1914
|
+
const ok = DEVIN_EVENTS;
|
|
1915
|
+
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
1916
|
+
}
|
|
1917
|
+
var homeOf = (env) => env.HOME || homeDir();
|
|
1918
|
+
var appData = (env) => env.APPDATA || path7.join(homeOf(env), "AppData", "Roaming");
|
|
1919
|
+
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");
|
|
1920
|
+
var configFile3 = (env) => path7.join(devinConfigDir(env), "config.json");
|
|
1921
|
+
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");
|
|
1922
|
+
var sessionsDb = (env) => path7.join(devinDataDir(env), "sessions.db");
|
|
1923
|
+
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1924
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1925
|
+
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1926
|
+
var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1927
|
+
function errText2(v) {
|
|
1928
|
+
if (typeof v === "string" && v) return v.slice(0, 2e3);
|
|
1929
|
+
if (v && typeof v === "object") {
|
|
1930
|
+
const e = v;
|
|
1931
|
+
if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
|
|
1932
|
+
if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
|
|
1933
|
+
}
|
|
1934
|
+
return void 0;
|
|
1935
|
+
}
|
|
1936
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
|
|
1937
|
+
var devin = {
|
|
1938
|
+
name: NAME4,
|
|
1939
|
+
events: DEVIN_EVENTS,
|
|
1940
|
+
detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
|
|
1941
|
+
configFiles: (env = process.env) => [configFile3(env)],
|
|
1942
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
1943
|
+
const file = configFile3(env);
|
|
1944
|
+
const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1945
|
+
if (r.skipped) return [r];
|
|
1946
|
+
const bad = foreignEvent(file);
|
|
1947
|
+
return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
|
|
1948
|
+
},
|
|
1949
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
1950
|
+
uninstallClaudeShaped(configFile3(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile3(env)) })
|
|
1951
|
+
],
|
|
1952
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
1953
|
+
const file = configFile3(env);
|
|
1954
|
+
const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
1955
|
+
const bad = state === "missing" ? void 0 : foreignEvent(file);
|
|
1956
|
+
return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
|
|
1957
|
+
},
|
|
1958
|
+
fromHook(input) {
|
|
1959
|
+
const event = input.argv[1] ?? "";
|
|
1960
|
+
let s = {};
|
|
1961
|
+
try {
|
|
1962
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1963
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1964
|
+
} catch {
|
|
1965
|
+
}
|
|
1966
|
+
const id = typeof s.session_id === "string" ? s.session_id : "";
|
|
1967
|
+
if (!id) return null;
|
|
1968
|
+
const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
|
|
1969
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
1970
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
1971
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
1972
|
+
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
1973
|
+
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
1974
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
1975
|
+
const events = [];
|
|
1976
|
+
switch (event) {
|
|
1977
|
+
case "SessionStart":
|
|
1978
|
+
events.push(ev("session.started", { source: pickSource2(s.source) }));
|
|
1979
|
+
break;
|
|
1980
|
+
case "UserPromptSubmit":
|
|
1981
|
+
events.push(
|
|
1982
|
+
ev("turn.started", {
|
|
1983
|
+
turn_id,
|
|
1984
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
1985
|
+
})
|
|
1986
|
+
);
|
|
1987
|
+
break;
|
|
1988
|
+
case "PreToolUse":
|
|
1989
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
1990
|
+
break;
|
|
1991
|
+
case "PostToolUse":
|
|
1992
|
+
if (tool)
|
|
1993
|
+
events.push(
|
|
1994
|
+
ev("tool.ended", {
|
|
1995
|
+
tool,
|
|
1996
|
+
call_id,
|
|
1997
|
+
turn_id,
|
|
1998
|
+
// `ok` is required on tool.ended (PROTOCOL.md §events), so a response
|
|
1999
|
+
// that carries no success flag at all reads as "nothing said it failed".
|
|
2000
|
+
ok: response?.success !== false,
|
|
2001
|
+
error: errText2(response?.error),
|
|
2002
|
+
input: s.tool_input,
|
|
2003
|
+
output: response ? response.output : s.tool_response
|
|
2004
|
+
})
|
|
2005
|
+
);
|
|
2006
|
+
break;
|
|
2007
|
+
case "PermissionRequest":
|
|
2008
|
+
events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
|
|
2009
|
+
break;
|
|
2010
|
+
case "Stop":
|
|
2011
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
2012
|
+
break;
|
|
2013
|
+
case "PostCompaction":
|
|
2014
|
+
events.push(ev("context.compacted", { trigger: "auto" }));
|
|
2015
|
+
break;
|
|
2016
|
+
case "SessionEnd":
|
|
2017
|
+
events.push(ev("session.ended", { reason: endReason3(s.reason) }));
|
|
2018
|
+
break;
|
|
2019
|
+
default:
|
|
2020
|
+
return null;
|
|
2021
|
+
}
|
|
2022
|
+
return {
|
|
2023
|
+
session,
|
|
2024
|
+
events,
|
|
2025
|
+
hints: {},
|
|
2026
|
+
transcript: transcriptRef(sessionsDb(input.env), id)
|
|
2027
|
+
};
|
|
2028
|
+
},
|
|
2029
|
+
readTranscript(file, fromLine) {
|
|
2030
|
+
const ref = splitRef(file);
|
|
2031
|
+
if (!ref) return { turns: [], line: fromLine };
|
|
2032
|
+
return readDevinChain(ref.db, ref.session, fromLine);
|
|
2033
|
+
},
|
|
2034
|
+
facts(session, transcript, _hints, _state, env = process.env) {
|
|
2035
|
+
const out = {};
|
|
2036
|
+
const version = devinVersion(env);
|
|
2037
|
+
if (version) out.harness_version = version;
|
|
2038
|
+
const account = devinAccount(env);
|
|
2039
|
+
if (account) out.account_id = account;
|
|
2040
|
+
const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
|
|
2041
|
+
if (!SESSION_ID.test(ref.session)) return out;
|
|
2042
|
+
const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
|
|
2043
|
+
const row = rows2?.[0];
|
|
2044
|
+
if (!row) return out;
|
|
2045
|
+
Object.assign(out, sessionFacts(row));
|
|
2046
|
+
return out;
|
|
2047
|
+
},
|
|
2048
|
+
backfill(sinceMs, env = process.env) {
|
|
2049
|
+
const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
|
|
2050
|
+
if (!rows2) return [];
|
|
2051
|
+
const out = [];
|
|
2052
|
+
for (const row of rows2) {
|
|
2053
|
+
const id = String(row.id);
|
|
2054
|
+
if (!SESSION_ID.test(id)) continue;
|
|
2055
|
+
const { started_at, last_at, ...rest } = sessionFacts(row);
|
|
2056
|
+
if (!started_at || !last_at) continue;
|
|
2057
|
+
out.push({ session: { id, ...rest }, started_at, last_at });
|
|
2058
|
+
}
|
|
2059
|
+
return out;
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
function prune4(o) {
|
|
2063
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2064
|
+
return o;
|
|
2065
|
+
}
|
|
2066
|
+
var transcriptRef = (db, session) => `${db}#${session}`;
|
|
2067
|
+
function splitRef(ref) {
|
|
2068
|
+
if (!ref) return null;
|
|
2069
|
+
const at = ref.lastIndexOf("#");
|
|
2070
|
+
if (at <= 0) return null;
|
|
2071
|
+
const db = ref.slice(0, at);
|
|
2072
|
+
const session = ref.slice(at + 1);
|
|
2073
|
+
return db && SESSION_ID.test(session) ? { db, session } : null;
|
|
2074
|
+
}
|
|
2075
|
+
var CHAIN_LIMIT = 4e3;
|
|
2076
|
+
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2077
|
+
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2078
|
+
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2079
|
+
UNION ALL
|
|
2080
|
+
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2081
|
+
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2082
|
+
)
|
|
2083
|
+
SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
|
|
2084
|
+
var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
|
|
2085
|
+
(SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
|
|
2086
|
+
ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
|
|
2087
|
+
var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
|
|
2088
|
+
var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
2089
|
+
WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
|
|
2090
|
+
ORDER BY s.last_activity_at DESC LIMIT 500`;
|
|
2091
|
+
function sessionFacts(row) {
|
|
2092
|
+
const out = {};
|
|
2093
|
+
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2094
|
+
if (typeof row.working_directory === "string" && path7.isAbsolute(row.working_directory))
|
|
2095
|
+
out.cwd = row.working_directory;
|
|
2096
|
+
const started = Number(row.created_at);
|
|
2097
|
+
const last = Number(row.last_activity_at);
|
|
2098
|
+
if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
|
|
2099
|
+
if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
|
|
2100
|
+
if (typeof row.first_ask === "string") {
|
|
2101
|
+
const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
|
|
2102
|
+
if (line.trim()) {
|
|
2103
|
+
out.title = cut(line.trim(), 80);
|
|
2104
|
+
out.title_source = "first-ask";
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
return out;
|
|
2108
|
+
}
|
|
2109
|
+
function readDevinChain(db, session, fromLine) {
|
|
2110
|
+
if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
|
|
2111
|
+
const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
|
|
2112
|
+
const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
|
|
2113
|
+
if (!rows2?.length) return { turns: [], line: fromLine };
|
|
2114
|
+
const msgs = [];
|
|
2115
|
+
let end = fromLine;
|
|
2116
|
+
for (const row of rows2) {
|
|
2117
|
+
const node = Number(row.node_id);
|
|
2118
|
+
if (!Number.isFinite(node)) continue;
|
|
2119
|
+
if (node + 1 > end) end = node + 1;
|
|
2120
|
+
if (node < fromLine) continue;
|
|
2121
|
+
let j;
|
|
2122
|
+
try {
|
|
2123
|
+
j = JSON.parse(String(row.chat_message ?? ""));
|
|
2124
|
+
} catch {
|
|
2125
|
+
continue;
|
|
2126
|
+
}
|
|
2127
|
+
const text = typeof j.content === "string" ? j.content.trim() : "";
|
|
2128
|
+
if (!text) continue;
|
|
2129
|
+
const secs = Number(row.created_at);
|
|
2130
|
+
const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
|
|
2131
|
+
if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
|
|
2132
|
+
else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
|
|
2133
|
+
}
|
|
2134
|
+
msgs.sort((a, b) => a.line - b.line);
|
|
2135
|
+
return pairTurns(msgs, end);
|
|
2136
|
+
}
|
|
2137
|
+
function devinVersion(env = process.env) {
|
|
2138
|
+
try {
|
|
2139
|
+
const target = readlinkSync(path7.join(devinDataDir(env), "_versions", "current"));
|
|
2140
|
+
const name = path7.basename(target);
|
|
2141
|
+
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2142
|
+
} catch {
|
|
2143
|
+
return void 0;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
function devinAccount(env = process.env) {
|
|
2147
|
+
const d = readJson(configFile3(env)).devin;
|
|
2148
|
+
const id = d && typeof d === "object" ? d.org_id : void 0;
|
|
2149
|
+
return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
// ../core/src/adapters/gemini-cli.ts
|
|
2153
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2154
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2155
|
+
import path8 from "node:path";
|
|
2156
|
+
var NAME5 = "gemini-cli";
|
|
1681
2157
|
var GEMINI_EVENTS = [
|
|
1682
2158
|
"SessionStart",
|
|
1683
2159
|
"BeforeAgent",
|
|
@@ -1688,11 +2164,11 @@ var GEMINI_EVENTS = [
|
|
|
1688
2164
|
"PreCompress",
|
|
1689
2165
|
"SessionEnd"
|
|
1690
2166
|
];
|
|
1691
|
-
var
|
|
1692
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1693
|
-
var settings = (env) =>
|
|
1694
|
-
var
|
|
1695
|
-
var
|
|
2167
|
+
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2168
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path8.join(homeDir(), ".gemini");
|
|
2169
|
+
var settings = (env) => path8.join(geminiDir(env), "settings.json");
|
|
2170
|
+
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2171
|
+
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1696
2172
|
function geminiHooksActive(settingsFile) {
|
|
1697
2173
|
let tools;
|
|
1698
2174
|
try {
|
|
@@ -1704,7 +2180,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1704
2180
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1705
2181
|
let version = "";
|
|
1706
2182
|
try {
|
|
1707
|
-
version =
|
|
2183
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1708
2184
|
encoding: "utf8",
|
|
1709
2185
|
timeout: 4e3,
|
|
1710
2186
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1723,13 +2199,13 @@ function geminiHooksActive(settingsFile) {
|
|
|
1723
2199
|
};
|
|
1724
2200
|
}
|
|
1725
2201
|
var geminiCli = {
|
|
1726
|
-
name:
|
|
2202
|
+
name: NAME5,
|
|
1727
2203
|
events: GEMINI_EVENTS,
|
|
1728
2204
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1729
|
-
detect: (env = process.env) =>
|
|
2205
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path8.join(geminiDir(env), "tmp")),
|
|
1730
2206
|
configFiles: (env = process.env) => [settings(env)],
|
|
1731
2207
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1732
|
-
const r = installClaudeShaped(settings(env),
|
|
2208
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1733
2209
|
const a = geminiHooksActive(settings(env));
|
|
1734
2210
|
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1735
2211
|
},
|
|
@@ -1737,7 +2213,7 @@ var geminiCli = {
|
|
|
1737
2213
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1738
2214
|
],
|
|
1739
2215
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1740
|
-
const state = installedClaudeShaped(settings(env),
|
|
2216
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1741
2217
|
const a = geminiHooksActive(settings(env));
|
|
1742
2218
|
return [
|
|
1743
2219
|
{
|
|
@@ -1751,14 +2227,15 @@ var geminiCli = {
|
|
|
1751
2227
|
const event = input.argv[1] ?? "";
|
|
1752
2228
|
let s = {};
|
|
1753
2229
|
try {
|
|
1754
|
-
|
|
2230
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2231
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1755
2232
|
} catch {
|
|
1756
2233
|
}
|
|
1757
2234
|
const id = s.session_id;
|
|
1758
2235
|
if (!id) return null;
|
|
1759
2236
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1760
|
-
const tool =
|
|
1761
|
-
const ev = (type, data) => ({ type, data:
|
|
2237
|
+
const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
|
|
2238
|
+
const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
|
|
1762
2239
|
const events = [];
|
|
1763
2240
|
switch (event) {
|
|
1764
2241
|
case "SessionStart":
|
|
@@ -1839,7 +2316,7 @@ var geminiCli = {
|
|
|
1839
2316
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1840
2317
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1841
2318
|
if (text) {
|
|
1842
|
-
const line = text.split("\n")
|
|
2319
|
+
const line = text.split("\n")[0];
|
|
1843
2320
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1844
2321
|
out.title_source = "first-ask";
|
|
1845
2322
|
}
|
|
@@ -1853,18 +2330,18 @@ var geminiCli = {
|
|
|
1853
2330
|
backfill: () => []
|
|
1854
2331
|
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
1855
2332
|
};
|
|
1856
|
-
function
|
|
2333
|
+
function prune5(o) {
|
|
1857
2334
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1858
2335
|
return o;
|
|
1859
2336
|
}
|
|
1860
2337
|
|
|
1861
2338
|
// ../core/src/adapters/index.ts
|
|
1862
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2339
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1863
2340
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1864
2341
|
|
|
1865
2342
|
// ../core/src/config.ts
|
|
1866
|
-
import { chmodSync, existsSync as
|
|
1867
|
-
import
|
|
2343
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2344
|
+
import path9 from "node:path";
|
|
1868
2345
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
1869
2346
|
function readConfig(file = configFile()) {
|
|
1870
2347
|
try {
|
|
@@ -1880,10 +2357,10 @@ function readConfig(file = configFile()) {
|
|
|
1880
2357
|
}
|
|
1881
2358
|
}
|
|
1882
2359
|
function writeConfig(c, file = configFile()) {
|
|
1883
|
-
mkdirSync4(
|
|
2360
|
+
mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
|
|
1884
2361
|
writeFileSync4(file, `${JSON.stringify(c, null, 2)}
|
|
1885
2362
|
`, { mode: 384 });
|
|
1886
|
-
if (process.platform !== "win32" &&
|
|
2363
|
+
if (process.platform !== "win32" && existsSync8(file)) {
|
|
1887
2364
|
try {
|
|
1888
2365
|
chmodSync(file, 384);
|
|
1889
2366
|
} catch {
|
|
@@ -1912,7 +2389,7 @@ function encodeTime(t) {
|
|
|
1912
2389
|
function ulid(now = Date.now()) {
|
|
1913
2390
|
if (now === lastTime) {
|
|
1914
2391
|
for (let i = lastRand.length - 1; i >= 0; i--) {
|
|
1915
|
-
const v =
|
|
2392
|
+
const v = lastRand[i] + 1;
|
|
1916
2393
|
if (v < 32) {
|
|
1917
2394
|
lastRand[i] = v;
|
|
1918
2395
|
break;
|
|
@@ -1921,8 +2398,7 @@ function ulid(now = Date.now()) {
|
|
|
1921
2398
|
}
|
|
1922
2399
|
} else {
|
|
1923
2400
|
lastTime = now;
|
|
1924
|
-
|
|
1925
|
-
lastRand = Array.from({ length: 16 }, (_, i) => (b[i] ?? 0) % 32);
|
|
2401
|
+
lastRand = Array.from(randomBytes(16), (b) => b % 32);
|
|
1926
2402
|
}
|
|
1927
2403
|
return encodeTime(now) + lastRand.map((v) => ALPHABET[v]).join("");
|
|
1928
2404
|
}
|
|
@@ -1949,7 +2425,7 @@ function makeEvent(a, ctx) {
|
|
|
1949
2425
|
import {
|
|
1950
2426
|
appendFileSync,
|
|
1951
2427
|
closeSync as closeSync2,
|
|
1952
|
-
existsSync as
|
|
2428
|
+
existsSync as existsSync9,
|
|
1953
2429
|
mkdirSync as mkdirSync5,
|
|
1954
2430
|
openSync as openSync2,
|
|
1955
2431
|
readdirSync as readdirSync5,
|
|
@@ -1960,42 +2436,45 @@ import {
|
|
|
1960
2436
|
unlinkSync as unlinkSync3,
|
|
1961
2437
|
writeFileSync as writeFileSync5
|
|
1962
2438
|
} from "node:fs";
|
|
1963
|
-
import
|
|
2439
|
+
import path10 from "node:path";
|
|
2440
|
+
var READ_CHUNK = 1024 * 1024;
|
|
2441
|
+
var READ_MAX = 8 * 1024 * 1024;
|
|
1964
2442
|
var Outbox = class {
|
|
1965
2443
|
constructor(root) {
|
|
1966
2444
|
this.root = root;
|
|
1967
2445
|
}
|
|
1968
2446
|
root;
|
|
1969
2447
|
dir(harness) {
|
|
1970
|
-
return
|
|
2448
|
+
return path10.join(this.root, "outbox", safeId(harness));
|
|
1971
2449
|
}
|
|
1972
2450
|
file(ref) {
|
|
1973
|
-
return
|
|
2451
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
1974
2452
|
}
|
|
1975
2453
|
seqFile(ref) {
|
|
1976
|
-
return
|
|
2454
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
1977
2455
|
}
|
|
1978
2456
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
1979
2457
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
1980
2458
|
nextSeq(ref) {
|
|
1981
2459
|
const f = this.seqFile(ref);
|
|
1982
|
-
let n =
|
|
2460
|
+
let n = -1;
|
|
1983
2461
|
try {
|
|
1984
|
-
|
|
2462
|
+
const t = readFileSync7(f, "utf8").trim();
|
|
2463
|
+
if (/^\d+$/.test(t)) n = Number(t);
|
|
1985
2464
|
} catch {
|
|
1986
|
-
n = this.lastSeqInFile(ref) + 1;
|
|
1987
2465
|
}
|
|
1988
|
-
|
|
2466
|
+
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2467
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
1989
2468
|
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
1990
2469
|
return n;
|
|
1991
2470
|
}
|
|
1992
2471
|
lastSeqInFile(ref) {
|
|
1993
2472
|
const f = this.file(ref);
|
|
1994
|
-
if (!
|
|
2473
|
+
if (!existsSync9(f)) return -1;
|
|
1995
2474
|
const lines = readFileSync7(f, "utf8").trim().split("\n");
|
|
1996
2475
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1997
2476
|
try {
|
|
1998
|
-
const j = JSON.parse(lines[i]
|
|
2477
|
+
const j = JSON.parse(lines[i]);
|
|
1999
2478
|
if (typeof j.session?.seq === "number") return j.session.seq;
|
|
2000
2479
|
} catch {
|
|
2001
2480
|
}
|
|
@@ -2011,10 +2490,10 @@ var Outbox = class {
|
|
|
2011
2490
|
/** Every session file, newest first. */
|
|
2012
2491
|
sessions() {
|
|
2013
2492
|
const out = [];
|
|
2014
|
-
const base =
|
|
2015
|
-
if (!
|
|
2493
|
+
const base = path10.join(this.root, "outbox");
|
|
2494
|
+
if (!existsSync9(base)) return [];
|
|
2016
2495
|
for (const h of readdirSync5(base)) {
|
|
2017
|
-
const d =
|
|
2496
|
+
const d = path10.join(base, h);
|
|
2018
2497
|
let names = [];
|
|
2019
2498
|
try {
|
|
2020
2499
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2023,7 +2502,7 @@ var Outbox = class {
|
|
|
2023
2502
|
}
|
|
2024
2503
|
for (const n of names) {
|
|
2025
2504
|
try {
|
|
2026
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync4(
|
|
2505
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync4(path10.join(d, n)).mtimeMs });
|
|
2027
2506
|
} catch {
|
|
2028
2507
|
}
|
|
2029
2508
|
}
|
|
@@ -2031,31 +2510,43 @@ var Outbox = class {
|
|
|
2031
2510
|
return out.sort((a, b) => b.mtime - a.mtime).map((x) => x.ref);
|
|
2032
2511
|
}
|
|
2033
2512
|
/** Read events after byte offset `from`; returns them and the new offset. Lines
|
|
2034
|
-
* are read whole; a partial trailing line (a write in progress) is left.
|
|
2513
|
+
* are read whole; a partial trailing line (a write in progress) is left. A read
|
|
2514
|
+
* takes READ_CHUNK bytes at a time, not the rest of the file: a session file grows
|
|
2515
|
+
* to tens of MB and a drain takes it in batches of 50. It reads on until the batch
|
|
2516
|
+
* is full or the file ends, so lines that parse to nothing never stop a drain; one
|
|
2517
|
+
* line longer than the chunk gets a larger one, up to READ_MAX. */
|
|
2035
2518
|
read(ref, from, maxEvents = 500) {
|
|
2036
2519
|
const f = this.file(ref);
|
|
2037
|
-
if (!
|
|
2520
|
+
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2038
2521
|
const size = statSync4(f).size;
|
|
2039
2522
|
if (from >= size) return { events: [], offset: from };
|
|
2040
2523
|
const fd = openSync2(f, "r");
|
|
2041
2524
|
try {
|
|
2042
|
-
const buf = Buffer.alloc(Math.min(size - from, 8 * 1024 * 1024));
|
|
2043
|
-
const n = readSync2(fd, buf, 0, buf.length, from);
|
|
2044
|
-
const text = buf.subarray(0, n).toString("utf8");
|
|
2045
2525
|
const events = [];
|
|
2046
2526
|
let offset = from;
|
|
2047
|
-
let
|
|
2048
|
-
while (events.length < maxEvents) {
|
|
2049
|
-
const
|
|
2050
|
-
|
|
2051
|
-
const
|
|
2052
|
-
pos =
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2527
|
+
let chunk = READ_CHUNK;
|
|
2528
|
+
while (events.length < maxEvents && offset < size) {
|
|
2529
|
+
const buf = Buffer.alloc(Math.min(size - offset, chunk));
|
|
2530
|
+
const n = readSync2(fd, buf, 0, buf.length, offset);
|
|
2531
|
+
const text = buf.subarray(0, n).toString("utf8");
|
|
2532
|
+
let pos = 0;
|
|
2533
|
+
let used = 0;
|
|
2534
|
+
while (events.length < maxEvents) {
|
|
2535
|
+
const nl = text.indexOf("\n", pos);
|
|
2536
|
+
if (nl < 0) break;
|
|
2537
|
+
const line = text.slice(pos, nl);
|
|
2538
|
+
pos = nl + 1;
|
|
2539
|
+
used += Buffer.byteLength(line, "utf8") + 1;
|
|
2540
|
+
if (!line.trim()) continue;
|
|
2541
|
+
try {
|
|
2542
|
+
events.push(JSON.parse(line));
|
|
2543
|
+
} catch {
|
|
2544
|
+
}
|
|
2058
2545
|
}
|
|
2546
|
+
offset += used;
|
|
2547
|
+
if (used) continue;
|
|
2548
|
+
if (offset + n >= size || chunk >= READ_MAX) break;
|
|
2549
|
+
chunk *= 2;
|
|
2059
2550
|
}
|
|
2060
2551
|
return { events, offset };
|
|
2061
2552
|
} finally {
|
|
@@ -2099,7 +2590,7 @@ var Cursors = class {
|
|
|
2099
2590
|
root;
|
|
2100
2591
|
sink;
|
|
2101
2592
|
file() {
|
|
2102
|
-
return
|
|
2593
|
+
return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2103
2594
|
}
|
|
2104
2595
|
read() {
|
|
2105
2596
|
try {
|
|
@@ -2110,7 +2601,7 @@ var Cursors = class {
|
|
|
2110
2601
|
}
|
|
2111
2602
|
write(c) {
|
|
2112
2603
|
const f = this.file();
|
|
2113
|
-
mkdirSync5(
|
|
2604
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
2114
2605
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2115
2606
|
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
2116
2607
|
renameSync(tmp, f);
|
|
@@ -2120,11 +2611,14 @@ var Cursors = class {
|
|
|
2120
2611
|
}
|
|
2121
2612
|
};
|
|
2122
2613
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
2123
|
-
|
|
2614
|
+
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
2615
|
+
}
|
|
2616
|
+
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
2617
|
+
const dir = path10.join(root, "locks");
|
|
2124
2618
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2125
|
-
const f =
|
|
2619
|
+
const f = path10.join(dir, `${name}.lock`);
|
|
2126
2620
|
try {
|
|
2127
|
-
if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
2621
|
+
if (Date.now() - statSync4(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
|
|
2128
2622
|
else return null;
|
|
2129
2623
|
} catch {
|
|
2130
2624
|
}
|
|
@@ -2140,9 +2634,18 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2140
2634
|
}
|
|
2141
2635
|
};
|
|
2142
2636
|
}
|
|
2637
|
+
function processAlive(pid) {
|
|
2638
|
+
if (!Number.isInteger(pid) || pid <= 0) return true;
|
|
2639
|
+
try {
|
|
2640
|
+
process.kill(pid, 0);
|
|
2641
|
+
return true;
|
|
2642
|
+
} catch (e) {
|
|
2643
|
+
return e.code === "EPERM";
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2143
2646
|
|
|
2144
2647
|
// ../core/src/privacy/pii.ts
|
|
2145
|
-
import { createHash as
|
|
2648
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2146
2649
|
var RULESET_PII = "pii@1";
|
|
2147
2650
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2148
2651
|
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
@@ -2164,7 +2667,7 @@ function reducePii(text, home) {
|
|
|
2164
2667
|
return out;
|
|
2165
2668
|
}
|
|
2166
2669
|
function hashMachine(name) {
|
|
2167
|
-
return
|
|
2670
|
+
return createHash3("sha256").update(name).digest("hex").slice(0, 8);
|
|
2168
2671
|
}
|
|
2169
2672
|
function reducePiiDeep(v, home) {
|
|
2170
2673
|
if (typeof v === "string") return reducePii(v, home);
|
|
@@ -2253,12 +2756,12 @@ function redactAssignments(text) {
|
|
|
2253
2756
|
let last = 0;
|
|
2254
2757
|
NAME_SEP.lastIndex = 0;
|
|
2255
2758
|
for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
|
|
2256
|
-
const name = m[1]
|
|
2759
|
+
const name = m[1];
|
|
2257
2760
|
if (!isSecretName(name)) continue;
|
|
2258
2761
|
VALUE.lastIndex = NAME_SEP.lastIndex;
|
|
2259
2762
|
const v = VALUE.exec(text);
|
|
2260
2763
|
if (!v) continue;
|
|
2261
|
-
const value = v[1] ?? v[2] ?? v[3] ?? v[4]
|
|
2764
|
+
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
2262
2765
|
NAME_SEP.lastIndex = VALUE.lastIndex;
|
|
2263
2766
|
if (isPlaceholder(value) || value.includes(REDACTED)) continue;
|
|
2264
2767
|
if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
|
|
@@ -2314,7 +2817,7 @@ function redactSecrets(text) {
|
|
|
2314
2817
|
let out = text;
|
|
2315
2818
|
for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
|
|
2316
2819
|
out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
|
|
2317
|
-
out = out.replace(AUTH_HEADER, (
|
|
2820
|
+
out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
|
|
2318
2821
|
out = redactAssignments(out);
|
|
2319
2822
|
out = out.replace(
|
|
2320
2823
|
PROSE,
|
|
@@ -2343,6 +2846,7 @@ var TYPE_TIER = {
|
|
|
2343
2846
|
"turn.started": 0,
|
|
2344
2847
|
"turn.ended": 0,
|
|
2345
2848
|
"turn.transcript": 2,
|
|
2849
|
+
"turn.progress": 2,
|
|
2346
2850
|
"tool.started": 1,
|
|
2347
2851
|
"tool.ended": 1,
|
|
2348
2852
|
"files.changed": 1,
|
|
@@ -2362,6 +2866,7 @@ var DATA_ALLOW = {
|
|
|
2362
2866
|
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2363
2867
|
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
2364
2868
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2869
|
+
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
2365
2870
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2366
2871
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
2367
2872
|
"files.changed": { 1: ["paths"] },
|
|
@@ -2402,10 +2907,12 @@ function cap(v, max) {
|
|
|
2402
2907
|
}
|
|
2403
2908
|
return v;
|
|
2404
2909
|
}
|
|
2910
|
+
function capTail(v, max) {
|
|
2911
|
+
return v.length > max ? `\u2026${v.slice(v.length - max + 1)}` : v;
|
|
2912
|
+
}
|
|
2405
2913
|
function filterEvent(event, tier, pii, opts = {}) {
|
|
2406
2914
|
const minTier = TYPE_TIER[event.type];
|
|
2407
2915
|
if (minTier !== void 0 && minTier > tier) return null;
|
|
2408
|
-
if (event.type === "native" && tier < 3) return null;
|
|
2409
2916
|
const session = {};
|
|
2410
2917
|
for (const k of SESSION_ALLOW)
|
|
2411
2918
|
if (event.session[k] !== void 0)
|
|
@@ -2415,7 +2922,8 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2415
2922
|
if (keys === null) data = { ...event.data };
|
|
2416
2923
|
else for (const k of keys) if (event.data[k] !== void 0) data[k] = event.data[k];
|
|
2417
2924
|
if (typeof data.user === "string") data.user = cap(data.user, MAX_SIDE);
|
|
2418
|
-
if (typeof data.assistant === "string")
|
|
2925
|
+
if (typeof data.assistant === "string")
|
|
2926
|
+
data.assistant = event.type === "turn.progress" ? capTail(data.assistant, MAX_SIDE) : cap(data.assistant, MAX_SIDE);
|
|
2419
2927
|
if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
|
|
2420
2928
|
if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
|
|
2421
2929
|
if (typeof data.message === "string") data.message = cap(data.message, 200);
|
|
@@ -2436,9 +2944,15 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2436
2944
|
return out;
|
|
2437
2945
|
}
|
|
2438
2946
|
|
|
2947
|
+
// ../core/src/short-id.ts
|
|
2948
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2949
|
+
function shortId(harness, id) {
|
|
2950
|
+
return createHash4("sha256").update(`sessionpipe.session:${harness}:${id}`).digest("hex").slice(0, 8);
|
|
2951
|
+
}
|
|
2952
|
+
|
|
2439
2953
|
// ../core/src/sinks/file.ts
|
|
2440
2954
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2441
|
-
import
|
|
2955
|
+
import path11 from "node:path";
|
|
2442
2956
|
var FileSink = class {
|
|
2443
2957
|
constructor(name, file, tier, pii) {
|
|
2444
2958
|
this.name = name;
|
|
@@ -2452,7 +2966,7 @@ var FileSink = class {
|
|
|
2452
2966
|
pii;
|
|
2453
2967
|
async send(events) {
|
|
2454
2968
|
try {
|
|
2455
|
-
mkdirSync6(
|
|
2969
|
+
mkdirSync6(path11.dirname(this.file), { recursive: true });
|
|
2456
2970
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2457
2971
|
`);
|
|
2458
2972
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -2476,15 +2990,19 @@ var StdoutSink = class {
|
|
|
2476
2990
|
pii;
|
|
2477
2991
|
write;
|
|
2478
2992
|
async send(events) {
|
|
2479
|
-
|
|
2480
|
-
|
|
2993
|
+
try {
|
|
2994
|
+
for (const e of events) this.write(JSON.stringify(e));
|
|
2995
|
+
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
2996
|
+
} catch (e) {
|
|
2997
|
+
return { ok: false, acknowledged: 0, error: String(e.message ?? e), action: "retry" };
|
|
2998
|
+
}
|
|
2481
2999
|
}
|
|
2482
3000
|
};
|
|
2483
3001
|
|
|
2484
3002
|
// src/run.ts
|
|
2485
3003
|
import {
|
|
2486
3004
|
appendFileSync as appendFileSync3,
|
|
2487
|
-
existsSync as
|
|
3005
|
+
existsSync as existsSync10,
|
|
2488
3006
|
mkdirSync as mkdirSync8,
|
|
2489
3007
|
readdirSync as readdirSync6,
|
|
2490
3008
|
readFileSync as readFileSync9,
|
|
@@ -2494,12 +3012,12 @@ import {
|
|
|
2494
3012
|
writeFileSync as writeFileSync7
|
|
2495
3013
|
} from "node:fs";
|
|
2496
3014
|
import os3 from "node:os";
|
|
2497
|
-
import
|
|
3015
|
+
import path14 from "node:path";
|
|
2498
3016
|
|
|
2499
3017
|
// src/control/aliases.ts
|
|
2500
3018
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2501
|
-
import
|
|
2502
|
-
var aliasFile = (controlDir) =>
|
|
3019
|
+
import path12 from "node:path";
|
|
3020
|
+
var aliasFile = (controlDir) => path12.join(controlDir, "aliases.json");
|
|
2503
3021
|
function readAliases(file) {
|
|
2504
3022
|
try {
|
|
2505
3023
|
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
@@ -2524,11 +3042,11 @@ function receiverIdOf(all, harness, id) {
|
|
|
2524
3042
|
// src/control/local.ts
|
|
2525
3043
|
import net from "node:net";
|
|
2526
3044
|
import os2 from "node:os";
|
|
2527
|
-
import
|
|
3045
|
+
import path13 from "node:path";
|
|
2528
3046
|
function socketPath(state, env = process.env) {
|
|
2529
3047
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
2530
3048
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
2531
|
-
return
|
|
3049
|
+
return path13.join(state, "control.sock");
|
|
2532
3050
|
}
|
|
2533
3051
|
function ask(sock, req, timeoutMs) {
|
|
2534
3052
|
return new Promise((resolve) => {
|
|
@@ -2561,10 +3079,17 @@ function ask(sock, req, timeoutMs) {
|
|
|
2561
3079
|
}
|
|
2562
3080
|
|
|
2563
3081
|
// src/http-sink.ts
|
|
2564
|
-
import { createHash as
|
|
3082
|
+
import { createHash as createHash5, createHmac } from "node:crypto";
|
|
2565
3083
|
var TIMEOUT_MS = 15e3;
|
|
2566
3084
|
function webhookId(events) {
|
|
2567
|
-
return `msg_${
|
|
3085
|
+
return `msg_${createHash5("sha256").update(events.map((e) => e.id).join(",")).digest("base64url").slice(0, 32)}`;
|
|
3086
|
+
}
|
|
3087
|
+
function retryAfterMs(header, now = Date.now()) {
|
|
3088
|
+
if (header === null || header.trim() === "") return 6e4;
|
|
3089
|
+
const secs = Number(header);
|
|
3090
|
+
if (Number.isFinite(secs)) return Math.max(0, secs) * 1e3;
|
|
3091
|
+
const at = Date.parse(header);
|
|
3092
|
+
return Number.isFinite(at) ? Math.max(0, at - now) : 6e4;
|
|
2568
3093
|
}
|
|
2569
3094
|
var HttpSink = class {
|
|
2570
3095
|
constructor(cfg, tier) {
|
|
@@ -2637,7 +3162,7 @@ var HttpSink = class {
|
|
|
2637
3162
|
status: 429,
|
|
2638
3163
|
error: err,
|
|
2639
3164
|
action: "retry",
|
|
2640
|
-
retryAfterMs:
|
|
3165
|
+
retryAfterMs: retryAfterMs(r.headers.get("retry-after"))
|
|
2641
3166
|
};
|
|
2642
3167
|
return { ok: false, acknowledged: 0, status: r.status, error: err, action: "retry" };
|
|
2643
3168
|
} catch (e) {
|
|
@@ -2650,7 +3175,7 @@ var HttpSink = class {
|
|
|
2650
3175
|
|
|
2651
3176
|
// src/secrets.ts
|
|
2652
3177
|
import { spawnSync } from "node:child_process";
|
|
2653
|
-
import { createHash as
|
|
3178
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
2654
3179
|
var SERVICE = "sessionpipe";
|
|
2655
3180
|
var defaultRunner = (cmd, args, input) => {
|
|
2656
3181
|
const r = spawnSync(cmd, args, {
|
|
@@ -2662,7 +3187,7 @@ var defaultRunner = (cmd, args, input) => {
|
|
|
2662
3187
|
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
2663
3188
|
};
|
|
2664
3189
|
var sinkAccount = (name, what = "token") => {
|
|
2665
|
-
const scope = process.env.SESSIONPIPE_CONFIG ? `@${
|
|
3190
|
+
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
2666
3191
|
return `sink:${name}:${what}${scope}`;
|
|
2667
3192
|
};
|
|
2668
3193
|
function secretGet(store, account, run = defaultRunner) {
|
|
@@ -2680,49 +3205,86 @@ function sinkWithSecrets(s, run = defaultRunner) {
|
|
|
2680
3205
|
}
|
|
2681
3206
|
|
|
2682
3207
|
// src/run.ts
|
|
2683
|
-
async function tellDaemon(state,
|
|
3208
|
+
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
2684
3209
|
const sock = socketPath(state);
|
|
2685
|
-
if (process.platform !== "win32" && !
|
|
3210
|
+
if (process.platform !== "win32" && !existsSync10(sock)) return;
|
|
2686
3211
|
await ask(
|
|
2687
3212
|
sock,
|
|
2688
3213
|
{
|
|
2689
3214
|
op: "event",
|
|
2690
|
-
session: `${
|
|
2691
|
-
event:
|
|
3215
|
+
session: `${job.harness}:${session}`,
|
|
3216
|
+
event: job.event,
|
|
2692
3217
|
...cwd ? { cwd } : {},
|
|
2693
3218
|
...transcript ? { transcript } : {}
|
|
2694
3219
|
},
|
|
2695
3220
|
300
|
|
2696
3221
|
);
|
|
2697
3222
|
}
|
|
2698
|
-
|
|
3223
|
+
var MAX_TRIES = 10;
|
|
3224
|
+
function enqueueJob(state, job) {
|
|
2699
3225
|
const jobs = jobsDir(state);
|
|
2700
3226
|
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
2701
|
-
const f =
|
|
2702
|
-
writeFileSync7(f, JSON.stringify(
|
|
3227
|
+
const f = path14.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
3228
|
+
writeFileSync7(f, JSON.stringify(job), { mode: 384 });
|
|
2703
3229
|
return f;
|
|
2704
3230
|
}
|
|
2705
|
-
|
|
3231
|
+
function claimJob(file) {
|
|
3232
|
+
const claimed = `${file}.${process.pid}.running`;
|
|
3233
|
+
try {
|
|
3234
|
+
renameSync3(file, claimed);
|
|
3235
|
+
} catch {
|
|
3236
|
+
return null;
|
|
3237
|
+
}
|
|
3238
|
+
try {
|
|
3239
|
+
return { job: JSON.parse(readFileSync9(claimed, "utf8")), file: claimed };
|
|
3240
|
+
} catch {
|
|
3241
|
+
releaseJob(claimed);
|
|
3242
|
+
return null;
|
|
3243
|
+
}
|
|
3244
|
+
}
|
|
3245
|
+
function releaseJob(claimed) {
|
|
3246
|
+
if (!claimed) return;
|
|
3247
|
+
try {
|
|
3248
|
+
unlinkSync4(claimed);
|
|
3249
|
+
} catch {
|
|
3250
|
+
}
|
|
3251
|
+
}
|
|
3252
|
+
async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4) {
|
|
2706
3253
|
const jobs = jobsDir(state);
|
|
2707
3254
|
let names = [];
|
|
2708
3255
|
try {
|
|
2709
|
-
names = readdirSync6(jobs)
|
|
3256
|
+
names = readdirSync6(jobs);
|
|
2710
3257
|
} catch {
|
|
2711
3258
|
return 0;
|
|
2712
3259
|
}
|
|
3260
|
+
const queued = names.filter((n) => n.endsWith(".json"));
|
|
3261
|
+
for (const n of names) {
|
|
3262
|
+
const m = /^(.*\.json)\.(\d+)\.running$/.exec(n);
|
|
3263
|
+
if (!m) continue;
|
|
3264
|
+
const f = path14.join(jobs, n);
|
|
3265
|
+
try {
|
|
3266
|
+
if (processAlive(Number(m[2])) && Date.now() - statSync5(f).mtimeMs < abandonedMs) continue;
|
|
3267
|
+
renameSync3(f, path14.join(jobs, m[1]));
|
|
3268
|
+
queued.push(m[1]);
|
|
3269
|
+
} catch {
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
2713
3272
|
let ran = 0;
|
|
2714
|
-
for (const n of
|
|
2715
|
-
const f =
|
|
2716
|
-
let job2 = null;
|
|
3273
|
+
for (const n of queued.sort().slice(0, limit)) {
|
|
3274
|
+
const f = path14.join(jobs, n);
|
|
2717
3275
|
try {
|
|
2718
3276
|
if (Date.now() - statSync5(f).mtimeMs < olderThanMs) continue;
|
|
2719
|
-
job2 = JSON.parse(readFileSync9(f, "utf8"));
|
|
2720
|
-
unlinkSync4(f);
|
|
2721
3277
|
} catch {
|
|
2722
3278
|
continue;
|
|
2723
3279
|
}
|
|
2724
|
-
|
|
2725
|
-
|
|
3280
|
+
const c = claimJob(f);
|
|
3281
|
+
if (!c) continue;
|
|
3282
|
+
try {
|
|
3283
|
+
await runJob(c.job, { state, claimed: c.file });
|
|
3284
|
+
} catch {
|
|
3285
|
+
} finally {
|
|
3286
|
+
releaseJob(c.file);
|
|
3287
|
+
}
|
|
2726
3288
|
ran++;
|
|
2727
3289
|
}
|
|
2728
3290
|
return ran;
|
|
@@ -2730,7 +3292,7 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20) {
|
|
|
2730
3292
|
function log(state, line) {
|
|
2731
3293
|
try {
|
|
2732
3294
|
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
2733
|
-
const f =
|
|
3295
|
+
const f = path14.join(state, "log");
|
|
2734
3296
|
try {
|
|
2735
3297
|
if (statSync5(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
2736
3298
|
} catch {
|
|
@@ -2741,7 +3303,7 @@ function log(state, line) {
|
|
|
2741
3303
|
}
|
|
2742
3304
|
}
|
|
2743
3305
|
function factsState(state) {
|
|
2744
|
-
const file = (h, s) =>
|
|
3306
|
+
const file = (h, s) => path14.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
2745
3307
|
return {
|
|
2746
3308
|
get(h, s) {
|
|
2747
3309
|
try {
|
|
@@ -2754,7 +3316,7 @@ function factsState(state) {
|
|
|
2754
3316
|
const cur = this.get(h, s);
|
|
2755
3317
|
const next = { ...cur, ...patch };
|
|
2756
3318
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
2757
|
-
mkdirSync8(
|
|
3319
|
+
mkdirSync8(path14.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
2758
3320
|
writeFileSync7(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
2759
3321
|
}
|
|
2760
3322
|
};
|
|
@@ -2776,40 +3338,76 @@ function buildSinks(sinks, o = {}) {
|
|
|
2776
3338
|
}
|
|
2777
3339
|
return out;
|
|
2778
3340
|
}
|
|
2779
|
-
|
|
3341
|
+
var PROGRESS_MS = 2e4;
|
|
3342
|
+
function openTurn(read, o) {
|
|
3343
|
+
const last = read.turns[read.turns.length - 1];
|
|
3344
|
+
const fresh = (user, at) => ({
|
|
3345
|
+
turn: o.base + read.turns.length,
|
|
3346
|
+
user: user.slice(0, MAX_TURN_SIDE),
|
|
3347
|
+
assistant: "",
|
|
3348
|
+
at
|
|
3349
|
+
});
|
|
3350
|
+
if (read.open) return fresh(read.open.user, read.open.at);
|
|
3351
|
+
const prompt = o.prompt?.trim();
|
|
3352
|
+
if (prompt && !isInjected(prompt) && !(last && last.user.includes(prompt.slice(0, 200)))) return fresh(prompt, o.at);
|
|
3353
|
+
if (!last) return null;
|
|
3354
|
+
return {
|
|
3355
|
+
turn: o.base + read.turns.length - 1,
|
|
3356
|
+
user: last.user.slice(0, MAX_TURN_SIDE),
|
|
3357
|
+
assistant: capTail(last.assistant, MAX_TURN_SIDE),
|
|
3358
|
+
at: last.at
|
|
3359
|
+
};
|
|
3360
|
+
}
|
|
3361
|
+
var MAX_TURN_SIDE = 2e4;
|
|
3362
|
+
async function runJob(job, opts = {}) {
|
|
2780
3363
|
const state = opts.state ?? stateDir();
|
|
2781
3364
|
const cfg = readConfig();
|
|
2782
|
-
const adapter = adapterByName(
|
|
2783
|
-
if (!adapter)
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
3365
|
+
const adapter = adapterByName(job.harness);
|
|
3366
|
+
if (!adapter) {
|
|
3367
|
+
log(state, `${job.harness} ${job.event}: no adapter for harness "${job.harness}"; event dropped`);
|
|
3368
|
+
return { events: [], delivered: {} };
|
|
3369
|
+
}
|
|
3370
|
+
const r = adapter.fromHook({ argv: job.argv, stdin: job.stdin, env: { ...process.env, ...job.env }, cwd: job.cwd });
|
|
3371
|
+
if (!r || !r.session.id) {
|
|
3372
|
+
log(state, `${job.harness} ${job.event}: the ${job.harness} adapter did not claim this payload; event dropped`);
|
|
3373
|
+
return { events: [], delivered: {} };
|
|
3374
|
+
}
|
|
3375
|
+
const id = receiverIdOf(readAliases(aliasFile(path14.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3376
|
+
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3377
|
+
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
2788
3378
|
const outbox = new Outbox(state);
|
|
2789
|
-
const ref = { harness:
|
|
3379
|
+
const ref = { harness: job.harness, session: id };
|
|
2790
3380
|
let release = takeLock(state, ref);
|
|
2791
|
-
|
|
2792
|
-
|
|
3381
|
+
const lockWait = opts.lockWaitMs ?? LOCK_WAIT_MS;
|
|
3382
|
+
for (const until = Date.now() + lockWait; !release && Date.now() < until; ) {
|
|
3383
|
+
await new Promise((res) => setTimeout(res, 50));
|
|
2793
3384
|
release = takeLock(state, ref);
|
|
2794
3385
|
}
|
|
2795
3386
|
if (!release) {
|
|
2796
|
-
log(state, `${
|
|
2797
|
-
enqueueJob(state,
|
|
2798
|
-
|
|
3387
|
+
log(state, `${job.harness} ${job.event}: session ${id} locked for ${lockWait / 1e3} s, re-queued`);
|
|
3388
|
+
const requeued = enqueueJob(state, { ...job, tries: (job.tries ?? 0) + 1 });
|
|
3389
|
+
releaseJob(opts.claimed);
|
|
3390
|
+
return { events: [], delivered: {}, requeued };
|
|
2799
3391
|
}
|
|
3392
|
+
const events = [];
|
|
2800
3393
|
try {
|
|
2801
3394
|
const facts = factsState(state);
|
|
2802
|
-
const mem = facts.get(
|
|
2803
|
-
|
|
3395
|
+
const mem = facts.get(job.harness, id);
|
|
3396
|
+
if (claudeEnd && !existsSync10(outbox.file(ref)) && Object.keys(mem).length === 0 && !(r.transcript && existsSync10(r.transcript))) {
|
|
3397
|
+
releaseJob(opts.claimed);
|
|
3398
|
+
return { events: [], delivered: {} };
|
|
3399
|
+
}
|
|
3400
|
+
if (claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
3401
|
+
const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
|
|
2804
3402
|
let learned = {};
|
|
2805
3403
|
if (heavy) {
|
|
2806
|
-
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts);
|
|
3404
|
+
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts, { ...process.env, ...job.env });
|
|
2807
3405
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
2808
3406
|
learned = rest;
|
|
2809
|
-
if (harness_version) facts.save(
|
|
2810
|
-
facts.save(
|
|
2811
|
-
if (
|
|
2812
|
-
facts.save(
|
|
3407
|
+
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
3408
|
+
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
3409
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path14.isAbsolute(r.session.cwd))
|
|
3410
|
+
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
2813
3411
|
}
|
|
2814
3412
|
const known = mem.session ?? {};
|
|
2815
3413
|
const cwdRaw = learned.cwd ?? r.session.cwd ?? known.cwd;
|
|
@@ -2825,19 +3423,49 @@ async function runJob(job2, opts = {}) {
|
|
|
2825
3423
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
2826
3424
|
...r.session.model ? { model: r.session.model } : {}
|
|
2827
3425
|
};
|
|
2828
|
-
const version = facts.get(
|
|
2829
|
-
const events = [];
|
|
3426
|
+
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
2830
3427
|
for (const a of r.events) {
|
|
2831
3428
|
const e = makeEvent(a, {
|
|
2832
|
-
harness: { name:
|
|
3429
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
2833
3430
|
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
2834
|
-
now: opts.now ??
|
|
3431
|
+
now: opts.now ?? job.at
|
|
2835
3432
|
});
|
|
2836
3433
|
events.push(e);
|
|
2837
3434
|
}
|
|
2838
3435
|
const wantsTranscript = cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 2);
|
|
3436
|
+
const ending = r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended");
|
|
3437
|
+
const starting = r.events.some((e) => e.type === "turn.started");
|
|
3438
|
+
const startedId = r.events.find((e) => e.type === "turn.started")?.data.turn_id;
|
|
3439
|
+
const absorbed = starting && typeof startedId === "string" && facts.get(job.harness, id).open_turn === startedId;
|
|
3440
|
+
if (wantsTranscript && r.transcript && !ending && (starting || r.events.some((e) => e.type === "tool.ended"))) {
|
|
3441
|
+
const cur = facts.get(job.harness, id);
|
|
3442
|
+
const at = opts.now ?? job.at;
|
|
3443
|
+
if (starting || at - (Number(cur.progress_at) || 0) >= PROGRESS_MS) {
|
|
3444
|
+
const p = openTurn(adapter.readTranscript(r.transcript, Number(cur.transcript_line) || 0), {
|
|
3445
|
+
base: Number(cur.transcript_turns) || 0,
|
|
3446
|
+
prompt: !absorbed && typeof r.hints?.prompt === "string" ? r.hints.prompt : void 0,
|
|
3447
|
+
at
|
|
3448
|
+
});
|
|
3449
|
+
const sig = p && `${p.turn}:${p.user.length}:${p.assistant.length}`;
|
|
3450
|
+
if (p && (starting || sig !== cur.progress_sig)) {
|
|
3451
|
+
events.push(
|
|
3452
|
+
makeEvent(
|
|
3453
|
+
{ type: "turn.progress", harnessEvent: job.event, data: { ...p, at: new Date(p.at).toISOString() } },
|
|
3454
|
+
{
|
|
3455
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
3456
|
+
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
3457
|
+
now: at
|
|
3458
|
+
}
|
|
3459
|
+
)
|
|
3460
|
+
);
|
|
3461
|
+
facts.save(job.harness, id, { progress_at: at, progress_sig: sig });
|
|
3462
|
+
}
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
if (starting && typeof startedId === "string" && !absorbed) facts.save(job.harness, id, { open_turn: startedId });
|
|
3466
|
+
if (ending) facts.save(job.harness, id, { open_turn: null });
|
|
2839
3467
|
if (wantsTranscript && r.transcript && r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended")) {
|
|
2840
|
-
const cur = facts.get(
|
|
3468
|
+
const cur = facts.get(job.harness, id);
|
|
2841
3469
|
const fromLine = Number(cur.transcript_line) || 0;
|
|
2842
3470
|
let turnNo = Number(cur.transcript_turns) || 0;
|
|
2843
3471
|
const read = adapter.readTranscript(r.transcript, fromLine);
|
|
@@ -2846,34 +3474,97 @@ async function runJob(job2, opts = {}) {
|
|
|
2846
3474
|
makeEvent(
|
|
2847
3475
|
{
|
|
2848
3476
|
type: "turn.transcript",
|
|
2849
|
-
harnessEvent:
|
|
3477
|
+
harnessEvent: job.event,
|
|
2850
3478
|
data: { turn: turnNo++, user: t.user, assistant: t.assistant, at: new Date(t.at).toISOString() }
|
|
2851
3479
|
},
|
|
2852
3480
|
{
|
|
2853
|
-
harness: { name:
|
|
3481
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
2854
3482
|
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
2855
|
-
now: opts.now ??
|
|
3483
|
+
now: opts.now ?? job.at
|
|
2856
3484
|
}
|
|
2857
3485
|
)
|
|
2858
3486
|
);
|
|
2859
3487
|
}
|
|
2860
|
-
if (read.turns.length) facts.save(
|
|
3488
|
+
if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
2861
3489
|
}
|
|
2862
3490
|
outbox.append(
|
|
2863
3491
|
ref,
|
|
2864
3492
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
2865
3493
|
);
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
3494
|
+
releaseJob(opts.claimed);
|
|
3495
|
+
log(
|
|
3496
|
+
state,
|
|
3497
|
+
`${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${shortId(job.harness, id)})`
|
|
3498
|
+
);
|
|
2870
3499
|
} finally {
|
|
2871
3500
|
release();
|
|
2872
3501
|
}
|
|
3502
|
+
const delivered = await drain(state, cfg.sinks, outbox, ref) ?? {};
|
|
3503
|
+
if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
|
|
3504
|
+
return { events, delivered };
|
|
3505
|
+
}
|
|
3506
|
+
async function drain(state, sinkConfigs, outbox, first) {
|
|
3507
|
+
const more = path14.join(state, "locks", "flush.more");
|
|
3508
|
+
let release = takeNamedLock(state, "flush");
|
|
3509
|
+
if (!release) {
|
|
3510
|
+
try {
|
|
3511
|
+
writeFileSync7(more, "", { mode: 384 });
|
|
3512
|
+
} catch {
|
|
3513
|
+
}
|
|
3514
|
+
release = takeNamedLock(state, "flush");
|
|
3515
|
+
if (!release) return null;
|
|
3516
|
+
}
|
|
3517
|
+
const delivered = {};
|
|
3518
|
+
for (; ; ) {
|
|
3519
|
+
try {
|
|
3520
|
+
unlinkSync4(more);
|
|
3521
|
+
} catch {
|
|
3522
|
+
}
|
|
3523
|
+
try {
|
|
3524
|
+
for (const [k, v] of Object.entries(await flush(state, sinkConfigs, outbox, first)))
|
|
3525
|
+
delivered[k] = (delivered[k] ?? 0) + v;
|
|
3526
|
+
} finally {
|
|
3527
|
+
release();
|
|
3528
|
+
}
|
|
3529
|
+
if (!existsSync10(more)) return delivered;
|
|
3530
|
+
release = takeNamedLock(state, "flush");
|
|
3531
|
+
if (!release) return delivered;
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3535
|
+
var backoffFile = (state, sink) => path14.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3536
|
+
function readBackoff(state, sink) {
|
|
3537
|
+
try {
|
|
3538
|
+
const b = JSON.parse(readFileSync9(backoffFile(state, sink), "utf8"));
|
|
3539
|
+
return typeof b.until === "number" ? b : null;
|
|
3540
|
+
} catch {
|
|
3541
|
+
return null;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
function noteFailure(state, sink, res, prev) {
|
|
3545
|
+
const failures = (prev?.failures ?? 0) + 1;
|
|
3546
|
+
const ladder = BACKOFF_MS[Math.min(failures, BACKOFF_MS.length) - 1];
|
|
3547
|
+
const wait = Number.isFinite(res.retryAfterMs) ? res.retryAfterMs : ladder;
|
|
3548
|
+
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3549
|
+
const f = backoffFile(state, sink);
|
|
3550
|
+
try {
|
|
3551
|
+
mkdirSync8(path14.dirname(f), { recursive: true, mode: 448 });
|
|
3552
|
+
writeFileSync7(f, JSON.stringify(b), { mode: 384 });
|
|
3553
|
+
} catch {
|
|
3554
|
+
}
|
|
3555
|
+
return b;
|
|
3556
|
+
}
|
|
3557
|
+
function clearBackoff(state, sink) {
|
|
3558
|
+
try {
|
|
3559
|
+
unlinkSync4(backoffFile(state, sink));
|
|
3560
|
+
} catch {
|
|
3561
|
+
}
|
|
2873
3562
|
}
|
|
3563
|
+
var LOCK_WAIT_MS = 6e3;
|
|
3564
|
+
var CWD_FROM_HOOKS = /* @__PURE__ */ new Set(["antigravity", "devin"]);
|
|
2874
3565
|
var BATCH = 50;
|
|
2875
3566
|
var MAX_PER_RUN = 500;
|
|
2876
|
-
async function flush(state, sinkConfigs, outbox, first) {
|
|
3567
|
+
async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
2877
3568
|
const delivered = {};
|
|
2878
3569
|
const sinks = buildSinks(sinkConfigs, {
|
|
2879
3570
|
locked: (name) => log(state, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
@@ -2883,13 +3574,39 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2883
3574
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
2884
3575
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
2885
3576
|
}
|
|
2886
|
-
|
|
3577
|
+
const drainSink = async (sink) => {
|
|
3578
|
+
let backoff = readBackoff(state, sink.name);
|
|
3579
|
+
if (backoff && !o.force && backoff.until > Date.now()) {
|
|
3580
|
+
delivered[sink.name] = 0;
|
|
3581
|
+
return;
|
|
3582
|
+
}
|
|
2887
3583
|
const cursors = new Cursors(state, sink.name);
|
|
2888
3584
|
const c = cursors.read();
|
|
2889
3585
|
let sent = 0;
|
|
2890
3586
|
let stalled = false;
|
|
2891
3587
|
let batch = [];
|
|
2892
3588
|
let pending = {};
|
|
3589
|
+
const deliver = async (events) => {
|
|
3590
|
+
const res = await sink.send(events);
|
|
3591
|
+
if (res.ok || res.action !== "split") return res;
|
|
3592
|
+
if (events.length === 1) {
|
|
3593
|
+
const e = events[0];
|
|
3594
|
+
log(state, `sink ${sink.name}: 413 ${res.error ?? ""} \u2192 event ${e.id} (${e.type}) is too large alone, dropped`);
|
|
3595
|
+
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected: [{ id: e.id, reason: "too_large" }] };
|
|
3596
|
+
}
|
|
3597
|
+
const half = Math.ceil(events.length / 2);
|
|
3598
|
+
const a = await deliver(events.slice(0, half));
|
|
3599
|
+
if (!a.ok) return a;
|
|
3600
|
+
const b = await deliver(events.slice(half));
|
|
3601
|
+
if (!b.ok) return b;
|
|
3602
|
+
return {
|
|
3603
|
+
ok: true,
|
|
3604
|
+
acknowledged: a.acknowledged + b.acknowledged,
|
|
3605
|
+
accepted: (a.accepted ?? 0) + (b.accepted ?? 0),
|
|
3606
|
+
duplicates: (a.duplicates ?? 0) + (b.duplicates ?? 0),
|
|
3607
|
+
rejected: [...a.rejected ?? [], ...b.rejected ?? []]
|
|
3608
|
+
};
|
|
3609
|
+
};
|
|
2893
3610
|
const send = async () => {
|
|
2894
3611
|
if (!batch.length) {
|
|
2895
3612
|
Object.assign(c, pending);
|
|
@@ -2897,15 +3614,24 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2897
3614
|
cursors.write(c);
|
|
2898
3615
|
return true;
|
|
2899
3616
|
}
|
|
2900
|
-
const res = await
|
|
3617
|
+
const res = await deliver(batch);
|
|
2901
3618
|
if (!res.ok) {
|
|
2902
|
-
|
|
3619
|
+
const action = res.action ?? "retry";
|
|
3620
|
+
let wait = "";
|
|
3621
|
+
if (action === "retry") {
|
|
3622
|
+
backoff = noteFailure(state, sink.name, res, backoff);
|
|
3623
|
+
wait = `, waits ${Math.round((backoff.until - Date.now()) / 1e3)} s`;
|
|
3624
|
+
}
|
|
3625
|
+
log(state, `sink ${sink.name}: ${res.status ?? ""} ${res.error ?? ""} \u2192 ${action}${wait}`);
|
|
2903
3626
|
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2904
3627
|
if (res.action !== "drop") {
|
|
2905
3628
|
batch = [];
|
|
2906
3629
|
pending = {};
|
|
2907
3630
|
return false;
|
|
2908
3631
|
}
|
|
3632
|
+
} else if (backoff) {
|
|
3633
|
+
clearBackoff(state, sink.name);
|
|
3634
|
+
backoff = null;
|
|
2909
3635
|
}
|
|
2910
3636
|
sent += batch.length;
|
|
2911
3637
|
Object.assign(c, pending);
|
|
@@ -2940,7 +3666,8 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2940
3666
|
if (!await send()) stalled = true;
|
|
2941
3667
|
}
|
|
2942
3668
|
delivered[sink.name] = sent;
|
|
2943
|
-
}
|
|
3669
|
+
};
|
|
3670
|
+
await Promise.all(sinks.map(drainSink));
|
|
2944
3671
|
return delivered;
|
|
2945
3672
|
}
|
|
2946
3673
|
function applySinkVerdict(name, res) {
|
|
@@ -2954,24 +3681,31 @@ function applySinkVerdict(name, res) {
|
|
|
2954
3681
|
} catch {
|
|
2955
3682
|
}
|
|
2956
3683
|
}
|
|
2957
|
-
var jobsDir = (state) =>
|
|
3684
|
+
var jobsDir = (state) => path14.join(state, "jobs");
|
|
2958
3685
|
|
|
2959
3686
|
// src/worker.ts
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
}).
|
|
2976
|
-
}).finally(() =>
|
|
3687
|
+
async function workerMain(argv) {
|
|
3688
|
+
const jobFile = argv[2];
|
|
3689
|
+
if (!jobFile) return;
|
|
3690
|
+
const claimed = claimJob(jobFile);
|
|
3691
|
+
if (!claimed) return;
|
|
3692
|
+
const deadline = setTimeout(() => process.exit(0), 15e3);
|
|
3693
|
+
deadline.unref();
|
|
3694
|
+
await runJob(claimed.job, { claimed: claimed.file }).then((r) => {
|
|
3695
|
+
if (!r.requeued || (claimed.job.tries ?? 0) + 1 >= MAX_TRIES) return;
|
|
3696
|
+
const p = spawn(process.execPath, [argv[1], r.requeued], {
|
|
3697
|
+
detached: true,
|
|
3698
|
+
stdio: "ignore",
|
|
3699
|
+
windowsHide: true
|
|
3700
|
+
});
|
|
3701
|
+
p.unref();
|
|
3702
|
+
}).catch(() => {
|
|
3703
|
+
}).finally(() => releaseJob(claimed.file)).then(() => sweepJobs(stateDir())).catch(() => {
|
|
3704
|
+
}).finally(() => clearTimeout(deadline));
|
|
3705
|
+
}
|
|
3706
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync2(process.argv[1])).href)
|
|
3707
|
+
workerMain(process.argv).finally(() => process.exit(0));
|
|
3708
|
+
export {
|
|
3709
|
+
workerMain
|
|
3710
|
+
};
|
|
2977
3711
|
//# sourceMappingURL=worker.js.map
|