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/worker.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// sessionpipe 0.
|
|
2
|
+
// sessionpipe 0.10.0 · Apache-2.0 · https://sessionpipe.org
|
|
3
3
|
|
|
4
4
|
// src/worker.ts
|
|
5
|
-
import {
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { realpathSync as realpathSync2 } from "node:fs";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
6
8
|
|
|
7
9
|
// ../core/src/adapters/antigravity.ts
|
|
8
10
|
import {
|
|
@@ -20,20 +22,20 @@ import path4 from "node:path";
|
|
|
20
22
|
// ../core/src/paths.ts
|
|
21
23
|
import os from "node:os";
|
|
22
24
|
import path from "node:path";
|
|
23
|
-
var
|
|
25
|
+
var homeDir = () => os.homedir();
|
|
24
26
|
function configFile(env = process.env) {
|
|
25
27
|
if (env.SESSIONPIPE_CONFIG) return path.resolve(env.SESSIONPIPE_CONFIG);
|
|
26
28
|
if (process.platform === "win32")
|
|
27
|
-
return path.join(env.APPDATA || path.join(
|
|
28
|
-
return path.join(env.XDG_CONFIG_HOME || path.join(
|
|
29
|
+
return path.join(env.APPDATA || path.join(homeDir(), "AppData", "Roaming"), "sessionpipe", "config.json");
|
|
30
|
+
return path.join(env.XDG_CONFIG_HOME || path.join(homeDir(), ".config"), "sessionpipe", "config.json");
|
|
29
31
|
}
|
|
30
32
|
function stateDir(env = process.env) {
|
|
31
33
|
if (env.SESSIONPIPE_STATE) return path.resolve(env.SESSIONPIPE_STATE);
|
|
32
34
|
if (process.platform === "win32")
|
|
33
|
-
return path.join(env.LOCALAPPDATA || path.join(
|
|
34
|
-
return path.join(env.XDG_STATE_HOME || path.join(
|
|
35
|
+
return path.join(env.LOCALAPPDATA || path.join(homeDir(), "AppData", "Local"), "sessionpipe");
|
|
36
|
+
return path.join(env.XDG_STATE_HOME || path.join(homeDir(), ".local", "state"), "sessionpipe");
|
|
35
37
|
}
|
|
36
|
-
function tilde(p, home =
|
|
38
|
+
function tilde(p, home = homeDir()) {
|
|
37
39
|
if (!p) return p;
|
|
38
40
|
if (p === home) return "~";
|
|
39
41
|
return p.startsWith(home + path.sep) || p.startsWith(`${home}/`) ? `~${p.slice(home.length)}` : p;
|
|
@@ -130,6 +132,19 @@ function recentFiles(root, depth, sinceMs, match) {
|
|
|
130
132
|
return out;
|
|
131
133
|
}
|
|
132
134
|
|
|
135
|
+
// ../core/src/adapters/codex-rollout.ts
|
|
136
|
+
function codexSubagent(meta) {
|
|
137
|
+
if (meta?.type !== "session_meta") return false;
|
|
138
|
+
const p = meta.payload;
|
|
139
|
+
return !!p?.source?.subagent;
|
|
140
|
+
}
|
|
141
|
+
function codexReplayBoundary(meta) {
|
|
142
|
+
if (meta?.type !== "session_meta") return void 0;
|
|
143
|
+
const p = meta.payload;
|
|
144
|
+
if (!p || !p.forked_from_id && !p.source?.subagent) return void 0;
|
|
145
|
+
return (record) => typeof p.id === "string" && !!p.id && record?.type === "event_msg" && record.payload?.thread_id === p.id;
|
|
146
|
+
}
|
|
147
|
+
|
|
133
148
|
// ../core/src/readers/transcripts.ts
|
|
134
149
|
function pairTurns(msgs, endLine) {
|
|
135
150
|
const turns = [];
|
|
@@ -152,10 +167,14 @@ ${m.text}`;
|
|
|
152
167
|
}
|
|
153
168
|
if (cur?.assistant) turns.push(cur);
|
|
154
169
|
const line = cur && !cur.assistant ? cur.startLine : endLine;
|
|
155
|
-
|
|
170
|
+
const out = {
|
|
171
|
+
turns: turns.map((t) => ({ user: t.user, assistant: t.assistant, at: t.at, endLine: t.endLine })),
|
|
172
|
+
line
|
|
173
|
+
};
|
|
174
|
+
if (cur && !cur.assistant && cur.user) out.open = { user: cur.user, at: cur.at };
|
|
175
|
+
return out;
|
|
156
176
|
}
|
|
157
|
-
var isInjected = (text) => /^<[a-z_-]
|
|
158
|
-
var endOf = (lines) => lines[lines.length - 1] === "" ? lines.length - 1 : lines.length;
|
|
177
|
+
var isInjected = (text) => /^<[a-z_-]+(?:\s[^>]*)?>/i.test(text) && /<\/[a-z_-]+>\s*$/i.test(text) || /^<!--\s/.test(text) || /^<command-/.test(text);
|
|
159
178
|
var parse = (raw) => {
|
|
160
179
|
try {
|
|
161
180
|
return JSON.parse(raw);
|
|
@@ -163,6 +182,11 @@ var parse = (raw) => {
|
|
|
163
182
|
return null;
|
|
164
183
|
}
|
|
165
184
|
};
|
|
185
|
+
var endOf = (lines) => {
|
|
186
|
+
const last = lines[lines.length - 1];
|
|
187
|
+
if (last === void 0) return 0;
|
|
188
|
+
return last === "" || parse(last) === null ? lines.length - 1 : lines.length;
|
|
189
|
+
};
|
|
166
190
|
var textParts = (c, kinds) => {
|
|
167
191
|
if (typeof c === "string") return c;
|
|
168
192
|
if (!Array.isArray(c)) return "";
|
|
@@ -170,13 +194,21 @@ var textParts = (c, kinds) => {
|
|
|
170
194
|
(b) => b && typeof b === "object" && kinds.includes(b.type ?? "") && typeof b.text === "string"
|
|
171
195
|
).map((b) => b.text).join("\n");
|
|
172
196
|
};
|
|
197
|
+
var queuedPrompt = (a) => {
|
|
198
|
+
if (!a || typeof a !== "object") return "";
|
|
199
|
+
const q = a;
|
|
200
|
+
if (q.type !== "queued_command" || (q.commandMode ?? "prompt") !== "prompt") return "";
|
|
201
|
+
if (q.origin?.kind && q.origin.kind !== "human") return "";
|
|
202
|
+
return textParts(q.prompt, ["text"]).trim();
|
|
203
|
+
};
|
|
173
204
|
function readClaude(lines, from) {
|
|
174
205
|
const msgs = [];
|
|
175
206
|
for (let i = from; i < lines.length; i++) {
|
|
176
207
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
177
208
|
if (!j || j.isSidechain) continue;
|
|
178
209
|
const origin = j.origin;
|
|
179
|
-
if (j.type === "user" && (j.isMeta || origin?.kind && origin.kind !== "human"))
|
|
210
|
+
if (j.type === "user" && (j.isMeta || j.isCompactSummary || j.repeatsCompactSummary || origin?.kind && origin.kind !== "human"))
|
|
211
|
+
continue;
|
|
180
212
|
const at = Date.parse(String(j.timestamp ?? "")) || Date.now();
|
|
181
213
|
const c = j.message?.content;
|
|
182
214
|
if (j.type === "user") {
|
|
@@ -186,6 +218,9 @@ function readClaude(lines, from) {
|
|
|
186
218
|
} else if (j.type === "assistant" && Array.isArray(c)) {
|
|
187
219
|
const text = textParts(c, ["text"]).trim();
|
|
188
220
|
if (text) msgs.push({ role: "assistant", text, at, line: i });
|
|
221
|
+
} else if (j.type === "attachment") {
|
|
222
|
+
const text = queuedPrompt(j.attachment);
|
|
223
|
+
if (text && !isInjected(text)) msgs.push({ role: "user", text, at, line: i });
|
|
189
224
|
}
|
|
190
225
|
}
|
|
191
226
|
return pairTurns(msgs, endOf(lines));
|
|
@@ -195,7 +230,10 @@ function codexInjected(text) {
|
|
|
195
230
|
}
|
|
196
231
|
function readCodex(lines, from) {
|
|
197
232
|
const msgs = [];
|
|
198
|
-
|
|
233
|
+
const boundary = codexReplayBoundary(parse(lines.find((line) => line.trim()) ?? ""));
|
|
234
|
+
const start = boundary ? lines.findIndex((line) => boundary(parse(line))) : 0;
|
|
235
|
+
if (start < 0) return pairTurns(msgs, endOf(lines));
|
|
236
|
+
for (let i = Math.max(from, start); i < lines.length; i++) {
|
|
199
237
|
const j = lines[i] ? parse(lines[i]) : null;
|
|
200
238
|
if (!j || j.type !== "response_item") continue;
|
|
201
239
|
const p = j.payload;
|
|
@@ -233,7 +271,7 @@ function readAntigravity(lines, from) {
|
|
|
233
271
|
const at = Date.parse(String(j.created_at ?? j.timestamp ?? "")) || Date.now();
|
|
234
272
|
if (j.type === "USER_INPUT") {
|
|
235
273
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
236
|
-
const text = (m ? m[1]
|
|
274
|
+
const text = (m ? m[1] : j.content).trim();
|
|
237
275
|
if (text) msgs.push({ role: "user", text, at, line: i });
|
|
238
276
|
} else if (j.type === "PLANNER_RESPONSE") {
|
|
239
277
|
const text = j.content.trim();
|
|
@@ -281,6 +319,10 @@ function formatJson(value, f) {
|
|
|
281
319
|
const text = JSON.stringify(value, null, f.indent).replace(/\n/g, f.eol);
|
|
282
320
|
return f.finalNewline ? text + f.eol : text;
|
|
283
321
|
}
|
|
322
|
+
function readJson(file) {
|
|
323
|
+
const f = readJsonFile(file);
|
|
324
|
+
return f.ok ? f.value : {};
|
|
325
|
+
}
|
|
284
326
|
function entryFor(event, harness, cmd, o) {
|
|
285
327
|
const matcher = o.matcherFor(event);
|
|
286
328
|
return {
|
|
@@ -350,8 +392,7 @@ function uninstallClaudeShaped(file, events, opts = {}) {
|
|
|
350
392
|
unlinkSync(file);
|
|
351
393
|
return { file, changed: true, note: "removed (sessionpipe created it)" };
|
|
352
394
|
}
|
|
353
|
-
|
|
354
|
-
if (next !== f.raw) writeFileSync(file, next);
|
|
395
|
+
writeFileSync(file, formatJson(settings2, f));
|
|
355
396
|
return { file, changed: true };
|
|
356
397
|
}
|
|
357
398
|
function installedClaudeShaped(file, harness, events, cmd, o) {
|
|
@@ -378,7 +419,7 @@ var HOOK_NAME = "sessionpipe";
|
|
|
378
419
|
var AG_EVENTS = ["PreInvocation", "PostInvocation", "PostToolUse", "Stop"];
|
|
379
420
|
var AG_STALE_PRETOOL = '{"decision":"ask","reason":"sessionpipe: an outdated PreToolUse hook is still loaded. Restart this conversation (or run sessionpipe install) to drop it."}\n';
|
|
380
421
|
var antigravityAnswer = (event) => event === "PreToolUse" ? AG_STALE_PRETOOL : "{}\n";
|
|
381
|
-
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(
|
|
422
|
+
var gemini = (env) => env.GEMINI_CLI_HOME || path4.join(homeDir(), ".gemini");
|
|
382
423
|
var hooksFile = (env) => path4.join(gemini(env), "config", "hooks.json");
|
|
383
424
|
var projectsDir = (env) => path4.join(gemini(env), "config", "projects");
|
|
384
425
|
var dataDirs = (env) => ["antigravity", "antigravity-cli", "antigravity-ide"].map((d) => path4.join(gemini(env), d));
|
|
@@ -444,7 +485,8 @@ var antigravity = {
|
|
|
444
485
|
const event = input.argv[1] ?? "";
|
|
445
486
|
let s = {};
|
|
446
487
|
try {
|
|
447
|
-
|
|
488
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
489
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
448
490
|
} catch {
|
|
449
491
|
}
|
|
450
492
|
const id = s.conversationId ?? s.session_id ?? s.sessionId;
|
|
@@ -500,19 +542,16 @@ var antigravity = {
|
|
|
500
542
|
}
|
|
501
543
|
return readAntigravity(text.split("\n"), fromLine);
|
|
502
544
|
},
|
|
503
|
-
facts(session, transcript, hints
|
|
545
|
+
facts(session, transcript, hints) {
|
|
504
546
|
if (!transcript) return {};
|
|
505
547
|
return antigravityFacts(transcript, {
|
|
506
548
|
conversation: session.id,
|
|
507
549
|
workspaces: hints.workspaces ?? (session.cwd ? [session.cwd] : []),
|
|
508
|
-
state,
|
|
509
550
|
env: process.env
|
|
510
551
|
});
|
|
511
552
|
},
|
|
512
553
|
backfill(sinceMs, env = process.env) {
|
|
513
|
-
const
|
|
514
|
-
const noState = { get: () => ({}), save: () => {
|
|
515
|
-
} };
|
|
554
|
+
const rows2 = [];
|
|
516
555
|
for (const data of dataDirs(env)) {
|
|
517
556
|
let ids = [];
|
|
518
557
|
try {
|
|
@@ -526,13 +565,13 @@ var antigravity = {
|
|
|
526
565
|
} catch {
|
|
527
566
|
continue;
|
|
528
567
|
}
|
|
529
|
-
const facts = antigravityFacts(f, { conversation: id, workspaces: [],
|
|
568
|
+
const facts = antigravityFacts(f, { conversation: id, workspaces: [], env });
|
|
530
569
|
if (!facts.started_at || !facts.last_at) continue;
|
|
531
570
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
532
|
-
|
|
571
|
+
rows2.push({ session: { id, ...rest }, started_at, last_at });
|
|
533
572
|
}
|
|
534
573
|
}
|
|
535
|
-
return
|
|
574
|
+
return rows2;
|
|
536
575
|
}
|
|
537
576
|
};
|
|
538
577
|
function prune(o) {
|
|
@@ -582,7 +621,7 @@ function agProjects(dir) {
|
|
|
582
621
|
}
|
|
583
622
|
const folders = [];
|
|
584
623
|
for (const r of j?.projectResources?.resources ?? []) {
|
|
585
|
-
const f = agPath(r?.folderUri);
|
|
624
|
+
const f = agPath(r?.folderUri ?? r?.gitFolder?.folderUri);
|
|
586
625
|
if (f) folders.push(...agVariants(f));
|
|
587
626
|
}
|
|
588
627
|
if (folders.length) out.push({ id, folders });
|
|
@@ -636,7 +675,7 @@ function antigravityFacts(file, opts) {
|
|
|
636
675
|
for (const j of H) {
|
|
637
676
|
if (j.type !== "USER_INPUT" || typeof j.content !== "string") continue;
|
|
638
677
|
const m = /<USER_REQUEST>([\s\S]*?)(?:<\/USER_REQUEST>|$)/.exec(j.content);
|
|
639
|
-
const text = (m ? m[1]
|
|
678
|
+
const text = (m ? m[1] : j.content).trim();
|
|
640
679
|
const line = (text.split("\n").find((l) => l.trim()) ?? "").trim();
|
|
641
680
|
if (!line || /^<[a-z_-]+>/i.test(line)) continue;
|
|
642
681
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
@@ -684,10 +723,9 @@ function gitFacts(cwd) {
|
|
|
684
723
|
return out;
|
|
685
724
|
}
|
|
686
725
|
function stripCredentials(url) {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
return u.replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
726
|
+
const ssh = /^(?:ssh:\/\/)?git@([^:/]+)[:/](.+?)(?:\.git)?$/.exec(url);
|
|
727
|
+
if (ssh) return `${ssh[1]}/${ssh[2]}`;
|
|
728
|
+
return url.replace(/\/\/[^/@]*@/, "//").replace(/^https?:\/\//, "").replace(/\.git$/, "");
|
|
691
729
|
}
|
|
692
730
|
|
|
693
731
|
// ../core/src/adapters/claude-code.ts
|
|
@@ -718,7 +756,7 @@ var OPTS = {
|
|
|
718
756
|
timeoutFor: (e) => e === "PermissionRequest" ? 130 : void 0
|
|
719
757
|
};
|
|
720
758
|
var DESKTOP_ID = /^local_[A-Za-z0-9-]{1,64}$/;
|
|
721
|
-
function claudeDirs(env = process.env, home = env.HOME ||
|
|
759
|
+
function claudeDirs(env = process.env, home = env.HOME || homeDir()) {
|
|
722
760
|
const out = [];
|
|
723
761
|
const add = (d) => {
|
|
724
762
|
if (!d) return;
|
|
@@ -759,11 +797,13 @@ var claudeCode = {
|
|
|
759
797
|
const event = input.argv[1] ?? "";
|
|
760
798
|
let s = {};
|
|
761
799
|
try {
|
|
762
|
-
|
|
800
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
801
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
763
802
|
} catch {
|
|
764
803
|
}
|
|
765
804
|
const id = s.session_id ?? s.sessionId;
|
|
766
805
|
if (!id) return null;
|
|
806
|
+
if (notClaudeCode(String(id), s)) return null;
|
|
767
807
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
768
808
|
if (typeof s.model === "string") session.model = s.model;
|
|
769
809
|
if (typeof s.agent_id === "string" && event.startsWith("Subagent") === false && s.agent_id)
|
|
@@ -780,14 +820,28 @@ var claudeCode = {
|
|
|
780
820
|
case "SessionStart":
|
|
781
821
|
events.push(ev("session.started", { source: pickSource(s.source ?? s.trigger) }));
|
|
782
822
|
break;
|
|
783
|
-
case "UserPromptSubmit":
|
|
823
|
+
case "UserPromptSubmit": {
|
|
824
|
+
const prompt = typeof s.prompt === "string" ? s.prompt : void 0;
|
|
784
825
|
events.push(
|
|
785
826
|
ev("turn.started", {
|
|
786
827
|
turn_id,
|
|
787
|
-
prompt_chars:
|
|
828
|
+
prompt_chars: prompt === void 0 ? void 0 : [...prompt].length,
|
|
829
|
+
// Claude Code's hook schema documents `source`
|
|
830
|
+
// (user | sdk | system | loop_wakeup | schedule_wakeup | poll_event) and says a
|
|
831
|
+
// payload may omit it while the field rolls out; 2.1.304's payload has no such
|
|
832
|
+
// key at all. Forwarded verbatim, shape-guarded only: the list is the harness's
|
|
833
|
+
// to extend.
|
|
834
|
+
submitted_by: harnessWord(s.source),
|
|
835
|
+
// One of Claude Code's own prompt wrappers: the prompt is a block the harness
|
|
836
|
+
// wrote, so it is not the person's words. Absent is unknown, never "the person" —
|
|
837
|
+
// and `true` is never "no person caused this turn", because a Talk or
|
|
838
|
+
// `sessionpipe wait` delivery hands the person's own message over inside a
|
|
839
|
+
// <task-notification> (PROTOCOL.md §4).
|
|
840
|
+
injected: prompt !== void 0 && injectedBlock(prompt) ? true : void 0
|
|
788
841
|
})
|
|
789
842
|
);
|
|
790
843
|
break;
|
|
844
|
+
}
|
|
791
845
|
case "PreToolUse":
|
|
792
846
|
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, agent_id, input: s.tool_input }));
|
|
793
847
|
break;
|
|
@@ -842,7 +896,13 @@ var claudeCode = {
|
|
|
842
896
|
);
|
|
843
897
|
break;
|
|
844
898
|
case "Stop":
|
|
845
|
-
events.push(
|
|
899
|
+
events.push(
|
|
900
|
+
ev("turn.ended", {
|
|
901
|
+
turn_id,
|
|
902
|
+
reason: "stop",
|
|
903
|
+
background: Array.isArray(s.background_tasks) ? s.background_tasks.length : void 0
|
|
904
|
+
})
|
|
905
|
+
);
|
|
846
906
|
break;
|
|
847
907
|
case "StopFailure":
|
|
848
908
|
events.push(
|
|
@@ -882,8 +942,11 @@ var claudeCode = {
|
|
|
882
942
|
return null;
|
|
883
943
|
}
|
|
884
944
|
const hints = {};
|
|
945
|
+
if (event === "UserPromptSubmit" && typeof s.prompt === "string") hints.prompt = s.prompt;
|
|
885
946
|
const host = input.env.CLAUDE_CODE_HOST_SESSION_ID;
|
|
886
947
|
if (host && DESKTOP_ID.test(host)) hints.desktop_id = host;
|
|
948
|
+
const attended = input.env.CLAUDE_CODE_SESSION_ATTENDED;
|
|
949
|
+
if (attended === "1" || attended === "0") hints.attended = attended === "1" || !!input.env.SESSIONPIPE_CONTROL_JOB;
|
|
887
950
|
if (event !== "PreToolUse" && event !== "PostToolUse") hints.claude_pid = claudePid(input.env);
|
|
888
951
|
const r = { session, events, hints };
|
|
889
952
|
if (typeof s.transcript_path === "string") r.transcript = s.transcript_path;
|
|
@@ -904,10 +967,11 @@ var claudeCode = {
|
|
|
904
967
|
const cwd = out.cwd ?? session.cwd;
|
|
905
968
|
Object.assign(out, gitFacts(cwd), out.branch ? { branch: out.branch } : {});
|
|
906
969
|
if (!out.url && typeof hints.desktop_id === "string") out.url = `claude://code/continue/${hints.desktop_id}`;
|
|
970
|
+
if (typeof hints.attended === "boolean") out.attended = hints.attended || !!out.url;
|
|
907
971
|
return out;
|
|
908
972
|
},
|
|
909
973
|
backfill(sinceMs, env = process.env) {
|
|
910
|
-
const
|
|
974
|
+
const rows2 = [];
|
|
911
975
|
const files = [];
|
|
912
976
|
for (const d of claudeDirs(env))
|
|
913
977
|
files.push(...recentFiles(path5.join(d, "projects"), 1, sinceMs, (n) => /^[0-9a-f-]{36}\.jsonl$/.test(n)));
|
|
@@ -919,22 +983,29 @@ var claudeCode = {
|
|
|
919
983
|
if (!facts.started_at || !facts.last_at) continue;
|
|
920
984
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
921
985
|
const cwd = rest.cwd;
|
|
922
|
-
|
|
986
|
+
rows2.push({
|
|
923
987
|
session: { id, ...rest, ...gitFacts(cwd), ...rest.branch ? { branch: rest.branch } : {} },
|
|
924
988
|
started_at,
|
|
925
989
|
last_at
|
|
926
990
|
});
|
|
927
991
|
}
|
|
928
|
-
return
|
|
992
|
+
return rows2;
|
|
929
993
|
}
|
|
930
994
|
};
|
|
931
995
|
function prune2(o) {
|
|
932
996
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
933
997
|
return o;
|
|
934
998
|
}
|
|
999
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1000
|
+
function notClaudeCode(id, s) {
|
|
1001
|
+
return typeof s.transcript_path !== "string" && !UUID.test(id);
|
|
1002
|
+
}
|
|
935
1003
|
function pickSource(v) {
|
|
936
1004
|
return typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
937
1005
|
}
|
|
1006
|
+
var harnessWord = (v) => typeof v === "string" && /^[A-Za-z0-9][A-Za-z0-9_.-]{0,39}$/.test(v) ? v : void 0;
|
|
1007
|
+
var WRAPPER = /^<(?:task-notification|cross-session-message|system-reminder|local-command-stdout|command-[a-z-]+)[\s/>]/i;
|
|
1008
|
+
var injectedBlock = (prompt) => WRAPPER.test(prompt.trim());
|
|
938
1009
|
function errText(v) {
|
|
939
1010
|
if (typeof v === "string") return v.slice(0, 2e3);
|
|
940
1011
|
if (v && typeof v === "object") {
|
|
@@ -1037,23 +1108,47 @@ function parentOf() {
|
|
|
1037
1108
|
}
|
|
1038
1109
|
return (pid) => table.get(pid) || 0;
|
|
1039
1110
|
}
|
|
1040
|
-
var
|
|
1041
|
-
function
|
|
1042
|
-
const hit = accountCache.get(dir);
|
|
1043
|
-
if (hit !== void 0) return hit;
|
|
1111
|
+
var EMAIL_RE = /^[^@\s]{1,64}@[^@\s]{1,189}$/;
|
|
1112
|
+
function claudeLogin(dir, home = homeDir()) {
|
|
1044
1113
|
const files = [path5.join(dir, ".claude.json")];
|
|
1045
|
-
if (path5.resolve(dir) === path5.join(
|
|
1046
|
-
let acct = null;
|
|
1114
|
+
if (path5.resolve(dir) === path5.join(home, ".claude")) files.unshift(path5.join(home, ".claude.json"));
|
|
1047
1115
|
for (const f of files) {
|
|
1048
1116
|
try {
|
|
1049
1117
|
const a = JSON.parse(readFileSync3(f, "utf8")).oauthAccount;
|
|
1050
|
-
if (a?.accountUuid) {
|
|
1051
|
-
|
|
1052
|
-
|
|
1118
|
+
if (typeof a?.accountUuid === "string" && a.accountUuid) {
|
|
1119
|
+
const email = typeof a.emailAddress === "string" && EMAIL_RE.test(a.emailAddress) ? a.emailAddress : void 0;
|
|
1120
|
+
return { id: a.accountUuid.slice(0, 128), ...email ? { email } : {} };
|
|
1053
1121
|
}
|
|
1054
1122
|
} catch {
|
|
1055
1123
|
}
|
|
1056
1124
|
}
|
|
1125
|
+
return apiAccount(dir);
|
|
1126
|
+
}
|
|
1127
|
+
var ANTHROPIC_HOST = /(^|\.)anthropic\.com$/i;
|
|
1128
|
+
function apiAccount(dir) {
|
|
1129
|
+
let base;
|
|
1130
|
+
try {
|
|
1131
|
+
base = JSON.parse(readFileSync3(settingsIn(dir), "utf8")).env?.ANTHROPIC_BASE_URL;
|
|
1132
|
+
} catch {
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
if (typeof base !== "string") return null;
|
|
1136
|
+
let u;
|
|
1137
|
+
try {
|
|
1138
|
+
u = new URL(base.trim());
|
|
1139
|
+
} catch {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
if (u.protocol !== "https:" && u.protocol !== "http:" || !u.hostname || ANTHROPIC_HOST.test(u.hostname))
|
|
1143
|
+
return null;
|
|
1144
|
+
const origin = `${u.protocol}//${u.host}`;
|
|
1145
|
+
return { id: `api-${createHash("sha256").update(origin).digest("hex").slice(0, 16)}`, name: u.host };
|
|
1146
|
+
}
|
|
1147
|
+
var accountCache = /* @__PURE__ */ new Map();
|
|
1148
|
+
function claudeAccount(dir) {
|
|
1149
|
+
const hit = accountCache.get(dir);
|
|
1150
|
+
if (hit !== void 0) return hit;
|
|
1151
|
+
const acct = claudeLogin(dir)?.id ?? null;
|
|
1057
1152
|
accountCache.set(dir, acct);
|
|
1058
1153
|
return acct;
|
|
1059
1154
|
}
|
|
@@ -1207,6 +1302,18 @@ function claudeFacts(file, sessionId, hints, state) {
|
|
|
1207
1302
|
return out;
|
|
1208
1303
|
}
|
|
1209
1304
|
|
|
1305
|
+
// ../core/src/control/verify.ts
|
|
1306
|
+
var GRANT_MAX_MS = 24 * 36e5;
|
|
1307
|
+
var SKEW_MS = 5 * 6e4;
|
|
1308
|
+
var MACHINE_MAX_AGE_MS = 24 * 36e5;
|
|
1309
|
+
var RECEIVER_MAX_AGE_MS = 10 * 6e4;
|
|
1310
|
+
var NONCE_TTL_MS = 25 * 36e5;
|
|
1311
|
+
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/@[\]-]*$/;
|
|
1312
|
+
var START_PERMISSIONS = ["read", "safe", "edits", "auto"];
|
|
1313
|
+
var levelRank = (l) => START_PERMISSIONS.indexOf(l);
|
|
1314
|
+
var enc = new TextEncoder();
|
|
1315
|
+
var dec = new TextDecoder();
|
|
1316
|
+
|
|
1210
1317
|
// ../core/src/adapters/codex.ts
|
|
1211
1318
|
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1212
1319
|
import path6 from "node:path";
|
|
@@ -1273,18 +1380,13 @@ function parseArgv(raw) {
|
|
|
1273
1380
|
function readNotify(text) {
|
|
1274
1381
|
const { head, tail, at } = split(text);
|
|
1275
1382
|
const m = NOTIFY_LINE.exec(head);
|
|
1276
|
-
if (m)
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
index: m.index,
|
|
1281
|
-
length: m[0].length,
|
|
1282
|
-
misplaced: false,
|
|
1283
|
-
other: !OURS2.test(m[1] ?? "")
|
|
1284
|
-
};
|
|
1383
|
+
if (m) {
|
|
1384
|
+
const raw = m[1];
|
|
1385
|
+
return { raw, argv: parseArgv(raw), index: m.index, length: m[0].length, misplaced: false, other: !OURS2.test(raw) };
|
|
1386
|
+
}
|
|
1285
1387
|
const k = NOTIFY_KEY.exec(head);
|
|
1286
1388
|
if (k) {
|
|
1287
|
-
const rest = head.slice(k.index).split("\n")[0]
|
|
1389
|
+
const rest = head.slice(k.index).split("\n")[0];
|
|
1288
1390
|
return {
|
|
1289
1391
|
raw: rest.replace(NOTIFY_KEY, "").trim(),
|
|
1290
1392
|
argv: null,
|
|
@@ -1295,15 +1397,11 @@ function readNotify(text) {
|
|
|
1295
1397
|
};
|
|
1296
1398
|
}
|
|
1297
1399
|
const t = NOTIFY_LINE.exec(tail);
|
|
1298
|
-
if (t
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
argv: parseArgv(t[
|
|
1302
|
-
|
|
1303
|
-
length: t[0].length,
|
|
1304
|
-
misplaced: true,
|
|
1305
|
-
other: false
|
|
1306
|
-
};
|
|
1400
|
+
if (t) {
|
|
1401
|
+
const raw = t[1];
|
|
1402
|
+
if (OURS2.test(raw))
|
|
1403
|
+
return { raw, argv: parseArgv(raw), index: at + t.index, length: t[0].length, misplaced: true, other: false };
|
|
1404
|
+
}
|
|
1307
1405
|
return null;
|
|
1308
1406
|
}
|
|
1309
1407
|
function dropLine(text, index, length) {
|
|
@@ -1322,9 +1420,7 @@ function withNotify(text, line) {
|
|
|
1322
1420
|
if (n?.misplaced) return insertTopLevel(dropLine(text, n.index, n.length), line);
|
|
1323
1421
|
return insertTopLevel(text, line);
|
|
1324
1422
|
}
|
|
1325
|
-
function replaceNotify(text, line) {
|
|
1326
|
-
const n = readNotify(text);
|
|
1327
|
-
if (!n || n.misplaced || !n.length) return null;
|
|
1423
|
+
function replaceNotify(text, n, line) {
|
|
1328
1424
|
return text.slice(0, n.index) + line + text.slice(n.index + n.length);
|
|
1329
1425
|
}
|
|
1330
1426
|
function withoutNotify(text) {
|
|
@@ -1365,7 +1461,7 @@ var CODEX_EVENTS = [
|
|
|
1365
1461
|
];
|
|
1366
1462
|
var OPTS2 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1367
1463
|
var eventsFor2 = (lean) => lean ? CODEX_EVENTS.filter((e) => e !== "PreToolUse") : [...CODEX_EVENTS];
|
|
1368
|
-
var codexHome = (env) => env.CODEX_HOME || path6.join(
|
|
1464
|
+
var codexHome = (env) => env.CODEX_HOME || path6.join(homeDir(), ".codex");
|
|
1369
1465
|
var hooksFile2 = (env) => path6.join(codexHome(env), "hooks.json");
|
|
1370
1466
|
var configFile2 = (env) => path6.join(codexHome(env), "config.toml");
|
|
1371
1467
|
var sessionsDir = (env) => path6.join(codexHome(env), "sessions");
|
|
@@ -1403,11 +1499,8 @@ function installNotify(cmd, env) {
|
|
|
1403
1499
|
return { file, changed: false, note: "notify current (after Codex Computer Use)" };
|
|
1404
1500
|
}
|
|
1405
1501
|
if (!prev || OURS2.test(JSON.stringify(prev))) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
write(next);
|
|
1409
|
-
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1410
|
-
}
|
|
1502
|
+
write(replaceNotify(text, n, `notify = ${withPreviousRaw(n.raw, argv)}`));
|
|
1503
|
+
return { file, changed: true, note: "chained after Codex Computer Use (--previous-notify)" };
|
|
1411
1504
|
}
|
|
1412
1505
|
}
|
|
1413
1506
|
return {
|
|
@@ -1436,8 +1529,7 @@ function uninstallNotify(env) {
|
|
|
1436
1529
|
}
|
|
1437
1530
|
const prev = previousNotify(n.argv);
|
|
1438
1531
|
if (isComputerUse(n.argv) && prev && OURS2.test(JSON.stringify(prev))) {
|
|
1439
|
-
|
|
1440
|
-
if (next) writeFileSync3(file, next);
|
|
1532
|
+
writeFileSync3(file, replaceNotify(text, n, `notify = ${stripPreviousRaw(n.raw)}`));
|
|
1441
1533
|
return { file, changed: true };
|
|
1442
1534
|
}
|
|
1443
1535
|
return { file, changed: false };
|
|
@@ -1467,6 +1559,13 @@ function findRollout(threadId, env = process.env) {
|
|
|
1467
1559
|
walk(sessionsDir(env), 0);
|
|
1468
1560
|
return best;
|
|
1469
1561
|
}
|
|
1562
|
+
function rolloutMeta(file) {
|
|
1563
|
+
return scanLines(file, (line) => parseLines([line])[0] ?? {});
|
|
1564
|
+
}
|
|
1565
|
+
function ofAnotherThread(file, id) {
|
|
1566
|
+
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);
|
|
1567
|
+
return !!m?.[1] && m[1].toLowerCase() !== id.toLowerCase();
|
|
1568
|
+
}
|
|
1470
1569
|
var codex = {
|
|
1471
1570
|
name: NAME3,
|
|
1472
1571
|
events: CODEX_EVENTS,
|
|
@@ -1509,11 +1608,14 @@ var codex = {
|
|
|
1509
1608
|
if (event === "notify") {
|
|
1510
1609
|
const raw = input.argv.find((a) => a.startsWith("{")) ?? input.stdin;
|
|
1511
1610
|
try {
|
|
1512
|
-
|
|
1611
|
+
const j = raw ? JSON.parse(raw) : {};
|
|
1612
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1513
1613
|
} catch {
|
|
1514
1614
|
}
|
|
1515
1615
|
const id2 = s["thread-id"] ?? s.thread_id ?? s["session-id"] ?? s.session_id;
|
|
1516
1616
|
if (!id2) return null;
|
|
1617
|
+
const file = findRollout(String(id2), input.env);
|
|
1618
|
+
if (!file || codexSubagent(rolloutMeta(file))) return null;
|
|
1517
1619
|
const r2 = {
|
|
1518
1620
|
session: { id: String(id2), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd },
|
|
1519
1621
|
events: [
|
|
@@ -1523,18 +1625,19 @@ var codex = {
|
|
|
1523
1625
|
harnessEvent: "notify"
|
|
1524
1626
|
}
|
|
1525
1627
|
],
|
|
1526
|
-
hints: {}
|
|
1628
|
+
hints: {},
|
|
1629
|
+
transcript: file
|
|
1527
1630
|
};
|
|
1528
|
-
const file = findRollout(String(id2), input.env);
|
|
1529
|
-
if (file) r2.transcript = file;
|
|
1530
1631
|
return r2;
|
|
1531
1632
|
}
|
|
1532
1633
|
try {
|
|
1533
|
-
|
|
1634
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
1635
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1534
1636
|
} catch {
|
|
1535
1637
|
}
|
|
1536
|
-
const id = s.session_id;
|
|
1638
|
+
const id = s.session_id ?? s.thread_id ?? s.conversation_id ?? s["thread-id"] ?? s["session-id"];
|
|
1537
1639
|
if (!id) return null;
|
|
1640
|
+
if (s.transcript_path === null) return null;
|
|
1538
1641
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1539
1642
|
if (typeof s.model === "string") session.model = s.model;
|
|
1540
1643
|
const turn_id = typeof s.turn_id === "string" ? s.turn_id : void 0;
|
|
@@ -1606,7 +1709,8 @@ var codex = {
|
|
|
1606
1709
|
return null;
|
|
1607
1710
|
}
|
|
1608
1711
|
const r = { session, events, hints: {} };
|
|
1609
|
-
if (typeof s.transcript_path === "string"
|
|
1712
|
+
if (typeof s.transcript_path === "string" && !ofAnotherThread(s.transcript_path, String(id)))
|
|
1713
|
+
r.transcript = s.transcript_path;
|
|
1610
1714
|
return r;
|
|
1611
1715
|
},
|
|
1612
1716
|
readTranscript(file, fromLine) {
|
|
@@ -1624,16 +1728,17 @@ var codex = {
|
|
|
1624
1728
|
return out;
|
|
1625
1729
|
},
|
|
1626
1730
|
backfill(sinceMs, env = process.env) {
|
|
1627
|
-
const
|
|
1731
|
+
const rows2 = [];
|
|
1628
1732
|
for (const f of recentFiles(sessionsDir(env), 3, sinceMs, (n) => /^rollout-.*\.jsonl$/.test(n))) {
|
|
1629
1733
|
const m = /([0-9a-f]{8}-[0-9a-f-]{27,})\.jsonl$/i.exec(path6.basename(f));
|
|
1630
1734
|
if (!m?.[1]) continue;
|
|
1735
|
+
if (codexSubagent(rolloutMeta(f))) continue;
|
|
1631
1736
|
const facts = codexFacts(f);
|
|
1632
1737
|
if (!facts.started_at || !facts.last_at) continue;
|
|
1633
1738
|
const { started_at, last_at, first_ask: _fa, harness_version: _hv, ...rest } = facts;
|
|
1634
|
-
|
|
1739
|
+
rows2.push({ session: { id: m[1], ...rest }, started_at, last_at });
|
|
1635
1740
|
}
|
|
1636
|
-
return
|
|
1741
|
+
return rows2;
|
|
1637
1742
|
}
|
|
1638
1743
|
};
|
|
1639
1744
|
function prune3(o) {
|
|
@@ -1642,10 +1747,11 @@ function prune3(o) {
|
|
|
1642
1747
|
}
|
|
1643
1748
|
function codexFacts(file) {
|
|
1644
1749
|
const { head, tail } = ends(file, 128 * 1024, 64 * 1024);
|
|
1645
|
-
|
|
1646
|
-
|
|
1750
|
+
let H = parseLines(head);
|
|
1751
|
+
let T = parseLines(tail);
|
|
1647
1752
|
const out = {};
|
|
1648
1753
|
const meta = H.find((j) => j.type === "session_meta" && j.payload);
|
|
1754
|
+
const boundary = codexReplayBoundary(meta);
|
|
1649
1755
|
if (meta?.payload) {
|
|
1650
1756
|
const p = meta.payload;
|
|
1651
1757
|
if (p.cwd) out.cwd = p.cwd;
|
|
@@ -1653,6 +1759,13 @@ function codexFacts(file) {
|
|
|
1653
1759
|
if (p.git?.branch) out.branch = p.git.branch;
|
|
1654
1760
|
if (p.git?.repository_url) out.repo = String(p.git.repository_url).replace(/\/\/[^/@]*@/, "//");
|
|
1655
1761
|
}
|
|
1762
|
+
if (boundary) {
|
|
1763
|
+
const h = H.findIndex(boundary);
|
|
1764
|
+
const t = T.findIndex(boundary);
|
|
1765
|
+
H = h < 0 ? [] : H.slice(h);
|
|
1766
|
+
const found = h >= 0 || t >= 0 || scanLines(file, (line) => boundary(parseLines([line])[0] ?? null) ? true : null, Number.POSITIVE_INFINITY);
|
|
1767
|
+
T = t >= 0 ? T.slice(t) : found ? T : [];
|
|
1768
|
+
}
|
|
1656
1769
|
const ctx = H.concat(T).reverse().find((j) => j.type === "turn_context" && j.payload?.model);
|
|
1657
1770
|
if (ctx?.payload?.model) out.model = ctx.payload.model;
|
|
1658
1771
|
for (const j of H) {
|
|
@@ -1660,12 +1773,12 @@ function codexFacts(file) {
|
|
|
1660
1773
|
if (j.type !== "response_item" || !p || p.type !== "message" || p.role !== "user") continue;
|
|
1661
1774
|
const text = (p.content ?? []).filter((b) => b && b.type === "input_text" && typeof b.text === "string").map((b) => b.text).join("\n").trim();
|
|
1662
1775
|
if (!text || codexInjected(text)) continue;
|
|
1663
|
-
const line = text.split("\n")
|
|
1776
|
+
const line = text.split("\n")[0];
|
|
1664
1777
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1665
1778
|
out.title_source = "first-ask";
|
|
1666
1779
|
break;
|
|
1667
1780
|
}
|
|
1668
|
-
const times = H.concat(T).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1781
|
+
const times = (boundary && meta ? [meta, ...H, ...T] : H.concat(T)).map((j) => Date.parse(String(j.timestamp ?? ""))).filter((n) => n > 0);
|
|
1669
1782
|
if (times.length) {
|
|
1670
1783
|
out.started_at = Math.min(...times);
|
|
1671
1784
|
out.last_at = Math.max(...times);
|
|
@@ -1673,11 +1786,432 @@ function codexFacts(file) {
|
|
|
1673
1786
|
return out;
|
|
1674
1787
|
}
|
|
1675
1788
|
|
|
1676
|
-
// ../core/src/adapters/
|
|
1677
|
-
import {
|
|
1678
|
-
import { existsSync as
|
|
1789
|
+
// ../core/src/adapters/devin.ts
|
|
1790
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1791
|
+
import { existsSync as existsSync6, readlinkSync } from "node:fs";
|
|
1679
1792
|
import path7 from "node:path";
|
|
1680
|
-
|
|
1793
|
+
|
|
1794
|
+
// ../core/src/readers/sqlite.ts
|
|
1795
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
1796
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1797
|
+
import { createRequire } from "node:module";
|
|
1798
|
+
function quiet(fn) {
|
|
1799
|
+
const original = process.emitWarning;
|
|
1800
|
+
try {
|
|
1801
|
+
process.emitWarning = ((warning, ...rest) => {
|
|
1802
|
+
const kind = typeof rest[0] === "string" ? rest[0] : rest[0]?.type;
|
|
1803
|
+
if (kind === "ExperimentalWarning" && /sqlite/i.test(String(warning))) return;
|
|
1804
|
+
original.call(process, warning, ...rest);
|
|
1805
|
+
});
|
|
1806
|
+
return fn();
|
|
1807
|
+
} finally {
|
|
1808
|
+
process.emitWarning = original;
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
var ctorCache;
|
|
1812
|
+
function dbCtor() {
|
|
1813
|
+
if (ctorCache !== void 0) return ctorCache;
|
|
1814
|
+
ctorCache = null;
|
|
1815
|
+
try {
|
|
1816
|
+
const req = createRequire(import.meta.url);
|
|
1817
|
+
const m = quiet(() => req("node:sqlite"));
|
|
1818
|
+
if (typeof m?.DatabaseSync === "function") ctorCache = m.DatabaseSync;
|
|
1819
|
+
} catch {
|
|
1820
|
+
}
|
|
1821
|
+
return ctorCache;
|
|
1822
|
+
}
|
|
1823
|
+
function plain(row) {
|
|
1824
|
+
if (!row || typeof row !== "object") return null;
|
|
1825
|
+
const out = {};
|
|
1826
|
+
for (const [k, v] of Object.entries(row)) {
|
|
1827
|
+
if (typeof v === "bigint") out[k] = Number(v);
|
|
1828
|
+
else if (v instanceof Uint8Array) out[k] = null;
|
|
1829
|
+
else out[k] = v;
|
|
1830
|
+
}
|
|
1831
|
+
return out;
|
|
1832
|
+
}
|
|
1833
|
+
var rows = (v) => {
|
|
1834
|
+
if (!Array.isArray(v)) return null;
|
|
1835
|
+
const out = [];
|
|
1836
|
+
for (const r of v) {
|
|
1837
|
+
const p = plain(r);
|
|
1838
|
+
if (p) out.push(p);
|
|
1839
|
+
}
|
|
1840
|
+
return out;
|
|
1841
|
+
};
|
|
1842
|
+
function viaNode(file, sql, params, o) {
|
|
1843
|
+
const C = dbCtor();
|
|
1844
|
+
if (!C) return null;
|
|
1845
|
+
let db;
|
|
1846
|
+
try {
|
|
1847
|
+
db = quiet(() => new C(file, { readOnly: true, timeout: o.timeoutMs }));
|
|
1848
|
+
} catch {
|
|
1849
|
+
return null;
|
|
1850
|
+
}
|
|
1851
|
+
try {
|
|
1852
|
+
try {
|
|
1853
|
+
db.exec(`PRAGMA busy_timeout = ${Math.round(o.timeoutMs)}`);
|
|
1854
|
+
} catch {
|
|
1855
|
+
}
|
|
1856
|
+
return quiet(() => rows(db.prepare(sql).all(...params)));
|
|
1857
|
+
} catch {
|
|
1858
|
+
return null;
|
|
1859
|
+
} finally {
|
|
1860
|
+
try {
|
|
1861
|
+
db.close();
|
|
1862
|
+
} catch {
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
var CHILD = `const { DatabaseSync } = require("node:sqlite");
|
|
1867
|
+
const [, file, sql, params, ms] = process.argv;
|
|
1868
|
+
const db = new DatabaseSync(file, { readOnly: true, timeout: Number(ms) });
|
|
1869
|
+
try { db.exec("PRAGMA busy_timeout = " + Number(ms)); } catch {}
|
|
1870
|
+
const out = db.prepare(sql).all(...JSON.parse(params));
|
|
1871
|
+
db.close();
|
|
1872
|
+
process.stdout.write(JSON.stringify(out));`;
|
|
1873
|
+
function viaChild(file, sql, params, o) {
|
|
1874
|
+
try {
|
|
1875
|
+
const out = execFileSync3(
|
|
1876
|
+
process.execPath,
|
|
1877
|
+
["--experimental-sqlite", "--no-warnings", "-e", CHILD, file, sql, JSON.stringify(params), String(o.timeoutMs)],
|
|
1878
|
+
{
|
|
1879
|
+
encoding: "utf8",
|
|
1880
|
+
timeout: o.timeoutMs + 3e3,
|
|
1881
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1882
|
+
maxBuffer: o.maxBuffer,
|
|
1883
|
+
windowsHide: true
|
|
1884
|
+
}
|
|
1885
|
+
);
|
|
1886
|
+
return rows(JSON.parse(out || "null"));
|
|
1887
|
+
} catch {
|
|
1888
|
+
return null;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
var SAFE = /^[A-Za-z0-9 ._:@/+-]{0,256}$/;
|
|
1892
|
+
function inlineParams(sql, params) {
|
|
1893
|
+
if (sql.includes("'")) return null;
|
|
1894
|
+
const holes = sql.split("?").length - 1;
|
|
1895
|
+
if (holes !== params.length) return null;
|
|
1896
|
+
let i = 0;
|
|
1897
|
+
let bad = false;
|
|
1898
|
+
const out = sql.replace(/\?/g, () => {
|
|
1899
|
+
const p = params[i++];
|
|
1900
|
+
if (typeof p === "number") {
|
|
1901
|
+
if (!Number.isFinite(p)) {
|
|
1902
|
+
bad = true;
|
|
1903
|
+
return "NULL";
|
|
1904
|
+
}
|
|
1905
|
+
return String(p);
|
|
1906
|
+
}
|
|
1907
|
+
if (typeof p !== "string" || !SAFE.test(p)) {
|
|
1908
|
+
bad = true;
|
|
1909
|
+
return "NULL";
|
|
1910
|
+
}
|
|
1911
|
+
return `'${p}'`;
|
|
1912
|
+
});
|
|
1913
|
+
return bad ? null : out;
|
|
1914
|
+
}
|
|
1915
|
+
function viaCli(file, sql, params, o) {
|
|
1916
|
+
const text = inlineParams(sql, params);
|
|
1917
|
+
if (!text) return null;
|
|
1918
|
+
try {
|
|
1919
|
+
const out = execFileSync3("sqlite3", ["-readonly", "-json", file, text], {
|
|
1920
|
+
encoding: "utf8",
|
|
1921
|
+
timeout: o.timeoutMs + 3e3,
|
|
1922
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
1923
|
+
maxBuffer: o.maxBuffer,
|
|
1924
|
+
windowsHide: true
|
|
1925
|
+
}).trim();
|
|
1926
|
+
if (!out) return [];
|
|
1927
|
+
return rows(JSON.parse(out));
|
|
1928
|
+
} catch {
|
|
1929
|
+
return null;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
function querySqlite(file, sql, params = [], opts = {}) {
|
|
1933
|
+
const o = {
|
|
1934
|
+
timeoutMs: opts.timeoutMs ?? 2e3,
|
|
1935
|
+
engine: opts.engine ?? "auto",
|
|
1936
|
+
maxBuffer: opts.maxBuffer ?? 32 * 1024 * 1024
|
|
1937
|
+
};
|
|
1938
|
+
if (!file) return null;
|
|
1939
|
+
try {
|
|
1940
|
+
if (!existsSync5(file)) return null;
|
|
1941
|
+
} catch {
|
|
1942
|
+
return null;
|
|
1943
|
+
}
|
|
1944
|
+
const ways = o.engine === "auto" ? [viaNode, viaChild, viaCli] : o.engine === "node" ? [viaNode] : o.engine === "child" ? [viaChild] : [viaCli];
|
|
1945
|
+
for (const way of ways) {
|
|
1946
|
+
const r = way(file, sql, params, o);
|
|
1947
|
+
if (r) return r;
|
|
1948
|
+
}
|
|
1949
|
+
return null;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// ../core/src/adapters/devin.ts
|
|
1953
|
+
var NAME4 = "devin";
|
|
1954
|
+
var DEVIN_EVENTS = [
|
|
1955
|
+
"SessionStart",
|
|
1956
|
+
"UserPromptSubmit",
|
|
1957
|
+
"PreToolUse",
|
|
1958
|
+
"PostToolUse",
|
|
1959
|
+
"PermissionRequest",
|
|
1960
|
+
"Stop",
|
|
1961
|
+
"PostCompaction",
|
|
1962
|
+
"SessionEnd"
|
|
1963
|
+
];
|
|
1964
|
+
var OPTS3 = { matcherFor: (e) => /Tool|Permission/.test(e) ? "" : void 0, timeout: 5 };
|
|
1965
|
+
var eventsFor3 = (lean) => lean ? DEVIN_EVENTS.filter((e) => e !== "PreToolUse") : [...DEVIN_EVENTS];
|
|
1966
|
+
var INSTALL_NOTE = "Devin drops a whole hooks file on one event name it doesn't know; `/hooks` in `devin` lists what loaded";
|
|
1967
|
+
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";
|
|
1968
|
+
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)`;
|
|
1969
|
+
function foreignEvent(file) {
|
|
1970
|
+
const hooks = readJson(file).hooks;
|
|
1971
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return void 0;
|
|
1972
|
+
const ok = DEVIN_EVENTS;
|
|
1973
|
+
return Object.keys(hooks).find((k) => !ok.includes(k));
|
|
1974
|
+
}
|
|
1975
|
+
var homeOf = (env) => env.HOME || homeDir();
|
|
1976
|
+
var appData = (env) => env.APPDATA || path7.join(homeOf(env), "AppData", "Roaming");
|
|
1977
|
+
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");
|
|
1978
|
+
var configFile3 = (env) => path7.join(devinConfigDir(env), "config.json");
|
|
1979
|
+
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");
|
|
1980
|
+
var sessionsDb = (env) => path7.join(devinDataDir(env), "sessions.db");
|
|
1981
|
+
var SESSION_ID = /^[A-Za-z0-9._:-]{1,128}$/;
|
|
1982
|
+
var validTool4 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
1983
|
+
var endReason3 = (r) => r === "clear" || r === "logout" || r === "exit" ? r : "other";
|
|
1984
|
+
var pickSource2 = (v) => typeof v === "string" && ["startup", "resume", "clear", "compact", "fork"].includes(v) ? v : "unknown";
|
|
1985
|
+
function errText2(v) {
|
|
1986
|
+
if (typeof v === "string" && v) return v.slice(0, 2e3);
|
|
1987
|
+
if (v && typeof v === "object") {
|
|
1988
|
+
const e = v;
|
|
1989
|
+
if (typeof e.message === "string" && e.message) return e.message.slice(0, 2e3);
|
|
1990
|
+
if (typeof e.error === "string" && e.error) return e.error.slice(0, 2e3);
|
|
1991
|
+
}
|
|
1992
|
+
return void 0;
|
|
1993
|
+
}
|
|
1994
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, n).replace(/\s+\S*$/, "")}\u2026` : s;
|
|
1995
|
+
var devin = {
|
|
1996
|
+
name: NAME4,
|
|
1997
|
+
events: DEVIN_EVENTS,
|
|
1998
|
+
detect: (env = process.env) => existsSync6(devinConfigDir(env)) || existsSync6(devinDataDir(env)),
|
|
1999
|
+
configFiles: (env = process.env) => [configFile3(env)],
|
|
2000
|
+
install: (cmd, env = process.env, opts = {}) => {
|
|
2001
|
+
const file = configFile3(env);
|
|
2002
|
+
const r = installClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
2003
|
+
if (r.skipped) return [r];
|
|
2004
|
+
const bad = foreignEvent(file);
|
|
2005
|
+
return [{ ...r, note: bad ? deadFileNote(bad) : INSTALL_NOTE }];
|
|
2006
|
+
},
|
|
2007
|
+
uninstall: (env = process.env, opts = {}) => [
|
|
2008
|
+
uninstallClaudeShaped(configFile3(env), DEVIN_EVENTS, { created: opts.created?.includes(configFile3(env)) })
|
|
2009
|
+
],
|
|
2010
|
+
installed: (cmd, env = process.env, opts = {}) => {
|
|
2011
|
+
const file = configFile3(env);
|
|
2012
|
+
const state = installedClaudeShaped(file, NAME4, eventsFor3(opts.lean), cmd, OPTS3);
|
|
2013
|
+
const bad = state === "missing" ? void 0 : foreignEvent(file);
|
|
2014
|
+
return [{ file, state: bad ? "inactive" : state, note: bad ? deadFileNote(bad) : CLAUDE_NOTE }];
|
|
2015
|
+
},
|
|
2016
|
+
fromHook(input) {
|
|
2017
|
+
const event = input.argv[1] ?? "";
|
|
2018
|
+
let s = {};
|
|
2019
|
+
try {
|
|
2020
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2021
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
2022
|
+
} catch {
|
|
2023
|
+
}
|
|
2024
|
+
const id = typeof s.session_id === "string" ? s.session_id : "";
|
|
2025
|
+
if (!id) return null;
|
|
2026
|
+
const session = { id, cwd: input.env.DEVIN_PROJECT_DIR ?? input.cwd };
|
|
2027
|
+
const turn_id = typeof s.prompt_id === "string" ? s.prompt_id : void 0;
|
|
2028
|
+
const tool = validTool4(s.tool_name) ? s.tool_name : void 0;
|
|
2029
|
+
const call_id = typeof s.tool_use_id === "string" ? s.tool_use_id : void 0;
|
|
2030
|
+
const response = s.tool_response && typeof s.tool_response === "object" && !Array.isArray(s.tool_response) ? s.tool_response : void 0;
|
|
2031
|
+
const permissionId = () => `perm-${createHash2("sha256").update(`${turn_id ?? ""}|${tool ?? ""}|${JSON.stringify(s.tool_input ?? null)}`).digest("hex").slice(0, 16)}`;
|
|
2032
|
+
const ev = (type, data) => ({ type, data: prune4(data), harnessEvent: event });
|
|
2033
|
+
const events = [];
|
|
2034
|
+
switch (event) {
|
|
2035
|
+
case "SessionStart":
|
|
2036
|
+
events.push(ev("session.started", { source: pickSource2(s.source) }));
|
|
2037
|
+
break;
|
|
2038
|
+
case "UserPromptSubmit":
|
|
2039
|
+
events.push(
|
|
2040
|
+
ev("turn.started", {
|
|
2041
|
+
turn_id,
|
|
2042
|
+
prompt_chars: typeof s.prompt === "string" ? [...s.prompt].length : void 0
|
|
2043
|
+
})
|
|
2044
|
+
);
|
|
2045
|
+
break;
|
|
2046
|
+
case "PreToolUse":
|
|
2047
|
+
if (tool) events.push(ev("tool.started", { tool, call_id, turn_id, input: s.tool_input }));
|
|
2048
|
+
break;
|
|
2049
|
+
case "PostToolUse":
|
|
2050
|
+
if (tool)
|
|
2051
|
+
events.push(
|
|
2052
|
+
ev("tool.ended", {
|
|
2053
|
+
tool,
|
|
2054
|
+
call_id,
|
|
2055
|
+
turn_id,
|
|
2056
|
+
// `ok` is required on tool.ended (PROTOCOL.md §events), so a response
|
|
2057
|
+
// that carries no success flag at all reads as "nothing said it failed".
|
|
2058
|
+
ok: response?.success !== false,
|
|
2059
|
+
error: errText2(response?.error),
|
|
2060
|
+
input: s.tool_input,
|
|
2061
|
+
output: response ? response.output : s.tool_response
|
|
2062
|
+
})
|
|
2063
|
+
);
|
|
2064
|
+
break;
|
|
2065
|
+
case "PermissionRequest":
|
|
2066
|
+
events.push(ev("attention.needed", { attention_id: call_id ?? permissionId(), kind: "permission", tool }));
|
|
2067
|
+
break;
|
|
2068
|
+
case "Stop":
|
|
2069
|
+
events.push(ev("turn.ended", { turn_id, reason: "stop" }));
|
|
2070
|
+
break;
|
|
2071
|
+
case "PostCompaction":
|
|
2072
|
+
events.push(ev("context.compacted", { trigger: "auto" }));
|
|
2073
|
+
break;
|
|
2074
|
+
case "SessionEnd":
|
|
2075
|
+
events.push(ev("session.ended", { reason: endReason3(s.reason) }));
|
|
2076
|
+
break;
|
|
2077
|
+
default:
|
|
2078
|
+
return null;
|
|
2079
|
+
}
|
|
2080
|
+
return {
|
|
2081
|
+
session,
|
|
2082
|
+
events,
|
|
2083
|
+
hints: {},
|
|
2084
|
+
transcript: transcriptRef(sessionsDb(input.env), id)
|
|
2085
|
+
};
|
|
2086
|
+
},
|
|
2087
|
+
readTranscript(file, fromLine) {
|
|
2088
|
+
const ref = splitRef(file);
|
|
2089
|
+
if (!ref) return { turns: [], line: fromLine };
|
|
2090
|
+
return readDevinChain(ref.db, ref.session, fromLine);
|
|
2091
|
+
},
|
|
2092
|
+
facts(session, transcript, _hints, _state, env = process.env) {
|
|
2093
|
+
const out = {};
|
|
2094
|
+
const version = devinVersion(env);
|
|
2095
|
+
if (version) out.harness_version = version;
|
|
2096
|
+
const account = devinAccount(env);
|
|
2097
|
+
if (account) out.account_id = account;
|
|
2098
|
+
const ref = splitRef(transcript) ?? { db: sessionsDb(env), session: session.id };
|
|
2099
|
+
if (!SESSION_ID.test(ref.session)) return out;
|
|
2100
|
+
const rows2 = querySqlite(ref.db, SESSION_SQL, [ref.session]);
|
|
2101
|
+
const row = rows2?.[0];
|
|
2102
|
+
if (!row) return out;
|
|
2103
|
+
Object.assign(out, sessionFacts(row));
|
|
2104
|
+
return out;
|
|
2105
|
+
},
|
|
2106
|
+
backfill(sinceMs, env = process.env) {
|
|
2107
|
+
const rows2 = querySqlite(sessionsDb(env), BACKFILL_SQL, ["text", Math.floor(sinceMs / 1e3)]);
|
|
2108
|
+
if (!rows2) return [];
|
|
2109
|
+
const out = [];
|
|
2110
|
+
for (const row of rows2) {
|
|
2111
|
+
const id = String(row.id);
|
|
2112
|
+
if (!SESSION_ID.test(id)) continue;
|
|
2113
|
+
const { started_at, last_at, ...rest } = sessionFacts(row);
|
|
2114
|
+
if (!started_at || !last_at) continue;
|
|
2115
|
+
out.push({ session: { id, ...rest }, started_at, last_at });
|
|
2116
|
+
}
|
|
2117
|
+
return out;
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
function prune4(o) {
|
|
2121
|
+
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
2122
|
+
return o;
|
|
2123
|
+
}
|
|
2124
|
+
var transcriptRef = (db, session) => `${db}#${session}`;
|
|
2125
|
+
function splitRef(ref) {
|
|
2126
|
+
if (!ref) return null;
|
|
2127
|
+
const at = ref.lastIndexOf("#");
|
|
2128
|
+
if (at <= 0) return null;
|
|
2129
|
+
const db = ref.slice(0, at);
|
|
2130
|
+
const session = ref.slice(at + 1);
|
|
2131
|
+
return db && SESSION_ID.test(session) ? { db, session } : null;
|
|
2132
|
+
}
|
|
2133
|
+
var CHAIN_LIMIT = 4e3;
|
|
2134
|
+
var CHAIN_SQL = `WITH RECURSIVE chain(node_id, parent_node_id, chat_message, created_at) AS (
|
|
2135
|
+
SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes
|
|
2136
|
+
WHERE session_id = ? AND node_id = (SELECT main_chain_id FROM sessions WHERE id = ?)
|
|
2137
|
+
UNION ALL
|
|
2138
|
+
SELECT m.node_id, m.parent_node_id, m.chat_message, m.created_at FROM message_nodes m
|
|
2139
|
+
JOIN chain c ON m.node_id = c.parent_node_id AND m.session_id = ? AND m.node_id >= ?
|
|
2140
|
+
)
|
|
2141
|
+
SELECT node_id, chat_message, created_at FROM chain ORDER BY node_id DESC LIMIT ${CHAIN_LIMIT}`;
|
|
2142
|
+
var SESSION_FIELDS = `s.model, s.working_directory, s.created_at, s.last_activity_at,
|
|
2143
|
+
(SELECT p.content FROM prompt_history p WHERE p.session_id = s.id AND p.is_shell = 0
|
|
2144
|
+
ORDER BY p.timestamp ASC, p.id ASC LIMIT 1) AS first_ask`;
|
|
2145
|
+
var SESSION_SQL = `SELECT ${SESSION_FIELDS} FROM sessions s WHERE s.id = ? LIMIT 1`;
|
|
2146
|
+
var BACKFILL_SQL = `SELECT s.id, ${SESSION_FIELDS} FROM sessions s
|
|
2147
|
+
WHERE s.hidden = 0 AND typeof(s.id) = ? AND s.last_activity_at >= ?
|
|
2148
|
+
ORDER BY s.last_activity_at DESC LIMIT 500`;
|
|
2149
|
+
function sessionFacts(row) {
|
|
2150
|
+
const out = {};
|
|
2151
|
+
if (typeof row.model === "string" && row.model) out.model = row.model;
|
|
2152
|
+
if (typeof row.working_directory === "string" && path7.isAbsolute(row.working_directory))
|
|
2153
|
+
out.cwd = row.working_directory;
|
|
2154
|
+
const started = Number(row.created_at);
|
|
2155
|
+
const last = Number(row.last_activity_at);
|
|
2156
|
+
if (Number.isFinite(started) && started > 0) out.started_at = Math.round(started * 1e3);
|
|
2157
|
+
if (Number.isFinite(last) && last > 0) out.last_at = Math.round(last * 1e3);
|
|
2158
|
+
if (typeof row.first_ask === "string") {
|
|
2159
|
+
const line = row.first_ask.split("\n").find((l) => l.trim()) ?? "";
|
|
2160
|
+
if (line.trim()) {
|
|
2161
|
+
out.title = cut(line.trim(), 80);
|
|
2162
|
+
out.title_source = "first-ask";
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
return out;
|
|
2166
|
+
}
|
|
2167
|
+
function readDevinChain(db, session, fromLine) {
|
|
2168
|
+
if (!SESSION_ID.test(session)) return { turns: [], line: fromLine };
|
|
2169
|
+
const floor = Number.isFinite(fromLine) && fromLine > 0 ? Math.floor(fromLine) : 0;
|
|
2170
|
+
const rows2 = querySqlite(db, CHAIN_SQL, [session, session, session, floor]);
|
|
2171
|
+
if (!rows2?.length) return { turns: [], line: fromLine };
|
|
2172
|
+
const msgs = [];
|
|
2173
|
+
let end = fromLine;
|
|
2174
|
+
for (const row of rows2) {
|
|
2175
|
+
const node = Number(row.node_id);
|
|
2176
|
+
if (!Number.isFinite(node)) continue;
|
|
2177
|
+
if (node + 1 > end) end = node + 1;
|
|
2178
|
+
if (node < fromLine) continue;
|
|
2179
|
+
let j;
|
|
2180
|
+
try {
|
|
2181
|
+
j = JSON.parse(String(row.chat_message ?? ""));
|
|
2182
|
+
} catch {
|
|
2183
|
+
continue;
|
|
2184
|
+
}
|
|
2185
|
+
const text = typeof j.content === "string" ? j.content.trim() : "";
|
|
2186
|
+
if (!text) continue;
|
|
2187
|
+
const secs = Number(row.created_at);
|
|
2188
|
+
const at = Date.parse(String(j.metadata?.created_at ?? "")) || (Number.isFinite(secs) && secs > 0 ? Math.round(secs * 1e3) : 0) || Date.now();
|
|
2189
|
+
if (j.role === "user" && j.metadata?.is_user_input === true) msgs.push({ role: "user", text, at, line: node });
|
|
2190
|
+
else if (j.role === "assistant") msgs.push({ role: "assistant", text, at, line: node });
|
|
2191
|
+
}
|
|
2192
|
+
msgs.sort((a, b) => a.line - b.line);
|
|
2193
|
+
return pairTurns(msgs, end);
|
|
2194
|
+
}
|
|
2195
|
+
function devinVersion(env = process.env) {
|
|
2196
|
+
try {
|
|
2197
|
+
const target = readlinkSync(path7.join(devinDataDir(env), "_versions", "current"));
|
|
2198
|
+
const name = path7.basename(target);
|
|
2199
|
+
return /^[0-9][0-9A-Za-z.+-]{0,63}$/.test(name) ? name : void 0;
|
|
2200
|
+
} catch {
|
|
2201
|
+
return void 0;
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
function devinAccount(env = process.env) {
|
|
2205
|
+
const d = readJson(configFile3(env)).devin;
|
|
2206
|
+
const id = d && typeof d === "object" ? d.org_id : void 0;
|
|
2207
|
+
return typeof id === "string" && /^[A-Za-z0-9_.:-]{1,128}$/.test(id) ? id : void 0;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
// ../core/src/adapters/gemini-cli.ts
|
|
2211
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
2212
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
2213
|
+
import path8 from "node:path";
|
|
2214
|
+
var NAME5 = "gemini-cli";
|
|
1681
2215
|
var GEMINI_EVENTS = [
|
|
1682
2216
|
"SessionStart",
|
|
1683
2217
|
"BeforeAgent",
|
|
@@ -1688,11 +2222,11 @@ var GEMINI_EVENTS = [
|
|
|
1688
2222
|
"PreCompress",
|
|
1689
2223
|
"SessionEnd"
|
|
1690
2224
|
];
|
|
1691
|
-
var
|
|
1692
|
-
var geminiDir = (env) => env.GEMINI_CLI_HOME ||
|
|
1693
|
-
var settings = (env) =>
|
|
1694
|
-
var
|
|
1695
|
-
var
|
|
2225
|
+
var OPTS4 = { matcherFor: (e) => /Tool/.test(e) ? ".*" : void 0, timeout: 5e3 };
|
|
2226
|
+
var geminiDir = (env) => env.GEMINI_CLI_HOME || path8.join(homeDir(), ".gemini");
|
|
2227
|
+
var settings = (env) => path8.join(geminiDir(env), "settings.json");
|
|
2228
|
+
var validTool5 = (n) => typeof n === "string" && /^[A-Za-z0-9_.:-]{1,120}$/.test(n);
|
|
2229
|
+
var eventsFor4 = (lean) => lean ? GEMINI_EVENTS.filter((e) => e !== "BeforeTool") : [...GEMINI_EVENTS];
|
|
1696
2230
|
function geminiHooksActive(settingsFile) {
|
|
1697
2231
|
let tools;
|
|
1698
2232
|
try {
|
|
@@ -1704,7 +2238,7 @@ function geminiHooksActive(settingsFile) {
|
|
|
1704
2238
|
return { active: false, note: "inactive: tools.enableHooks is false in settings.json" };
|
|
1705
2239
|
let version = "";
|
|
1706
2240
|
try {
|
|
1707
|
-
version =
|
|
2241
|
+
version = execFileSync4("gemini", ["--version"], {
|
|
1708
2242
|
encoding: "utf8",
|
|
1709
2243
|
timeout: 4e3,
|
|
1710
2244
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1723,13 +2257,13 @@ function geminiHooksActive(settingsFile) {
|
|
|
1723
2257
|
};
|
|
1724
2258
|
}
|
|
1725
2259
|
var geminiCli = {
|
|
1726
|
-
name:
|
|
2260
|
+
name: NAME5,
|
|
1727
2261
|
events: GEMINI_EVENTS,
|
|
1728
2262
|
// ~/.gemini also belongs to Antigravity; Gemini CLI is here when its settings or its chat logs exist.
|
|
1729
|
-
detect: (env = process.env) =>
|
|
2263
|
+
detect: (env = process.env) => existsSync7(settings(env)) || existsSync7(path8.join(geminiDir(env), "tmp")),
|
|
1730
2264
|
configFiles: (env = process.env) => [settings(env)],
|
|
1731
2265
|
install: (cmd, env = process.env, opts = {}) => {
|
|
1732
|
-
const r = installClaudeShaped(settings(env),
|
|
2266
|
+
const r = installClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1733
2267
|
const a = geminiHooksActive(settings(env));
|
|
1734
2268
|
return [a.note && !r.skipped ? { ...r, note: [r.note, a.note].filter(Boolean).join("; ") } : r];
|
|
1735
2269
|
},
|
|
@@ -1737,7 +2271,7 @@ var geminiCli = {
|
|
|
1737
2271
|
uninstallClaudeShaped(settings(env), GEMINI_EVENTS, { created: opts.created?.includes(settings(env)) })
|
|
1738
2272
|
],
|
|
1739
2273
|
installed: (cmd, env = process.env, opts = {}) => {
|
|
1740
|
-
const state = installedClaudeShaped(settings(env),
|
|
2274
|
+
const state = installedClaudeShaped(settings(env), NAME5, eventsFor4(opts.lean), cmd, OPTS4);
|
|
1741
2275
|
const a = geminiHooksActive(settings(env));
|
|
1742
2276
|
return [
|
|
1743
2277
|
{
|
|
@@ -1751,14 +2285,15 @@ var geminiCli = {
|
|
|
1751
2285
|
const event = input.argv[1] ?? "";
|
|
1752
2286
|
let s = {};
|
|
1753
2287
|
try {
|
|
1754
|
-
|
|
2288
|
+
const j = input.stdin ? JSON.parse(input.stdin) : {};
|
|
2289
|
+
if (j && typeof j === "object" && !Array.isArray(j)) s = j;
|
|
1755
2290
|
} catch {
|
|
1756
2291
|
}
|
|
1757
2292
|
const id = s.session_id;
|
|
1758
2293
|
if (!id) return null;
|
|
1759
2294
|
const session = { id: String(id), cwd: typeof s.cwd === "string" ? s.cwd : input.cwd };
|
|
1760
|
-
const tool =
|
|
1761
|
-
const ev = (type, data) => ({ type, data:
|
|
2295
|
+
const tool = validTool5(s.tool_name) ? s.tool_name : void 0;
|
|
2296
|
+
const ev = (type, data) => ({ type, data: prune5(data), harnessEvent: event });
|
|
1762
2297
|
const events = [];
|
|
1763
2298
|
switch (event) {
|
|
1764
2299
|
case "SessionStart":
|
|
@@ -1839,7 +2374,7 @@ var geminiCli = {
|
|
|
1839
2374
|
const raw = first ? first.content ?? first.message ?? first.text : void 0;
|
|
1840
2375
|
const text = typeof raw === "string" ? raw.trim() : "";
|
|
1841
2376
|
if (text) {
|
|
1842
|
-
const line = text.split("\n")
|
|
2377
|
+
const line = text.split("\n")[0];
|
|
1843
2378
|
out.title = line.length > 80 ? `${line.slice(0, 80).replace(/\s+\S*$/, "")}\u2026` : line;
|
|
1844
2379
|
out.title_source = "first-ask";
|
|
1845
2380
|
}
|
|
@@ -1853,18 +2388,18 @@ var geminiCli = {
|
|
|
1853
2388
|
backfill: () => []
|
|
1854
2389
|
// chat logs are keyed by a project hash, not a session id we can name; the first hook of a resumed session reports it
|
|
1855
2390
|
};
|
|
1856
|
-
function
|
|
2391
|
+
function prune5(o) {
|
|
1857
2392
|
for (const k of Object.keys(o)) if (o[k] === void 0) delete o[k];
|
|
1858
2393
|
return o;
|
|
1859
2394
|
}
|
|
1860
2395
|
|
|
1861
2396
|
// ../core/src/adapters/index.ts
|
|
1862
|
-
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity];
|
|
2397
|
+
var ADAPTERS = [claudeCode, codex, geminiCli, antigravity, devin];
|
|
1863
2398
|
var adapterByName = (name) => ADAPTERS.find((a) => a.name === name);
|
|
1864
2399
|
|
|
1865
2400
|
// ../core/src/config.ts
|
|
1866
|
-
import { chmodSync, existsSync as
|
|
1867
|
-
import
|
|
2401
|
+
import { chmodSync, existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
2402
|
+
import path9 from "node:path";
|
|
1868
2403
|
var DEFAULT_CONFIG = { sinks: [], harnesses: {}, update_check: true, keep_days: 30 };
|
|
1869
2404
|
function readConfig(file = configFile()) {
|
|
1870
2405
|
try {
|
|
@@ -1880,10 +2415,10 @@ function readConfig(file = configFile()) {
|
|
|
1880
2415
|
}
|
|
1881
2416
|
}
|
|
1882
2417
|
function writeConfig(c, file = configFile()) {
|
|
1883
|
-
mkdirSync4(
|
|
2418
|
+
mkdirSync4(path9.dirname(file), { recursive: true, mode: 448 });
|
|
1884
2419
|
writeFileSync4(file, `${JSON.stringify(c, null, 2)}
|
|
1885
2420
|
`, { mode: 384 });
|
|
1886
|
-
if (process.platform !== "win32" &&
|
|
2421
|
+
if (process.platform !== "win32" && existsSync8(file)) {
|
|
1887
2422
|
try {
|
|
1888
2423
|
chmodSync(file, 384);
|
|
1889
2424
|
} catch {
|
|
@@ -1912,7 +2447,7 @@ function encodeTime(t) {
|
|
|
1912
2447
|
function ulid(now = Date.now()) {
|
|
1913
2448
|
if (now === lastTime) {
|
|
1914
2449
|
for (let i = lastRand.length - 1; i >= 0; i--) {
|
|
1915
|
-
const v =
|
|
2450
|
+
const v = lastRand[i] + 1;
|
|
1916
2451
|
if (v < 32) {
|
|
1917
2452
|
lastRand[i] = v;
|
|
1918
2453
|
break;
|
|
@@ -1921,8 +2456,7 @@ function ulid(now = Date.now()) {
|
|
|
1921
2456
|
}
|
|
1922
2457
|
} else {
|
|
1923
2458
|
lastTime = now;
|
|
1924
|
-
|
|
1925
|
-
lastRand = Array.from({ length: 16 }, (_, i) => (b[i] ?? 0) % 32);
|
|
2459
|
+
lastRand = Array.from(randomBytes(16), (b) => b % 32);
|
|
1926
2460
|
}
|
|
1927
2461
|
return encodeTime(now) + lastRand.map((v) => ALPHABET[v]).join("");
|
|
1928
2462
|
}
|
|
@@ -1945,11 +2479,27 @@ function makeEvent(a, ctx) {
|
|
|
1945
2479
|
};
|
|
1946
2480
|
}
|
|
1947
2481
|
|
|
2482
|
+
// ../core/src/launched-by.ts
|
|
2483
|
+
var LAUNCHED_BY_ENV = "SESSIONPIPE_LAUNCHED_BY";
|
|
2484
|
+
var LAUNCHED_BY = /^[a-z][a-z0-9-]{0,39}:[A-Za-z0-9._:-]{1,128}$/;
|
|
2485
|
+
function launchedBy(env) {
|
|
2486
|
+
const v = env[LAUNCHED_BY_ENV]?.trim();
|
|
2487
|
+
return v && LAUNCHED_BY.test(v) ? v : void 0;
|
|
2488
|
+
}
|
|
2489
|
+
function readHook(adapter, input) {
|
|
2490
|
+
const r = adapter.fromHook(input);
|
|
2491
|
+
if (r && !r.session.parent_id) {
|
|
2492
|
+
const launcher = launchedBy(input.env);
|
|
2493
|
+
if (launcher && launcher !== `${adapter.name}:${r.session.id}`) r.session.launched_by = launcher;
|
|
2494
|
+
}
|
|
2495
|
+
return r;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
1948
2498
|
// ../core/src/outbox.ts
|
|
1949
2499
|
import {
|
|
1950
2500
|
appendFileSync,
|
|
1951
2501
|
closeSync as closeSync2,
|
|
1952
|
-
existsSync as
|
|
2502
|
+
existsSync as existsSync9,
|
|
1953
2503
|
mkdirSync as mkdirSync5,
|
|
1954
2504
|
openSync as openSync2,
|
|
1955
2505
|
readdirSync as readdirSync5,
|
|
@@ -1960,42 +2510,45 @@ import {
|
|
|
1960
2510
|
unlinkSync as unlinkSync3,
|
|
1961
2511
|
writeFileSync as writeFileSync5
|
|
1962
2512
|
} from "node:fs";
|
|
1963
|
-
import
|
|
2513
|
+
import path10 from "node:path";
|
|
2514
|
+
var READ_CHUNK = 1024 * 1024;
|
|
2515
|
+
var READ_MAX = 8 * 1024 * 1024;
|
|
1964
2516
|
var Outbox = class {
|
|
1965
2517
|
constructor(root) {
|
|
1966
2518
|
this.root = root;
|
|
1967
2519
|
}
|
|
1968
2520
|
root;
|
|
1969
2521
|
dir(harness) {
|
|
1970
|
-
return
|
|
2522
|
+
return path10.join(this.root, "outbox", safeId(harness));
|
|
1971
2523
|
}
|
|
1972
2524
|
file(ref) {
|
|
1973
|
-
return
|
|
2525
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.jsonl`);
|
|
1974
2526
|
}
|
|
1975
2527
|
seqFile(ref) {
|
|
1976
|
-
return
|
|
2528
|
+
return path10.join(this.dir(ref.harness), `${safeId(ref.session)}.seq`);
|
|
1977
2529
|
}
|
|
1978
2530
|
/** The next seq for a session, persisted (survives a hibernating machine and a
|
|
1979
2531
|
* restart; an in-memory counter would restart at 0 and collide). */
|
|
1980
2532
|
nextSeq(ref) {
|
|
1981
2533
|
const f = this.seqFile(ref);
|
|
1982
|
-
let n =
|
|
2534
|
+
let n = -1;
|
|
1983
2535
|
try {
|
|
1984
|
-
|
|
2536
|
+
const t = readFileSync7(f, "utf8").trim();
|
|
2537
|
+
if (/^\d+$/.test(t)) n = Number(t);
|
|
1985
2538
|
} catch {
|
|
1986
|
-
n = this.lastSeqInFile(ref) + 1;
|
|
1987
2539
|
}
|
|
1988
|
-
|
|
2540
|
+
if (n < 0) n = this.lastSeqInFile(ref) + 1;
|
|
2541
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
1989
2542
|
writeFileSync5(f, String(n + 1), { mode: 384 });
|
|
1990
2543
|
return n;
|
|
1991
2544
|
}
|
|
1992
2545
|
lastSeqInFile(ref) {
|
|
1993
2546
|
const f = this.file(ref);
|
|
1994
|
-
if (!
|
|
2547
|
+
if (!existsSync9(f)) return -1;
|
|
1995
2548
|
const lines = readFileSync7(f, "utf8").trim().split("\n");
|
|
1996
2549
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1997
2550
|
try {
|
|
1998
|
-
const j = JSON.parse(lines[i]
|
|
2551
|
+
const j = JSON.parse(lines[i]);
|
|
1999
2552
|
if (typeof j.session?.seq === "number") return j.session.seq;
|
|
2000
2553
|
} catch {
|
|
2001
2554
|
}
|
|
@@ -2011,10 +2564,10 @@ var Outbox = class {
|
|
|
2011
2564
|
/** Every session file, newest first. */
|
|
2012
2565
|
sessions() {
|
|
2013
2566
|
const out = [];
|
|
2014
|
-
const base =
|
|
2015
|
-
if (!
|
|
2567
|
+
const base = path10.join(this.root, "outbox");
|
|
2568
|
+
if (!existsSync9(base)) return [];
|
|
2016
2569
|
for (const h of readdirSync5(base)) {
|
|
2017
|
-
const d =
|
|
2570
|
+
const d = path10.join(base, h);
|
|
2018
2571
|
let names = [];
|
|
2019
2572
|
try {
|
|
2020
2573
|
names = readdirSync5(d).filter((n) => n.endsWith(".jsonl"));
|
|
@@ -2023,7 +2576,7 @@ var Outbox = class {
|
|
|
2023
2576
|
}
|
|
2024
2577
|
for (const n of names) {
|
|
2025
2578
|
try {
|
|
2026
|
-
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync4(
|
|
2579
|
+
out.push({ ref: { harness: h, session: n.slice(0, -6) }, mtime: statSync4(path10.join(d, n)).mtimeMs });
|
|
2027
2580
|
} catch {
|
|
2028
2581
|
}
|
|
2029
2582
|
}
|
|
@@ -2031,31 +2584,43 @@ var Outbox = class {
|
|
|
2031
2584
|
return out.sort((a, b) => b.mtime - a.mtime).map((x) => x.ref);
|
|
2032
2585
|
}
|
|
2033
2586
|
/** Read events after byte offset `from`; returns them and the new offset. Lines
|
|
2034
|
-
* are read whole; a partial trailing line (a write in progress) is left.
|
|
2587
|
+
* are read whole; a partial trailing line (a write in progress) is left. A read
|
|
2588
|
+
* takes READ_CHUNK bytes at a time, not the rest of the file: a session file grows
|
|
2589
|
+
* to tens of MB and a drain takes it in batches of 50. It reads on until the batch
|
|
2590
|
+
* is full or the file ends, so lines that parse to nothing never stop a drain; one
|
|
2591
|
+
* line longer than the chunk gets a larger one, up to READ_MAX. */
|
|
2035
2592
|
read(ref, from, maxEvents = 500) {
|
|
2036
2593
|
const f = this.file(ref);
|
|
2037
|
-
if (!
|
|
2594
|
+
if (!existsSync9(f)) return { events: [], offset: from };
|
|
2038
2595
|
const size = statSync4(f).size;
|
|
2039
2596
|
if (from >= size) return { events: [], offset: from };
|
|
2040
2597
|
const fd = openSync2(f, "r");
|
|
2041
2598
|
try {
|
|
2042
|
-
const buf = Buffer.alloc(Math.min(size - from, 8 * 1024 * 1024));
|
|
2043
|
-
const n = readSync2(fd, buf, 0, buf.length, from);
|
|
2044
|
-
const text = buf.subarray(0, n).toString("utf8");
|
|
2045
2599
|
const events = [];
|
|
2046
2600
|
let offset = from;
|
|
2047
|
-
let
|
|
2048
|
-
while (events.length < maxEvents) {
|
|
2049
|
-
const
|
|
2050
|
-
|
|
2051
|
-
const
|
|
2052
|
-
pos =
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2601
|
+
let chunk = READ_CHUNK;
|
|
2602
|
+
while (events.length < maxEvents && offset < size) {
|
|
2603
|
+
const buf = Buffer.alloc(Math.min(size - offset, chunk));
|
|
2604
|
+
const n = readSync2(fd, buf, 0, buf.length, offset);
|
|
2605
|
+
const text = buf.subarray(0, n).toString("utf8");
|
|
2606
|
+
let pos = 0;
|
|
2607
|
+
let used = 0;
|
|
2608
|
+
while (events.length < maxEvents) {
|
|
2609
|
+
const nl = text.indexOf("\n", pos);
|
|
2610
|
+
if (nl < 0) break;
|
|
2611
|
+
const line = text.slice(pos, nl);
|
|
2612
|
+
pos = nl + 1;
|
|
2613
|
+
used += Buffer.byteLength(line, "utf8") + 1;
|
|
2614
|
+
if (!line.trim()) continue;
|
|
2615
|
+
try {
|
|
2616
|
+
events.push(JSON.parse(line));
|
|
2617
|
+
} catch {
|
|
2618
|
+
}
|
|
2058
2619
|
}
|
|
2620
|
+
offset += used;
|
|
2621
|
+
if (used) continue;
|
|
2622
|
+
if (offset + n >= size || chunk >= READ_MAX) break;
|
|
2623
|
+
chunk *= 2;
|
|
2059
2624
|
}
|
|
2060
2625
|
return { events, offset };
|
|
2061
2626
|
} finally {
|
|
@@ -2099,7 +2664,7 @@ var Cursors = class {
|
|
|
2099
2664
|
root;
|
|
2100
2665
|
sink;
|
|
2101
2666
|
file() {
|
|
2102
|
-
return
|
|
2667
|
+
return path10.join(this.root, "cursors", `${safeId(this.sink)}.json`);
|
|
2103
2668
|
}
|
|
2104
2669
|
read() {
|
|
2105
2670
|
try {
|
|
@@ -2110,7 +2675,7 @@ var Cursors = class {
|
|
|
2110
2675
|
}
|
|
2111
2676
|
write(c) {
|
|
2112
2677
|
const f = this.file();
|
|
2113
|
-
mkdirSync5(
|
|
2678
|
+
mkdirSync5(path10.dirname(f), { recursive: true, mode: 448 });
|
|
2114
2679
|
const tmp = `${f}.${process.pid}.tmp`;
|
|
2115
2680
|
writeFileSync5(tmp, JSON.stringify(c), { mode: 384 });
|
|
2116
2681
|
renameSync(tmp, f);
|
|
@@ -2120,11 +2685,14 @@ var Cursors = class {
|
|
|
2120
2685
|
}
|
|
2121
2686
|
};
|
|
2122
2687
|
function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
2123
|
-
|
|
2688
|
+
return takeNamedLock(root, `${safeId(ref.harness)}-${safeId(ref.session)}`, staleMs);
|
|
2689
|
+
}
|
|
2690
|
+
function takeNamedLock(root, name, staleMs = 5 * 6e4) {
|
|
2691
|
+
const dir = path10.join(root, "locks");
|
|
2124
2692
|
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
2125
|
-
const f =
|
|
2693
|
+
const f = path10.join(dir, `${name}.lock`);
|
|
2126
2694
|
try {
|
|
2127
|
-
if (Date.now() - statSync4(f).mtimeMs > staleMs) unlinkSync3(f);
|
|
2695
|
+
if (Date.now() - statSync4(f).mtimeMs > staleMs || !processAlive(Number(readFileSync7(f, "utf8")))) unlinkSync3(f);
|
|
2128
2696
|
else return null;
|
|
2129
2697
|
} catch {
|
|
2130
2698
|
}
|
|
@@ -2140,9 +2708,18 @@ function takeLock(root, ref, staleMs = 5 * 6e4) {
|
|
|
2140
2708
|
}
|
|
2141
2709
|
};
|
|
2142
2710
|
}
|
|
2711
|
+
function processAlive(pid) {
|
|
2712
|
+
if (!Number.isInteger(pid) || pid <= 0) return true;
|
|
2713
|
+
try {
|
|
2714
|
+
process.kill(pid, 0);
|
|
2715
|
+
return true;
|
|
2716
|
+
} catch (e) {
|
|
2717
|
+
return e.code === "EPERM";
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2143
2720
|
|
|
2144
2721
|
// ../core/src/privacy/pii.ts
|
|
2145
|
-
import { createHash as
|
|
2722
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
2146
2723
|
var RULESET_PII = "pii@1";
|
|
2147
2724
|
var EMAIL = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g;
|
|
2148
2725
|
var PHONE = /(?<![\w.])(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d{2,4}(?:[ .-]?\d{2,4}){1,3}(?!\w)/g;
|
|
@@ -2164,7 +2741,7 @@ function reducePii(text, home) {
|
|
|
2164
2741
|
return out;
|
|
2165
2742
|
}
|
|
2166
2743
|
function hashMachine(name) {
|
|
2167
|
-
return
|
|
2744
|
+
return createHash3("sha256").update(name).digest("hex").slice(0, 8);
|
|
2168
2745
|
}
|
|
2169
2746
|
function reducePiiDeep(v, home) {
|
|
2170
2747
|
if (typeof v === "string") return reducePii(v, home);
|
|
@@ -2253,12 +2830,12 @@ function redactAssignments(text) {
|
|
|
2253
2830
|
let last = 0;
|
|
2254
2831
|
NAME_SEP.lastIndex = 0;
|
|
2255
2832
|
for (let m = NAME_SEP.exec(text); m; m = NAME_SEP.exec(text)) {
|
|
2256
|
-
const name = m[1]
|
|
2833
|
+
const name = m[1];
|
|
2257
2834
|
if (!isSecretName(name)) continue;
|
|
2258
2835
|
VALUE.lastIndex = NAME_SEP.lastIndex;
|
|
2259
2836
|
const v = VALUE.exec(text);
|
|
2260
2837
|
if (!v) continue;
|
|
2261
|
-
const value = v[1] ?? v[2] ?? v[3] ?? v[4]
|
|
2838
|
+
const value = v[1] ?? v[2] ?? v[3] ?? v[4];
|
|
2262
2839
|
NAME_SEP.lastIndex = VALUE.lastIndex;
|
|
2263
2840
|
if (isPlaceholder(value) || value.includes(REDACTED)) continue;
|
|
2264
2841
|
if (/tokens?$/i.test(name) && /^\d+(?:[.,]\d+)*[km]?$/i.test(value)) continue;
|
|
@@ -2314,7 +2891,7 @@ function redactSecrets(text) {
|
|
|
2314
2891
|
let out = text;
|
|
2315
2892
|
for (const re of TOKEN_PATTERNS) out = out.replace(re, REDACTED);
|
|
2316
2893
|
out = out.replace(URL_CREDENTIALS, (_m, head, _pw, at) => `${head}${REDACTED}${at}`);
|
|
2317
|
-
out = out.replace(AUTH_HEADER, (
|
|
2894
|
+
out = out.replace(AUTH_HEADER, (_m, head) => `${head}${REDACTED}`);
|
|
2318
2895
|
out = redactAssignments(out);
|
|
2319
2896
|
out = out.replace(
|
|
2320
2897
|
PROSE,
|
|
@@ -2343,6 +2920,7 @@ var TYPE_TIER = {
|
|
|
2343
2920
|
"turn.started": 0,
|
|
2344
2921
|
"turn.ended": 0,
|
|
2345
2922
|
"turn.transcript": 2,
|
|
2923
|
+
"turn.progress": 2,
|
|
2346
2924
|
"tool.started": 1,
|
|
2347
2925
|
"tool.ended": 1,
|
|
2348
2926
|
"files.changed": 1,
|
|
@@ -2359,9 +2937,10 @@ var DATA_ALLOW = {
|
|
|
2359
2937
|
"session.ended": { 0: ["reason"] },
|
|
2360
2938
|
"session.backfill": { 0: ["started_at", "last_at", "turns"] },
|
|
2361
2939
|
"session.forgotten": { 0: [] },
|
|
2362
|
-
"turn.started": { 0: ["turn_id"], 1: ["prompt_chars"] },
|
|
2363
|
-
"turn.ended": { 0: ["turn_id", "reason", "ms"], 1: ["error"] },
|
|
2940
|
+
"turn.started": { 0: ["turn_id", "submitted_by", "injected"], 1: ["prompt_chars"] },
|
|
2941
|
+
"turn.ended": { 0: ["turn_id", "reason", "ms", "background"], 1: ["error"] },
|
|
2364
2942
|
"turn.transcript": { 2: ["turn", "user", "assistant", "at"] },
|
|
2943
|
+
"turn.progress": { 2: ["turn", "user", "assistant", "at"] },
|
|
2365
2944
|
"tool.started": { 1: ["tool", "call_id", "turn_id", "agent_id"], 3: ["input"] },
|
|
2366
2945
|
"tool.ended": { 1: ["tool", "call_id", "turn_id", "agent_id", "ms", "ok", "error"], 3: ["input", "output"] },
|
|
2367
2946
|
"files.changed": { 1: ["paths"] },
|
|
@@ -2383,7 +2962,9 @@ var SESSION_ALLOW = [
|
|
|
2383
2962
|
"title_source",
|
|
2384
2963
|
"url",
|
|
2385
2964
|
"account_id",
|
|
2386
|
-
"
|
|
2965
|
+
"attended",
|
|
2966
|
+
"parent_id",
|
|
2967
|
+
"launched_by"
|
|
2387
2968
|
];
|
|
2388
2969
|
var MAX_SIDE = 2e4;
|
|
2389
2970
|
var MAX_IO = 64 * 1024;
|
|
@@ -2402,10 +2983,12 @@ function cap(v, max) {
|
|
|
2402
2983
|
}
|
|
2403
2984
|
return v;
|
|
2404
2985
|
}
|
|
2986
|
+
function capTail(v, max) {
|
|
2987
|
+
return v.length > max ? `\u2026${v.slice(v.length - max + 1)}` : v;
|
|
2988
|
+
}
|
|
2405
2989
|
function filterEvent(event, tier, pii, opts = {}) {
|
|
2406
2990
|
const minTier = TYPE_TIER[event.type];
|
|
2407
2991
|
if (minTier !== void 0 && minTier > tier) return null;
|
|
2408
|
-
if (event.type === "native" && tier < 3) return null;
|
|
2409
2992
|
const session = {};
|
|
2410
2993
|
for (const k of SESSION_ALLOW)
|
|
2411
2994
|
if (event.session[k] !== void 0)
|
|
@@ -2415,7 +2998,8 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2415
2998
|
if (keys === null) data = { ...event.data };
|
|
2416
2999
|
else for (const k of keys) if (event.data[k] !== void 0) data[k] = event.data[k];
|
|
2417
3000
|
if (typeof data.user === "string") data.user = cap(data.user, MAX_SIDE);
|
|
2418
|
-
if (typeof data.assistant === "string")
|
|
3001
|
+
if (typeof data.assistant === "string")
|
|
3002
|
+
data.assistant = event.type === "turn.progress" ? capTail(data.assistant, MAX_SIDE) : cap(data.assistant, MAX_SIDE);
|
|
2419
3003
|
if (data.input !== void 0) data.input = cap(data.input, MAX_IO);
|
|
2420
3004
|
if (data.output !== void 0) data.output = cap(data.output, MAX_IO);
|
|
2421
3005
|
if (typeof data.message === "string") data.message = cap(data.message, 200);
|
|
@@ -2436,9 +3020,15 @@ function filterEvent(event, tier, pii, opts = {}) {
|
|
|
2436
3020
|
return out;
|
|
2437
3021
|
}
|
|
2438
3022
|
|
|
3023
|
+
// ../core/src/short-id.ts
|
|
3024
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
3025
|
+
function shortId(harness, id) {
|
|
3026
|
+
return createHash4("sha256").update(`sessionpipe.session:${harness}:${id}`).digest("hex").slice(0, 8);
|
|
3027
|
+
}
|
|
3028
|
+
|
|
2439
3029
|
// ../core/src/sinks/file.ts
|
|
2440
3030
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
2441
|
-
import
|
|
3031
|
+
import path11 from "node:path";
|
|
2442
3032
|
var FileSink = class {
|
|
2443
3033
|
constructor(name, file, tier, pii) {
|
|
2444
3034
|
this.name = name;
|
|
@@ -2452,7 +3042,7 @@ var FileSink = class {
|
|
|
2452
3042
|
pii;
|
|
2453
3043
|
async send(events) {
|
|
2454
3044
|
try {
|
|
2455
|
-
mkdirSync6(
|
|
3045
|
+
mkdirSync6(path11.dirname(this.file), { recursive: true });
|
|
2456
3046
|
appendFileSync2(this.file, `${events.map((e) => JSON.stringify(e)).join("\n")}
|
|
2457
3047
|
`);
|
|
2458
3048
|
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
@@ -2476,15 +3066,19 @@ var StdoutSink = class {
|
|
|
2476
3066
|
pii;
|
|
2477
3067
|
write;
|
|
2478
3068
|
async send(events) {
|
|
2479
|
-
|
|
2480
|
-
|
|
3069
|
+
try {
|
|
3070
|
+
for (const e of events) this.write(JSON.stringify(e));
|
|
3071
|
+
return { ok: true, acknowledged: events.length, accepted: events.length, duplicates: 0, rejected: [] };
|
|
3072
|
+
} catch (e) {
|
|
3073
|
+
return { ok: false, acknowledged: 0, error: String(e.message ?? e), action: "retry" };
|
|
3074
|
+
}
|
|
2481
3075
|
}
|
|
2482
3076
|
};
|
|
2483
3077
|
|
|
2484
3078
|
// src/run.ts
|
|
2485
3079
|
import {
|
|
2486
3080
|
appendFileSync as appendFileSync3,
|
|
2487
|
-
existsSync as
|
|
3081
|
+
existsSync as existsSync10,
|
|
2488
3082
|
mkdirSync as mkdirSync8,
|
|
2489
3083
|
readdirSync as readdirSync6,
|
|
2490
3084
|
readFileSync as readFileSync9,
|
|
@@ -2494,12 +3088,55 @@ import {
|
|
|
2494
3088
|
writeFileSync as writeFileSync7
|
|
2495
3089
|
} from "node:fs";
|
|
2496
3090
|
import os3 from "node:os";
|
|
2497
|
-
import
|
|
3091
|
+
import path14 from "node:path";
|
|
2498
3092
|
|
|
2499
3093
|
// src/control/aliases.ts
|
|
2500
3094
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync7, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
2501
|
-
import
|
|
2502
|
-
|
|
3095
|
+
import path12 from "node:path";
|
|
3096
|
+
|
|
3097
|
+
// src/secrets.ts
|
|
3098
|
+
import { spawnSync } from "node:child_process";
|
|
3099
|
+
import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
|
|
3100
|
+
var SERVICE = "sessionpipe";
|
|
3101
|
+
var defaultRunner = (cmd, args, input) => {
|
|
3102
|
+
const r = spawnSync(cmd, args, {
|
|
3103
|
+
input: input ?? "",
|
|
3104
|
+
encoding: "utf8",
|
|
3105
|
+
timeout: 8e3,
|
|
3106
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
3107
|
+
});
|
|
3108
|
+
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
3109
|
+
};
|
|
3110
|
+
var sinkAccount = (name, what = "token") => {
|
|
3111
|
+
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash5("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
3112
|
+
return `sink:${name}:${what}${scope}`;
|
|
3113
|
+
};
|
|
3114
|
+
function secretGet(store, account, run = defaultRunner) {
|
|
3115
|
+
const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
|
|
3116
|
+
if (r.status !== 0) return null;
|
|
3117
|
+
const v = r.stdout.replace(/\r?\n$/, "");
|
|
3118
|
+
return v || null;
|
|
3119
|
+
}
|
|
3120
|
+
function sinkWithSecrets(s, run = defaultRunner) {
|
|
3121
|
+
if (!s.token_in) return s;
|
|
3122
|
+
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
3123
|
+
if (!token) return null;
|
|
3124
|
+
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
3125
|
+
return { ...s, token, ...secret ? { secret } : {} };
|
|
3126
|
+
}
|
|
3127
|
+
|
|
3128
|
+
// src/control/store.ts
|
|
3129
|
+
function startOptionsOf(v) {
|
|
3130
|
+
const o = v;
|
|
3131
|
+
if (!o || typeof o !== "object" || levelRank(o.permission) < 0 && o.permission !== "inherit") return null;
|
|
3132
|
+
return {
|
|
3133
|
+
permission: o.permission,
|
|
3134
|
+
...typeof o.model === "string" && MODEL_RE.test(o.model) ? { model: o.model } : {}
|
|
3135
|
+
};
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
// src/control/aliases.ts
|
|
3139
|
+
var aliasFile = (controlDir) => path12.join(controlDir, "aliases.json");
|
|
2503
3140
|
function readAliases(file) {
|
|
2504
3141
|
try {
|
|
2505
3142
|
const j = JSON.parse(readFileSync8(file, "utf8"));
|
|
@@ -2508,7 +3145,7 @@ function readAliases(file) {
|
|
|
2508
3145
|
for (const [k, v] of Object.entries(j)) {
|
|
2509
3146
|
const a = v;
|
|
2510
3147
|
if (a && typeof a.id === "string" && a.id && typeof a.cwd === "string")
|
|
2511
|
-
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "" };
|
|
3148
|
+
out[k] = { id: a.id, cwd: a.cwd, at: typeof a.at === "string" ? a.at : "", ...startOptionsOf(a) };
|
|
2512
3149
|
}
|
|
2513
3150
|
return out;
|
|
2514
3151
|
} catch {
|
|
@@ -2524,11 +3161,11 @@ function receiverIdOf(all, harness, id) {
|
|
|
2524
3161
|
// src/control/local.ts
|
|
2525
3162
|
import net from "node:net";
|
|
2526
3163
|
import os2 from "node:os";
|
|
2527
|
-
import
|
|
3164
|
+
import path13 from "node:path";
|
|
2528
3165
|
function socketPath(state, env = process.env) {
|
|
2529
3166
|
if (env.SESSIONPIPE_SOCKET) return env.SESSIONPIPE_SOCKET;
|
|
2530
3167
|
if (process.platform === "win32") return `\\\\.\\pipe\\sessionpipe-control-${os2.userInfo().username}`;
|
|
2531
|
-
return
|
|
3168
|
+
return path13.join(state, "control.sock");
|
|
2532
3169
|
}
|
|
2533
3170
|
function ask(sock, req, timeoutMs) {
|
|
2534
3171
|
return new Promise((resolve) => {
|
|
@@ -2561,10 +3198,17 @@ function ask(sock, req, timeoutMs) {
|
|
|
2561
3198
|
}
|
|
2562
3199
|
|
|
2563
3200
|
// src/http-sink.ts
|
|
2564
|
-
import { createHash as
|
|
3201
|
+
import { createHash as createHash6, createHmac } from "node:crypto";
|
|
2565
3202
|
var TIMEOUT_MS = 15e3;
|
|
2566
3203
|
function webhookId(events) {
|
|
2567
|
-
return `msg_${
|
|
3204
|
+
return `msg_${createHash6("sha256").update(events.map((e) => e.id).join(",")).digest("base64url").slice(0, 32)}`;
|
|
3205
|
+
}
|
|
3206
|
+
function retryAfterMs(header, now = Date.now()) {
|
|
3207
|
+
if (header === null || header.trim() === "") return 6e4;
|
|
3208
|
+
const secs = Number(header);
|
|
3209
|
+
if (Number.isFinite(secs)) return Math.max(0, secs) * 1e3;
|
|
3210
|
+
const at = Date.parse(header);
|
|
3211
|
+
return Number.isFinite(at) ? Math.max(0, at - now) : 6e4;
|
|
2568
3212
|
}
|
|
2569
3213
|
var HttpSink = class {
|
|
2570
3214
|
constructor(cfg, tier) {
|
|
@@ -2637,7 +3281,7 @@ var HttpSink = class {
|
|
|
2637
3281
|
status: 429,
|
|
2638
3282
|
error: err,
|
|
2639
3283
|
action: "retry",
|
|
2640
|
-
retryAfterMs:
|
|
3284
|
+
retryAfterMs: retryAfterMs(r.headers.get("retry-after"))
|
|
2641
3285
|
};
|
|
2642
3286
|
return { ok: false, acknowledged: 0, status: r.status, error: err, action: "retry" };
|
|
2643
3287
|
} catch (e) {
|
|
@@ -2648,81 +3292,87 @@ var HttpSink = class {
|
|
|
2648
3292
|
}
|
|
2649
3293
|
};
|
|
2650
3294
|
|
|
2651
|
-
// src/secrets.ts
|
|
2652
|
-
import { spawnSync } from "node:child_process";
|
|
2653
|
-
import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
|
|
2654
|
-
var SERVICE = "sessionpipe";
|
|
2655
|
-
var defaultRunner = (cmd, args, input) => {
|
|
2656
|
-
const r = spawnSync(cmd, args, {
|
|
2657
|
-
input: input ?? "",
|
|
2658
|
-
encoding: "utf8",
|
|
2659
|
-
timeout: 8e3,
|
|
2660
|
-
stdio: ["pipe", "pipe", "ignore"]
|
|
2661
|
-
});
|
|
2662
|
-
return { status: r.error ? null : r.status, stdout: r.stdout ?? "" };
|
|
2663
|
-
};
|
|
2664
|
-
var sinkAccount = (name, what = "token") => {
|
|
2665
|
-
const scope = process.env.SESSIONPIPE_CONFIG ? `@${createHash4("sha256").update(configFile()).digest("hex").slice(0, 8)}` : "";
|
|
2666
|
-
return `sink:${name}:${what}${scope}`;
|
|
2667
|
-
};
|
|
2668
|
-
function secretGet(store, account, run = defaultRunner) {
|
|
2669
|
-
const r = store === "keychain" ? run("security", ["find-generic-password", "-s", SERVICE, "-a", account, "-w"]) : run("secret-tool", ["lookup", "service", SERVICE, "account", account]);
|
|
2670
|
-
if (r.status !== 0) return null;
|
|
2671
|
-
const v = r.stdout.replace(/\r?\n$/, "");
|
|
2672
|
-
return v || null;
|
|
2673
|
-
}
|
|
2674
|
-
function sinkWithSecrets(s, run = defaultRunner) {
|
|
2675
|
-
if (!s.token_in) return s;
|
|
2676
|
-
const token = secretGet(s.token_in, sinkAccount(s.name), run);
|
|
2677
|
-
if (!token) return null;
|
|
2678
|
-
const secret = secretGet(s.token_in, sinkAccount(s.name, "secret"), run);
|
|
2679
|
-
return { ...s, token, ...secret ? { secret } : {} };
|
|
2680
|
-
}
|
|
2681
|
-
|
|
2682
3295
|
// src/run.ts
|
|
2683
|
-
async function tellDaemon(state,
|
|
3296
|
+
async function tellDaemon(state, job, session, cwd, transcript) {
|
|
2684
3297
|
const sock = socketPath(state);
|
|
2685
|
-
if (process.platform !== "win32" && !
|
|
3298
|
+
if (process.platform !== "win32" && !existsSync10(sock)) return;
|
|
2686
3299
|
await ask(
|
|
2687
3300
|
sock,
|
|
2688
3301
|
{
|
|
2689
3302
|
op: "event",
|
|
2690
|
-
session: `${
|
|
2691
|
-
event:
|
|
3303
|
+
session: `${job.harness}:${session}`,
|
|
3304
|
+
event: job.event,
|
|
2692
3305
|
...cwd ? { cwd } : {},
|
|
2693
3306
|
...transcript ? { transcript } : {}
|
|
2694
3307
|
},
|
|
2695
3308
|
300
|
|
2696
3309
|
);
|
|
2697
3310
|
}
|
|
2698
|
-
|
|
3311
|
+
var MAX_TRIES = 10;
|
|
3312
|
+
function enqueueJob(state, job) {
|
|
2699
3313
|
const jobs = jobsDir(state);
|
|
2700
3314
|
mkdirSync8(jobs, { recursive: true, mode: 448 });
|
|
2701
|
-
const f =
|
|
2702
|
-
writeFileSync7(f, JSON.stringify(
|
|
3315
|
+
const f = path14.join(jobs, `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
3316
|
+
writeFileSync7(f, JSON.stringify(job), { mode: 384 });
|
|
2703
3317
|
return f;
|
|
2704
3318
|
}
|
|
2705
|
-
|
|
3319
|
+
function claimJob(file) {
|
|
3320
|
+
const claimed = `${file}.${process.pid}.running`;
|
|
3321
|
+
try {
|
|
3322
|
+
renameSync3(file, claimed);
|
|
3323
|
+
} catch {
|
|
3324
|
+
return null;
|
|
3325
|
+
}
|
|
3326
|
+
try {
|
|
3327
|
+
return { job: JSON.parse(readFileSync9(claimed, "utf8")), file: claimed };
|
|
3328
|
+
} catch {
|
|
3329
|
+
releaseJob(claimed);
|
|
3330
|
+
return null;
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
function releaseJob(claimed) {
|
|
3334
|
+
if (!claimed) return;
|
|
3335
|
+
try {
|
|
3336
|
+
unlinkSync4(claimed);
|
|
3337
|
+
} catch {
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
async function sweepJobs(state, olderThanMs = 3e3, limit = 20, abandonedMs = 6e4) {
|
|
2706
3341
|
const jobs = jobsDir(state);
|
|
2707
3342
|
let names = [];
|
|
2708
3343
|
try {
|
|
2709
|
-
names = readdirSync6(jobs)
|
|
3344
|
+
names = readdirSync6(jobs);
|
|
2710
3345
|
} catch {
|
|
2711
3346
|
return 0;
|
|
2712
3347
|
}
|
|
3348
|
+
const queued = names.filter((n) => n.endsWith(".json"));
|
|
3349
|
+
for (const n of names) {
|
|
3350
|
+
const m = /^(.*\.json)\.(\d+)\.running$/.exec(n);
|
|
3351
|
+
if (!m) continue;
|
|
3352
|
+
const f = path14.join(jobs, n);
|
|
3353
|
+
try {
|
|
3354
|
+
if (processAlive(Number(m[2])) && Date.now() - statSync5(f).mtimeMs < abandonedMs) continue;
|
|
3355
|
+
renameSync3(f, path14.join(jobs, m[1]));
|
|
3356
|
+
queued.push(m[1]);
|
|
3357
|
+
} catch {
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
2713
3360
|
let ran = 0;
|
|
2714
|
-
for (const n of
|
|
2715
|
-
const f =
|
|
2716
|
-
let job2 = null;
|
|
3361
|
+
for (const n of queued.sort().slice(0, limit)) {
|
|
3362
|
+
const f = path14.join(jobs, n);
|
|
2717
3363
|
try {
|
|
2718
3364
|
if (Date.now() - statSync5(f).mtimeMs < olderThanMs) continue;
|
|
2719
|
-
job2 = JSON.parse(readFileSync9(f, "utf8"));
|
|
2720
|
-
unlinkSync4(f);
|
|
2721
3365
|
} catch {
|
|
2722
3366
|
continue;
|
|
2723
3367
|
}
|
|
2724
|
-
|
|
2725
|
-
|
|
3368
|
+
const c = claimJob(f);
|
|
3369
|
+
if (!c) continue;
|
|
3370
|
+
try {
|
|
3371
|
+
await runJob(c.job, { state, claimed: c.file });
|
|
3372
|
+
} catch {
|
|
3373
|
+
} finally {
|
|
3374
|
+
releaseJob(c.file);
|
|
3375
|
+
}
|
|
2726
3376
|
ran++;
|
|
2727
3377
|
}
|
|
2728
3378
|
return ran;
|
|
@@ -2730,7 +3380,7 @@ async function sweepJobs(state, olderThanMs = 3e3, limit = 20) {
|
|
|
2730
3380
|
function log(state, line) {
|
|
2731
3381
|
try {
|
|
2732
3382
|
mkdirSync8(state, { recursive: true, mode: 448 });
|
|
2733
|
-
const f =
|
|
3383
|
+
const f = path14.join(state, "log");
|
|
2734
3384
|
try {
|
|
2735
3385
|
if (statSync5(f).size > 1024 * 1024) renameSync3(f, `${f}.1`);
|
|
2736
3386
|
} catch {
|
|
@@ -2741,7 +3391,7 @@ function log(state, line) {
|
|
|
2741
3391
|
}
|
|
2742
3392
|
}
|
|
2743
3393
|
function factsState(state) {
|
|
2744
|
-
const file = (h, s) =>
|
|
3394
|
+
const file = (h, s) => path14.join(state, "facts", safeId(h), `${safeId(s)}.json`);
|
|
2745
3395
|
return {
|
|
2746
3396
|
get(h, s) {
|
|
2747
3397
|
try {
|
|
@@ -2754,7 +3404,7 @@ function factsState(state) {
|
|
|
2754
3404
|
const cur = this.get(h, s);
|
|
2755
3405
|
const next = { ...cur, ...patch };
|
|
2756
3406
|
if (JSON.stringify(next) === JSON.stringify(cur)) return;
|
|
2757
|
-
mkdirSync8(
|
|
3407
|
+
mkdirSync8(path14.dirname(file(h, s)), { recursive: true, mode: 448 });
|
|
2758
3408
|
writeFileSync7(file(h, s), JSON.stringify(next), { mode: 384 });
|
|
2759
3409
|
}
|
|
2760
3410
|
};
|
|
@@ -2776,40 +3426,93 @@ function buildSinks(sinks, o = {}) {
|
|
|
2776
3426
|
}
|
|
2777
3427
|
return out;
|
|
2778
3428
|
}
|
|
2779
|
-
|
|
3429
|
+
var PROGRESS_MS = 2e4;
|
|
3430
|
+
function openTurn(read, o) {
|
|
3431
|
+
const last = read.turns[read.turns.length - 1];
|
|
3432
|
+
const fresh = (user, at) => ({
|
|
3433
|
+
turn: o.base + read.turns.length,
|
|
3434
|
+
user: user.slice(0, MAX_TURN_SIDE),
|
|
3435
|
+
assistant: "",
|
|
3436
|
+
at
|
|
3437
|
+
});
|
|
3438
|
+
if (read.open) return fresh(read.open.user, read.open.at);
|
|
3439
|
+
const prompt = o.prompt?.trim();
|
|
3440
|
+
if (prompt && !isInjected(prompt) && !(last && last.user.includes(prompt.slice(0, 200)))) return fresh(prompt, o.at);
|
|
3441
|
+
if (!last) return null;
|
|
3442
|
+
return {
|
|
3443
|
+
turn: o.base + read.turns.length - 1,
|
|
3444
|
+
user: last.user.slice(0, MAX_TURN_SIDE),
|
|
3445
|
+
assistant: capTail(last.assistant, MAX_TURN_SIDE),
|
|
3446
|
+
at: last.at
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
var MAX_TURN_SIDE = 2e4;
|
|
3450
|
+
var JOB_OWN_ENV = [
|
|
3451
|
+
"CLAUDE_CODE_SESSION_ATTENDED",
|
|
3452
|
+
"CLAUDE_CODE_HOST_SESSION_ID",
|
|
3453
|
+
"SESSIONPIPE_CONTROL_JOB",
|
|
3454
|
+
"SESSIONPIPE_LAUNCHED_BY"
|
|
3455
|
+
];
|
|
3456
|
+
async function runJob(job, opts = {}) {
|
|
2780
3457
|
const state = opts.state ?? stateDir();
|
|
2781
3458
|
const cfg = readConfig();
|
|
2782
|
-
const adapter = adapterByName(
|
|
2783
|
-
if (!adapter)
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
3459
|
+
const adapter = adapterByName(job.harness);
|
|
3460
|
+
if (!adapter) {
|
|
3461
|
+
log(state, `${job.harness} ${job.event}: no adapter for harness "${job.harness}"; event dropped`);
|
|
3462
|
+
return { events: [], delivered: {} };
|
|
3463
|
+
}
|
|
3464
|
+
const env = { ...process.env };
|
|
3465
|
+
for (const k of JOB_OWN_ENV) delete env[k];
|
|
3466
|
+
const r = readHook(adapter, { argv: job.argv, stdin: job.stdin, env: { ...env, ...job.env }, cwd: job.cwd });
|
|
3467
|
+
if (!r || !r.session.id) {
|
|
3468
|
+
let keys = [];
|
|
3469
|
+
try {
|
|
3470
|
+
const payload = JSON.parse(job.argv.find((a) => a.startsWith("{")) ?? job.stdin);
|
|
3471
|
+
if (payload && typeof payload === "object" && !Array.isArray(payload)) keys = Object.keys(payload);
|
|
3472
|
+
} catch {
|
|
3473
|
+
}
|
|
3474
|
+
log(
|
|
3475
|
+
state,
|
|
3476
|
+
`${job.harness} ${job.event}: the ${job.harness} adapter did not claim this payload; event dropped; keys: ${keys.join(", ") || "(none)"}`
|
|
3477
|
+
);
|
|
3478
|
+
return { events: [], delivered: {} };
|
|
3479
|
+
}
|
|
3480
|
+
const id = receiverIdOf(readAliases(aliasFile(path14.join(state, "control"))), job.harness, r.session.id) ?? r.session.id;
|
|
3481
|
+
const claudeEnd = job.harness === "claude-code" && r.events.some((e) => e.type === "session.ended");
|
|
3482
|
+
if (!claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
2788
3483
|
const outbox = new Outbox(state);
|
|
2789
|
-
const ref = { harness:
|
|
3484
|
+
const ref = { harness: job.harness, session: id };
|
|
2790
3485
|
let release = takeLock(state, ref);
|
|
2791
|
-
|
|
2792
|
-
|
|
3486
|
+
const lockWait = opts.lockWaitMs ?? LOCK_WAIT_MS;
|
|
3487
|
+
for (const until = Date.now() + lockWait; !release && Date.now() < until; ) {
|
|
3488
|
+
await new Promise((res) => setTimeout(res, 50));
|
|
2793
3489
|
release = takeLock(state, ref);
|
|
2794
3490
|
}
|
|
2795
3491
|
if (!release) {
|
|
2796
|
-
log(state, `${
|
|
2797
|
-
enqueueJob(state,
|
|
2798
|
-
|
|
3492
|
+
log(state, `${job.harness} ${job.event}: session ${id} locked for ${lockWait / 1e3} s, re-queued`);
|
|
3493
|
+
const requeued = enqueueJob(state, { ...job, tries: (job.tries ?? 0) + 1 });
|
|
3494
|
+
releaseJob(opts.claimed);
|
|
3495
|
+
return { events: [], delivered: {}, requeued };
|
|
2799
3496
|
}
|
|
3497
|
+
const events = [];
|
|
2800
3498
|
try {
|
|
2801
3499
|
const facts = factsState(state);
|
|
2802
|
-
const mem = facts.get(
|
|
2803
|
-
|
|
3500
|
+
const mem = facts.get(job.harness, id);
|
|
3501
|
+
if (claudeEnd && !existsSync10(outbox.file(ref)) && Object.keys(mem).length === 0 && !(r.transcript && existsSync10(r.transcript))) {
|
|
3502
|
+
releaseJob(opts.claimed);
|
|
3503
|
+
return { events: [], delivered: {} };
|
|
3504
|
+
}
|
|
3505
|
+
if (claudeEnd) await tellDaemon(state, job, id, r.session.cwd, r.transcript);
|
|
3506
|
+
const heavy = !/^(PreToolUse|PostToolUse|PostToolUseFailure|BeforeTool|AfterTool|PostInvocation)$/.test(job.event);
|
|
2804
3507
|
let learned = {};
|
|
2805
3508
|
if (heavy) {
|
|
2806
|
-
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts);
|
|
3509
|
+
const f = adapter.facts(r.session, r.transcript, r.hints ?? {}, facts, { ...process.env, ...job.env });
|
|
2807
3510
|
const { started_at: _s, last_at: _l, first_ask: _fa, harness_version, ...rest } = f;
|
|
2808
3511
|
learned = rest;
|
|
2809
|
-
if (harness_version) facts.save(
|
|
2810
|
-
facts.save(
|
|
2811
|
-
if (
|
|
2812
|
-
facts.save(
|
|
3512
|
+
if (harness_version) facts.save(job.harness, id, { harness_version });
|
|
3513
|
+
facts.save(job.harness, id, { session: { ...mem.session, ...learned } });
|
|
3514
|
+
if (CWD_FROM_HOOKS.has(job.harness) && r.session.cwd && path14.isAbsolute(r.session.cwd))
|
|
3515
|
+
facts.save(job.harness, id, { cwd: r.session.cwd });
|
|
2813
3516
|
}
|
|
2814
3517
|
const known = mem.session ?? {};
|
|
2815
3518
|
const cwdRaw = learned.cwd ?? r.session.cwd ?? known.cwd;
|
|
@@ -2823,21 +3526,52 @@ async function runJob(job2, opts = {}) {
|
|
|
2823
3526
|
seq: 0,
|
|
2824
3527
|
machine: machineName(cfg, os3.hostname()),
|
|
2825
3528
|
...cwdRaw ? { cwd: tilde(cwdRaw) } : {},
|
|
2826
|
-
...r.session.model ? { model: r.session.model } : {}
|
|
3529
|
+
...r.session.model ? { model: r.session.model } : {},
|
|
3530
|
+
...r.session.launched_by ? { launched_by: r.session.launched_by } : {}
|
|
2827
3531
|
};
|
|
2828
|
-
const version = facts.get(
|
|
2829
|
-
const events = [];
|
|
3532
|
+
const version = facts.get(job.harness, id).harness_version ?? void 0;
|
|
2830
3533
|
for (const a of r.events) {
|
|
2831
3534
|
const e = makeEvent(a, {
|
|
2832
|
-
harness: { name:
|
|
3535
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
2833
3536
|
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
2834
|
-
now: opts.now ??
|
|
3537
|
+
now: opts.now ?? job.at
|
|
2835
3538
|
});
|
|
2836
3539
|
events.push(e);
|
|
2837
3540
|
}
|
|
2838
3541
|
const wantsTranscript = cfg.sinks.some((s) => !s.paused && Math.min(s.tier, s.max_tier ?? 3) >= 2);
|
|
3542
|
+
const ending = r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended");
|
|
3543
|
+
const starting = r.events.some((e) => e.type === "turn.started");
|
|
3544
|
+
const startedId = r.events.find((e) => e.type === "turn.started")?.data.turn_id;
|
|
3545
|
+
const absorbed = starting && typeof startedId === "string" && facts.get(job.harness, id).open_turn === startedId;
|
|
3546
|
+
if (wantsTranscript && r.transcript && !ending && (starting || r.events.some((e) => e.type === "tool.ended"))) {
|
|
3547
|
+
const cur = facts.get(job.harness, id);
|
|
3548
|
+
const at = opts.now ?? job.at;
|
|
3549
|
+
if (starting || at - (Number(cur.progress_at) || 0) >= PROGRESS_MS) {
|
|
3550
|
+
const p = openTurn(adapter.readTranscript(r.transcript, Number(cur.transcript_line) || 0), {
|
|
3551
|
+
base: Number(cur.transcript_turns) || 0,
|
|
3552
|
+
prompt: !absorbed && typeof r.hints?.prompt === "string" ? r.hints.prompt : void 0,
|
|
3553
|
+
at
|
|
3554
|
+
});
|
|
3555
|
+
const sig = p && `${p.turn}:${p.user.length}:${p.assistant.length}`;
|
|
3556
|
+
if (p && (starting || sig !== cur.progress_sig)) {
|
|
3557
|
+
events.push(
|
|
3558
|
+
makeEvent(
|
|
3559
|
+
{ type: "turn.progress", harnessEvent: job.event, data: { ...p, at: new Date(p.at).toISOString() } },
|
|
3560
|
+
{
|
|
3561
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
3562
|
+
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
3563
|
+
now: at
|
|
3564
|
+
}
|
|
3565
|
+
)
|
|
3566
|
+
);
|
|
3567
|
+
facts.save(job.harness, id, { progress_at: at, progress_sig: sig });
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
if (starting && typeof startedId === "string" && !absorbed) facts.save(job.harness, id, { open_turn: startedId });
|
|
3572
|
+
if (ending) facts.save(job.harness, id, { open_turn: null });
|
|
2839
3573
|
if (wantsTranscript && r.transcript && r.events.some((e) => e.type === "turn.ended" || e.type === "session.ended")) {
|
|
2840
|
-
const cur = facts.get(
|
|
3574
|
+
const cur = facts.get(job.harness, id);
|
|
2841
3575
|
const fromLine = Number(cur.transcript_line) || 0;
|
|
2842
3576
|
let turnNo = Number(cur.transcript_turns) || 0;
|
|
2843
3577
|
const read = adapter.readTranscript(r.transcript, fromLine);
|
|
@@ -2846,34 +3580,97 @@ async function runJob(job2, opts = {}) {
|
|
|
2846
3580
|
makeEvent(
|
|
2847
3581
|
{
|
|
2848
3582
|
type: "turn.transcript",
|
|
2849
|
-
harnessEvent:
|
|
3583
|
+
harnessEvent: job.event,
|
|
2850
3584
|
data: { turn: turnNo++, user: t.user, assistant: t.assistant, at: new Date(t.at).toISOString() }
|
|
2851
3585
|
},
|
|
2852
3586
|
{
|
|
2853
|
-
harness: { name:
|
|
3587
|
+
harness: { name: job.harness, ...version ? { version } : {} },
|
|
2854
3588
|
session: { ...session, seq: outbox.nextSeq(ref) },
|
|
2855
|
-
now: opts.now ??
|
|
3589
|
+
now: opts.now ?? job.at
|
|
2856
3590
|
}
|
|
2857
3591
|
)
|
|
2858
3592
|
);
|
|
2859
3593
|
}
|
|
2860
|
-
if (read.turns.length) facts.save(
|
|
3594
|
+
if (read.turns.length) facts.save(job.harness, id, { transcript_line: read.line, transcript_turns: turnNo });
|
|
2861
3595
|
}
|
|
2862
3596
|
outbox.append(
|
|
2863
3597
|
ref,
|
|
2864
3598
|
events.map((e) => ({ ...e, session: redactDeep(e.session), data: redactDeep(e.data) }))
|
|
2865
3599
|
);
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
3600
|
+
releaseJob(opts.claimed);
|
|
3601
|
+
log(
|
|
3602
|
+
state,
|
|
3603
|
+
`${job.harness} ${job.event}: ${events.map((e) => e.type).join(",") || "nothing"} (${shortId(job.harness, id)})`
|
|
3604
|
+
);
|
|
2870
3605
|
} finally {
|
|
2871
3606
|
release();
|
|
2872
3607
|
}
|
|
3608
|
+
const delivered = await drain(state, cfg.sinks, outbox, ref) ?? {};
|
|
3609
|
+
if (Math.random() < 0.02) outbox.prune(cfg.keep_days ?? 30);
|
|
3610
|
+
return { events, delivered };
|
|
3611
|
+
}
|
|
3612
|
+
async function drain(state, sinkConfigs, outbox, first) {
|
|
3613
|
+
const more = path14.join(state, "locks", "flush.more");
|
|
3614
|
+
let release = takeNamedLock(state, "flush");
|
|
3615
|
+
if (!release) {
|
|
3616
|
+
try {
|
|
3617
|
+
writeFileSync7(more, "", { mode: 384 });
|
|
3618
|
+
} catch {
|
|
3619
|
+
}
|
|
3620
|
+
release = takeNamedLock(state, "flush");
|
|
3621
|
+
if (!release) return null;
|
|
3622
|
+
}
|
|
3623
|
+
const delivered = {};
|
|
3624
|
+
for (; ; ) {
|
|
3625
|
+
try {
|
|
3626
|
+
unlinkSync4(more);
|
|
3627
|
+
} catch {
|
|
3628
|
+
}
|
|
3629
|
+
try {
|
|
3630
|
+
for (const [k, v] of Object.entries(await flush(state, sinkConfigs, outbox, first)))
|
|
3631
|
+
delivered[k] = (delivered[k] ?? 0) + v;
|
|
3632
|
+
} finally {
|
|
3633
|
+
release();
|
|
3634
|
+
}
|
|
3635
|
+
if (!existsSync10(more)) return delivered;
|
|
3636
|
+
release = takeNamedLock(state, "flush");
|
|
3637
|
+
if (!release) return delivered;
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
var BACKOFF_MS = [2e3, 1e4, 6e4, 5 * 6e4, 30 * 6e4];
|
|
3641
|
+
var backoffFile = (state, sink) => path14.join(state, "backoff", `${safeId(sink)}.json`);
|
|
3642
|
+
function readBackoff(state, sink) {
|
|
3643
|
+
try {
|
|
3644
|
+
const b = JSON.parse(readFileSync9(backoffFile(state, sink), "utf8"));
|
|
3645
|
+
return typeof b.until === "number" ? b : null;
|
|
3646
|
+
} catch {
|
|
3647
|
+
return null;
|
|
3648
|
+
}
|
|
2873
3649
|
}
|
|
3650
|
+
function noteFailure(state, sink, res, prev) {
|
|
3651
|
+
const failures = (prev?.failures ?? 0) + 1;
|
|
3652
|
+
const ladder = BACKOFF_MS[Math.min(failures, BACKOFF_MS.length) - 1];
|
|
3653
|
+
const wait = Number.isFinite(res.retryAfterMs) ? res.retryAfterMs : ladder;
|
|
3654
|
+
const b = { until: Date.now() + wait, failures, why: `${res.status ?? ""} ${res.error ?? ""}`.trim() };
|
|
3655
|
+
const f = backoffFile(state, sink);
|
|
3656
|
+
try {
|
|
3657
|
+
mkdirSync8(path14.dirname(f), { recursive: true, mode: 448 });
|
|
3658
|
+
writeFileSync7(f, JSON.stringify(b), { mode: 384 });
|
|
3659
|
+
} catch {
|
|
3660
|
+
}
|
|
3661
|
+
return b;
|
|
3662
|
+
}
|
|
3663
|
+
function clearBackoff(state, sink) {
|
|
3664
|
+
try {
|
|
3665
|
+
unlinkSync4(backoffFile(state, sink));
|
|
3666
|
+
} catch {
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
var LOCK_WAIT_MS = 6e3;
|
|
3670
|
+
var CWD_FROM_HOOKS = /* @__PURE__ */ new Set(["antigravity", "devin"]);
|
|
2874
3671
|
var BATCH = 50;
|
|
2875
3672
|
var MAX_PER_RUN = 500;
|
|
2876
|
-
async function flush(state, sinkConfigs, outbox, first) {
|
|
3673
|
+
async function flush(state, sinkConfigs, outbox, first, o = {}) {
|
|
2877
3674
|
const delivered = {};
|
|
2878
3675
|
const sinks = buildSinks(sinkConfigs, {
|
|
2879
3676
|
locked: (name) => log(state, `sink ${name}: its token is in a keychain this process can't read; left for later`)
|
|
@@ -2883,13 +3680,39 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2883
3680
|
const k = `${first.harness}/${safeId(first.session)}`;
|
|
2884
3681
|
refs.sort((a, b) => `${a.harness}/${a.session}` === k ? -1 : `${b.harness}/${b.session}` === k ? 1 : 0);
|
|
2885
3682
|
}
|
|
2886
|
-
|
|
3683
|
+
const drainSink = async (sink) => {
|
|
3684
|
+
let backoff = readBackoff(state, sink.name);
|
|
3685
|
+
if (backoff && !o.force && backoff.until > Date.now()) {
|
|
3686
|
+
delivered[sink.name] = 0;
|
|
3687
|
+
return;
|
|
3688
|
+
}
|
|
2887
3689
|
const cursors = new Cursors(state, sink.name);
|
|
2888
3690
|
const c = cursors.read();
|
|
2889
3691
|
let sent = 0;
|
|
2890
3692
|
let stalled = false;
|
|
2891
3693
|
let batch = [];
|
|
2892
3694
|
let pending = {};
|
|
3695
|
+
const deliver = async (events) => {
|
|
3696
|
+
const res = await sink.send(events);
|
|
3697
|
+
if (res.ok || res.action !== "split") return res;
|
|
3698
|
+
if (events.length === 1) {
|
|
3699
|
+
const e = events[0];
|
|
3700
|
+
log(state, `sink ${sink.name}: 413 ${res.error ?? ""} \u2192 event ${e.id} (${e.type}) is too large alone, dropped`);
|
|
3701
|
+
return { ok: true, acknowledged: 0, accepted: 0, duplicates: 0, rejected: [{ id: e.id, reason: "too_large" }] };
|
|
3702
|
+
}
|
|
3703
|
+
const half = Math.ceil(events.length / 2);
|
|
3704
|
+
const a = await deliver(events.slice(0, half));
|
|
3705
|
+
if (!a.ok) return a;
|
|
3706
|
+
const b = await deliver(events.slice(half));
|
|
3707
|
+
if (!b.ok) return b;
|
|
3708
|
+
return {
|
|
3709
|
+
ok: true,
|
|
3710
|
+
acknowledged: a.acknowledged + b.acknowledged,
|
|
3711
|
+
accepted: (a.accepted ?? 0) + (b.accepted ?? 0),
|
|
3712
|
+
duplicates: (a.duplicates ?? 0) + (b.duplicates ?? 0),
|
|
3713
|
+
rejected: [...a.rejected ?? [], ...b.rejected ?? []]
|
|
3714
|
+
};
|
|
3715
|
+
};
|
|
2893
3716
|
const send = async () => {
|
|
2894
3717
|
if (!batch.length) {
|
|
2895
3718
|
Object.assign(c, pending);
|
|
@@ -2897,15 +3720,24 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2897
3720
|
cursors.write(c);
|
|
2898
3721
|
return true;
|
|
2899
3722
|
}
|
|
2900
|
-
const res = await
|
|
3723
|
+
const res = await deliver(batch);
|
|
2901
3724
|
if (!res.ok) {
|
|
2902
|
-
|
|
3725
|
+
const action = res.action ?? "retry";
|
|
3726
|
+
let wait = "";
|
|
3727
|
+
if (action === "retry") {
|
|
3728
|
+
backoff = noteFailure(state, sink.name, res, backoff);
|
|
3729
|
+
wait = `, waits ${Math.round((backoff.until - Date.now()) / 1e3)} s`;
|
|
3730
|
+
}
|
|
3731
|
+
log(state, `sink ${sink.name}: ${res.status ?? ""} ${res.error ?? ""} \u2192 ${action}${wait}`);
|
|
2903
3732
|
if (res.action === "pause" || res.action === "lower-tier") applySinkVerdict(sink.name, res);
|
|
2904
3733
|
if (res.action !== "drop") {
|
|
2905
3734
|
batch = [];
|
|
2906
3735
|
pending = {};
|
|
2907
3736
|
return false;
|
|
2908
3737
|
}
|
|
3738
|
+
} else if (backoff) {
|
|
3739
|
+
clearBackoff(state, sink.name);
|
|
3740
|
+
backoff = null;
|
|
2909
3741
|
}
|
|
2910
3742
|
sent += batch.length;
|
|
2911
3743
|
Object.assign(c, pending);
|
|
@@ -2940,7 +3772,8 @@ async function flush(state, sinkConfigs, outbox, first) {
|
|
|
2940
3772
|
if (!await send()) stalled = true;
|
|
2941
3773
|
}
|
|
2942
3774
|
delivered[sink.name] = sent;
|
|
2943
|
-
}
|
|
3775
|
+
};
|
|
3776
|
+
await Promise.all(sinks.map(drainSink));
|
|
2944
3777
|
return delivered;
|
|
2945
3778
|
}
|
|
2946
3779
|
function applySinkVerdict(name, res) {
|
|
@@ -2954,24 +3787,31 @@ function applySinkVerdict(name, res) {
|
|
|
2954
3787
|
} catch {
|
|
2955
3788
|
}
|
|
2956
3789
|
}
|
|
2957
|
-
var jobsDir = (state) =>
|
|
3790
|
+
var jobsDir = (state) => path14.join(state, "jobs");
|
|
2958
3791
|
|
|
2959
3792
|
// src/worker.ts
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
}).
|
|
2976
|
-
}).finally(() =>
|
|
3793
|
+
async function workerMain(argv) {
|
|
3794
|
+
const jobFile = argv[2];
|
|
3795
|
+
if (!jobFile) return;
|
|
3796
|
+
const claimed = claimJob(jobFile);
|
|
3797
|
+
if (!claimed) return;
|
|
3798
|
+
const deadline = setTimeout(() => process.exit(0), 15e3);
|
|
3799
|
+
deadline.unref();
|
|
3800
|
+
await runJob(claimed.job, { claimed: claimed.file }).then((r) => {
|
|
3801
|
+
if (!r.requeued || (claimed.job.tries ?? 0) + 1 >= MAX_TRIES) return;
|
|
3802
|
+
const p = spawn(process.execPath, [argv[1], r.requeued], {
|
|
3803
|
+
detached: true,
|
|
3804
|
+
stdio: "ignore",
|
|
3805
|
+
windowsHide: true
|
|
3806
|
+
});
|
|
3807
|
+
p.unref();
|
|
3808
|
+
}).catch(() => {
|
|
3809
|
+
}).finally(() => releaseJob(claimed.file)).then(() => sweepJobs(stateDir())).catch(() => {
|
|
3810
|
+
}).finally(() => clearTimeout(deadline));
|
|
3811
|
+
}
|
|
3812
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync2(process.argv[1])).href)
|
|
3813
|
+
workerMain(process.argv).finally(() => process.exit(0));
|
|
3814
|
+
export {
|
|
3815
|
+
workerMain
|
|
3816
|
+
};
|
|
2977
3817
|
//# sourceMappingURL=worker.js.map
|