sessionpipe 0.7.0 → 0.10.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 +83 -6
- package/dist/cli.d.ts +11 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +5043 -2609
- package/dist/cli.js.map +4 -4
- package/dist/connect.d.ts.map +1 -1
- package/dist/control/aliases.d.ts +5 -0
- package/dist/control/aliases.d.ts.map +1 -1
- package/dist/control/autoupdate.d.ts +1 -18
- 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 +95 -9
- package/dist/control/daemon.d.ts.map +1 -1
- package/dist/control/files.d.ts +35 -0
- package/dist/control/files.d.ts.map +1 -0
- package/dist/control/pair.d.ts +22 -23
- package/dist/control/pair.d.ts.map +1 -1
- package/dist/control/route.d.ts +1 -1
- package/dist/control/route.d.ts.map +1 -1
- package/dist/control/sdk.d.ts.map +1 -1
- package/dist/control/store.d.ts +21 -2
- 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 +190 -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 +1038 -237
- 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 +1 -6
- 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 +1127 -287
- package/dist/worker.js.map +4 -4
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// sessionpipe 0.
|
|
1
|
+
// sessionpipe 0.10.0 · Apache-2.0 · https://sessionpipe.org
|
|
2
2
|
|
|
3
3
|
// src/http-sink.ts
|
|
4
4
|
import { createHash, createHmac } from "node:crypto";
|
|
@@ -6,6 +6,13 @@ var TIMEOUT_MS = 15e3;
|
|
|
6
6
|
function webhookId(events) {
|
|
7
7
|
return `msg_${createHash("sha256").update(events.map((e) => e.id).join(",")).digest("base64url").slice(0, 32)}`;
|
|
8
8
|
}
|
|
9
|
+
function retryAfterMs(header, now = Date.now()) {
|
|
10
|
+
if (header === null || header.trim() === "") return 6e4;
|
|
11
|
+
const secs = Number(header);
|
|
12
|
+
if (Number.isFinite(secs)) return Math.max(0, secs) * 1e3;
|
|
13
|
+
const at = Date.parse(header);
|
|
14
|
+
return Number.isFinite(at) ? Math.max(0, at - now) : 6e4;
|
|
15
|
+
}
|
|
9
16
|
var HttpSink = class {
|
|
10
17
|
constructor(cfg, tier) {
|
|
11
18
|
this.cfg = cfg;
|
|
@@ -77,7 +84,7 @@ var HttpSink = class {
|
|
|
77
84
|
status: 429,
|
|
78
85
|
error: err,
|
|
79
86
|
action: "retry",
|
|
80
|
-
retryAfterMs:
|
|
87
|
+
retryAfterMs: retryAfterMs(r.headers.get("retry-after"))
|
|
81
88
|
};
|
|
82
89
|
return { ok: false, acknowledged: 0, status: r.status, error: err, action: "retry" };
|
|
83
90
|
} catch (e) {
|
|
@@ -91,7 +98,7 @@ var HttpSink = class {
|
|
|
91
98
|
// src/run.ts
|
|
92
99
|
import {
|
|
93
100
|
appendFileSync as appendFileSync3,
|
|
94
|
-
existsSync as
|
|
101
|
+
existsSync as existsSync10,
|
|
95
102
|
mkdirSync as mkdirSync8,
|
|
96
103
|
readdirSync as readdirSync6,
|
|
97
104
|
readFileSync as readFileSync9,
|
|
@@ -101,7 +108,7 @@ import {
|
|
|
101
108
|
writeFileSync as writeFileSync7
|
|
102
109
|
} from "node:fs";
|
|
103
110
|
import os3 from "node:os";
|
|
104
|
-
import
|
|
111
|
+
import path14 from "node:path";
|
|
105
112
|
|
|
106
113
|
// ../core/src/adapters/antigravity.ts
|
|
107
114
|
import {
|
|
@@ -119,20 +126,20 @@ import path4 from "node:path";
|
|
|
119
126
|
// ../core/src/paths.ts
|
|
120
127
|
import os from "node:os";
|
|
121
128
|
import path from "node:path";
|
|
122
|
-
var
|
|
129
|
+
var homeDir = () => os.homedir();
|
|
123
130
|
function configFile(env = process.env) {
|
|
124
131
|
if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);
|
|
125
132
|
if (process.platform === "win32")
|
|
126
|
-
return path.join(env.APPDATA || path.join(
|
|
127
|
-
return path.join(env.XDG_CONFIG_HOME || path.join(
|
|
133
|
+
return path.join(env.APPDATA || path.join(homeDir(), "AppData", "Roaming"), "sessionpipe", "config.json");
|
|
134
|
+
return path.join(env.XDG_CONFIG_HOME || path.join(homeDir(), ".config"), "sessionpipe", "config.json");
|
|
128
135
|
}
|
|
129
136
|
function stateDir(env = process.env) {
|
|
130
137
|
if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);
|
|
131
138
|
if (process.platform === "win32")
|
|
132
|
-
return path.join(env.LOCALAPPDATA || path.join(
|
|
133
|
-
return path.join(env.XDG_STATE_HOME || path.join(
|
|
139
|
+
return path.join(env.LOCALAPPDATA || path.join(homeDir(), "AppData", "Local"), "sessionpipe");
|
|
140
|
+
return path.join(env.XDG_STATE_HOME || path.join(homeDir(), ".local", "state"), "sessionpipe");
|
|
134
141
|
}
|
|
135
|
-
function tilde(p, home =
|
|
142
|
+
function tilde(p, home = homeDir()) {
|
|
136
143
|
if (!p) return p;
|
|
137
144
|
if (p === home) return "~";
|
|
138
145
|
return p.startsWith(home + path.sep) || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
|
|
@@ -229,6 +236,19 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
229
236
|
return out;
|
|
230
237
|
}
|
|
231
238
|
|
|
239
|
+
// ../core/src/adapters/codex-rollout.ts
|
|
240
|
+
function codexSubagent(meta) {
|
|
241
|
+
if (meta?.type !== "session_meta") return false;
|
|
242
|
+
const p = meta.payload;
|
|
243
|
+
return !!p?.source?.subagent;
|
|
244
|
+
}
|
|
245
|
+
function codexReplayBoundary(meta) {
|
|
246
|
+
if (meta?.type !== "session_meta") return void 0;
|
|
247
|
+
const p = meta.payload;
|
|
248
|
+
if (!p || !p.forked_from_id && !p.source?.subagent) return void 0;
|
|
249
|
+
return (record) => typeof p.id === "string" && !!p.id && record?.type === "event_msg" && record.payload?.thread_id === p.id;
|
|
250
|
+
}
|
|
251
|
+
|
|
232
252
|
// ../core/src/readers/transcripts.ts
|
|
233
253
|
function pairTurns(msgs, endLine) {
|
|
234
254
|
const turns = [];
|
|
@@ -251,10 +271,14 @@ ${m.text}`;
|
|
|
251
271
|
}
|
|
252
272
|
if (cur?.assistant) turns.push(cur);
|
|
253
273
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
254
|
-
|
|
274
|
+
const out = {
|
|
275
|
+
turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
|
|
276
|
+
line
|
|
277
|
+
};
|
|
278
|
+
if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
|
|
279
|
+
return out;
|
|
255
280
|
}
|
|
256
|
-
var isInjected = (text) => /^<[a-z_-]
|
|
257
|
-
var endOf = (lines) => lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
281
|
+
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
258
282
|
var parse = (raw) => {
|
|
259
283
|
try {
|
|
260
284
|
return JSON.parse(raw);
|
|
@@ -262,6 +286,11 @@ var parse = (raw) => {
|
|
|
262
286
|
return null;
|
|
263
287
|
}
|
|
264
288
|
};
|
|
289
|
+
var endOf = (lines) => {
|
|
290
|
+
const last = lines[lines.length - 1];
|
|
291
|
+
if (last === void 0) return 0;
|
|
292
|
+
return last === "" || parse(last) === null ? lines.length - 1 : lines.length;
|
|
293
|
+
};
|
|
265
294
|
var textParts = (c, kinds) => {
|
|
266
295
|
if (typeof c === "string") return c;
|
|
267
296
|
if (!Array.isArray(c)) return "";
|
|
@@ -269,13 +298,21 @@ var textParts = (c, kinds) => {
|
|
|
269
298
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
270
299
|
).map((b) => b.text).join("\n");
|
|
271
300
|
};
|
|
301
|
+
var queuedPrompt = (a) => {
|
|
302
|
+
if (!a || typeof a !== "object") return "";
|
|
303
|
+
const q = a;
|
|
304
|
+
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
305
|
+
if (q.origin?.kind && q.origin.kind !== "human") return "";
|
|
306
|
+
return textParts(q.prompt, ["text"]).trim();
|
|
307
|
+
};
|
|
272
308
|
function readClaude(lines, from) {
|
|
273
309
|
const msgs = [];
|
|
274
310
|
for (let i = from; i < lines.length; i++) {
|
|
275
311
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
276
312
|
if (!j || j.isSidechain) continue;
|
|
277
313
|
const origin = j.origin;
|
|
278
|
-
if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human"))
|
|
314
|
+
if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
|
|
315
|
+
continue;
|
|
279
316
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
280
317
|
const c = j.message?.content;
|
|
281
318
|
if (j.type === "user") {
|
|
@@ -285,6 +322,9 @@ function readClaude(lines, from) {
|
|
|
285
322
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
286
323
|
const text = textParts(c, ["text"]).trim();
|
|
287
324
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
325
|
+
} else if (j.type === "attachment") {
|
|
326
|
+
const text = queuedPrompt(j.attachment);
|
|
327
|
+
if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
|
|
288
328
|
}
|
|
289
329
|
}
|
|
290
330
|
return pairTurns(msgs, endOf(lines));
|
|
@@ -294,7 +334,10 @@ function codexInjected(text) {
|
|
|
294
334
|
}
|
|
295
335
|
function readCodex(lines, from) {
|
|
296
336
|
const msgs = [];
|
|
297
|
-
|
|
337
|
+
const boundary = codexReplayBoundary(parse(lines.find((line) => line.trim()) ?? ""));
|
|
338
|
+
const start = boundary ? lines.findIndex((line) => boundary(parse(line))) : 0;
|
|
339
|
+
if (start < 0) return pairTurns(msgs, endOf(lines));
|
|
340
|
+
for (let i = Math.max(from, start); i < lines.length; i++) {
|
|
298
341
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
299
342
|
if (!j || j.type !== "response_item") continue;
|
|
300
343
|
const p = j.payload;
|
|
@@ -332,7 +375,7 @@ function readAntigravity(lines, from) {
|
|
|
332
375
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
333
376
|
if (j.type === "USER_INPUT") {
|
|
334
377
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
335
|
-
const text = (m ? m[1]
|
|
378
|
+
const text = (m ? m[1] : j.content).trim();
|
|
336
379
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
337
380
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
338
381
|
const text = j.content.trim();
|
|
@@ -380,6 +423,10 @@ function formatJson(value, f) {
|
|
|
380
423
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
381
424
|
return f.finalNewline ? text + f.eol : text;
|
|
382
425
|
}
|
|
426
|
+
function readJson(file) {
|
|
427
|
+
const f = readJsonFile(file);
|
|
428
|
+
return f.ok ? f.value : {};
|
|
429
|
+
}
|
|
383
430
|
function entryFor(event, harness, cmd, o) {
|
|
384
431
|
const matcher = o.matcherFor(event);
|
|
385
432
|
return {
|
|
@@ -449,8 +496,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
449
496
|
unlinkSync(file);
|
|
450
497
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
451
498
|
}
|
|
452
|
-
|
|
453
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
499
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
454
500
|
return { file, changed: true };
|
|
455
501
|
}
|
|
456
502
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -477,7 +523,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
477
523
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
478
524
|
var AG_STALE_PRETOOL = '{"decision":"ask","reason":"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it."}\n';
|
|
479
525
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
480
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(
|
|
526
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
|
|
481
527
|
var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
|
|
482
528
|
var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
483
529
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
@@ -543,7 +589,8 @@ var antigravity = {
|
|
|
543
589
|
const event = input.argv[1] ?? "";
|
|
544
590
|
let s = {};
|
|
545
591
|
try {
|
|
546
|
-
|
|
592
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
593
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
547
594
|
} catch {
|
|
548
595
|
}
|
|
549
596
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -599,19 +646,16 @@ var antigravity = {
|
|
|
599
646
|
}
|
|
600
647
|
return readAntigravity(text.split("\n"), fromLine);
|
|
601
648
|
},
|
|
602
|
-
facts(session, transcript, hints
|
|
649
|
+
facts(session, transcript, hints) {
|
|
603
650
|
if (!transcript) return {};
|
|
604
651
|
return antigravityFacts(transcript, {
|
|
605
652
|
conversation: session.id,
|
|
606
653
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
607
|
-
state,
|
|
608
654
|
env: process.env
|
|
609
655
|
});
|
|
610
656
|
},
|
|
611
657
|
backfill(sinceMs, env = process.env) {
|
|
612
|
-
const
|
|
613
|
-
const noState = { get: () => ({}), save: () => {
|
|
614
|
-
} };
|
|
658
|
+
const rows2 = [];
|
|
615
659
|
for (const data of dataDirs(env)) {
|
|
616
660
|
let ids = [];
|
|
617
661
|
try {
|
|
@@ -625,13 +669,13 @@ var antigravity = {
|
|
|
625
669
|
} catch {
|
|
626
670
|
continue;
|
|
627
671
|
}
|
|
628
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
672
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
629
673
|
if (!facts.started_at || !facts.last_at) continue;
|
|
630
674
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
631
|
-
|
|
675
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
632
676
|
}
|
|
633
677
|
}
|
|
634
|
-
return
|
|
678
|
+
return rows2;
|
|
635
679
|
}
|
|
636
680
|
};
|
|
637
681
|
function prune(o) {
|
|
@@ -681,7 +725,7 @@ function agProjects(dir) {
|
|
|
681
725
|
}
|
|
682
726
|
const folders = [];
|
|
683
727
|
for (const r of j?.projectResources?.resources ?? []) {
|
|
684
|
-
const f = agPath(r?.folderUri);
|
|
728
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
685
729
|
if (f) folders.push(...agVariants(f));
|
|
686
730
|
}
|
|
687
731
|
if (folders.length) out.push({ id, folders });
|
|
@@ -735,7 +779,7 @@ function antigravityFacts(file, opts) {
|
|
|
735
779
|
for (const j of H) {
|
|
736
780
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
737
781
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
738
|
-
const text = (m ? m[1]
|
|
782
|
+
const text = (m ? m[1] : j.content).trim();
|
|
739
783
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
740
784
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
741
785
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -783,10 +827,9 @@ function gitFacts(cwd) {
|
|
|
783
827
|
return out;
|
|
784
828
|
}
|
|
785
829
|
function stripCredentials(url) {
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
return u.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
830
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
831
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
832
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
790
833
|
}
|
|
791
834
|
|
|
792
835
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -817,7 +860,7 @@ var OPTS = {
|
|
|
817
860
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
818
861
|
};
|
|
819
862
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
820
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
863
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
821
864
|
const out = [];
|
|
822
865
|
const add = (d) => {
|
|
823
866
|
if (!d) return;
|
|
@@ -858,11 +901,13 @@ var claudeCode = {
|
|
|
858
901
|
const event = input.argv[1] ?? "";
|
|
859
902
|
let s = {};
|
|
860
903
|
try {
|
|
861
|
-
|
|
904
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
905
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
862
906
|
} catch {
|
|
863
907
|
}
|
|
864
908
|
const id = s.session_id ?? s.sessionId;
|
|
865
909
|
if (!id) return null;
|
|
910
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
866
911
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
867
912
|
if (typeof s.model === "string") session.model = s.model;
|
|
868
913
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -879,14 +924,28 @@ var claudeCode = {
|
|
|
879
924
|
case "SessionStart":
|
|
880
925
|
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
881
926
|
break;
|
|
882
|
-
case "UserPromptSubmit":
|
|
927
|
+
case "UserPromptSubmit": {
|
|
928
|
+
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
883
929
|
events.push(
|
|
884
930
|
ev("turn.started", {
|
|
885
931
|
turn_id,
|
|
886
|
-
prompt_chars:
|
|
932
|
+
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
933
|
+
// Claude Code's hook schema documents `source`
|
|
934
|
+
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
935
|
+
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
936
|
+
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
937
|
+
// to extend.
|
|
938
|
+
submitted_by: harnessWord(s.source),
|
|
939
|
+
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
940
|
+
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
941
|
+
// and `true` is never "no person caused this turn", because a Talk or
|
|
942
|
+
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
943
|
+
// <task-notification> (PROTOCOL.md §4).
|
|
944
|
+
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
887
945
|
})
|
|
888
946
|
);
|
|
889
947
|
break;
|
|
948
|
+
}
|
|
890
949
|
case "PreToolUse":
|
|
891
950
|
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
892
951
|
break;
|
|
@@ -941,7 +1000,13 @@ var claudeCode = {
|
|
|
941
1000
|
);
|
|
942
1001
|
break;
|
|
943
1002
|
case "Stop":
|
|
944
|
-
events.push(
|
|
1003
|
+
events.push(
|
|
1004
|
+
ev("turn.ended", {
|
|
1005
|
+
turn_id,
|
|
1006
|
+
reason: "stop",
|
|
1007
|
+
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
1008
|
+
})
|
|
1009
|
+
);
|
|
945
1010
|
break;
|
|
946
1011
|
case "StopFailure":
|
|
947
1012
|
events.push(
|
|
@@ -981,8 +1046,11 @@ var claudeCode = {
|
|
|
981
1046
|
return null;
|
|
982
1047
|
}
|
|
983
1048
|
const hints = {};
|
|
1049
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
984
1050
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
985
1051
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
1052
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
1053
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
986
1054
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
987
1055
|
const r = { session, events, hints };
|
|
988
1056
|
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
@@ -1003,10 +1071,11 @@ var claudeCode = {
|
|
|
1003
1071
|
const cwd = out.cwd ?? session.cwd;
|
|
1004
1072
|
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
1005
1073
|
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
1074
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
1006
1075
|
return out;
|
|
1007
1076
|
},
|
|
1008
1077
|
backfill(sinceMs, env = process.env) {
|
|
1009
|
-
const
|
|
1078
|
+
const rows2 = [];
|
|
1010
1079
|
const files = [];
|
|
1011
1080
|
for (const d of claudeDirs(env))
|
|
1012
1081
|
files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -1018,22 +1087,29 @@ var claudeCode = {
|
|
|
1018
1087
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1019
1088
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1020
1089
|
const cwd = rest.cwd;
|
|
1021
|
-
|
|
1090
|
+
rows2.push({
|
|
1022
1091
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
1023
1092
|
started_at,
|
|
1024
1093
|
last_at
|
|
1025
1094
|
});
|
|
1026
1095
|
}
|
|
1027
|
-
return
|
|
1096
|
+
return rows2;
|
|
1028
1097
|
}
|
|
1029
1098
|
};
|
|
1030
1099
|
function prune2(o) {
|
|
1031
1100
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1032
1101
|
return o;
|
|
1033
1102
|
}
|
|
1103
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1104
|
+
function notClaudeCode(id, s) {
|
|
1105
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
1106
|
+
}
|
|
1034
1107
|
function pickSource(v) {
|
|
1035
1108
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1036
1109
|
}
|
|
1110
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
1111
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
1112
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
1037
1113
|
function errText(v) {
|
|
1038
1114
|
if (typeof v === "string") return v.slice(0, 2e3);
|
|
1039
1115
|
if (v && typeof v === "object") {
|
|
@@ -1136,23 +1212,47 @@ function parentOf() {
|
|
|
1136
1212
|
}
|
|
1137
1213
|
return (pid) => table.get(pid) || 0;
|
|
1138
1214
|
}
|
|
1139
|
-
var
|
|
1140
|
-
function
|
|
1141
|
-
const hit = accountCache.get(dir);
|
|
1142
|
-
if (hit !== void 0) return hit;
|
|
1215
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1216
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1143
1217
|
const files = [path5.join(dir, ".claude.json")];
|
|
1144
|
-
if (path5.resolve(dir) === path5.join(
|
|
1145
|
-
let acct = null;
|
|
1218
|
+
if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
|
|
1146
1219
|
for (const f of files) {
|
|
1147
1220
|
try {
|
|
1148
1221
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1149
|
-
if (a?.accountUuid) {
|
|
1150
|
-
|
|
1151
|
-
|
|
1222
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1223
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1224
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1152
1225
|
}
|
|
1153
1226
|
} catch {
|
|
1154
1227
|
}
|
|
1155
1228
|
}
|
|
1229
|
+
return apiAccount(dir);
|
|
1230
|
+
}
|
|
1231
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
1232
|
+
function apiAccount(dir) {
|
|
1233
|
+
let base;
|
|
1234
|
+
try {
|
|
1235
|
+
base = JSON.parse(readFileSync3(settingsIn(dir), "utf8")).env?.ANTHROPIC_BASE_URL;
|
|
1236
|
+
} catch {
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
if (typeof base !== "string") return null;
|
|
1240
|
+
let u;
|
|
1241
|
+
try {
|
|
1242
|
+
u = new URL(base.trim());
|
|
1243
|
+
} catch {
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
1247
|
+
return null;
|
|
1248
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
1249
|
+
return { id: `api-${createHash2("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
1250
|
+
}
|
|
1251
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1252
|
+
function claudeAccount(dir) {
|
|
1253
|
+
const hit = accountCache.get(dir);
|
|
1254
|
+
if (hit !== void 0) return hit;
|
|
1255
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1156
1256
|
accountCache.set(dir, acct);
|
|
1157
1257
|
return acct;
|
|
1158
1258
|
}
|
|
@@ -1306,6 +1406,18 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1306
1406
|
return out;
|
|
1307
1407
|
}
|
|
1308
1408
|
|
|
1409
|
+
// ../core/src/control/verify.ts
|
|
1410
|
+
var GRANT_MAX_MS = 24 * 36e5;
|
|
1411
|
+
var SKEW_MS = 5 * 6e4;
|
|
1412
|
+
var MACHINE_MAX_AGE_MS = 24 * 36e5;
|
|
1413
|
+
var RECEIVER_MAX_AGE_MS = 10 * 6e4;
|
|
1414
|
+
var NONCE_TTL_MS = 25 * 36e5;
|
|
1415
|
+
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
1416
|
+
var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
|
|
1417
|
+
var levelRank = (l) => START_PERMISSIONS.indexOf(l);
|
|
1418
|
+
var enc = new TextEncoder();
|
|
1419
|
+
var dec = new TextDecoder();
|
|
1420
|
+
|
|
1309
1421
|
// ../core/src/adapters/codex.ts
|
|
1310
1422
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1311
1423
|
import path6 from "node:path";
|
|
@@ -1372,18 +1484,13 @@ function parseArgv(raw) {
|
|
|
1372
1484
|
function readNotify(text) {
|
|
1373
1485
|
const { head, tail, at } = split(text);
|
|
1374
1486
|
const m = NOTIFY_LINE.exec(head);
|
|
1375
|
-
if (m)
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
index: m.index,
|
|
1380
|
-
length: m[0].length,
|
|
1381
|
-
misplaced: false,
|
|
1382
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1383
|
-
};
|
|
1487
|
+
if (m) {
|
|
1488
|
+
const raw = m[1];
|
|
1489
|
+
return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
|
|
1490
|
+
}
|
|
1384
1491
|
const k = NOTIFY_KEY.exec(head);
|
|
1385
1492
|
if (k) {
|
|
1386
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1493
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1387
1494
|
return {
|
|
1388
1495
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1389
1496
|
argv: null,
|
|
@@ -1394,15 +1501,11 @@ function readNotify(text) {
|
|
|
1394
1501
|
};
|
|
1395
1502
|
}
|
|
1396
1503
|
const t = NOTIFY_LINE.exec(tail);
|
|
1397
|
-
if (t
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
argv: parseArgv(t[
|
|
1401
|
-
|
|
1402
|
-
length: t[0].length,
|
|
1403
|
-
misplaced: true,
|
|
1404
|
-
other: false
|
|
1405
|
-
};
|
|
1504
|
+
if (t) {
|
|
1505
|
+
const raw = t[1];
|
|
1506
|
+
if (OURS2.test(raw))
|
|
1507
|
+
return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
|
|
1508
|
+
}
|
|
1406
1509
|
return null;
|
|
1407
1510
|
}
|
|
1408
1511
|
function dropLine(text, index, length) {
|
|
@@ -1421,9 +1524,7 @@ function withNotify(text, line) {
|
|
|
1421
1524
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1422
1525
|
return insertTopLevel(text, line);
|
|
1423
1526
|
}
|
|
1424
|
-
function replaceNotify(text, line) {
|
|
1425
|
-
const n = readNotify(text);
|
|
1426
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1527
|
+
function replaceNotify(text, n, line) {
|
|
1427
1528
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1428
1529
|
}
|
|
1429
1530
|
function withoutNotify(text) {
|
|
@@ -1464,7 +1565,7 @@ var CODEX_EVENTS = [
|
|
|
1464
1565
|
];
|
|
1465
1566
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1466
1567
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1467
|
-
var codexHome = (env) => env.CODEX_HOME || path6.join(
|
|
1568
|
+
var codexHome = (env) => env.CODEX_HOME || path6.join(homeDir(), ".codex");
|
|
1468
1569
|
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1469
1570
|
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
1470
1571
|
var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
|
|
@@ -1502,11 +1603,8 @@ function installNotify(cmd, env) {
|
|
|
1502
1603
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1503
1604
|
}
|
|
1504
1605
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
write(next);
|
|
1508
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1509
|
-
}
|
|
1606
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1607
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1510
1608
|
}
|
|
1511
1609
|
}
|
|
1512
1610
|
return {
|
|
@@ -1535,8 +1633,7 @@ function uninstallNotify(env) {
|
|
|
1535
1633
|
}
|
|
1536
1634
|
const prev = previousNotify(n.argv);
|
|
1537
1635
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1538
|
-
|
|
1539
|
-
if (next) writeFileSync3(file, next);
|
|
1636
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1540
1637
|
return { file, changed: true };
|
|
1541
1638
|
}
|
|
1542
1639
|
return { file, changed: false };
|
|
@@ -1566,6 +1663,13 @@ function findRollout(threadId, env = process.env) {
|
|
|
1566
1663
|
walk(sessionsDir(env), 0);
|
|
1567
1664
|
return best;
|
|
1568
1665
|
}
|
|
1666
|
+
function rolloutMeta(file) {
|
|
1667
|
+
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1668
|
+
}
|
|
1669
|
+
function ofAnotherThread(file, id) {
|
|
1670
|
+
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);
|
|
1671
|
+
return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
|
|
1672
|
+
}
|
|
1569
1673
|
var codex = {
|
|
1570
1674
|
name: NAME3,
|
|
1571
1675
|
events: CODEX_EVENTS,
|
|
@@ -1608,11 +1712,14 @@ var codex = {
|
|
|
1608
1712
|
if (event === "notify") {
|
|
1609
1713
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1610
1714
|
try {
|
|
1611
|
-
|
|
1715
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1716
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1612
1717
|
} catch {
|
|
1613
1718
|
}
|
|
1614
1719
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1615
1720
|
if (!id2) return null;
|
|
1721
|
+
const file = findRollout(String(id2), input.env);
|
|
1722
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1616
1723
|
const r2 = {
|
|
1617
1724
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1618
1725
|
events: [
|
|
@@ -1622,18 +1729,19 @@ var codex = {
|
|
|
1622
1729
|
harnessEvent: "notify"
|
|
1623
1730
|
}
|
|
1624
1731
|
],
|
|
1625
|
-
hints: {}
|
|
1732
|
+
hints: {},
|
|
1733
|
+
transcript: file
|
|
1626
1734
|
};
|
|
1627
|
-
const file = findRollout(String(id2), input.env);
|
|
1628
|
-
if (file) r2.transcript = file;
|
|
1629
1735
|
return r2;
|
|
1630
1736
|
}
|
|
1631
1737
|
try {
|
|
1632
|
-
|
|
1738
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1739
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1633
1740
|
} catch {
|
|
1634
1741
|
}
|
|
1635
|
-
const id = s.session_id;
|
|
1742
|
+
const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
|
|
1636
1743
|
if (!id) return null;
|
|
1744
|
+
if (s.transcript_path === null) return null;
|
|
1637
1745
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1638
1746
|
if (typeof s.model === "string") session.model = s.model;
|
|
1639
1747
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1705,7 +1813,8 @@ var codex = {
|
|
|
1705
1813
|
return null;
|
|
1706
1814
|
}
|
|
1707
1815
|
const r = { session, events, hints: {} };
|
|
1708
|
-
if (typeof s.transcript_path === "string"
|
|
1816
|
+
if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
|
|
1817
|
+
r.transcript = s.transcript_path;
|
|
1709
1818
|
return r;
|
|
1710
1819
|
},
|
|
1711
1820
|
readTranscript(file, fromLine) {
|
|
@@ -1723,16 +1832,17 @@ var codex = {
|
|
|
1723
1832
|
return out;
|
|
1724
1833
|
},
|
|
1725
1834
|
backfill(sinceMs, env = process.env) {
|
|
1726
|
-
const
|
|
1835
|
+
const rows2 = [];
|
|
1727
1836
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1728
1837
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
|
|
1729
1838
|
if (!m?.[1]) continue;
|
|
1839
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1730
1840
|
const facts = codexFacts(f);
|
|
1731
1841
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1732
1842
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1733
|
-
|
|
1843
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1734
1844
|
}
|
|
1735
|
-
return
|
|
1845
|
+
return rows2;
|
|
1736
1846
|
}
|
|
1737
1847
|
};
|
|
1738
1848
|
function prune3(o) {
|
|
@@ -1741,10 +1851,11 @@ function prune3(o) {
|
|
|
1741
1851
|
}
|
|
1742
1852
|
function codexFacts(file) {
|
|
1743
1853
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1744
|
-
|
|
1745
|
-
|
|
1854
|
+
let H = parseLines(head);
|
|
1855
|
+
let T = parseLines(tail);
|
|
1746
1856
|
const out = {};
|
|
1747
1857
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1858
|
+
const boundary = codexReplayBoundary(meta);
|
|
1748
1859
|
if (meta?.payload) {
|
|
1749
1860
|
const p = meta.payload;
|
|
1750
1861
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1752,6 +1863,13 @@ function codexFacts(file) {
|
|
|
1752
1863
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1753
1864
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1754
1865
|
}
|
|
1866
|
+
if (boundary) {
|
|
1867
|
+
const h = H.findIndex(boundary);
|
|
1868
|
+
const t = T.findIndex(boundary);
|
|
1869
|
+
H = h < 0 ? [] : H.slice(h);
|
|
1870
|
+
const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
|
|
1871
|
+
T = t >= 0 ? T.slice(t) : found ? T : [];
|
|
1872
|
+
}
|
|
1755
1873
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1756
1874
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1757
1875
|
for (const j of H) {
|
|
@@ -1759,12 +1877,12 @@ function codexFacts(file) {
|
|
|
1759
1877
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1760
1878
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1761
1879
|
if (!text || codexInjected(text)) continue;
|
|
1762
|
-
const line = text.split("\n")
|
|
1880
|
+
const line = text.split("\n")[0];
|
|
1763
1881
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1764
1882
|
out.title_source = "first-ask";
|
|
1765
1883
|
break;
|
|
1766
1884
|
}
|
|
1767
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1885
|
+
const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1768
1886
|
if (times.length) {
|
|
1769
1887
|
out.started_at = Math.min(...times);
|
|
1770
1888
|
out.last_at = Math.max(...times);
|
|
@@ -1772,11 +1890,432 @@ function codexFacts(file) {
|
|
|
1772
1890
|
return out;
|
|
1773
1891
|
}
|
|
1774
1892
|
|
|
1775
|
-
// ../core/src/adapters/
|
|
1776
|
-
import {
|
|
1777
|
-
import { existsSync as
|
|
1893
|
+
// ../core/src/adapters/devin.ts
|
|
1894
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
1895
|
+
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1778
1896
|
import path7 from "node:path";
|
|
1779
|
-
|
|
1897
|
+
|
|
1898
|
+
// ../core/src/readers/sqlite.ts
|
|
1899
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1900
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1901
|
+
import { createRequire } from "node:module";
|
|
1902
|
+
function quiet(fn) {
|
|
1903
|
+
const original = process.emitWarning;
|
|
1904
|
+
try {
|
|
1905
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
1906
|
+
const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
|
|
1907
|
+
if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
|
|
1908
|
+
original.call(process, warning, ...rest);
|
|
1909
|
+
});
|
|
1910
|
+
return fn();
|
|
1911
|
+
} finally {
|
|
1912
|
+
process.emitWarning = original;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
var ctorCache;
|
|
1916
|
+
function dbCtor() {
|
|
1917
|
+
if (ctorCache !== void 0) return ctorCache;
|
|
1918
|
+
ctorCache = null;
|
|
1919
|
+
try {
|
|
1920
|
+
const req = createRequire(import.meta.url);
|
|
1921
|
+
const m = quiet(() => req("node:sqlite"));
|
|
1922
|
+
if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
|
|
1923
|
+
} catch {
|
|
1924
|
+
}
|
|
1925
|
+
return ctorCache;
|
|
1926
|
+
}
|
|
1927
|
+
function plain(row) {
|
|
1928
|
+
if (!row || typeof row !== "object") return null;
|
|
1929
|
+
const out = {};
|
|
1930
|
+
for (const [k, v] of Object.entries(row)) {
|
|
1931
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
1932
|
+
else if (v instanceof Uint8Array) out[k] = null;
|
|
1933
|
+
else out[k] = v;
|
|
1934
|
+
}
|
|
1935
|
+
return out;
|
|
1936
|
+
}
|
|
1937
|
+
var rows = (v) => {
|
|
1938
|
+
if (!Array.isArray(v)) return null;
|
|
1939
|
+
const out = [];
|
|
1940
|
+
for (const r of v) {
|
|
1941
|
+
const p = plain(r);
|
|
1942
|
+
if (p) out.push(p);
|
|
1943
|
+
}
|
|
1944
|
+
return out;
|
|
1945
|
+
};
|
|
1946
|
+
function viaNode(file, sql, params, o) {
|
|
1947
|
+
const C = dbCtor();
|
|
1948
|
+
if (!C) return null;
|
|
1949
|
+
let db;
|
|
1950
|
+
try {
|
|
1951
|
+
db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
|
|
1952
|
+
} catch {
|
|
1953
|
+
return null;
|
|
1954
|
+
}
|
|
1955
|
+
try {
|
|
1956
|
+
try {
|
|
1957
|
+
db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
|
|
1958
|
+
} catch {
|
|
1959
|
+
}
|
|
1960
|
+
return quiet(() => rows(db.prepare(sql).all(...params)));
|
|
1961
|
+
} catch {
|
|
1962
|
+
return null;
|
|
1963
|
+
} finally {
|
|
1964
|
+
try {
|
|
1965
|
+
db.close();
|
|
1966
|
+
} catch {
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
var CHILD = `const { DatabaseSync } = require("node:sqlite");
|
|
1971
|
+
const [, file, sql, params, ms] = process.argv;
|
|
1972
|
+
const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
|
|
1973
|
+
try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
|
|
1974
|
+
const out = db.prepare(sql).all(...JSON.parse(params));
|
|
1975
|
+
db.close();
|
|
1976
|
+
process.stdout.write(JSON.stringify(out));`;
|
|
1977
|
+
function viaChild(file, sql, params, o) {
|
|
1978
|
+
try {
|
|
1979
|
+
const out = execFileSync3(
|
|
1980
|
+
process.execPath,
|
|
1981
|
+
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
1982
|
+
{
|
|
1983
|
+
encoding: "utf8",
|
|
1984
|
+
timeout: o.timeoutMs + 3e3,
|
|
1985
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1986
|
+
maxBuffer: o.maxBuffer,
|
|
1987
|
+
windowsHide: true
|
|
1988
|
+
}
|
|
1989
|
+
);
|
|
1990
|
+
return rows(JSON.parse(out || "null"));
|
|
1991
|
+
} catch {
|
|
1992
|
+
return null;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
|
|
1996
|
+
function inlineParams(sql, params) {
|
|
1997
|
+
if (sql.includes("'")) return null;
|
|
1998
|
+
const holes = sql.split("?").length - 1;
|
|
1999
|
+
if (holes !== params.length) return null;
|
|
2000
|
+
let i = 0;
|
|
2001
|
+
let bad = false;
|
|
2002
|
+
const out = sql.replace(/\?/g, () => {
|
|
2003
|
+
const p = params[i++];
|
|
2004
|
+
if (typeof p === "number") {
|
|
2005
|
+
if (!Number.isFinite(p)) {
|
|
2006
|
+
bad = true;
|
|
2007
|
+
return "NULL";
|
|
2008
|
+
}
|
|
2009
|
+
return String(p);
|
|
2010
|
+
}
|
|
2011
|
+
if (typeof p !== "string" || !SAFE.test(p)) {
|
|
2012
|
+
bad = true;
|
|
2013
|
+
return "NULL";
|
|
2014
|
+
}
|
|
2015
|
+
return `'${p}'`;
|
|
2016
|
+
});
|
|
2017
|
+
return bad ? null : out;
|
|
2018
|
+
}
|
|
2019
|
+
function viaCli(file, sql, params, o) {
|
|
2020
|
+
const text = inlineParams(sql, params);
|
|
2021
|
+
if (!text) return null;
|
|
2022
|
+
try {
|
|
2023
|
+
const out = execFileSync3("sqlite3", ["-readonly", "-json", file, text], {
|
|
2024
|
+
encoding: "utf8",
|
|
2025
|
+
timeout: o.timeoutMs + 3e3,
|
|
2026
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2027
|
+
maxBuffer: o.maxBuffer,
|
|
2028
|
+
windowsHide: true
|
|
2029
|
+
}).trim();
|
|
2030
|
+
if (!out) return [];
|
|
2031
|
+
return rows(JSON.parse(out));
|
|
2032
|
+
} catch {
|
|
2033
|
+
return null;
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
function querySqlite(file, sql, params = [], opts = {}) {
|
|
2037
|
+
const o = {
|
|
2038
|
+
timeoutMs: opts.timeoutMs ?? 2e3,
|
|
2039
|
+
engine: opts.engine ?? "auto",
|
|
2040
|
+
maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
|
|
2041
|
+
};
|
|
2042
|
+
if (!file) return null;
|
|
2043
|
+
try {
|
|
2044
|
+
if (!existsSync5(file)) return null;
|
|
2045
|
+
} catch {
|
|
2046
|
+
return null;
|
|
2047
|
+
}
|
|
2048
|
+
const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
|
|
2049
|
+
for (const way of ways) {
|
|
2050
|
+
const r = way(file, sql, params, o);
|
|
2051
|
+
if (r) return r;
|
|
2052
|
+
}
|
|
2053
|
+
return null;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
// ../core/src/adapters/devin.ts
|
|
2057
|
+
var NAME4 = "devin";
|
|
2058
|
+
var DEVIN_EVENTS = [
|
|
2059
|
+
"SessionStart",
|
|
2060
|
+
"UserPromptSubmit",
|
|
2061
|
+
"PreToolUse",
|
|
2062
|
+
"PostToolUse",
|
|
2063
|
+
"PermissionRequest",
|
|
2064
|
+
"Stop",
|
|
2065
|
+
"PostCompaction",
|
|
2066
|
+
"SessionEnd"
|
|
2067
|
+
];
|
|
2068
|
+
var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
2069
|
+
var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
|
|
2070
|
+
var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
|
|
2071
|
+
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";
|
|
2072
|
+
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)`;
|
|
2073
|
+
function foreignEvent(file) {
|
|
2074
|
+
const hooks = readJson(file).hooks;
|
|
2075
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
|
|
2076
|
+
const ok = DEVIN_EVENTS;
|
|
2077
|
+
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
2078
|
+
}
|
|
2079
|
+
var homeOf = (env) => env.HOME || homeDir();
|
|
2080
|
+
var appData = (env) => env.APPDATA || path7.join(homeOf(env), "AppData", "Roaming");
|
|
2081
|
+
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");
|
|
2082
|
+
var configFile3 = (env) => path7.join(devinConfigDir(env), "config.json");
|
|
2083
|
+
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");
|
|
2084
|
+
var sessionsDb = (env) => path7.join(devinDataDir(env), "sessions.db");
|
|
2085
|
+
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
2086
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2087
|
+
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
2088
|
+
var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
2089
|
+
function errText2(v) {
|
|
2090
|
+
if (typeof v === "string" && v) return v.slice(0, 2e3);
|
|
2091
|
+
if (v && typeof v === "object") {
|
|
2092
|
+
const e = v;
|
|
2093
|
+
if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
|
|
2094
|
+
if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
|
|
2095
|
+
}
|
|
2096
|
+
return void 0;
|
|
2097
|
+
}
|
|
2098
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
|
|
2099
|
+
var devin = {
|
|
2100
|
+
name: NAME4,
|
|
2101
|
+
events: DEVIN_EVENTS,
|
|
2102
|
+
detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
|
|
2103
|
+
configFiles: (env = process.env) => [configFile3(env)],
|
|
2104
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
2105
|
+
const file = configFile3(env);
|
|
2106
|
+
const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
2107
|
+
if (r.skipped) return [r];
|
|
2108
|
+
const bad = foreignEvent(file);
|
|
2109
|
+
return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
|
|
2110
|
+
},
|
|
2111
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
2112
|
+
uninstallClaudeShaped(configFile3(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile3(env)) })
|
|
2113
|
+
],
|
|
2114
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
2115
|
+
const file = configFile3(env);
|
|
2116
|
+
const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
2117
|
+
const bad = state === "missing" ? void 0 : foreignEvent(file);
|
|
2118
|
+
return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
|
|
2119
|
+
},
|
|
2120
|
+
fromHook(input) {
|
|
2121
|
+
const event = input.argv[1] ?? "";
|
|
2122
|
+
let s = {};
|
|
2123
|
+
try {
|
|
2124
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2125
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
2126
|
+
} catch {
|
|
2127
|
+
}
|
|
2128
|
+
const id = typeof s.session_id === "string" ? s.session_id : "";
|
|
2129
|
+
if (!id) return null;
|
|
2130
|
+
const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
|
|
2131
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
2132
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
2133
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
2134
|
+
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
2135
|
+
const permissionId = () => `perm-${createHash3("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
2136
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
2137
|
+
const events = [];
|
|
2138
|
+
switch (event) {
|
|
2139
|
+
case "SessionStart":
|
|
2140
|
+
events.push(ev("session.started", { source: pickSource2(s.source) }));
|
|
2141
|
+
break;
|
|
2142
|
+
case "UserPromptSubmit":
|
|
2143
|
+
events.push(
|
|
2144
|
+
ev("turn.started", {
|
|
2145
|
+
turn_id,
|
|
2146
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
2147
|
+
})
|
|
2148
|
+
);
|
|
2149
|
+
break;
|
|
2150
|
+
case "PreToolUse":
|
|
2151
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
2152
|
+
break;
|
|
2153
|
+
case "PostToolUse":
|
|
2154
|
+
if (tool)
|
|
2155
|
+
events.push(
|
|
2156
|
+
ev("tool.ended", {
|
|
2157
|
+
tool,
|
|
2158
|
+
call_id,
|
|
2159
|
+
turn_id,
|
|
2160
|
+
// `ok` is required on tool.ended (PROTOCOL.md §events), so a response
|
|
2161
|
+
// that carries no success flag at all reads as "nothing said it failed".
|
|
2162
|
+
ok: response?.success !== false,
|
|
2163
|
+
error: errText2(response?.error),
|
|
2164
|
+
input: s.tool_input,
|
|
2165
|
+
output: response ? response.output : s.tool_response
|
|
2166
|
+
})
|
|
2167
|
+
);
|
|
2168
|
+
break;
|
|
2169
|
+
case "PermissionRequest":
|
|
2170
|
+
events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
|
|
2171
|
+
break;
|
|
2172
|
+
case "Stop":
|
|
2173
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
2174
|
+
break;
|
|
2175
|
+
case "PostCompaction":
|
|
2176
|
+
events.push(ev("context.compacted", { trigger: "auto" }));
|
|
2177
|
+
break;
|
|
2178
|
+
case "SessionEnd":
|
|
2179
|
+
events.push(ev("session.ended", { reason: endReason3(s.reason) }));
|
|
2180
|
+
break;
|
|
2181
|
+
default:
|
|
2182
|
+
return null;
|
|
2183
|
+
}
|
|
2184
|
+
return {
|
|
2185
|
+
session,
|
|
2186
|
+
events,
|
|
2187
|
+
hints: {},
|
|
2188
|
+
transcript: transcriptRef(sessionsDb(input.env), id)
|
|
2189
|
+
};
|
|
2190
|
+
},
|
|
2191
|
+
readTranscript(file, fromLine) {
|
|
2192
|
+
const ref = splitRef(file);
|
|
2193
|
+
if (!ref) return { turns: [], line: fromLine };
|
|
2194
|
+
return readDevinChain(ref.db, ref.session, fromLine);
|
|
2195
|
+
},
|
|
2196
|
+
facts(session, transcript, _hints, _state, env = process.env) {
|
|
2197
|
+
const out = {};
|
|
2198
|
+
const version = devinVersion(env);
|
|
2199
|
+
if (version) out.harness_version = version;
|
|
2200
|
+
const account = devinAccount(env);
|
|
2201
|
+
if (account) out.account_id = account;
|
|
2202
|
+
const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
|
|
2203
|
+
if (!SESSION_ID.test(ref.session)) return out;
|
|
2204
|
+
const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
|
|
2205
|
+
const row = rows2?.[0];
|
|
2206
|
+
if (!row) return out;
|
|
2207
|
+
Object.assign(out, sessionFacts(row));
|
|
2208
|
+
return out;
|
|
2209
|
+
},
|
|
2210
|
+
backfill(sinceMs, env = process.env) {
|
|
2211
|
+
const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
|
|
2212
|
+
if (!rows2) return [];
|
|
2213
|
+
const out = [];
|
|
2214
|
+
for (const row of rows2) {
|
|
2215
|
+
const id = String(row.id);
|
|
2216
|
+
if (!SESSION_ID.test(id)) continue;
|
|
2217
|
+
const { started_at, last_at, ...rest } = sessionFacts(row);
|
|
2218
|
+
if (!started_at || !last_at) continue;
|
|
2219
|
+
out.push({ session: { id, ...rest }, started_at, last_at });
|
|
2220
|
+
}
|
|
2221
|
+
return out;
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
function prune4(o) {
|
|
2225
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2226
|
+
return o;
|
|
2227
|
+
}
|
|
2228
|
+
var transcriptRef = (db, session) => `${db}#${session}`;
|
|
2229
|
+
function splitRef(ref) {
|
|
2230
|
+
if (!ref) return null;
|
|
2231
|
+
const at = ref.lastIndexOf("#");
|
|
2232
|
+
if (at <= 0) return null;
|
|
2233
|
+
const db = ref.slice(0, at);
|
|
2234
|
+
const session = ref.slice(at + 1);
|
|
2235
|
+
return db && SESSION_ID.test(session) ? { db, session } : null;
|
|
2236
|
+
}
|
|
2237
|
+
var CHAIN_LIMIT = 4e3;
|
|
2238
|
+
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2239
|
+
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2240
|
+
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2241
|
+
UNION ALL
|
|
2242
|
+
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2243
|
+
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2244
|
+
)
|
|
2245
|
+
SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
|
|
2246
|
+
var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
|
|
2247
|
+
(SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
|
|
2248
|
+
ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
|
|
2249
|
+
var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
|
|
2250
|
+
var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
2251
|
+
WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
|
|
2252
|
+
ORDER BY s.last_activity_at DESC LIMIT 500`;
|
|
2253
|
+
function sessionFacts(row) {
|
|
2254
|
+
const out = {};
|
|
2255
|
+
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2256
|
+
if (typeof row.working_directory === "string" && path7.isAbsolute(row.working_directory))
|
|
2257
|
+
out.cwd = row.working_directory;
|
|
2258
|
+
const started = Number(row.created_at);
|
|
2259
|
+
const last = Number(row.last_activity_at);
|
|
2260
|
+
if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
|
|
2261
|
+
if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
|
|
2262
|
+
if (typeof row.first_ask === "string") {
|
|
2263
|
+
const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
|
|
2264
|
+
if (line.trim()) {
|
|
2265
|
+
out.title = cut(line.trim(), 80);
|
|
2266
|
+
out.title_source = "first-ask";
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
return out;
|
|
2270
|
+
}
|
|
2271
|
+
function readDevinChain(db, session, fromLine) {
|
|
2272
|
+
if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
|
|
2273
|
+
const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
|
|
2274
|
+
const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
|
|
2275
|
+
if (!rows2?.length) return { turns: [], line: fromLine };
|
|
2276
|
+
const msgs = [];
|
|
2277
|
+
let end = fromLine;
|
|
2278
|
+
for (const row of rows2) {
|
|
2279
|
+
const node = Number(row.node_id);
|
|
2280
|
+
if (!Number.isFinite(node)) continue;
|
|
2281
|
+
if (node + 1 > end) end = node + 1;
|
|
2282
|
+
if (node < fromLine) continue;
|
|
2283
|
+
let j;
|
|
2284
|
+
try {
|
|
2285
|
+
j = JSON.parse(String(row.chat_message ?? ""));
|
|
2286
|
+
} catch {
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2289
|
+
const text = typeof j.content === "string" ? j.content.trim() : "";
|
|
2290
|
+
if (!text) continue;
|
|
2291
|
+
const secs = Number(row.created_at);
|
|
2292
|
+
const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
|
|
2293
|
+
if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
|
|
2294
|
+
else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
|
|
2295
|
+
}
|
|
2296
|
+
msgs.sort((a, b) => a.line - b.line);
|
|
2297
|
+
return pairTurns(msgs, end);
|
|
2298
|
+
}
|
|
2299
|
+
function devinVersion(env = process.env) {
|
|
2300
|
+
try {
|
|
2301
|
+
const target = readlinkSync(path7.join(devinDataDir(env), "_versions", "current"));
|
|
2302
|
+
const name = path7.basename(target);
|
|
2303
|
+
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2304
|
+
} catch {
|
|
2305
|
+
return void 0;
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
function devinAccount(env = process.env) {
|
|
2309
|
+
const d = readJson(configFile3(env)).devin;
|
|
2310
|
+
const id = d && typeof d === "object" ? d.org_id : void 0;
|
|
2311
|
+
return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// ../core/src/adapters/gemini-cli.ts
|
|
2315
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2316
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2317
|
+
import path8 from "node:path";
|
|
2318
|
+
var NAME5 = "gemini-cli";
|
|
1780
2319
|
var GEMINI_EVENTS = [
|
|
1781
2320
|
"SessionStart",
|
|
1782
2321
|
"BeforeAgent",
|
|
@@ -1787,11 +2326,11 @@ var GEMINI_EVENTS = [
|
|
|
1787
2326
|
"PreCompress",
|
|
1788
2327
|
"SessionEnd"
|
|
1789
2328
|
];
|
|
1790
|
-
var
|
|
1791
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1792
|
-
var settings = (env) =>
|
|
1793
|
-
var
|
|
1794
|
-
var
|
|
2329
|
+
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2330
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path8.join(homeDir(), ".gemini");
|
|
2331
|
+
var settings = (env) => path8.join(geminiDir(env), "settings.json");
|
|
2332
|
+
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2333
|
+
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1795
2334
|
function geminiHooksActive(settingsFile) {
|
|
1796
2335
|
let tools;
|
|
1797
2336
|
try {
|
|
@@ -1803,7 +2342,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1803
2342
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1804
2343
|
let version = "";
|
|
1805
2344
|
try {
|
|
1806
|
-
version =
|
|
2345
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1807
2346
|
encoding: "utf8",
|
|
1808
2347
|
timeout: 4e3,
|
|
1809
2348
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1822,13 +2361,13 @@ function geminiHooksActive(settingsFile) {
|
|
|
1822
2361
|
};
|
|
1823
2362
|
}
|
|
1824
2363
|
var geminiCli = {
|
|
1825
|
-
name:
|
|
2364
|
+
name: NAME5,
|
|
1826
2365
|
events: GEMINI_EVENTS,
|
|
1827
2366
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1828
|
-
detect: (env = process.env) =>
|
|
2367
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path8.join(geminiDir(env), "tmp")),
|
|
1829
2368
|
configFiles: (env = process.env) => [settings(env)],
|
|
1830
2369
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1831
|
-
const r = installClaudeShaped(settings(env),
|
|
2370
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1832
2371
|
const a = geminiHooksActive(settings(env));
|
|
1833
2372
|
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1834
2373
|
},
|
|
@@ -1836,7 +2375,7 @@ var geminiCli = {
|
|
|
1836
2375
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1837
2376
|
],
|
|
1838
2377
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1839
|
-
const state = installedClaudeShaped(settings(env),
|
|
2378
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1840
2379
|
const a = geminiHooksActive(settings(env));
|
|
1841
2380
|
return [
|
|
1842
2381
|
{
|
|
@@ -1850,14 +2389,15 @@ var geminiCli = {
|
|
|
1850
2389
|
const event = input.argv[1] ?? "";
|
|
1851
2390
|
let s = {};
|
|
1852
2391
|
try {
|
|
1853
|
-
|
|
2392
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2393
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1854
2394
|
} catch {
|
|
1855
2395
|
}
|
|
1856
2396
|
const id = s.session_id;
|
|
1857
2397
|
if (!id) return null;
|
|
1858
2398
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1859
|
-
const tool =
|
|
1860
|
-
const ev = (type, data) => ({ type, data:
|
|
2399
|
+
const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
|
|
2400
|
+
const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
|
|
1861
2401
|
const events = [];
|
|
1862
2402
|
switch (event) {
|
|
1863
2403
|
case "SessionStart":
|
|
@@ -1938,7 +2478,7 @@ var geminiCli = {
|
|
|
1938
2478
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1939
2479
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1940
2480
|
if (text) {
|
|
1941
|
-
const line = text.split("\n")
|
|
2481
|
+
const line = text.split("\n")[0];
|
|
1942
2482
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1943
2483
|
out.title_source = "first-ask";
|
|
1944
2484
|
}
|
|
@@ -1952,18 +2492,18 @@ var geminiCli = {
|
|
|
1952
2492
|
backfill: () => []
|
|
1953
2493
|
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
1954
2494
|
};
|
|
1955
|
-
function
|
|
2495
|
+
function prune5(o) {
|
|
1956
2496
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1957
2497
|
return o;
|
|
1958
2498
|
}
|
|
1959
2499
|
|
|
1960
2500
|
// ../core/src/adapters/index.ts
|
|
1961
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2501
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1962
2502
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1963
2503
|
|
|
1964
2504
|
// ../core/src/config.ts
|
|
1965
|
-
import { chmodSync, existsSync as
|
|
1966
|
-
import
|
|
2505
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2506
|
+
import path9 from "node:path";
|
|
1967
2507
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
1968
2508
|
function readConfig(file = configFile()) {
|
|
1969
2509
|
try {
|
|
@@ -1979,10 +2519,10 @@ function readConfig(file = configFile()) {
|
|
|
1979
2519
|
}
|
|
1980
2520
|
}
|
|
1981
2521
|
function writeConfig(c, file = configFile()) {
|
|
1982
|
-
mkdirSync4(
|
|
2522
|
+
mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
|
|
1983
2523
|
writeFileSync4(file, `${JSON.stringify(c, null, 2)}
|
|
1984
2524
|
`, { mode: 384 });
|
|
1985
|
-
if (process.platform !== "win32" &&
|
|
2525
|
+
if (process.platform !== "win32" && existsSync8(file)) {
|
|
1986
2526
|
try {
|
|
1987
2527
|
chmodSync(file, 384);
|
|
1988
2528
|
} catch {
|
|
@@ -2011,7 +2551,7 @@ function encodeTime(t) {
|
|
|
2011
2551
|
function ulid(now = Date.now()) {
|
|
2012
2552
|
if (now === lastTime) {
|
|
2013
2553
|
for (let i = lastRand.length - 1; i >= 0; i--) {
|
|
2014
|
-
const v =
|
|
2554
|
+
const v = lastRand[i] + 1;
|
|
2015
2555
|
if (v < 32) {
|
|
2016
2556
|
lastRand[i] = v;
|
|
2017
2557
|
break;
|
|
@@ -2020,8 +2560,7 @@ function ulid(now = Date.now()) {
|
|
|
2020
2560
|
}
|
|
2021
2561
|
} else {
|
|
2022
2562
|
lastTime = now;
|
|
2023
|
-
|
|
2024
|
-
lastRand = Array.from({ length: 16 }, (_, i) => (b[i] ?? 0) % 32);
|
|
2563
|
+
lastRand = Array.from(randomBytes(16), (b) => b % 32);
|
|
2025
2564
|
}
|
|
2026
2565
|
return encodeTime(now) + lastRand.map((v) => ALPHABET[v]).join("");
|
|
2027
2566
|
}
|
|
@@ -2044,11 +2583,27 @@ function makeEvent(a, ctx) {
|
|
|
2044
2583
|
};
|
|
2045
2584
|
}
|
|
2046
2585
|
|
|
2586
|
+
// ../core/src/launched-by.ts
|
|
2587
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2588
|
+
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2589
|
+
function launchedBy(env) {
|
|
2590
|
+
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2591
|
+
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2592
|
+
}
|
|
2593
|
+
function readHook(adapter, input) {
|
|
2594
|
+
const r = adapter.fromHook(input);
|
|
2595
|
+
if (r && !r.session.parent_id) {
|
|
2596
|
+
const launcher = launchedBy(input.env);
|
|
2597
|
+
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2598
|
+
}
|
|
2599
|
+
return r;
|
|
2600
|
+
}
|
|
2601
|
+
|
|
2047
2602
|
// ../core/src/outbox.ts
|
|
2048
2603
|
import {
|
|
2049
2604
|
appendFileSync,
|
|
2050
2605
|
closeSync as closeSync2,
|
|
2051
|
-
existsSync as
|
|
2606
|
+
existsSync as existsSync9,
|
|
2052
2607
|
mkdirSync as mkdirSync5,
|
|
2053
2608
|
openSync as openSync2,
|
|
2054
2609
|
readdirSync as readdirSync5,
|
|
@@ -2059,42 +2614,45 @@ import {
|
|
|
2059
2614
|
unlinkSync as unlinkSync3,
|
|
2060
2615
|
writeFileSync as writeFileSync5
|
|
2061
2616
|
} from "node:fs";
|
|
2062
|
-
import
|
|
2617
|
+
import path10 from "node:path";
|
|
2618
|
+
var READ_CHUNK = 1024 * 1024;
|
|
2619
|
+
var READ_MAX = 8 * 1024 * 1024;
|
|
2063
2620
|
var Outbox = class {
|
|
2064
2621
|
constructor(root) {
|
|
2065
2622
|
this.root = root;
|
|
2066
2623
|
}
|
|
2067
2624
|
root;
|
|
2068
2625
|
dir(harness) {
|
|
2069
|
-
return
|
|
2626
|
+
return path10.join(this.root, "outbox", safeId(harness));
|
|
2070
2627
|
}
|
|
2071
2628
|
file(ref) {
|
|
2072
|
-
return
|
|
2629
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
2073
2630
|
}
|
|
2074
2631
|
seqFile(ref) {
|
|
2075
|
-
return
|
|
2632
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
2076
2633
|
}
|
|
2077
2634
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
2078
2635
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
2079
2636
|
nextSeq(ref) {
|
|
2080
2637
|
const f = this.seqFile(ref);
|
|
2081
|
-
let n =
|
|
2638
|
+
let n = -1;
|
|
2082
2639
|
try {
|
|
2083
|
-
|
|
2640
|
+
const t = readFileSync7(f, "utf8").trim();
|
|
2641
|
+
if (/^\d+$/.test(t)) n = Number(t);
|
|
2084
2642
|
} catch {
|
|
2085
|
-
n = this.lastSeqInFile(ref) + 1;
|
|
2086
2643
|
}
|
|
2087
|
-
|
|
2644
|
+
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2645
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
2088
2646
|
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
2089
2647
|
return n;
|
|
2090
2648
|
}
|
|
2091
2649
|
lastSeqInFile(ref) {
|
|
2092
2650
|
const f = this.file(ref);
|
|
2093
|
-
if (!
|
|
2651
|
+
if (!existsSync9(f)) return -1;
|
|
2094
2652
|
const lines = readFileSync7(f, "utf8").trim().split("\n");
|
|
2095
2653
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
2096
2654
|
try {
|
|
2097
|
-
const j = JSON.parse(lines[i]
|
|
2655
|
+
const j = JSON.parse(lines[i]);
|
|
2098
2656
|
if (typeof j.session?.seq === "number") return j.session.seq;
|
|
2099
2657
|
} catch {
|
|
2100
2658
|
}
|
|
@@ -2110,10 +2668,10 @@ var Outbox = class {
|
|
|
2110
2668
|
/** Every session file, newest first. */
|
|
2111
2669
|
sessions() {
|
|
2112
2670
|
const out = [];
|
|
2113
|
-
const base =
|
|
2114
|
-
if (!
|
|
2671
|
+
const base = path10.join(this.root, "outbox");
|
|
2672
|
+
if (!existsSync9(base)) return [];
|
|
2115
2673
|
for (const h of readdirSync5(base)) {
|
|
2116
|
-
const d =
|
|
2674
|
+
const d = path10.join(base, h);
|
|
2117
2675
|
let names = [];
|
|
2118
2676
|
try {
|
|
2119
2677
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2122,7 +2680,7 @@ var Outbox = class {
|
|
|
2122
2680
|
}
|
|
2123
2681
|
for (const n of names) {
|
|
2124
2682
|
try {
|
|
2125
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync4(
|
|
2683
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync4(path10.join(d, n)).mtimeMs });
|
|
2126
2684
|
} catch {
|
|
2127
2685
|
}
|
|
2128
2686
|
}
|
|
@@ -2130,31 +2688,43 @@ var Outbox = class {
|
|
|
2130
2688
|
return out.sort((a, b) => b.mtime - a.mtime).map((x) => x.ref);
|
|
2131
2689
|
}
|
|
2132
2690
|
/** Read events after byte offset `from`; returns them and the new offset. Lines
|
|
2133
|
-
* are read whole; a partial trailing line (a write in progress) is left.
|
|
2691
|
+
* are read whole; a partial trailing line (a write in progress) is left. A read
|
|
2692
|
+
* takes READ_CHUNK bytes at a time, not the rest of the file: a session file grows
|
|
2693
|
+
* to tens of MB and a drain takes it in batches of 50. It reads on until the batch
|
|
2694
|
+
* is full or the file ends, so lines that parse to nothing never stop a drain; one
|
|
2695
|
+
* line longer than the chunk gets a larger one, up to READ_MAX. */
|
|
2134
2696
|
read(ref, from, maxEvents = 500) {
|
|
2135
2697
|
const f = this.file(ref);
|
|
2136
|
-
if (!
|
|
2698
|
+
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2137
2699
|
const size = statSync4(f).size;
|
|
2138
2700
|
if (from >= size) return { events: [], offset: from };
|
|
2139
2701
|
const fd = openSync2(f, "r");
|
|
2140
2702
|
try {
|
|
2141
|
-
const buf = Buffer.alloc(Math.min(size - from, 8 * 1024 * 1024));
|
|
2142
|
-
const n = readSync2(fd, buf, 0, buf.length, from);
|
|
2143
|
-
const text = buf.subarray(0, n).toString("utf8");
|
|
2144
2703
|
const events = [];
|
|
2145
2704
|
let offset = from;
|
|
2146
|
-
let
|
|
2147
|
-
while (events.length < maxEvents) {
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2150
|
-
const
|
|
2151
|
-
pos =
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2705
|
+
let chunk = READ_CHUNK;
|
|
2706
|
+
while (events.length < maxEvents && offset < size) {
|
|
2707
|
+
const buf = Buffer.alloc(Math.min(size - offset, chunk));
|
|
2708
|
+
const n = readSync2(fd, buf, 0, buf.length, offset);
|
|
2709
|
+
const text = buf.subarray(0, n).toString("utf8");
|
|
2710
|
+
let pos = 0;
|
|
2711
|
+
let used = 0;
|
|
2712
|
+
while (events.length < maxEvents) {
|
|
2713
|
+
const nl = text.indexOf("\n", pos);
|
|
2714
|
+
if (nl < 0) break;
|
|
2715
|
+
const line = text.slice(pos, nl);
|
|
2716
|
+
pos = nl + 1;
|
|
2717
|
+
used += Buffer.byteLength(line, "utf8") + 1;
|
|
2718
|
+
if (!line.trim()) continue;
|
|
2719
|
+
try {
|
|
2720
|
+
events.push(JSON.parse(line));
|
|
2721
|
+
} catch {
|
|
2722
|
+
}
|
|
2157
2723
|
}
|
|
2724
|
+
offset += used;
|
|
2725
|
+
if (used) continue;
|
|
2726
|
+
if (offset + n >= size || chunk >= READ_MAX) break;
|
|
2727
|
+
chunk *= 2;
|
|
2158
2728
|
}
|
|
2159
2729
|
return { events, offset };
|
|
2160
2730
|
} finally {
|
|
@@ -2198,7 +2768,7 @@ var Cursors = class {
|
|
|
2198
2768
|
root;
|
|
2199
2769
|
sink;
|
|
2200
2770
|
file() {
|
|
2201
|
-
return
|
|
2771
|
+
return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2202
2772
|
}
|
|
2203
2773
|
read() {
|
|
2204
2774
|
try {
|
|
@@ -2209,7 +2779,7 @@ var Cursors = class {
|
|
|
2209
2779
|
}
|
|
2210
2780
|
write(c) {
|
|
2211
2781
|
const f = this.file();
|
|
2212
|
-
mkdirSync5(
|
|
2782
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
2213
2783
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2214
2784
|
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
2215
2785
|
renameSync(tmp, f);
|
|
@@ -2219,11 +2789,14 @@ var Cursors = class {
|
|
|
2219
2789
|
}
|
|
2220
2790
|
};
|
|
2221
2791
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
2222
|
-
|
|
2792
|
+
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
2793
|
+
}
|
|
2794
|
+
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
2795
|
+
const dir = path10.join(root, "locks");
|
|
2223
2796
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2224
|
-
const f =
|
|
2797
|
+
const f = path10.join(dir, `${name}.lock`);
|
|
2225
2798
|
try {
|
|
2226
|
-
if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
2799
|
+
if (Date.now() - statSync4(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
|
|
2227
2800
|
else return null;
|
|
2228
2801
|
} catch {
|
|
2229
2802
|
}
|
|
@@ -2239,9 +2812,18 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2239
2812
|
}
|
|
2240
2813
|
};
|
|
2241
2814
|
}
|
|
2815
|
+
function processAlive(pid) {
|
|
2816
|
+
if (!Number.isInteger(pid) || pid <= 0) return true;
|
|
2817
|
+
try {
|
|
2818
|
+
process.kill(pid, 0);
|
|
2819
|
+
return true;
|
|
2820
|
+
} catch (e) {
|
|
2821
|
+
return e.code === "EPERM";
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2242
2824
|
|
|
2243
2825
|
// ../core/src/privacy/pii.ts
|
|
2244
|
-
import { createHash as
|
|
2826
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
2245
2827
|
var RULESET_PII = "pii@1";
|
|
2246
2828
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2247
2829
|
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
@@ -2263,7 +2845,7 @@ function reducePii(text, home) {
|
|
|
2263
2845
|
return out;
|
|
2264
2846
|
}
|
|
2265
2847
|
function hashMachine(name) {
|
|
2266
|
-
return
|
|
2848
|
+
return createHash4("sha256").update(name).digest("hex").slice(0, 8);
|
|
2267
2849
|
}
|
|
2268
2850
|
function reducePiiDeep(v, home) {
|
|
2269
2851
|
if (typeof v === "string") return reducePii(v, home);
|
|
@@ -2352,12 +2934,12 @@ function redactAssignments(text) {
|
|
|
2352
2934
|
let last = 0;
|
|
2353
2935
|
NAME_SEP.lastIndex = 0;
|
|
2354
2936
|
for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
|
|
2355
|
-
const name = m[1]
|
|
2937
|
+
const name = m[1];
|
|
2356
2938
|
if (!isSecretName(name)) continue;
|
|
2357
2939
|
VALUE.lastIndex = NAME_SEP.lastIndex;
|
|
2358
2940
|
const v = VALUE.exec(text);
|
|
2359
2941
|
if (!v) continue;
|
|
2360
|
-
const value = v[1] ?? v[2] ?? v[3] ?? v[4]
|
|
2942
|
+
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
2361
2943
|
NAME_SEP.lastIndex = VALUE.lastIndex;
|
|
2362
2944
|
if (isPlaceholder(value) || value.includes(REDACTED)) continue;
|
|
2363
2945
|
if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
|
|
@@ -2413,7 +2995,7 @@ function redactSecrets(text) {
|
|
|
2413
2995
|
let out = text;
|
|
2414
2996
|
for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
|
|
2415
2997
|
out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
|
|
2416
|
-
out = out.replace(AUTH_HEADER, (
|
|
2998
|
+
out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
|
|
2417
2999
|
out = redactAssignments(out);
|
|
2418
3000
|
out = out.replace(
|
|
2419
3001
|
PROSE,
|
|
@@ -2442,6 +3024,7 @@ var TYPE_TIER = {
|
|
|
2442
3024
|
"turn.started": 0,
|
|
2443
3025
|
"turn.ended": 0,
|
|
2444
3026
|
"turn.transcript": 2,
|
|
3027
|
+
"turn.progress": 2,
|
|
2445
3028
|
"tool.started": 1,
|
|
2446
3029
|
"tool.ended": 1,
|
|
2447
3030
|
"files.changed": 1,
|
|
@@ -2458,9 +3041,10 @@ var DATA_ALLOW = {
|
|
|
2458
3041
|
"session.ended": { 0: ["reason"] },
|
|
2459
3042
|
"session.backfill": { 0: ["started_at", "last_at", "turns"] },
|
|
2460
3043
|
"session.forgotten": { 0: [] },
|
|
2461
|
-
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2462
|
-
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
3044
|
+
"turn.started": { 0: ["turn_id", "submitted_by", "injected"], 1: ["prompt_chars"] },
|
|
3045
|
+
"turn.ended": { 0: ["turn_id", "reason", "ms", "background"], 1: ["error"] },
|
|
2463
3046
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
3047
|
+
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
2464
3048
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2465
3049
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
2466
3050
|
"files.changed": { 1: ["paths"] },
|
|
@@ -2482,7 +3066,9 @@ var SESSION_ALLOW = [
|
|
|
2482
3066
|
"title_source",
|
|
2483
3067
|
"url",
|
|
2484
3068
|
"account_id",
|
|
2485
|
-
"
|
|
3069
|
+
"attended",
|
|
3070
|
+
"parent_id",
|
|
3071
|
+
"launched_by"
|
|
2486
3072
|
];
|
|
2487
3073
|
var MAX_SIDE = 2e4;
|
|
2488
3074
|
var MAX_IO = 64 * 1024;
|
|
@@ -2501,10 +3087,12 @@ function cap(v, max) {
|
|
|
2501
3087
|
}
|
|
2502
3088
|
return v;
|
|
2503
3089
|
}
|
|
3090
|
+
function capTail(v, max) {
|
|
3091
|
+
return v.length > max ? `\u2026${v.slice(v.length - max + 1)}` : v;
|
|
3092
|
+
}
|
|
2504
3093
|
function filterEvent(event, tier, pii, opts = {}) {
|
|
2505
3094
|
const minTier = TYPE_TIER[event.type];
|
|
2506
3095
|
if (minTier !== void 0 && minTier > tier) return null;
|
|
2507
|
-
if (event.type === "native" && tier < 3) return null;
|
|
2508
3096
|
const session = {};
|
|
2509
3097
|
for (const k of SESSION_ALLOW)
|
|
2510
3098
|
if (event.session[k] !== void 0)
|
|
@@ -2514,7 +3102,8 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2514
3102
|
if (keys === null) data = { ...event.data };
|
|
2515
3103
|
else for (const k of keys) if (event.data[k] !== void 0) data[k] = event.data[k];
|
|
2516
3104
|
if (typeof data.user === "string") data.user = cap(data.user, MAX_SIDE);
|
|
2517
|
-
if (typeof data.assistant === "string")
|
|
3105
|
+
if (typeof data.assistant === "string")
|
|
3106
|
+
data.assistant = event.type === "turn.progress" ? capTail(data.assistant, MAX_SIDE) : cap(data.assistant, MAX_SIDE);
|
|
2518
3107
|
if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
|
|
2519
3108
|
if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
|
|
2520
3109
|
if (typeof data.message === "string") data.message = cap(data.message, 200);
|
|
@@ -2535,9 +3124,15 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2535
3124
|
return out;
|
|
2536
3125
|
}
|
|
2537
3126
|
|
|
3127
|
+
// ../core/src/short-id.ts
|
|
3128
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
3129
|
+
function shortId(harness, id) {
|
|
3130
|
+
return createHash5("sha256").update(`sessionpipe.session:${harness}:${id}`).digest("hex").slice(0, 8);
|
|
3131
|
+
}
|
|
3132
|
+
|
|
2538
3133
|
// ../core/src/sinks/file.ts
|
|
2539
3134
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2540
|
-
import
|
|
3135
|
+
import path11 from "node:path";
|
|
2541
3136
|
var FileSink = class {
|
|
2542
3137
|
constructor(name, file, tier, pii) {
|
|
2543
3138
|
this.name = name;
|
|
@@ -2551,7 +3146,7 @@ var FileSink = class {
|
|
|
2551
3146
|
pii;
|
|
2552
3147
|
async send(events) {
|
|
2553
3148
|
try {
|
|
2554
|
-
mkdirSync6(
|
|
3149
|
+
mkdirSync6(path11.dirname(this.file), { recursive: true });
|
|
2555
3150
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2556
3151
|
`);
|
|
2557
3152
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -2575,15 +3170,62 @@ var StdoutSink = class {
|
|
|
2575
3170
|
pii;
|
|
2576
3171
|
write;
|
|
2577
3172
|
async send(events) {
|
|
2578
|
-
|
|
2579
|
-
|
|
3173
|
+
try {
|
|
3174
|
+
for (const e of events) this.write(JSON.stringify(e));
|
|
3175
|
+
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
3176
|
+
} catch (e) {
|
|
3177
|
+
return { ok: false, acknowledged: 0, error: String(e.message ?? e), action: "retry" };
|
|
3178
|
+
}
|
|
2580
3179
|
}
|
|
2581
3180
|
};
|
|
2582
3181
|
|
|
2583
3182
|
// src/control/aliases.ts
|
|
2584
3183
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2585
|
-
import
|
|
2586
|
-
|
|
3184
|
+
import path12 from "node:path";
|
|
3185
|
+
|
|
3186
|
+
// src/secrets.ts
|
|
3187
|
+
import { spawnSync } from "node:child_process";
|
|
3188
|
+
import { createHash as createHash6, randomBytes as randomBytes2 } from "node:crypto";
|
|
3189
|
+
var SERVICE = "sessionpipe";
|
|
3190
|
+
var defaultRunner = (cmd, args, input) => {
|
|
3191
|
+
const r = spawnSync(cmd, args, {
|
|
3192
|
+
input: input ?? "",
|
|
3193
|
+
encoding: "utf8",
|
|
3194
|
+
timeout: 8e3,
|
|
3195
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
3196
|
+
});
|
|
3197
|
+
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
3198
|
+
};
|
|
3199
|
+
var sinkAccount = (name, what = "token") => {
|
|
3200
|
+
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash6("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
3201
|
+
return `sink:${name}:${what}${scope}`;
|
|
3202
|
+
};
|
|
3203
|
+
function secretGet(store, account, run = defaultRunner) {
|
|
3204
|
+
const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
|
|
3205
|
+
if (r.status !== 0) return null;
|
|
3206
|
+
const v = r.stdout.replace(/\r?\n$/, "");
|
|
3207
|
+
return v || null;
|
|
3208
|
+
}
|
|
3209
|
+
function sinkWithSecrets(s, run = defaultRunner) {
|
|
3210
|
+
if (!s.token_in) return s;
|
|
3211
|
+
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
3212
|
+
if (!token) return null;
|
|
3213
|
+
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
3214
|
+
return { ...s, token, ...secret ? { secret } : {} };
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
// src/control/store.ts
|
|
3218
|
+
function startOptionsOf(v) {
|
|
3219
|
+
const o = v;
|
|
3220
|
+
if (!o || typeof o !== "object" || levelRank(o.permission) < 0 && o.permission !== "inherit") return null;
|
|
3221
|
+
return {
|
|
3222
|
+
permission: o.permission,
|
|
3223
|
+
...typeof o.model === "string" && MODEL_RE.test(o.model) ? { model: o.model } : {}
|
|
3224
|
+
};
|
|
3225
|
+
}
|
|
3226
|
+
|
|
3227
|
+
// src/control/aliases.ts
|
|
3228
|
+
var aliasFile = (controlDir) => path12.join(controlDir, "aliases.json");
|
|
2587
3229
|
function readAliases(file) {
|
|
2588
3230
|
try {
|
|
2589
3231
|
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
@@ -2592,7 +3234,7 @@ function readAliases(file) {
|
|
|
2592
3234
|
for (const [k, v] of Object.entries(j)) {
|
|
2593
3235
|
const a = v;
|
|
2594
3236
|
if (a && typeof a.id === "string" && a.id && typeof a.cwd === "string")
|
|
2595
|
-
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "" };
|
|
3237
|
+
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "", ...startOptionsOf(a) };
|
|
2596
3238
|
}
|
|
2597
3239
|
return out;
|
|
2598
3240
|
} catch {
|
|
@@ -2608,11 +3250,11 @@ function receiverIdOf(all, harness, id) {
|
|
|
2608
3250
|
// src/control/local.ts
|
|
2609
3251
|
import net from "node:net";
|
|
2610
3252
|
import os2 from "node:os";
|
|
2611
|
-
import
|
|
3253
|
+
import path13 from "node:path";
|
|
2612
3254
|
function socketPath(state, env = process.env) {
|
|
2613
3255
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
2614
3256
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
2615
|
-
return
|
|
3257
|
+
return path13.join(state, "control.sock");
|
|
2616
3258
|
}
|
|
2617
3259
|
function ask(sock, req, timeoutMs) {
|
|
2618
3260
|
return new Promise((resolve) => {
|
|
@@ -2644,41 +3286,10 @@ function ask(sock, req, timeoutMs) {
|
|
|
2644
3286
|
});
|
|
2645
3287
|
}
|
|
2646
3288
|
|
|
2647
|
-
// src/secrets.ts
|
|
2648
|
-
import { spawnSync } from "node:child_process";
|
|
2649
|
-
import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
|
|
2650
|
-
var SERVICE = "sessionpipe";
|
|
2651
|
-
var defaultRunner = (cmd, args, input) => {
|
|
2652
|
-
const r = spawnSync(cmd, args, {
|
|
2653
|
-
input: input ?? "",
|
|
2654
|
-
encoding: "utf8",
|
|
2655
|
-
timeout: 8e3,
|
|
2656
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
2657
|
-
});
|
|
2658
|
-
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
2659
|
-
};
|
|
2660
|
-
var sinkAccount = (name, what = "token") => {
|
|
2661
|
-
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash4("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
2662
|
-
return `sink:${name}:${what}${scope}`;
|
|
2663
|
-
};
|
|
2664
|
-
function secretGet(store, account, run = defaultRunner) {
|
|
2665
|
-
const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
|
|
2666
|
-
if (r.status !== 0) return null;
|
|
2667
|
-
const v = r.stdout.replace(/\r?\n$/, "");
|
|
2668
|
-
return v || null;
|
|
2669
|
-
}
|
|
2670
|
-
function sinkWithSecrets(s, run = defaultRunner) {
|
|
2671
|
-
if (!s.token_in) return s;
|
|
2672
|
-
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
2673
|
-
if (!token) return null;
|
|
2674
|
-
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
2675
|
-
return { ...s, token, ...secret ? { secret } : {} };
|
|
2676
|
-
}
|
|
2677
|
-
|
|
2678
3289
|
// src/run.ts
|
|
2679
3290
|
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
2680
3291
|
const sock = socketPath(state);
|
|
2681
|
-
if (process.platform !== "win32" && !
|
|
3292
|
+
if (process.platform !== "win32" && !existsSync10(sock)) return;
|
|
2682
3293
|
await ask(
|
|
2683
3294
|
sock,
|
|
2684
3295
|
{
|
|
@@ -2694,14 +3305,21 @@ async function tellDaemon(state, job, session, cwd, transcript) {
|
|
|
2694
3305
|
function enqueueJob(state, job) {
|
|
2695
3306
|
const jobs = jobsDir(state);
|
|
2696
3307
|
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
2697
|
-
const f =
|
|
3308
|
+
const f = path14.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
2698
3309
|
writeFileSync7(f, JSON.stringify(job), { mode: 384 });
|
|
2699
3310
|
return f;
|
|
2700
3311
|
}
|
|
3312
|
+
function releaseJob(claimed) {
|
|
3313
|
+
if (!claimed) return;
|
|
3314
|
+
try {
|
|
3315
|
+
unlinkSync4(claimed);
|
|
3316
|
+
} catch {
|
|
3317
|
+
}
|
|
3318
|
+
}
|
|
2701
3319
|
function log(state, line) {
|
|
2702
3320
|
try {
|
|
2703
3321
|
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
2704
|
-
const f =
|
|
3322
|
+
const f = path14.join(state, "log");
|
|
2705
3323
|
try {
|
|
2706
3324
|
if (statSync5(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
2707
3325
|
} catch {
|
|
@@ -2712,7 +3330,7 @@ function log(state, line) {
|
|
|
2712
3330
|
}
|
|
2713
3331
|
}
|
|
2714
3332
|
function factsState(state) {
|
|
2715
|
-
const file = (h, s) =>
|
|
3333
|
+
const file = (h, s) => path14.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
2716
3334
|
return {
|
|
2717
3335
|
get(h, s) {
|
|
2718
3336
|
try {
|
|
@@ -2725,7 +3343,7 @@ function factsState(state) {
|
|
|
2725
3343
|
const cur = this.get(h, s);
|
|
2726
3344
|
const next = { ...cur, ...patch };
|
|
2727
3345
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
2728
|
-
mkdirSync8(
|
|
3346
|
+
mkdirSync8(path14.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
2729
3347
|
writeFileSync7(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
2730
3348
|
}
|
|
2731
3349
|
};
|
|
@@ -2747,39 +3365,92 @@ function buildSinks(sinks, o = {}) {
|
|
|
2747
3365
|
}
|
|
2748
3366
|
return out;
|
|
2749
3367
|
}
|
|
3368
|
+
var PROGRESS_MS = 2e4;
|
|
3369
|
+
function openTurn(read, o) {
|
|
3370
|
+
const last = read.turns[read.turns.length - 1];
|
|
3371
|
+
const fresh = (user, at) => ({
|
|
3372
|
+
turn: o.base + read.turns.length,
|
|
3373
|
+
user: user.slice(0, MAX_TURN_SIDE),
|
|
3374
|
+
assistant: "",
|
|
3375
|
+
at
|
|
3376
|
+
});
|
|
3377
|
+
if (read.open) return fresh(read.open.user, read.open.at);
|
|
3378
|
+
const prompt = o.prompt?.trim();
|
|
3379
|
+
if (prompt && !isInjected(prompt) && !(last && last.user.includes(prompt.slice(0, 200)))) return fresh(prompt, o.at);
|
|
3380
|
+
if (!last) return null;
|
|
3381
|
+
return {
|
|
3382
|
+
turn: o.base + read.turns.length - 1,
|
|
3383
|
+
user: last.user.slice(0, MAX_TURN_SIDE),
|
|
3384
|
+
assistant: capTail(last.assistant, MAX_TURN_SIDE),
|
|
3385
|
+
at: last.at
|
|
3386
|
+
};
|
|
3387
|
+
}
|
|
3388
|
+
var MAX_TURN_SIDE = 2e4;
|
|
3389
|
+
var JOB_OWN_ENV = [
|
|
3390
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
3391
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
3392
|
+
"SESSIONPIPE_CONTROL_JOB",
|
|
3393
|
+
"SESSIONPIPE_LAUNCHED_BY"
|
|
3394
|
+
];
|
|
2750
3395
|
async function runJob(job, opts = {}) {
|
|
2751
3396
|
const state = opts.state ?? stateDir();
|
|
2752
3397
|
const cfg = readConfig();
|
|
2753
3398
|
const adapter = adapterByName(job.harness);
|
|
2754
|
-
if (!adapter)
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
3399
|
+
if (!adapter) {
|
|
3400
|
+
log(state, `${job.harness} ${job.event}: no adapter for harness "${job.harness}"; event dropped`);
|
|
3401
|
+
return { events: [], delivered: {} };
|
|
3402
|
+
}
|
|
3403
|
+
const env = { ...process.env };
|
|
3404
|
+
for (const k of JOB_OWN_ENV) delete env[k];
|
|
3405
|
+
const r = readHook(adapter, { argv: job.argv, stdin: job.stdin, env: { ...env, ...job.env }, cwd: job.cwd });
|
|
3406
|
+
if (!r || !r.session.id) {
|
|
3407
|
+
let keys = [];
|
|
3408
|
+
try {
|
|
3409
|
+
const payload = JSON.parse(job.argv.find((a) => a.startsWith("{")) ?? job.stdin);
|
|
3410
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) keys = Object.keys(payload);
|
|
3411
|
+
} catch {
|
|
3412
|
+
}
|
|
3413
|
+
log(
|
|
3414
|
+
state,
|
|
3415
|
+
`${job.harness} ${job.event}: the ${job.harness} adapter did not claim this payload; event dropped; keys: ${keys.join(", ") || "(none)"}`
|
|
3416
|
+
);
|
|
3417
|
+
return { events: [], delivered: {} };
|
|
3418
|
+
}
|
|
3419
|
+
const id = receiverIdOf(readAliases(aliasFile(path14.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3420
|
+
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3421
|
+
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
2759
3422
|
const outbox = new Outbox(state);
|
|
2760
3423
|
const ref = { harness: job.harness, session: id };
|
|
2761
3424
|
let release = takeLock(state, ref);
|
|
2762
|
-
|
|
2763
|
-
|
|
3425
|
+
const lockWait = opts.lockWaitMs ?? LOCK_WAIT_MS;
|
|
3426
|
+
for (const until = Date.now() + lockWait; !release && Date.now() < until; ) {
|
|
3427
|
+
await new Promise((res) => setTimeout(res, 50));
|
|
2764
3428
|
release = takeLock(state, ref);
|
|
2765
3429
|
}
|
|
2766
3430
|
if (!release) {
|
|
2767
|
-
log(state, `${job.harness} ${job.event}: session ${id} locked for
|
|
2768
|
-
enqueueJob(state, job);
|
|
2769
|
-
|
|
3431
|
+
log(state, `${job.harness} ${job.event}: session ${id} locked for ${lockWait / 1e3} s, re-queued`);
|
|
3432
|
+
const requeued = enqueueJob(state, { ...job, tries: (job.tries ?? 0) + 1 });
|
|
3433
|
+
releaseJob(opts.claimed);
|
|
3434
|
+
return { events: [], delivered: {}, requeued };
|
|
2770
3435
|
}
|
|
3436
|
+
const events = [];
|
|
2771
3437
|
try {
|
|
2772
3438
|
const facts = factsState(state);
|
|
2773
3439
|
const mem = facts.get(job.harness, id);
|
|
3440
|
+
if (claudeEnd && !existsSync10(outbox.file(ref)) && Object.keys(mem).length === 0 && !(r.transcript && existsSync10(r.transcript))) {
|
|
3441
|
+
releaseJob(opts.claimed);
|
|
3442
|
+
return { events: [], delivered: {} };
|
|
3443
|
+
}
|
|
3444
|
+
if (claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
2774
3445
|
const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
|
|
2775
3446
|
let learned = {};
|
|
2776
3447
|
if (heavy) {
|
|
2777
|
-
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts);
|
|
3448
|
+
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts, { ...process.env, ...job.env });
|
|
2778
3449
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
2779
3450
|
learned = rest;
|
|
2780
3451
|
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
2781
3452
|
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
2782
|
-
if (job.harness
|
|
3453
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path14.isAbsolute(r.session.cwd))
|
|
2783
3454
|
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
2784
3455
|
}
|
|
2785
3456
|
const known = mem.session ?? {};
|
|
@@ -2794,10 +3465,10 @@ async function runJob(job, opts = {}) {
|
|
|
2794
3465
|
seq: 0,
|
|
2795
3466
|
machine: machineName(cfg, os3.hostname()),
|
|
2796
3467
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
2797
|
-
...r.session.model ? { model: r.session.model } : {}
|
|
3468
|
+
...r.session.model ? { model: r.session.model } : {},
|
|
3469
|
+
...r.session.launched_by ? { launched_by: r.session.launched_by } : {}
|
|
2798
3470
|
};
|
|
2799
3471
|
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
2800
|
-
const events = [];
|
|
2801
3472
|
for (const a of r.events) {
|
|
2802
3473
|
const e = makeEvent(a, {
|
|
2803
3474
|
harness: { name: job.harness, ...version ? { version } : {} },
|
|
@@ -2807,6 +3478,37 @@ async function runJob(job, opts = {}) {
|
|
|
2807
3478
|
events.push(e);
|
|
2808
3479
|
}
|
|
2809
3480
|
const wantsTranscript = cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 2);
|
|
3481
|
+
const ending = r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended");
|
|
3482
|
+
const starting = r.events.some((e) => e.type === "turn.started");
|
|
3483
|
+
const startedId = r.events.find((e) => e.type === "turn.started")?.data.turn_id;
|
|
3484
|
+
const absorbed = starting && typeof startedId === "string" && facts.get(job.harness, id).open_turn === startedId;
|
|
3485
|
+
if (wantsTranscript && r.transcript && !ending && (starting || r.events.some((e) => e.type === "tool.ended"))) {
|
|
3486
|
+
const cur = facts.get(job.harness, id);
|
|
3487
|
+
const at = opts.now ?? job.at;
|
|
3488
|
+
if (starting || at - (Number(cur.progress_at) || 0) >= PROGRESS_MS) {
|
|
3489
|
+
const p = openTurn(adapter.readTranscript(r.transcript, Number(cur.transcript_line) || 0), {
|
|
3490
|
+
base: Number(cur.transcript_turns) || 0,
|
|
3491
|
+
prompt: !absorbed && typeof r.hints?.prompt === "string" ? r.hints.prompt : void 0,
|
|
3492
|
+
at
|
|
3493
|
+
});
|
|
3494
|
+
const sig = p && `${p.turn}:${p.user.length}:${p.assistant.length}`;
|
|
3495
|
+
if (p && (starting || sig !== cur.progress_sig)) {
|
|
3496
|
+
events.push(
|
|
3497
|
+
makeEvent(
|
|
3498
|
+
{ type: "turn.progress", harnessEvent: job.event, data: { ...p, at: new Date(p.at).toISOString() } },
|
|
3499
|
+
{
|
|
3500
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
3501
|
+
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
3502
|
+
now: at
|
|
3503
|
+
}
|
|
3504
|
+
)
|
|
3505
|
+
);
|
|
3506
|
+
facts.save(job.harness, id, { progress_at: at, progress_sig: sig });
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
}
|
|
3510
|
+
if (starting && typeof startedId === "string" && !absorbed) facts.save(job.harness, id, { open_turn: startedId });
|
|
3511
|
+
if (ending) facts.save(job.harness, id, { open_turn: null });
|
|
2810
3512
|
if (wantsTranscript && r.transcript && r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended")) {
|
|
2811
3513
|
const cur = facts.get(job.harness, id);
|
|
2812
3514
|
const fromLine = Number(cur.transcript_line) || 0;
|
|
@@ -2834,17 +3536,80 @@ async function runJob(job, opts = {}) {
|
|
|
2834
3536
|
ref,
|
|
2835
3537
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
2836
3538
|
);
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
3539
|
+
releaseJob(opts.claimed);
|
|
3540
|
+
log(
|
|
3541
|
+
state,
|
|
3542
|
+
`${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${shortId(job.harness, id)})`
|
|
3543
|
+
);
|
|
2841
3544
|
} finally {
|
|
2842
3545
|
release();
|
|
2843
3546
|
}
|
|
3547
|
+
const delivered = await drain(state, cfg.sinks, outbox, ref) ?? {};
|
|
3548
|
+
if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
|
|
3549
|
+
return { events, delivered };
|
|
2844
3550
|
}
|
|
3551
|
+
async function drain(state, sinkConfigs, outbox, first) {
|
|
3552
|
+
const more = path14.join(state, "locks", "flush.more");
|
|
3553
|
+
let release = takeNamedLock(state, "flush");
|
|
3554
|
+
if (!release) {
|
|
3555
|
+
try {
|
|
3556
|
+
writeFileSync7(more, "", { mode: 384 });
|
|
3557
|
+
} catch {
|
|
3558
|
+
}
|
|
3559
|
+
release = takeNamedLock(state, "flush");
|
|
3560
|
+
if (!release) return null;
|
|
3561
|
+
}
|
|
3562
|
+
const delivered = {};
|
|
3563
|
+
for (; ; ) {
|
|
3564
|
+
try {
|
|
3565
|
+
unlinkSync4(more);
|
|
3566
|
+
} catch {
|
|
3567
|
+
}
|
|
3568
|
+
try {
|
|
3569
|
+
for (const [k, v] of Object.entries(await flush(state, sinkConfigs, outbox, first)))
|
|
3570
|
+
delivered[k] = (delivered[k] ?? 0) + v;
|
|
3571
|
+
} finally {
|
|
3572
|
+
release();
|
|
3573
|
+
}
|
|
3574
|
+
if (!existsSync10(more)) return delivered;
|
|
3575
|
+
release = takeNamedLock(state, "flush");
|
|
3576
|
+
if (!release) return delivered;
|
|
3577
|
+
}
|
|
3578
|
+
}
|
|
3579
|
+
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3580
|
+
var backoffFile = (state, sink) => path14.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3581
|
+
function readBackoff(state, sink) {
|
|
3582
|
+
try {
|
|
3583
|
+
const b = JSON.parse(readFileSync9(backoffFile(state, sink), "utf8"));
|
|
3584
|
+
return typeof b.until === "number" ? b : null;
|
|
3585
|
+
} catch {
|
|
3586
|
+
return null;
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
function noteFailure(state, sink, res, prev) {
|
|
3590
|
+
const failures = (prev?.failures ?? 0) + 1;
|
|
3591
|
+
const ladder = BACKOFF_MS[Math.min(failures, BACKOFF_MS.length) - 1];
|
|
3592
|
+
const wait = Number.isFinite(res.retryAfterMs) ? res.retryAfterMs : ladder;
|
|
3593
|
+
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3594
|
+
const f = backoffFile(state, sink);
|
|
3595
|
+
try {
|
|
3596
|
+
mkdirSync8(path14.dirname(f), { recursive: true, mode: 448 });
|
|
3597
|
+
writeFileSync7(f, JSON.stringify(b), { mode: 384 });
|
|
3598
|
+
} catch {
|
|
3599
|
+
}
|
|
3600
|
+
return b;
|
|
3601
|
+
}
|
|
3602
|
+
function clearBackoff(state, sink) {
|
|
3603
|
+
try {
|
|
3604
|
+
unlinkSync4(backoffFile(state, sink));
|
|
3605
|
+
} catch {
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
var LOCK_WAIT_MS = 6e3;
|
|
3609
|
+
var CWD_FROM_HOOKS = /* @__PURE__ */ new Set(["antigravity", "devin"]);
|
|
2845
3610
|
var BATCH = 50;
|
|
2846
3611
|
var MAX_PER_RUN = 500;
|
|
2847
|
-
async function flush(state, sinkConfigs, outbox, first) {
|
|
3612
|
+
async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
2848
3613
|
const delivered = {};
|
|
2849
3614
|
const sinks = buildSinks(sinkConfigs, {
|
|
2850
3615
|
locked: (name) => log(state, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
@@ -2854,13 +3619,39 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2854
3619
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
2855
3620
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
2856
3621
|
}
|
|
2857
|
-
|
|
3622
|
+
const drainSink = async (sink) => {
|
|
3623
|
+
let backoff = readBackoff(state, sink.name);
|
|
3624
|
+
if (backoff && !o.force && backoff.until > Date.now()) {
|
|
3625
|
+
delivered[sink.name] = 0;
|
|
3626
|
+
return;
|
|
3627
|
+
}
|
|
2858
3628
|
const cursors = new Cursors(state, sink.name);
|
|
2859
3629
|
const c = cursors.read();
|
|
2860
3630
|
let sent = 0;
|
|
2861
3631
|
let stalled = false;
|
|
2862
3632
|
let batch = [];
|
|
2863
3633
|
let pending = {};
|
|
3634
|
+
const deliver = async (events) => {
|
|
3635
|
+
const res = await sink.send(events);
|
|
3636
|
+
if (res.ok || res.action !== "split") return res;
|
|
3637
|
+
if (events.length === 1) {
|
|
3638
|
+
const e = events[0];
|
|
3639
|
+
log(state, `sink ${sink.name}: 413 ${res.error ?? ""} \u2192 event ${e.id} (${e.type}) is too large alone, dropped`);
|
|
3640
|
+
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected: [{ id: e.id, reason: "too_large" }] };
|
|
3641
|
+
}
|
|
3642
|
+
const half = Math.ceil(events.length / 2);
|
|
3643
|
+
const a = await deliver(events.slice(0, half));
|
|
3644
|
+
if (!a.ok) return a;
|
|
3645
|
+
const b = await deliver(events.slice(half));
|
|
3646
|
+
if (!b.ok) return b;
|
|
3647
|
+
return {
|
|
3648
|
+
ok: true,
|
|
3649
|
+
acknowledged: a.acknowledged + b.acknowledged,
|
|
3650
|
+
accepted: (a.accepted ?? 0) + (b.accepted ?? 0),
|
|
3651
|
+
duplicates: (a.duplicates ?? 0) + (b.duplicates ?? 0),
|
|
3652
|
+
rejected: [...a.rejected ?? [], ...b.rejected ?? []]
|
|
3653
|
+
};
|
|
3654
|
+
};
|
|
2864
3655
|
const send = async () => {
|
|
2865
3656
|
if (!batch.length) {
|
|
2866
3657
|
Object.assign(c, pending);
|
|
@@ -2868,15 +3659,24 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2868
3659
|
cursors.write(c);
|
|
2869
3660
|
return true;
|
|
2870
3661
|
}
|
|
2871
|
-
const res = await
|
|
3662
|
+
const res = await deliver(batch);
|
|
2872
3663
|
if (!res.ok) {
|
|
2873
|
-
|
|
3664
|
+
const action = res.action ?? "retry";
|
|
3665
|
+
let wait = "";
|
|
3666
|
+
if (action === "retry") {
|
|
3667
|
+
backoff = noteFailure(state, sink.name, res, backoff);
|
|
3668
|
+
wait = `, waits ${Math.round((backoff.until - Date.now()) / 1e3)} s`;
|
|
3669
|
+
}
|
|
3670
|
+
log(state, `sink ${sink.name}: ${res.status ?? ""} ${res.error ?? ""} \u2192 ${action}${wait}`);
|
|
2874
3671
|
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2875
3672
|
if (res.action !== "drop") {
|
|
2876
3673
|
batch = [];
|
|
2877
3674
|
pending = {};
|
|
2878
3675
|
return false;
|
|
2879
3676
|
}
|
|
3677
|
+
} else if (backoff) {
|
|
3678
|
+
clearBackoff(state, sink.name);
|
|
3679
|
+
backoff = null;
|
|
2880
3680
|
}
|
|
2881
3681
|
sent += batch.length;
|
|
2882
3682
|
Object.assign(c, pending);
|
|
@@ -2911,7 +3711,8 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2911
3711
|
if (!await send()) stalled = true;
|
|
2912
3712
|
}
|
|
2913
3713
|
delivered[sink.name] = sent;
|
|
2914
|
-
}
|
|
3714
|
+
};
|
|
3715
|
+
await Promise.all(sinks.map(drainSink));
|
|
2915
3716
|
return delivered;
|
|
2916
3717
|
}
|
|
2917
3718
|
function applySinkVerdict(name, res) {
|
|
@@ -2925,7 +3726,7 @@ function applySinkVerdict(name, res) {
|
|
|
2925
3726
|
} catch {
|
|
2926
3727
|
}
|
|
2927
3728
|
}
|
|
2928
|
-
var jobsDir = (state) =>
|
|
3729
|
+
var jobsDir = (state) => path14.join(state, "jobs");
|
|
2929
3730
|
export {
|
|
2930
3731
|
HttpSink,
|
|
2931
3732
|
buildSinks,
|