sessionpipe 0.6.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -5
- package/dist/cli.d.ts +11 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3331 -1371
- package/dist/cli.js.map +4 -4
- package/dist/connect.d.ts.map +1 -1
- package/dist/control/aliases.d.ts +15 -0
- package/dist/control/aliases.d.ts.map +1 -0
- package/dist/control/autoupdate.d.ts.map +1 -1
- package/dist/control/cli.d.ts +8 -1
- package/dist/control/cli.d.ts.map +1 -1
- package/dist/control/daemon.d.ts +65 -10
- package/dist/control/daemon.d.ts.map +1 -1
- package/dist/control/files.d.ts +33 -0
- package/dist/control/files.d.ts.map +1 -0
- package/dist/control/pair.d.ts +9 -0
- package/dist/control/pair.d.ts.map +1 -1
- package/dist/control/route.d.ts +5 -2
- package/dist/control/route.d.ts.map +1 -1
- package/dist/control/store.d.ts.map +1 -1
- package/dist/control/stream.d.ts.map +1 -1
- package/dist/help.d.ts +3 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/hook.d.ts +9 -1
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +184 -158
- package/dist/hook.js.map +3 -3
- package/dist/http-sink.d.ts +3 -0
- package/dist/http-sink.d.ts.map +1 -1
- package/dist/index.js +943 -220
- package/dist/index.js.map +4 -4
- package/dist/launcher.d.ts.map +1 -1
- package/dist/run.d.ts +70 -8
- package/dist/run.d.ts.map +1 -1
- package/dist/runtime.d.ts +5 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/secrets.d.ts.map +1 -1
- package/dist/update.d.ts +3 -2
- package/dist/update.d.ts.map +1 -1
- package/dist/worker.d.ts +3 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +1021 -259
- package/dist/worker.js.map +4 -4
- package/package.json +5 -4
package/dist/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,13 +218,22 @@ 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));
|
|
192
227
|
}
|
|
228
|
+
function codexInjected(text) {
|
|
229
|
+
return /^<[a-z_-]+>/i.test(text) || /^# AGENTS\.md instructions\b/i.test(text);
|
|
230
|
+
}
|
|
193
231
|
function readCodex(lines, from) {
|
|
194
232
|
const msgs = [];
|
|
195
|
-
|
|
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++) {
|
|
196
237
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
197
238
|
if (!j || j.type !== "response_item") continue;
|
|
198
239
|
const p = j.payload;
|
|
@@ -201,7 +242,7 @@ function readCodex(lines, from) {
|
|
|
201
242
|
const text = textParts(p.content, ["input_text", "output_text"]).trim();
|
|
202
243
|
if (!text) continue;
|
|
203
244
|
if (p.role === "user") {
|
|
204
|
-
if (
|
|
245
|
+
if (codexInjected(text)) continue;
|
|
205
246
|
msgs.push({ role: "user", text, at, line: i });
|
|
206
247
|
} else if (p.role === "assistant") msgs.push({ role: "assistant", text, at, line: i });
|
|
207
248
|
}
|
|
@@ -230,7 +271,7 @@ function readAntigravity(lines, from) {
|
|
|
230
271
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
231
272
|
if (j.type === "USER_INPUT") {
|
|
232
273
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
233
|
-
const text = (m ? m[1]
|
|
274
|
+
const text = (m ? m[1] : j.content).trim();
|
|
234
275
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
235
276
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
236
277
|
const text = j.content.trim();
|
|
@@ -278,6 +319,10 @@ function formatJson(value, f) {
|
|
|
278
319
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
279
320
|
return f.finalNewline ? text + f.eol : text;
|
|
280
321
|
}
|
|
322
|
+
function readJson(file) {
|
|
323
|
+
const f = readJsonFile(file);
|
|
324
|
+
return f.ok ? f.value : {};
|
|
325
|
+
}
|
|
281
326
|
function entryFor(event, harness, cmd, o) {
|
|
282
327
|
const matcher = o.matcherFor(event);
|
|
283
328
|
return {
|
|
@@ -347,8 +392,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
347
392
|
unlinkSync(file);
|
|
348
393
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
349
394
|
}
|
|
350
|
-
|
|
351
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
395
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
352
396
|
return { file, changed: true };
|
|
353
397
|
}
|
|
354
398
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -375,7 +419,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
375
419
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
376
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';
|
|
377
421
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
378
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(
|
|
422
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
|
|
379
423
|
var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
|
|
380
424
|
var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
381
425
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
@@ -441,7 +485,8 @@ var antigravity = {
|
|
|
441
485
|
const event = input.argv[1] ?? "";
|
|
442
486
|
let s = {};
|
|
443
487
|
try {
|
|
444
|
-
|
|
488
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
489
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
445
490
|
} catch {
|
|
446
491
|
}
|
|
447
492
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -497,19 +542,16 @@ var antigravity = {
|
|
|
497
542
|
}
|
|
498
543
|
return readAntigravity(text.split("\n"), fromLine);
|
|
499
544
|
},
|
|
500
|
-
facts(session, transcript, hints
|
|
545
|
+
facts(session, transcript, hints) {
|
|
501
546
|
if (!transcript) return {};
|
|
502
547
|
return antigravityFacts(transcript, {
|
|
503
548
|
conversation: session.id,
|
|
504
549
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
505
|
-
state,
|
|
506
550
|
env: process.env
|
|
507
551
|
});
|
|
508
552
|
},
|
|
509
553
|
backfill(sinceMs, env = process.env) {
|
|
510
|
-
const
|
|
511
|
-
const noState = { get: () => ({}), save: () => {
|
|
512
|
-
} };
|
|
554
|
+
const rows2 = [];
|
|
513
555
|
for (const data of dataDirs(env)) {
|
|
514
556
|
let ids = [];
|
|
515
557
|
try {
|
|
@@ -523,13 +565,13 @@ var antigravity = {
|
|
|
523
565
|
} catch {
|
|
524
566
|
continue;
|
|
525
567
|
}
|
|
526
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
568
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
527
569
|
if (!facts.started_at || !facts.last_at) continue;
|
|
528
570
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
529
|
-
|
|
571
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
530
572
|
}
|
|
531
573
|
}
|
|
532
|
-
return
|
|
574
|
+
return rows2;
|
|
533
575
|
}
|
|
534
576
|
};
|
|
535
577
|
function prune(o) {
|
|
@@ -579,7 +621,7 @@ function agProjects(dir) {
|
|
|
579
621
|
}
|
|
580
622
|
const folders = [];
|
|
581
623
|
for (const r of j?.projectResources?.resources ?? []) {
|
|
582
|
-
const f = agPath(r?.folderUri);
|
|
624
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
583
625
|
if (f) folders.push(...agVariants(f));
|
|
584
626
|
}
|
|
585
627
|
if (folders.length) out.push({ id, folders });
|
|
@@ -633,7 +675,7 @@ function antigravityFacts(file, opts) {
|
|
|
633
675
|
for (const j of H) {
|
|
634
676
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
635
677
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
636
|
-
const text = (m ? m[1]
|
|
678
|
+
const text = (m ? m[1] : j.content).trim();
|
|
637
679
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
638
680
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
639
681
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -681,10 +723,9 @@ function gitFacts(cwd) {
|
|
|
681
723
|
return out;
|
|
682
724
|
}
|
|
683
725
|
function stripCredentials(url) {
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
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$/, "");
|
|
688
729
|
}
|
|
689
730
|
|
|
690
731
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -715,7 +756,7 @@ var OPTS = {
|
|
|
715
756
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
716
757
|
};
|
|
717
758
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
718
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
759
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
719
760
|
const out = [];
|
|
720
761
|
const add = (d) => {
|
|
721
762
|
if (!d) return;
|
|
@@ -756,11 +797,13 @@ var claudeCode = {
|
|
|
756
797
|
const event = input.argv[1] ?? "";
|
|
757
798
|
let s = {};
|
|
758
799
|
try {
|
|
759
|
-
|
|
800
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
801
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
760
802
|
} catch {
|
|
761
803
|
}
|
|
762
804
|
const id = s.session_id ?? s.sessionId;
|
|
763
805
|
if (!id) return null;
|
|
806
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
764
807
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
765
808
|
if (typeof s.model === "string") session.model = s.model;
|
|
766
809
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -879,6 +922,7 @@ var claudeCode = {
|
|
|
879
922
|
return null;
|
|
880
923
|
}
|
|
881
924
|
const hints = {};
|
|
925
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
882
926
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
883
927
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
884
928
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
@@ -904,7 +948,7 @@ var claudeCode = {
|
|
|
904
948
|
return out;
|
|
905
949
|
},
|
|
906
950
|
backfill(sinceMs, env = process.env) {
|
|
907
|
-
const
|
|
951
|
+
const rows2 = [];
|
|
908
952
|
const files = [];
|
|
909
953
|
for (const d of claudeDirs(env))
|
|
910
954
|
files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -916,19 +960,23 @@ var claudeCode = {
|
|
|
916
960
|
if (!facts.started_at || !facts.last_at) continue;
|
|
917
961
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
918
962
|
const cwd = rest.cwd;
|
|
919
|
-
|
|
963
|
+
rows2.push({
|
|
920
964
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
921
965
|
started_at,
|
|
922
966
|
last_at
|
|
923
967
|
});
|
|
924
968
|
}
|
|
925
|
-
return
|
|
969
|
+
return rows2;
|
|
926
970
|
}
|
|
927
971
|
};
|
|
928
972
|
function prune2(o) {
|
|
929
973
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
930
974
|
return o;
|
|
931
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
|
+
}
|
|
932
980
|
function pickSource(v) {
|
|
933
981
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
934
982
|
}
|
|
@@ -1034,23 +1082,27 @@ function parentOf() {
|
|
|
1034
1082
|
}
|
|
1035
1083
|
return (pid) => table.get(pid) || 0;
|
|
1036
1084
|
}
|
|
1037
|
-
var
|
|
1038
|
-
function
|
|
1039
|
-
const hit = accountCache.get(dir);
|
|
1040
|
-
if (hit !== void 0) return hit;
|
|
1085
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1086
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1041
1087
|
const files = [path5.join(dir, ".claude.json")];
|
|
1042
|
-
if (path5.resolve(dir) === path5.join(
|
|
1043
|
-
let acct = null;
|
|
1088
|
+
if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
|
|
1044
1089
|
for (const f of files) {
|
|
1045
1090
|
try {
|
|
1046
1091
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1047
|
-
if (a?.accountUuid) {
|
|
1048
|
-
|
|
1049
|
-
|
|
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 } : {} };
|
|
1050
1095
|
}
|
|
1051
1096
|
} catch {
|
|
1052
1097
|
}
|
|
1053
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;
|
|
1054
1106
|
accountCache.set(dir, acct);
|
|
1055
1107
|
return acct;
|
|
1056
1108
|
}
|
|
@@ -1270,18 +1322,13 @@ function parseArgv(raw) {
|
|
|
1270
1322
|
function readNotify(text) {
|
|
1271
1323
|
const { head, tail, at } = split(text);
|
|
1272
1324
|
const m = NOTIFY_LINE.exec(head);
|
|
1273
|
-
if (m)
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
index: m.index,
|
|
1278
|
-
length: m[0].length,
|
|
1279
|
-
misplaced: false,
|
|
1280
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1281
|
-
};
|
|
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
|
+
}
|
|
1282
1329
|
const k = NOTIFY_KEY.exec(head);
|
|
1283
1330
|
if (k) {
|
|
1284
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1331
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1285
1332
|
return {
|
|
1286
1333
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1287
1334
|
argv: null,
|
|
@@ -1292,15 +1339,11 @@ function readNotify(text) {
|
|
|
1292
1339
|
};
|
|
1293
1340
|
}
|
|
1294
1341
|
const t = NOTIFY_LINE.exec(tail);
|
|
1295
|
-
if (t
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
argv: parseArgv(t[
|
|
1299
|
-
|
|
1300
|
-
length: t[0].length,
|
|
1301
|
-
misplaced: true,
|
|
1302
|
-
other: false
|
|
1303
|
-
};
|
|
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
|
+
}
|
|
1304
1347
|
return null;
|
|
1305
1348
|
}
|
|
1306
1349
|
function dropLine(text, index, length) {
|
|
@@ -1319,9 +1362,7 @@ function withNotify(text, line) {
|
|
|
1319
1362
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1320
1363
|
return insertTopLevel(text, line);
|
|
1321
1364
|
}
|
|
1322
|
-
function replaceNotify(text, line) {
|
|
1323
|
-
const n = readNotify(text);
|
|
1324
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1365
|
+
function replaceNotify(text, n, line) {
|
|
1325
1366
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1326
1367
|
}
|
|
1327
1368
|
function withoutNotify(text) {
|
|
@@ -1362,7 +1403,7 @@ var CODEX_EVENTS = [
|
|
|
1362
1403
|
];
|
|
1363
1404
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1364
1405
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1365
|
-
var codexHome = (env) => env.CODEX_HOME || path6.join(
|
|
1406
|
+
var codexHome = (env) => env.CODEX_HOME || path6.join(homeDir(), ".codex");
|
|
1366
1407
|
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1367
1408
|
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
1368
1409
|
var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
|
|
@@ -1400,11 +1441,8 @@ function installNotify(cmd, env) {
|
|
|
1400
1441
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1401
1442
|
}
|
|
1402
1443
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
write(next);
|
|
1406
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1407
|
-
}
|
|
1444
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1445
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1408
1446
|
}
|
|
1409
1447
|
}
|
|
1410
1448
|
return {
|
|
@@ -1433,13 +1471,13 @@ function uninstallNotify(env) {
|
|
|
1433
1471
|
}
|
|
1434
1472
|
const prev = previousNotify(n.argv);
|
|
1435
1473
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1436
|
-
|
|
1437
|
-
if (next) writeFileSync3(file, next);
|
|
1474
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1438
1475
|
return { file, changed: true };
|
|
1439
1476
|
}
|
|
1440
1477
|
return { file, changed: false };
|
|
1441
1478
|
}
|
|
1442
|
-
function findRollout(threadId, env) {
|
|
1479
|
+
function findRollout(threadId, env = process.env) {
|
|
1480
|
+
if (!/^[A-Za-z0-9-]{8,128}$/.test(threadId)) return null;
|
|
1443
1481
|
let best = null;
|
|
1444
1482
|
const walk = (dir, depth) => {
|
|
1445
1483
|
let names = [];
|
|
@@ -1450,7 +1488,7 @@ function findRollout(threadId, env) {
|
|
|
1450
1488
|
}
|
|
1451
1489
|
for (const n of names.sort().reverse()) {
|
|
1452
1490
|
const p = path6.join(dir, n);
|
|
1453
|
-
if (n.endsWith(
|
|
1491
|
+
if (n.endsWith(`-${threadId}.jsonl`)) {
|
|
1454
1492
|
best = p;
|
|
1455
1493
|
return;
|
|
1456
1494
|
}
|
|
@@ -1463,6 +1501,13 @@ function findRollout(threadId, env) {
|
|
|
1463
1501
|
walk(sessionsDir(env), 0);
|
|
1464
1502
|
return best;
|
|
1465
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
|
+
}
|
|
1466
1511
|
var codex = {
|
|
1467
1512
|
name: NAME3,
|
|
1468
1513
|
events: CODEX_EVENTS,
|
|
@@ -1505,11 +1550,14 @@ var codex = {
|
|
|
1505
1550
|
if (event === "notify") {
|
|
1506
1551
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1507
1552
|
try {
|
|
1508
|
-
|
|
1553
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1554
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1509
1555
|
} catch {
|
|
1510
1556
|
}
|
|
1511
1557
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1512
1558
|
if (!id2) return null;
|
|
1559
|
+
const file = findRollout(String(id2), input.env);
|
|
1560
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1513
1561
|
const r2 = {
|
|
1514
1562
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1515
1563
|
events: [
|
|
@@ -1519,18 +1567,19 @@ var codex = {
|
|
|
1519
1567
|
harnessEvent: "notify"
|
|
1520
1568
|
}
|
|
1521
1569
|
],
|
|
1522
|
-
hints: {}
|
|
1570
|
+
hints: {},
|
|
1571
|
+
transcript: file
|
|
1523
1572
|
};
|
|
1524
|
-
const file = findRollout(String(id2), input.env);
|
|
1525
|
-
if (file) r2.transcript = file;
|
|
1526
1573
|
return r2;
|
|
1527
1574
|
}
|
|
1528
1575
|
try {
|
|
1529
|
-
|
|
1576
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1577
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1530
1578
|
} catch {
|
|
1531
1579
|
}
|
|
1532
1580
|
const id = s.session_id;
|
|
1533
1581
|
if (!id) return null;
|
|
1582
|
+
if (s.transcript_path === null) return null;
|
|
1534
1583
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1535
1584
|
if (typeof s.model === "string") session.model = s.model;
|
|
1536
1585
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1602,7 +1651,8 @@ var codex = {
|
|
|
1602
1651
|
return null;
|
|
1603
1652
|
}
|
|
1604
1653
|
const r = { session, events, hints: {} };
|
|
1605
|
-
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;
|
|
1606
1656
|
return r;
|
|
1607
1657
|
},
|
|
1608
1658
|
readTranscript(file, fromLine) {
|
|
@@ -1620,16 +1670,17 @@ var codex = {
|
|
|
1620
1670
|
return out;
|
|
1621
1671
|
},
|
|
1622
1672
|
backfill(sinceMs, env = process.env) {
|
|
1623
|
-
const
|
|
1673
|
+
const rows2 = [];
|
|
1624
1674
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1625
1675
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
|
|
1626
1676
|
if (!m?.[1]) continue;
|
|
1677
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1627
1678
|
const facts = codexFacts(f);
|
|
1628
1679
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1629
1680
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1630
|
-
|
|
1681
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1631
1682
|
}
|
|
1632
|
-
return
|
|
1683
|
+
return rows2;
|
|
1633
1684
|
}
|
|
1634
1685
|
};
|
|
1635
1686
|
function prune3(o) {
|
|
@@ -1638,10 +1689,11 @@ function prune3(o) {
|
|
|
1638
1689
|
}
|
|
1639
1690
|
function codexFacts(file) {
|
|
1640
1691
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1641
|
-
|
|
1642
|
-
|
|
1692
|
+
let H = parseLines(head);
|
|
1693
|
+
let T = parseLines(tail);
|
|
1643
1694
|
const out = {};
|
|
1644
1695
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1696
|
+
const boundary = codexReplayBoundary(meta);
|
|
1645
1697
|
if (meta?.payload) {
|
|
1646
1698
|
const p = meta.payload;
|
|
1647
1699
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1649,19 +1701,26 @@ function codexFacts(file) {
|
|
|
1649
1701
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1650
1702
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1651
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
|
+
}
|
|
1652
1711
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1653
1712
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1654
1713
|
for (const j of H) {
|
|
1655
1714
|
const p = j.payload;
|
|
1656
1715
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1657
1716
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1658
|
-
if (!text ||
|
|
1659
|
-
const line = text.split("\n")
|
|
1717
|
+
if (!text || codexInjected(text)) continue;
|
|
1718
|
+
const line = text.split("\n")[0];
|
|
1660
1719
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1661
1720
|
out.title_source = "first-ask";
|
|
1662
1721
|
break;
|
|
1663
1722
|
}
|
|
1664
|
-
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);
|
|
1665
1724
|
if (times.length) {
|
|
1666
1725
|
out.started_at = Math.min(...times);
|
|
1667
1726
|
out.last_at = Math.max(...times);
|
|
@@ -1669,11 +1728,432 @@ function codexFacts(file) {
|
|
|
1669
1728
|
return out;
|
|
1670
1729
|
}
|
|
1671
1730
|
|
|
1672
|
-
// ../core/src/adapters/
|
|
1673
|
-
import {
|
|
1674
|
-
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";
|
|
1675
1734
|
import path7 from "node:path";
|
|
1676
|
-
|
|
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";
|
|
1677
2157
|
var GEMINI_EVENTS = [
|
|
1678
2158
|
"SessionStart",
|
|
1679
2159
|
"BeforeAgent",
|
|
@@ -1684,11 +2164,11 @@ var GEMINI_EVENTS = [
|
|
|
1684
2164
|
"PreCompress",
|
|
1685
2165
|
"SessionEnd"
|
|
1686
2166
|
];
|
|
1687
|
-
var
|
|
1688
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1689
|
-
var settings = (env) =>
|
|
1690
|
-
var
|
|
1691
|
-
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];
|
|
1692
2172
|
function geminiHooksActive(settingsFile) {
|
|
1693
2173
|
let tools;
|
|
1694
2174
|
try {
|
|
@@ -1700,7 +2180,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1700
2180
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1701
2181
|
let version = "";
|
|
1702
2182
|
try {
|
|
1703
|
-
version =
|
|
2183
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1704
2184
|
encoding: "utf8",
|
|
1705
2185
|
timeout: 4e3,
|
|
1706
2186
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1719,13 +2199,13 @@ function geminiHooksActive(settingsFile) {
|
|
|
1719
2199
|
};
|
|
1720
2200
|
}
|
|
1721
2201
|
var geminiCli = {
|
|
1722
|
-
name:
|
|
2202
|
+
name: NAME5,
|
|
1723
2203
|
events: GEMINI_EVENTS,
|
|
1724
2204
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1725
|
-
detect: (env = process.env) =>
|
|
2205
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path8.join(geminiDir(env), "tmp")),
|
|
1726
2206
|
configFiles: (env = process.env) => [settings(env)],
|
|
1727
2207
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1728
|
-
const r = installClaudeShaped(settings(env),
|
|
2208
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1729
2209
|
const a = geminiHooksActive(settings(env));
|
|
1730
2210
|
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1731
2211
|
},
|
|
@@ -1733,7 +2213,7 @@ var geminiCli = {
|
|
|
1733
2213
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1734
2214
|
],
|
|
1735
2215
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1736
|
-
const state = installedClaudeShaped(settings(env),
|
|
2216
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1737
2217
|
const a = geminiHooksActive(settings(env));
|
|
1738
2218
|
return [
|
|
1739
2219
|
{
|
|
@@ -1747,14 +2227,15 @@ var geminiCli = {
|
|
|
1747
2227
|
const event = input.argv[1] ?? "";
|
|
1748
2228
|
let s = {};
|
|
1749
2229
|
try {
|
|
1750
|
-
|
|
2230
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2231
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1751
2232
|
} catch {
|
|
1752
2233
|
}
|
|
1753
2234
|
const id = s.session_id;
|
|
1754
2235
|
if (!id) return null;
|
|
1755
2236
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1756
|
-
const tool =
|
|
1757
|
-
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 });
|
|
1758
2239
|
const events = [];
|
|
1759
2240
|
switch (event) {
|
|
1760
2241
|
case "SessionStart":
|
|
@@ -1835,7 +2316,7 @@ var geminiCli = {
|
|
|
1835
2316
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1836
2317
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1837
2318
|
if (text) {
|
|
1838
|
-
const line = text.split("\n")
|
|
2319
|
+
const line = text.split("\n")[0];
|
|
1839
2320
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1840
2321
|
out.title_source = "first-ask";
|
|
1841
2322
|
}
|
|
@@ -1849,18 +2330,18 @@ var geminiCli = {
|
|
|
1849
2330
|
backfill: () => []
|
|
1850
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
|
|
1851
2332
|
};
|
|
1852
|
-
function
|
|
2333
|
+
function prune5(o) {
|
|
1853
2334
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1854
2335
|
return o;
|
|
1855
2336
|
}
|
|
1856
2337
|
|
|
1857
2338
|
// ../core/src/adapters/index.ts
|
|
1858
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2339
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1859
2340
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1860
2341
|
|
|
1861
2342
|
// ../core/src/config.ts
|
|
1862
|
-
import { chmodSync, existsSync as
|
|
1863
|
-
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";
|
|
1864
2345
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
1865
2346
|
function readConfig(file = configFile()) {
|
|
1866
2347
|
try {
|
|
@@ -1876,10 +2357,10 @@ function readConfig(file = configFile()) {
|
|
|
1876
2357
|
}
|
|
1877
2358
|
}
|
|
1878
2359
|
function writeConfig(c, file = configFile()) {
|
|
1879
|
-
mkdirSync4(
|
|
2360
|
+
mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
|
|
1880
2361
|
writeFileSync4(file, `${JSON.stringify(c, null, 2)}
|
|
1881
2362
|
`, { mode: 384 });
|
|
1882
|
-
if (process.platform !== "win32" &&
|
|
2363
|
+
if (process.platform !== "win32" && existsSync8(file)) {
|
|
1883
2364
|
try {
|
|
1884
2365
|
chmodSync(file, 384);
|
|
1885
2366
|
} catch {
|
|
@@ -1908,7 +2389,7 @@ function encodeTime(t) {
|
|
|
1908
2389
|
function ulid(now = Date.now()) {
|
|
1909
2390
|
if (now === lastTime) {
|
|
1910
2391
|
for (let i = lastRand.length - 1; i >= 0; i--) {
|
|
1911
|
-
const v =
|
|
2392
|
+
const v = lastRand[i] + 1;
|
|
1912
2393
|
if (v < 32) {
|
|
1913
2394
|
lastRand[i] = v;
|
|
1914
2395
|
break;
|
|
@@ -1917,8 +2398,7 @@ function ulid(now = Date.now()) {
|
|
|
1917
2398
|
}
|
|
1918
2399
|
} else {
|
|
1919
2400
|
lastTime = now;
|
|
1920
|
-
|
|
1921
|
-
lastRand = Array.from({ length: 16 }, (_, i) => (b[i] ?? 0) % 32);
|
|
2401
|
+
lastRand = Array.from(randomBytes(16), (b) => b % 32);
|
|
1922
2402
|
}
|
|
1923
2403
|
return encodeTime(now) + lastRand.map((v) => ALPHABET[v]).join("");
|
|
1924
2404
|
}
|
|
@@ -1945,7 +2425,7 @@ function makeEvent(a, ctx) {
|
|
|
1945
2425
|
import {
|
|
1946
2426
|
appendFileSync,
|
|
1947
2427
|
closeSync as closeSync2,
|
|
1948
|
-
existsSync as
|
|
2428
|
+
existsSync as existsSync9,
|
|
1949
2429
|
mkdirSync as mkdirSync5,
|
|
1950
2430
|
openSync as openSync2,
|
|
1951
2431
|
readdirSync as readdirSync5,
|
|
@@ -1956,42 +2436,45 @@ import {
|
|
|
1956
2436
|
unlinkSync as unlinkSync3,
|
|
1957
2437
|
writeFileSync as writeFileSync5
|
|
1958
2438
|
} from "node:fs";
|
|
1959
|
-
import
|
|
2439
|
+
import path10 from "node:path";
|
|
2440
|
+
var READ_CHUNK = 1024 * 1024;
|
|
2441
|
+
var READ_MAX = 8 * 1024 * 1024;
|
|
1960
2442
|
var Outbox = class {
|
|
1961
2443
|
constructor(root) {
|
|
1962
2444
|
this.root = root;
|
|
1963
2445
|
}
|
|
1964
2446
|
root;
|
|
1965
2447
|
dir(harness) {
|
|
1966
|
-
return
|
|
2448
|
+
return path10.join(this.root, "outbox", safeId(harness));
|
|
1967
2449
|
}
|
|
1968
2450
|
file(ref) {
|
|
1969
|
-
return
|
|
2451
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
1970
2452
|
}
|
|
1971
2453
|
seqFile(ref) {
|
|
1972
|
-
return
|
|
2454
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
1973
2455
|
}
|
|
1974
2456
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
1975
2457
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
1976
2458
|
nextSeq(ref) {
|
|
1977
2459
|
const f = this.seqFile(ref);
|
|
1978
|
-
let n =
|
|
2460
|
+
let n = -1;
|
|
1979
2461
|
try {
|
|
1980
|
-
|
|
2462
|
+
const t = readFileSync7(f, "utf8").trim();
|
|
2463
|
+
if (/^\d+$/.test(t)) n = Number(t);
|
|
1981
2464
|
} catch {
|
|
1982
|
-
n = this.lastSeqInFile(ref) + 1;
|
|
1983
2465
|
}
|
|
1984
|
-
|
|
2466
|
+
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2467
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
1985
2468
|
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
1986
2469
|
return n;
|
|
1987
2470
|
}
|
|
1988
2471
|
lastSeqInFile(ref) {
|
|
1989
2472
|
const f = this.file(ref);
|
|
1990
|
-
if (!
|
|
2473
|
+
if (!existsSync9(f)) return -1;
|
|
1991
2474
|
const lines = readFileSync7(f, "utf8").trim().split("\n");
|
|
1992
2475
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1993
2476
|
try {
|
|
1994
|
-
const j = JSON.parse(lines[i]
|
|
2477
|
+
const j = JSON.parse(lines[i]);
|
|
1995
2478
|
if (typeof j.session?.seq === "number") return j.session.seq;
|
|
1996
2479
|
} catch {
|
|
1997
2480
|
}
|
|
@@ -2007,10 +2490,10 @@ var Outbox = class {
|
|
|
2007
2490
|
/** Every session file, newest first. */
|
|
2008
2491
|
sessions() {
|
|
2009
2492
|
const out = [];
|
|
2010
|
-
const base =
|
|
2011
|
-
if (!
|
|
2493
|
+
const base = path10.join(this.root, "outbox");
|
|
2494
|
+
if (!existsSync9(base)) return [];
|
|
2012
2495
|
for (const h of readdirSync5(base)) {
|
|
2013
|
-
const d =
|
|
2496
|
+
const d = path10.join(base, h);
|
|
2014
2497
|
let names = [];
|
|
2015
2498
|
try {
|
|
2016
2499
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2019,7 +2502,7 @@ var Outbox = class {
|
|
|
2019
2502
|
}
|
|
2020
2503
|
for (const n of names) {
|
|
2021
2504
|
try {
|
|
2022
|
-
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 });
|
|
2023
2506
|
} catch {
|
|
2024
2507
|
}
|
|
2025
2508
|
}
|
|
@@ -2027,31 +2510,43 @@ var Outbox = class {
|
|
|
2027
2510
|
return out.sort((a, b) => b.mtime - a.mtime).map((x) => x.ref);
|
|
2028
2511
|
}
|
|
2029
2512
|
/** Read events after byte offset `from`; returns them and the new offset. Lines
|
|
2030
|
-
* 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. */
|
|
2031
2518
|
read(ref, from, maxEvents = 500) {
|
|
2032
2519
|
const f = this.file(ref);
|
|
2033
|
-
if (!
|
|
2520
|
+
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2034
2521
|
const size = statSync4(f).size;
|
|
2035
2522
|
if (from >= size) return { events: [], offset: from };
|
|
2036
2523
|
const fd = openSync2(f, "r");
|
|
2037
2524
|
try {
|
|
2038
|
-
const buf = Buffer.alloc(Math.min(size - from, 8 * 1024 * 1024));
|
|
2039
|
-
const n = readSync2(fd, buf, 0, buf.length, from);
|
|
2040
|
-
const text = buf.subarray(0, n).toString("utf8");
|
|
2041
2525
|
const events = [];
|
|
2042
2526
|
let offset = from;
|
|
2043
|
-
let
|
|
2044
|
-
while (events.length < maxEvents) {
|
|
2045
|
-
const
|
|
2046
|
-
|
|
2047
|
-
const
|
|
2048
|
-
pos =
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
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
|
+
}
|
|
2054
2545
|
}
|
|
2546
|
+
offset += used;
|
|
2547
|
+
if (used) continue;
|
|
2548
|
+
if (offset + n >= size || chunk >= READ_MAX) break;
|
|
2549
|
+
chunk *= 2;
|
|
2055
2550
|
}
|
|
2056
2551
|
return { events, offset };
|
|
2057
2552
|
} finally {
|
|
@@ -2095,7 +2590,7 @@ var Cursors = class {
|
|
|
2095
2590
|
root;
|
|
2096
2591
|
sink;
|
|
2097
2592
|
file() {
|
|
2098
|
-
return
|
|
2593
|
+
return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2099
2594
|
}
|
|
2100
2595
|
read() {
|
|
2101
2596
|
try {
|
|
@@ -2106,7 +2601,7 @@ var Cursors = class {
|
|
|
2106
2601
|
}
|
|
2107
2602
|
write(c) {
|
|
2108
2603
|
const f = this.file();
|
|
2109
|
-
mkdirSync5(
|
|
2604
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
2110
2605
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2111
2606
|
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
2112
2607
|
renameSync(tmp, f);
|
|
@@ -2116,11 +2611,14 @@ var Cursors = class {
|
|
|
2116
2611
|
}
|
|
2117
2612
|
};
|
|
2118
2613
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
2119
|
-
|
|
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");
|
|
2120
2618
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2121
|
-
const f =
|
|
2619
|
+
const f = path10.join(dir, `${name}.lock`);
|
|
2122
2620
|
try {
|
|
2123
|
-
if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
2621
|
+
if (Date.now() - statSync4(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
|
|
2124
2622
|
else return null;
|
|
2125
2623
|
} catch {
|
|
2126
2624
|
}
|
|
@@ -2136,9 +2634,18 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2136
2634
|
}
|
|
2137
2635
|
};
|
|
2138
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
|
+
}
|
|
2139
2646
|
|
|
2140
2647
|
// ../core/src/privacy/pii.ts
|
|
2141
|
-
import { createHash as
|
|
2648
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2142
2649
|
var RULESET_PII = "pii@1";
|
|
2143
2650
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2144
2651
|
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
@@ -2160,7 +2667,7 @@ function reducePii(text, home) {
|
|
|
2160
2667
|
return out;
|
|
2161
2668
|
}
|
|
2162
2669
|
function hashMachine(name) {
|
|
2163
|
-
return
|
|
2670
|
+
return createHash3("sha256").update(name).digest("hex").slice(0, 8);
|
|
2164
2671
|
}
|
|
2165
2672
|
function reducePiiDeep(v, home) {
|
|
2166
2673
|
if (typeof v === "string") return reducePii(v, home);
|
|
@@ -2249,12 +2756,12 @@ function redactAssignments(text) {
|
|
|
2249
2756
|
let last = 0;
|
|
2250
2757
|
NAME_SEP.lastIndex = 0;
|
|
2251
2758
|
for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
|
|
2252
|
-
const name = m[1]
|
|
2759
|
+
const name = m[1];
|
|
2253
2760
|
if (!isSecretName(name)) continue;
|
|
2254
2761
|
VALUE.lastIndex = NAME_SEP.lastIndex;
|
|
2255
2762
|
const v = VALUE.exec(text);
|
|
2256
2763
|
if (!v) continue;
|
|
2257
|
-
const value = v[1] ?? v[2] ?? v[3] ?? v[4]
|
|
2764
|
+
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
2258
2765
|
NAME_SEP.lastIndex = VALUE.lastIndex;
|
|
2259
2766
|
if (isPlaceholder(value) || value.includes(REDACTED)) continue;
|
|
2260
2767
|
if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
|
|
@@ -2310,7 +2817,7 @@ function redactSecrets(text) {
|
|
|
2310
2817
|
let out = text;
|
|
2311
2818
|
for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
|
|
2312
2819
|
out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
|
|
2313
|
-
out = out.replace(AUTH_HEADER, (
|
|
2820
|
+
out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
|
|
2314
2821
|
out = redactAssignments(out);
|
|
2315
2822
|
out = out.replace(
|
|
2316
2823
|
PROSE,
|
|
@@ -2339,6 +2846,7 @@ var TYPE_TIER = {
|
|
|
2339
2846
|
"turn.started": 0,
|
|
2340
2847
|
"turn.ended": 0,
|
|
2341
2848
|
"turn.transcript": 2,
|
|
2849
|
+
"turn.progress": 2,
|
|
2342
2850
|
"tool.started": 1,
|
|
2343
2851
|
"tool.ended": 1,
|
|
2344
2852
|
"files.changed": 1,
|
|
@@ -2358,6 +2866,7 @@ var DATA_ALLOW = {
|
|
|
2358
2866
|
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2359
2867
|
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
2360
2868
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2869
|
+
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
2361
2870
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2362
2871
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
2363
2872
|
"files.changed": { 1: ["paths"] },
|
|
@@ -2398,10 +2907,12 @@ function cap(v, max) {
|
|
|
2398
2907
|
}
|
|
2399
2908
|
return v;
|
|
2400
2909
|
}
|
|
2910
|
+
function capTail(v, max) {
|
|
2911
|
+
return v.length > max ? `\u2026${v.slice(v.length - max + 1)}` : v;
|
|
2912
|
+
}
|
|
2401
2913
|
function filterEvent(event, tier, pii, opts = {}) {
|
|
2402
2914
|
const minTier = TYPE_TIER[event.type];
|
|
2403
2915
|
if (minTier !== void 0 && minTier > tier) return null;
|
|
2404
|
-
if (event.type === "native" && tier < 3) return null;
|
|
2405
2916
|
const session = {};
|
|
2406
2917
|
for (const k of SESSION_ALLOW)
|
|
2407
2918
|
if (event.session[k] !== void 0)
|
|
@@ -2411,7 +2922,8 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2411
2922
|
if (keys === null) data = { ...event.data };
|
|
2412
2923
|
else for (const k of keys) if (event.data[k] !== void 0) data[k] = event.data[k];
|
|
2413
2924
|
if (typeof data.user === "string") data.user = cap(data.user, MAX_SIDE);
|
|
2414
|
-
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);
|
|
2415
2927
|
if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
|
|
2416
2928
|
if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
|
|
2417
2929
|
if (typeof data.message === "string") data.message = cap(data.message, 200);
|
|
@@ -2432,9 +2944,15 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2432
2944
|
return out;
|
|
2433
2945
|
}
|
|
2434
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
|
+
|
|
2435
2953
|
// ../core/src/sinks/file.ts
|
|
2436
2954
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2437
|
-
import
|
|
2955
|
+
import path11 from "node:path";
|
|
2438
2956
|
var FileSink = class {
|
|
2439
2957
|
constructor(name, file, tier, pii) {
|
|
2440
2958
|
this.name = name;
|
|
@@ -2448,7 +2966,7 @@ var FileSink = class {
|
|
|
2448
2966
|
pii;
|
|
2449
2967
|
async send(events) {
|
|
2450
2968
|
try {
|
|
2451
|
-
mkdirSync6(
|
|
2969
|
+
mkdirSync6(path11.dirname(this.file), { recursive: true });
|
|
2452
2970
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2453
2971
|
`);
|
|
2454
2972
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -2472,34 +2990,63 @@ var StdoutSink = class {
|
|
|
2472
2990
|
pii;
|
|
2473
2991
|
write;
|
|
2474
2992
|
async send(events) {
|
|
2475
|
-
|
|
2476
|
-
|
|
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
|
+
}
|
|
2477
2999
|
}
|
|
2478
3000
|
};
|
|
2479
3001
|
|
|
2480
3002
|
// src/run.ts
|
|
2481
3003
|
import {
|
|
2482
3004
|
appendFileSync as appendFileSync3,
|
|
2483
|
-
existsSync as
|
|
2484
|
-
mkdirSync as
|
|
3005
|
+
existsSync as existsSync10,
|
|
3006
|
+
mkdirSync as mkdirSync8,
|
|
2485
3007
|
readdirSync as readdirSync6,
|
|
2486
|
-
readFileSync as
|
|
2487
|
-
renameSync as
|
|
3008
|
+
readFileSync as readFileSync9,
|
|
3009
|
+
renameSync as renameSync3,
|
|
2488
3010
|
statSync as statSync5,
|
|
2489
3011
|
unlinkSync as unlinkSync4,
|
|
2490
|
-
writeFileSync as
|
|
3012
|
+
writeFileSync as writeFileSync7
|
|
2491
3013
|
} from "node:fs";
|
|
2492
3014
|
import os3 from "node:os";
|
|
3015
|
+
import path14 from "node:path";
|
|
3016
|
+
|
|
3017
|
+
// src/control/aliases.ts
|
|
3018
|
+
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2493
3019
|
import path12 from "node:path";
|
|
3020
|
+
var aliasFile = (controlDir) => path12.join(controlDir, "aliases.json");
|
|
3021
|
+
function readAliases(file) {
|
|
3022
|
+
try {
|
|
3023
|
+
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
3024
|
+
if (!j || typeof j !== "object" || Array.isArray(j)) return {};
|
|
3025
|
+
const out = {};
|
|
3026
|
+
for (const [k, v] of Object.entries(j)) {
|
|
3027
|
+
const a = v;
|
|
3028
|
+
if (a && typeof a.id === "string" && a.id && typeof a.cwd === "string")
|
|
3029
|
+
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "" };
|
|
3030
|
+
}
|
|
3031
|
+
return out;
|
|
3032
|
+
} catch {
|
|
3033
|
+
return {};
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
function receiverIdOf(all, harness, id) {
|
|
3037
|
+
const prefix = `${harness}:`;
|
|
3038
|
+
for (const [k, a] of Object.entries(all)) if (a.id === id && k.startsWith(prefix)) return k.slice(prefix.length);
|
|
3039
|
+
return null;
|
|
3040
|
+
}
|
|
2494
3041
|
|
|
2495
3042
|
// src/control/local.ts
|
|
2496
3043
|
import net from "node:net";
|
|
2497
3044
|
import os2 from "node:os";
|
|
2498
|
-
import
|
|
3045
|
+
import path13 from "node:path";
|
|
2499
3046
|
function socketPath(state, env = process.env) {
|
|
2500
3047
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
2501
3048
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
2502
|
-
return
|
|
3049
|
+
return path13.join(state, "control.sock");
|
|
2503
3050
|
}
|
|
2504
3051
|
function ask(sock, req, timeoutMs) {
|
|
2505
3052
|
return new Promise((resolve) => {
|
|
@@ -2532,10 +3079,17 @@ function ask(sock, req, timeoutMs) {
|
|
|
2532
3079
|
}
|
|
2533
3080
|
|
|
2534
3081
|
// src/http-sink.ts
|
|
2535
|
-
import { createHash as
|
|
3082
|
+
import { createHash as createHash5, createHmac } from "node:crypto";
|
|
2536
3083
|
var TIMEOUT_MS = 15e3;
|
|
2537
3084
|
function webhookId(events) {
|
|
2538
|
-
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;
|
|
2539
3093
|
}
|
|
2540
3094
|
var HttpSink = class {
|
|
2541
3095
|
constructor(cfg, tier) {
|
|
@@ -2608,7 +3162,7 @@ var HttpSink = class {
|
|
|
2608
3162
|
status: 429,
|
|
2609
3163
|
error: err,
|
|
2610
3164
|
action: "retry",
|
|
2611
|
-
retryAfterMs:
|
|
3165
|
+
retryAfterMs: retryAfterMs(r.headers.get("retry-after"))
|
|
2612
3166
|
};
|
|
2613
3167
|
return { ok: false, acknowledged: 0, status: r.status, error: err, action: "retry" };
|
|
2614
3168
|
} catch (e) {
|
|
@@ -2621,7 +3175,7 @@ var HttpSink = class {
|
|
|
2621
3175
|
|
|
2622
3176
|
// src/secrets.ts
|
|
2623
3177
|
import { spawnSync } from "node:child_process";
|
|
2624
|
-
import { createHash as
|
|
3178
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
2625
3179
|
var SERVICE = "sessionpipe";
|
|
2626
3180
|
var defaultRunner = (cmd, args, input) => {
|
|
2627
3181
|
const r = spawnSync(cmd, args, {
|
|
@@ -2633,7 +3187,7 @@ var defaultRunner = (cmd, args, input) => {
|
|
|
2633
3187
|
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
2634
3188
|
};
|
|
2635
3189
|
var sinkAccount = (name, what = "token") => {
|
|
2636
|
-
const scope = process.env.SESSIONPIPE_CONFIG ? `@${
|
|
3190
|
+
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
2637
3191
|
return `sink:${name}:${what}${scope}`;
|
|
2638
3192
|
};
|
|
2639
3193
|
function secretGet(store, account, run = defaultRunner) {
|
|
@@ -2651,59 +3205,96 @@ function sinkWithSecrets(s, run = defaultRunner) {
|
|
|
2651
3205
|
}
|
|
2652
3206
|
|
|
2653
3207
|
// src/run.ts
|
|
2654
|
-
async function tellDaemon(state,
|
|
3208
|
+
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
2655
3209
|
const sock = socketPath(state);
|
|
2656
|
-
if (process.platform !== "win32" && !
|
|
3210
|
+
if (process.platform !== "win32" && !existsSync10(sock)) return;
|
|
2657
3211
|
await ask(
|
|
2658
3212
|
sock,
|
|
2659
3213
|
{
|
|
2660
3214
|
op: "event",
|
|
2661
|
-
session:
|
|
2662
|
-
event:
|
|
2663
|
-
cwd:
|
|
3215
|
+
session: `${job.harness}:${session}`,
|
|
3216
|
+
event: job.event,
|
|
3217
|
+
...cwd ? { cwd } : {},
|
|
2664
3218
|
...transcript ? { transcript } : {}
|
|
2665
3219
|
},
|
|
2666
3220
|
300
|
|
2667
3221
|
);
|
|
2668
3222
|
}
|
|
2669
|
-
|
|
3223
|
+
var MAX_TRIES = 10;
|
|
3224
|
+
function enqueueJob(state, job) {
|
|
2670
3225
|
const jobs = jobsDir(state);
|
|
2671
|
-
|
|
2672
|
-
const f =
|
|
2673
|
-
|
|
3226
|
+
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
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 });
|
|
2674
3229
|
return f;
|
|
2675
3230
|
}
|
|
2676
|
-
|
|
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) {
|
|
2677
3253
|
const jobs = jobsDir(state);
|
|
2678
3254
|
let names = [];
|
|
2679
3255
|
try {
|
|
2680
|
-
names = readdirSync6(jobs)
|
|
3256
|
+
names = readdirSync6(jobs);
|
|
2681
3257
|
} catch {
|
|
2682
3258
|
return 0;
|
|
2683
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
|
+
}
|
|
2684
3272
|
let ran = 0;
|
|
2685
|
-
for (const n of
|
|
2686
|
-
const f =
|
|
2687
|
-
let job2 = null;
|
|
3273
|
+
for (const n of queued.sort().slice(0, limit)) {
|
|
3274
|
+
const f = path14.join(jobs, n);
|
|
2688
3275
|
try {
|
|
2689
3276
|
if (Date.now() - statSync5(f).mtimeMs < olderThanMs) continue;
|
|
2690
|
-
job2 = JSON.parse(readFileSync8(f, "utf8"));
|
|
2691
|
-
unlinkSync4(f);
|
|
2692
3277
|
} catch {
|
|
2693
3278
|
continue;
|
|
2694
3279
|
}
|
|
2695
|
-
|
|
2696
|
-
|
|
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
|
+
}
|
|
2697
3288
|
ran++;
|
|
2698
3289
|
}
|
|
2699
3290
|
return ran;
|
|
2700
3291
|
}
|
|
2701
3292
|
function log(state, line) {
|
|
2702
3293
|
try {
|
|
2703
|
-
|
|
2704
|
-
const f =
|
|
3294
|
+
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
3295
|
+
const f = path14.join(state, "log");
|
|
2705
3296
|
try {
|
|
2706
|
-
if (statSync5(f).size > 1024 * 1024)
|
|
3297
|
+
if (statSync5(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
2707
3298
|
} catch {
|
|
2708
3299
|
}
|
|
2709
3300
|
appendFileSync3(f, `${(/* @__PURE__ */ new Date()).toISOString()} ${line}
|
|
@@ -2712,11 +3303,11 @@ function log(state, line) {
|
|
|
2712
3303
|
}
|
|
2713
3304
|
}
|
|
2714
3305
|
function factsState(state) {
|
|
2715
|
-
const file = (h, s) =>
|
|
3306
|
+
const file = (h, s) => path14.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
2716
3307
|
return {
|
|
2717
3308
|
get(h, s) {
|
|
2718
3309
|
try {
|
|
2719
|
-
return JSON.parse(
|
|
3310
|
+
return JSON.parse(readFileSync9(file(h, s), "utf8"));
|
|
2720
3311
|
} catch {
|
|
2721
3312
|
return {};
|
|
2722
3313
|
}
|
|
@@ -2725,8 +3316,8 @@ function factsState(state) {
|
|
|
2725
3316
|
const cur = this.get(h, s);
|
|
2726
3317
|
const next = { ...cur, ...patch };
|
|
2727
3318
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
2728
|
-
|
|
2729
|
-
|
|
3319
|
+
mkdirSync8(path14.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
3320
|
+
writeFileSync7(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
2730
3321
|
}
|
|
2731
3322
|
};
|
|
2732
3323
|
}
|
|
@@ -2747,37 +3338,76 @@ function buildSinks(sinks, o = {}) {
|
|
|
2747
3338
|
}
|
|
2748
3339
|
return out;
|
|
2749
3340
|
}
|
|
2750
|
-
|
|
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 = {}) {
|
|
2751
3363
|
const state = opts.state ?? stateDir();
|
|
2752
3364
|
const cfg = readConfig();
|
|
2753
|
-
const adapter = adapterByName(
|
|
2754
|
-
if (!adapter)
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
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);
|
|
2758
3378
|
const outbox = new Outbox(state);
|
|
2759
|
-
const ref = { harness:
|
|
3379
|
+
const ref = { harness: job.harness, session: id };
|
|
2760
3380
|
let release = takeLock(state, ref);
|
|
2761
|
-
|
|
2762
|
-
|
|
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));
|
|
2763
3384
|
release = takeLock(state, ref);
|
|
2764
3385
|
}
|
|
2765
3386
|
if (!release) {
|
|
2766
|
-
log(state, `${
|
|
2767
|
-
enqueueJob(state,
|
|
2768
|
-
|
|
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 };
|
|
2769
3391
|
}
|
|
3392
|
+
const events = [];
|
|
2770
3393
|
try {
|
|
2771
3394
|
const facts = factsState(state);
|
|
2772
|
-
const mem = facts.get(
|
|
2773
|
-
|
|
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);
|
|
2774
3402
|
let learned = {};
|
|
2775
3403
|
if (heavy) {
|
|
2776
|
-
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 });
|
|
2777
3405
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
2778
3406
|
learned = rest;
|
|
2779
|
-
if (harness_version) facts.save(
|
|
2780
|
-
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 });
|
|
2781
3411
|
}
|
|
2782
3412
|
const known = mem.session ?? {};
|
|
2783
3413
|
const cwdRaw = learned.cwd ?? r.session.cwd ?? known.cwd;
|
|
@@ -2787,25 +3417,55 @@ async function runJob(job2, opts = {}) {
|
|
|
2787
3417
|
...learned,
|
|
2788
3418
|
...git,
|
|
2789
3419
|
...learned.branch ? { branch: learned.branch } : {},
|
|
2790
|
-
id
|
|
3420
|
+
id,
|
|
2791
3421
|
seq: 0,
|
|
2792
3422
|
machine: machineName(cfg, os3.hostname()),
|
|
2793
3423
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
2794
3424
|
...r.session.model ? { model: r.session.model } : {}
|
|
2795
3425
|
};
|
|
2796
|
-
const version = facts.get(
|
|
2797
|
-
const events = [];
|
|
3426
|
+
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
2798
3427
|
for (const a of r.events) {
|
|
2799
3428
|
const e = makeEvent(a, {
|
|
2800
|
-
harness: { name:
|
|
3429
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
2801
3430
|
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
2802
|
-
now: opts.now ??
|
|
3431
|
+
now: opts.now ?? job.at
|
|
2803
3432
|
});
|
|
2804
3433
|
events.push(e);
|
|
2805
3434
|
}
|
|
2806
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 });
|
|
2807
3467
|
if (wantsTranscript && r.transcript && r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended")) {
|
|
2808
|
-
const cur = facts.get(
|
|
3468
|
+
const cur = facts.get(job.harness, id);
|
|
2809
3469
|
const fromLine = Number(cur.transcript_line) || 0;
|
|
2810
3470
|
let turnNo = Number(cur.transcript_turns) || 0;
|
|
2811
3471
|
const read = adapter.readTranscript(r.transcript, fromLine);
|
|
@@ -2814,38 +3474,97 @@ async function runJob(job2, opts = {}) {
|
|
|
2814
3474
|
makeEvent(
|
|
2815
3475
|
{
|
|
2816
3476
|
type: "turn.transcript",
|
|
2817
|
-
harnessEvent:
|
|
3477
|
+
harnessEvent: job.event,
|
|
2818
3478
|
data: { turn: turnNo++, user: t.user, assistant: t.assistant, at: new Date(t.at).toISOString() }
|
|
2819
3479
|
},
|
|
2820
3480
|
{
|
|
2821
|
-
harness: { name:
|
|
3481
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
2822
3482
|
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
2823
|
-
now: opts.now ??
|
|
3483
|
+
now: opts.now ?? job.at
|
|
2824
3484
|
}
|
|
2825
3485
|
)
|
|
2826
3486
|
);
|
|
2827
3487
|
}
|
|
2828
|
-
if (read.turns.length)
|
|
2829
|
-
facts.save(job2.harness, r.session.id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
3488
|
+
if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
2830
3489
|
}
|
|
2831
3490
|
outbox.append(
|
|
2832
3491
|
ref,
|
|
2833
3492
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
2834
3493
|
);
|
|
3494
|
+
releaseJob(opts.claimed);
|
|
2835
3495
|
log(
|
|
2836
3496
|
state,
|
|
2837
|
-
`${
|
|
3497
|
+
`${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${shortId(job.harness, id)})`
|
|
2838
3498
|
);
|
|
2839
|
-
const delivered = await flush(state, cfg.sinks, outbox, ref);
|
|
2840
|
-
if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
|
|
2841
|
-
return { events, delivered };
|
|
2842
3499
|
} finally {
|
|
2843
3500
|
release();
|
|
2844
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
|
+
}
|
|
2845
3562
|
}
|
|
3563
|
+
var LOCK_WAIT_MS = 6e3;
|
|
3564
|
+
var CWD_FROM_HOOKS = /* @__PURE__ */ new Set(["antigravity", "devin"]);
|
|
2846
3565
|
var BATCH = 50;
|
|
2847
3566
|
var MAX_PER_RUN = 500;
|
|
2848
|
-
async function flush(state, sinkConfigs, outbox, first) {
|
|
3567
|
+
async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
2849
3568
|
const delivered = {};
|
|
2850
3569
|
const sinks = buildSinks(sinkConfigs, {
|
|
2851
3570
|
locked: (name) => log(state, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
@@ -2855,13 +3574,39 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2855
3574
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
2856
3575
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
2857
3576
|
}
|
|
2858
|
-
|
|
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
|
+
}
|
|
2859
3583
|
const cursors = new Cursors(state, sink.name);
|
|
2860
3584
|
const c = cursors.read();
|
|
2861
3585
|
let sent = 0;
|
|
2862
3586
|
let stalled = false;
|
|
2863
3587
|
let batch = [];
|
|
2864
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
|
+
};
|
|
2865
3610
|
const send = async () => {
|
|
2866
3611
|
if (!batch.length) {
|
|
2867
3612
|
Object.assign(c, pending);
|
|
@@ -2869,15 +3614,24 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2869
3614
|
cursors.write(c);
|
|
2870
3615
|
return true;
|
|
2871
3616
|
}
|
|
2872
|
-
const res = await
|
|
3617
|
+
const res = await deliver(batch);
|
|
2873
3618
|
if (!res.ok) {
|
|
2874
|
-
|
|
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}`);
|
|
2875
3626
|
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2876
3627
|
if (res.action !== "drop") {
|
|
2877
3628
|
batch = [];
|
|
2878
3629
|
pending = {};
|
|
2879
3630
|
return false;
|
|
2880
3631
|
}
|
|
3632
|
+
} else if (backoff) {
|
|
3633
|
+
clearBackoff(state, sink.name);
|
|
3634
|
+
backoff = null;
|
|
2881
3635
|
}
|
|
2882
3636
|
sent += batch.length;
|
|
2883
3637
|
Object.assign(c, pending);
|
|
@@ -2912,7 +3666,8 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2912
3666
|
if (!await send()) stalled = true;
|
|
2913
3667
|
}
|
|
2914
3668
|
delivered[sink.name] = sent;
|
|
2915
|
-
}
|
|
3669
|
+
};
|
|
3670
|
+
await Promise.all(sinks.map(drainSink));
|
|
2916
3671
|
return delivered;
|
|
2917
3672
|
}
|
|
2918
3673
|
function applySinkVerdict(name, res) {
|
|
@@ -2926,24 +3681,31 @@ function applySinkVerdict(name, res) {
|
|
|
2926
3681
|
} catch {
|
|
2927
3682
|
}
|
|
2928
3683
|
}
|
|
2929
|
-
var jobsDir = (state) =>
|
|
3684
|
+
var jobsDir = (state) => path14.join(state, "jobs");
|
|
2930
3685
|
|
|
2931
3686
|
// src/worker.ts
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
}).
|
|
2948
|
-
}).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
|
+
};
|
|
2949
3711
|
//# sourceMappingURL=worker.js.map
|