@agentproto/runtime 2.6.0 → 2.8.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 +16 -0
- package/dist/catalog-models.d.ts +3 -3
- package/dist/config.d.ts +27 -3
- package/dist/config.mjs.map +1 -1
- package/dist/{context-continuity-B9n0t0v-.d.ts → context-continuity-ib9_bVYM.d.ts} +12 -0
- package/dist/index.d.ts +334 -23
- package/dist/index.mjs +1111 -309
- package/dist/index.mjs.map +1 -1
- package/dist/pr-provenance.d.ts +29 -1
- package/dist/pr-provenance.mjs +17 -2
- package/dist/pr-provenance.mjs.map +1 -1
- package/dist/resume-strategies.d.ts +13 -1
- package/dist/resume-strategies.mjs +25 -14
- package/dist/resume-strategies.mjs.map +1 -1
- package/dist/{session-config-DIf6wYYP.d.ts → session-config-DbWP9RRj.d.ts} +1 -1
- package/dist/{spawn-defaults-CYJoeHeO.d.ts → spawn-defaults-DVgmfxWo.d.ts} +1 -1
- package/dist/user-presets.d.ts +2 -2
- package/package.json +11 -11
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { join, resolve, dirname, basename, isAbsolute, normalize, relative, extname, delimiter, sep } from 'path';
|
|
1
2
|
import { createReadStream, promises, readdirSync, readFileSync, mkdirSync, writeFileSync, existsSync, renameSync, chmodSync, openSync, closeSync, realpathSync, statSync, createWriteStream } from 'fs';
|
|
2
3
|
import { homedir, hostname, tmpdir } from 'os';
|
|
3
|
-
import { join, resolve, dirname, basename, isAbsolute, normalize, relative, extname, sep } from 'path';
|
|
4
4
|
import { createInterface } from 'readline';
|
|
5
5
|
import { timingSafeEqual, createHmac, randomBytes, randomUUID, createHash } from 'crypto';
|
|
6
6
|
import { mkdir, writeFile, unlink, readdir, readFile, stat, chmod, rename, rm, appendFile, mkdtemp, realpath, access } from 'fs/promises';
|
|
@@ -198,6 +198,127 @@ var init_tool_call_record = __esm({
|
|
|
198
198
|
"src/tool-call-record.ts"() {
|
|
199
199
|
}
|
|
200
200
|
});
|
|
201
|
+
function cwdLabel(cwd, workspaceSlug) {
|
|
202
|
+
const leaf = basename(cwd);
|
|
203
|
+
if (!workspaceSlug || workspaceSlug === leaf) return workspaceSlug ?? leaf;
|
|
204
|
+
return `${workspaceSlug}/${leaf}`;
|
|
205
|
+
}
|
|
206
|
+
function sessionFooterProvenance(session, options = {}) {
|
|
207
|
+
const prov = {
|
|
208
|
+
sessionId: session.id,
|
|
209
|
+
label: session.label,
|
|
210
|
+
adapter: session.harness ?? session.adapterSlug,
|
|
211
|
+
model: session.model,
|
|
212
|
+
authProfile: session.accessProfile?.label ?? session.accessProfile?.profileRef,
|
|
213
|
+
parentSessionId: options.supervisor?.id,
|
|
214
|
+
costUsd: session.costUsd,
|
|
215
|
+
tokensIn: session.tokensIn,
|
|
216
|
+
tokensOut: session.tokensOut,
|
|
217
|
+
source: options.source ?? "daemon",
|
|
218
|
+
host: options.host,
|
|
219
|
+
cwd: session.cwd,
|
|
220
|
+
workspaceSlug: session.workspaceSlug
|
|
221
|
+
};
|
|
222
|
+
return { prov, authMode: session.auth?.mode };
|
|
223
|
+
}
|
|
224
|
+
function buildSessionPrFooter(session, options = {}) {
|
|
225
|
+
const { prov, authMode } = sessionFooterProvenance(session, options);
|
|
226
|
+
return buildFooter({ prov, authMode, sha: options.sha, kind: "PR" });
|
|
227
|
+
}
|
|
228
|
+
function appendFooterOnce(body, footer) {
|
|
229
|
+
if (hasProvenanceFooter(body)) return body;
|
|
230
|
+
return `${body}${footer}`;
|
|
231
|
+
}
|
|
232
|
+
function hasProvenanceFooter(body) {
|
|
233
|
+
return new RegExp(`<sub>[^\\n]*${MARKER}`).test(body);
|
|
234
|
+
}
|
|
235
|
+
function parseGhPrCreate(command, args, stdout) {
|
|
236
|
+
if (basename(command) !== "gh") return null;
|
|
237
|
+
const positionals = args.filter((a) => !a.startsWith("-"));
|
|
238
|
+
if (positionals[0] !== "pr" || positionals[1] !== "create") return null;
|
|
239
|
+
const re = /https?:\/\/\S+?\/pull\/(\d+)/g;
|
|
240
|
+
let match;
|
|
241
|
+
let last = null;
|
|
242
|
+
while ((match = re.exec(stdout)) !== null) {
|
|
243
|
+
last = { url: match[0], number: Number(match[1]) };
|
|
244
|
+
}
|
|
245
|
+
return last;
|
|
246
|
+
}
|
|
247
|
+
function detectShellPrCreate(command, resultText) {
|
|
248
|
+
if (!command || !resultText) return null;
|
|
249
|
+
const unquoted = command.replace(/'[^']*'/g, " ").replace(/"(?:[^"\\]|\\.)*"/g, " ");
|
|
250
|
+
if (!/(^|[\s;&|({])gh\s+pr\s+create(\s|$)/.test(unquoted)) return null;
|
|
251
|
+
const re = /https?:\/\/\S+?\/pull\/(\d+)/g;
|
|
252
|
+
let match;
|
|
253
|
+
let last = null;
|
|
254
|
+
while ((match = re.exec(resultText)) !== null) {
|
|
255
|
+
last = { url: match[0], number: Number(match[1]) };
|
|
256
|
+
}
|
|
257
|
+
return last;
|
|
258
|
+
}
|
|
259
|
+
function cwdRelated(sessionCwd, cwd) {
|
|
260
|
+
if (sessionCwd === cwd) return true;
|
|
261
|
+
const sep2 = "/";
|
|
262
|
+
return cwd.startsWith(sessionCwd + sep2) || sessionCwd.startsWith(cwd + sep2);
|
|
263
|
+
}
|
|
264
|
+
function pickExecutorSession(sessions, cwd) {
|
|
265
|
+
const candidates = sessions.filter(
|
|
266
|
+
(s) => s.kind === "agent-cli" && typeof s.cwd === "string" && cwdRelated(s.cwd, cwd)
|
|
267
|
+
);
|
|
268
|
+
if (candidates.length === 0) return void 0;
|
|
269
|
+
const alive = (s) => s.status === "running" || s.status === "starting";
|
|
270
|
+
const byRecency = (a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? "");
|
|
271
|
+
const live = candidates.filter(alive).sort(byRecency);
|
|
272
|
+
if (live.length > 0) return live[0];
|
|
273
|
+
return [...candidates].sort(byRecency)[0];
|
|
274
|
+
}
|
|
275
|
+
var MARKER, fmtTokens, buildFooter;
|
|
276
|
+
var init_pr_provenance = __esm({
|
|
277
|
+
"src/pr-provenance.ts"() {
|
|
278
|
+
MARKER = "@agentproto-bot";
|
|
279
|
+
fmtTokens = (n) => {
|
|
280
|
+
if (typeof n !== "number" || !Number.isFinite(n)) return null;
|
|
281
|
+
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
|
|
282
|
+
};
|
|
283
|
+
buildFooter = ({
|
|
284
|
+
prov,
|
|
285
|
+
authMode,
|
|
286
|
+
runId,
|
|
287
|
+
runUrl,
|
|
288
|
+
sha,
|
|
289
|
+
kind = "review"
|
|
290
|
+
}) => {
|
|
291
|
+
const parts = [`\u{1F916} **${MARKER}** \u2014 ${kind}`];
|
|
292
|
+
if (prov?.sessionId) {
|
|
293
|
+
parts.push(`session \`${prov.sessionId}\`${prov.label ? ` (\`${prov.label}\`)` : ""}`);
|
|
294
|
+
}
|
|
295
|
+
if (prov?.adapter) parts.push([prov.adapter, authMode].filter(Boolean).join(" / "));
|
|
296
|
+
else if (!prov?.sessionId) parts.push(`legacy fallback${authMode ? ` (${authMode})` : ""}`);
|
|
297
|
+
else if (authMode) parts.push(authMode);
|
|
298
|
+
if (prov?.authProfile) parts.push(`auth-profile \`${prov.authProfile}\``);
|
|
299
|
+
if (prov?.model) parts.push(`model \`${prov.model}\``);
|
|
300
|
+
if (prov?.sandboxId) parts.push(`e2b \`${prov.sandboxId}\``);
|
|
301
|
+
if (prov?.parentSessionId) parts.push(`supervisor \`${prov.parentSessionId}\``);
|
|
302
|
+
const tin = fmtTokens(prov?.tokensIn);
|
|
303
|
+
const tout = fmtTokens(prov?.tokensOut);
|
|
304
|
+
if (tin || tout) parts.push(`${tin ?? "?"} in / ${tout ?? "?"} out`);
|
|
305
|
+
if (typeof prov?.costUsd === "number") {
|
|
306
|
+
parts.push(`$${prov.costUsd.toFixed(4)}${prov.source && prov.source !== "adapter" ? ` (${prov.source})` : ""}`);
|
|
307
|
+
}
|
|
308
|
+
const showLocalHostCwd = prov?.source === "local" || prov?.source === "daemon" || !runId;
|
|
309
|
+
if (runId && !showLocalHostCwd) parts.push(`run [${runId}](${runUrl})`);
|
|
310
|
+
if (showLocalHostCwd) {
|
|
311
|
+
if (prov?.host) parts.push(`host \`${prov.host}\``);
|
|
312
|
+
if (prov?.cwd) parts.push(`cwd \`${cwdLabel(prov.cwd, prov.workspaceSlug)}\``);
|
|
313
|
+
}
|
|
314
|
+
if (sha) parts.push(`sha \`${sha.slice(0, 7)}\``);
|
|
315
|
+
return `
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
<sub>${parts.join(" \xB7 ")}</sub>`;
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
});
|
|
201
322
|
function sessionTranscriptDir(sessionId, baseDir) {
|
|
202
323
|
return join(baseDir ?? join(homedir(), ".agentproto", "sessions"), sessionId);
|
|
203
324
|
}
|
|
@@ -390,6 +511,10 @@ function createTranscriptWriter(opts) {
|
|
|
390
511
|
const meta = evt.toolCallId ? state.toolCallMeta.get(evt.toolCallId) : void 0;
|
|
391
512
|
if (evt.toolCallId) state.toolCallMeta.delete(evt.toolCallId);
|
|
392
513
|
const { command, args } = extractCommandArgs(meta?.arguments);
|
|
514
|
+
const createdPr = evt.isError ?? false ? null : detectShellPrCreate(
|
|
515
|
+
command ?? meta?.toolName,
|
|
516
|
+
typeof evt.result === "string" ? evt.result : void 0
|
|
517
|
+
);
|
|
393
518
|
writeRecord(sessionId, state, {
|
|
394
519
|
kind: "tool-call-record",
|
|
395
520
|
sessionId,
|
|
@@ -397,7 +522,8 @@ function createTranscriptWriter(opts) {
|
|
|
397
522
|
...command !== void 0 ? { command } : {},
|
|
398
523
|
...args !== void 0 ? { args } : {},
|
|
399
524
|
isError: evt.isError ?? false,
|
|
400
|
-
...meta ? { durationMs: Date.now() - meta.startedAt } : {}
|
|
525
|
+
...meta ? { durationMs: Date.now() - meta.startedAt } : {},
|
|
526
|
+
...createdPr ? { createdPrUrl: createdPr.url, createdPrNumber: createdPr.number } : {}
|
|
401
527
|
});
|
|
402
528
|
break;
|
|
403
529
|
}
|
|
@@ -487,8 +613,8 @@ function createTranscriptWriter(opts) {
|
|
|
487
613
|
if (!state) return Promise.resolve();
|
|
488
614
|
flushBuffers(sessionId, state);
|
|
489
615
|
states.delete(sessionId);
|
|
490
|
-
return new Promise((
|
|
491
|
-
state.stream.end(() =>
|
|
616
|
+
return new Promise((resolve27) => {
|
|
617
|
+
state.stream.end(() => resolve27());
|
|
492
618
|
});
|
|
493
619
|
},
|
|
494
620
|
closeAll() {
|
|
@@ -497,7 +623,7 @@ function createTranscriptWriter(opts) {
|
|
|
497
623
|
const state = states.get(sessionId);
|
|
498
624
|
if (!state) continue;
|
|
499
625
|
flushBuffers(sessionId, state);
|
|
500
|
-
closings.push(new Promise((
|
|
626
|
+
closings.push(new Promise((resolve27) => state.stream.end(() => resolve27())));
|
|
501
627
|
}
|
|
502
628
|
states.clear();
|
|
503
629
|
return Promise.all(closings).then(() => void 0);
|
|
@@ -522,6 +648,7 @@ var DEBOUNCE_MS;
|
|
|
522
648
|
var init_transcript_writer = __esm({
|
|
523
649
|
"src/transcript-writer.ts"() {
|
|
524
650
|
init_tool_call_record();
|
|
651
|
+
init_pr_provenance();
|
|
525
652
|
DEBOUNCE_MS = 250;
|
|
526
653
|
}
|
|
527
654
|
});
|
|
@@ -634,20 +761,19 @@ function renderMarkdown(session, opts = {}) {
|
|
|
634
761
|
function renderJson(session) {
|
|
635
762
|
return JSON.stringify(session, null, 2);
|
|
636
763
|
}
|
|
637
|
-
async function exportClaudeCodeSession(adapterSessionId, cwd) {
|
|
764
|
+
async function exportClaudeCodeSession(adapterSessionId, cwd, configDir) {
|
|
638
765
|
if (!cwd) {
|
|
639
766
|
throw new Error(
|
|
640
767
|
"claude-code exporter: cwd is required to locate the JSONL file.\nPass cwd explicitly or use a session id that is in the registry."
|
|
641
768
|
);
|
|
642
769
|
}
|
|
643
|
-
const
|
|
644
|
-
const filePath = join(homedir(), ".claude", "projects", encoded, `${adapterSessionId}.jsonl`);
|
|
770
|
+
const filePath = join(claudeCodeProjectDir(cwd, configDir), `${adapterSessionId}.jsonl`);
|
|
645
771
|
let stream;
|
|
646
772
|
try {
|
|
647
773
|
stream = createReadStream(filePath, { encoding: "utf8" });
|
|
648
|
-
await new Promise((
|
|
774
|
+
await new Promise((resolve27, reject) => {
|
|
649
775
|
stream.once("error", reject);
|
|
650
|
-
stream.once("open",
|
|
776
|
+
stream.once("open", resolve27);
|
|
651
777
|
});
|
|
652
778
|
} catch (err) {
|
|
653
779
|
const code = err.code;
|
|
@@ -921,7 +1047,7 @@ async function discoverHermesSessions(cwd, since, expectedId) {
|
|
|
921
1047
|
}
|
|
922
1048
|
async function defaultHermesRunner(adapterSessionId) {
|
|
923
1049
|
const { spawn: spawn7 } = await import('child_process');
|
|
924
|
-
return new Promise((
|
|
1050
|
+
return new Promise((resolve27, reject) => {
|
|
925
1051
|
const chunks = [];
|
|
926
1052
|
const errChunks = [];
|
|
927
1053
|
const proc = spawn7(
|
|
@@ -940,7 +1066,7 @@ async function defaultHermesRunner(adapterSessionId) {
|
|
|
940
1066
|
)
|
|
941
1067
|
);
|
|
942
1068
|
} else {
|
|
943
|
-
|
|
1069
|
+
resolve27(Buffer.concat(chunks).toString("utf8"));
|
|
944
1070
|
}
|
|
945
1071
|
});
|
|
946
1072
|
});
|
|
@@ -977,9 +1103,9 @@ async function exportDaemonEventsSession(sessionId, desc) {
|
|
|
977
1103
|
let stream;
|
|
978
1104
|
try {
|
|
979
1105
|
stream = createReadStream(filePath, { encoding: "utf8" });
|
|
980
|
-
await new Promise((
|
|
1106
|
+
await new Promise((resolve27, reject) => {
|
|
981
1107
|
stream.once("error", reject);
|
|
982
|
-
stream.once("open",
|
|
1108
|
+
stream.once("open", resolve27);
|
|
983
1109
|
});
|
|
984
1110
|
} catch (err) {
|
|
985
1111
|
const code = err.code;
|
|
@@ -1735,6 +1861,7 @@ async function exportAgentSession(input) {
|
|
|
1735
1861
|
let adapterSlug = input.adapter;
|
|
1736
1862
|
let cwd = input.cwd;
|
|
1737
1863
|
let adapterSessionId = sessionId;
|
|
1864
|
+
let configDir;
|
|
1738
1865
|
const err = (msg) => ({
|
|
1739
1866
|
sessionId,
|
|
1740
1867
|
adapter: adapterSlug ?? "unknown",
|
|
@@ -1747,6 +1874,7 @@ async function exportAgentSession(input) {
|
|
|
1747
1874
|
if (desc) {
|
|
1748
1875
|
adapterSlug = adapterSlug ?? desc.adapterSlug;
|
|
1749
1876
|
cwd = cwd ?? desc.cwd;
|
|
1877
|
+
configDir = desc.adapterConfigDir;
|
|
1750
1878
|
if (desc.adapterSessionId) adapterSessionId = desc.adapterSessionId;
|
|
1751
1879
|
}
|
|
1752
1880
|
const tryNative = async () => {
|
|
@@ -1764,7 +1892,7 @@ Pass adapter explicitly or use a known session id (sess_xxx or name).`
|
|
|
1764
1892
|
Only sessions spawned via claude-code or hermes can be exported.`
|
|
1765
1893
|
);
|
|
1766
1894
|
}
|
|
1767
|
-
return exporter.exportSession(adapterSessionId, cwd);
|
|
1895
|
+
return exporter.exportSession(adapterSessionId, cwd, configDir);
|
|
1768
1896
|
};
|
|
1769
1897
|
const tryDaemon = () => exportDaemonEventsSession(daemonSessionId, desc);
|
|
1770
1898
|
let session;
|
|
@@ -1844,8 +1972,9 @@ var init_transcript_export = __esm({
|
|
|
1844
1972
|
function claudeProjectSlug(cwd) {
|
|
1845
1973
|
return cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
1846
1974
|
}
|
|
1847
|
-
function claudeCodeProjectDir(cwd) {
|
|
1848
|
-
|
|
1975
|
+
function claudeCodeProjectDir(cwd, configDir) {
|
|
1976
|
+
const base = configDir ?? resolve(homedir(), ".claude");
|
|
1977
|
+
return resolve(base, "projects", claudeProjectSlug(cwd));
|
|
1849
1978
|
}
|
|
1850
1979
|
function extractFirstText(content) {
|
|
1851
1980
|
if (typeof content === "string") {
|
|
@@ -1906,8 +2035,8 @@ function claudeEntrypointFor(mode) {
|
|
|
1906
2035
|
return mode === "native" ? "cli" : "sdk-ts";
|
|
1907
2036
|
}
|
|
1908
2037
|
async function discoverClaudeCode(input) {
|
|
1909
|
-
const { cwd, since, until, attachmentMode, expectedId } = input;
|
|
1910
|
-
const dir = claudeCodeProjectDir(cwd);
|
|
2038
|
+
const { cwd, since, until, attachmentMode, configDir, expectedId } = input;
|
|
2039
|
+
const dir = claudeCodeProjectDir(cwd, configDir);
|
|
1911
2040
|
if (expectedId) {
|
|
1912
2041
|
const filePath = join(dir, `${expectedId}.jsonl`);
|
|
1913
2042
|
try {
|
|
@@ -1952,9 +2081,9 @@ async function discoverClaudeCode(input) {
|
|
|
1952
2081
|
scored.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
1953
2082
|
return scored.map((s) => s.candidate);
|
|
1954
2083
|
}
|
|
1955
|
-
async function readClaudeCode(conversationId, cwd) {
|
|
2084
|
+
async function readClaudeCode(conversationId, cwd, configDir) {
|
|
1956
2085
|
const { exportClaudeCodeSession: exportClaudeCodeSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
|
|
1957
|
-
return exportClaudeCodeSession2(conversationId, cwd);
|
|
2086
|
+
return exportClaudeCodeSession2(conversationId, cwd, configDir);
|
|
1958
2087
|
}
|
|
1959
2088
|
async function discoverHermes(input) {
|
|
1960
2089
|
const { discoverHermesSessions: discoverHermesSessions2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
|
|
@@ -2045,6 +2174,108 @@ var init_conversation_store = __esm({
|
|
|
2045
2174
|
};
|
|
2046
2175
|
}
|
|
2047
2176
|
});
|
|
2177
|
+
|
|
2178
|
+
// src/config.ts
|
|
2179
|
+
var config_exports = {};
|
|
2180
|
+
__export(config_exports, {
|
|
2181
|
+
CONFIG_FILE_PATH: () => CONFIG_FILE_PATH,
|
|
2182
|
+
CONFIG_VERSION: () => CONFIG_VERSION,
|
|
2183
|
+
getConfigKey: () => getConfigKey,
|
|
2184
|
+
loadConfig: () => loadConfig,
|
|
2185
|
+
saveConfig: () => saveConfig,
|
|
2186
|
+
setConfigKey: () => setConfigKey
|
|
2187
|
+
});
|
|
2188
|
+
function sanitizeAcpAgents(raw, target) {
|
|
2189
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
2190
|
+
console.warn(
|
|
2191
|
+
`[runtime/config] ${target}: 'acpAgents' is not an object \u2014 ignoring`
|
|
2192
|
+
);
|
|
2193
|
+
return void 0;
|
|
2194
|
+
}
|
|
2195
|
+
const out = {};
|
|
2196
|
+
for (const [slug, value] of Object.entries(raw)) {
|
|
2197
|
+
if (value && typeof value === "object" && !Array.isArray(value) && typeof value.bin === "string" && value.bin.length > 0) {
|
|
2198
|
+
out[slug] = value;
|
|
2199
|
+
} else {
|
|
2200
|
+
console.warn(
|
|
2201
|
+
`[runtime/config] ${target}: acpAgents.${slug} is missing a string 'bin' \u2014 ignoring`
|
|
2202
|
+
);
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
2206
|
+
}
|
|
2207
|
+
async function loadConfig(path) {
|
|
2208
|
+
const target = path ?? CONFIG_FILE_PATH();
|
|
2209
|
+
try {
|
|
2210
|
+
const raw = await promises.readFile(target, "utf8");
|
|
2211
|
+
const parsed = JSON.parse(raw);
|
|
2212
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
2213
|
+
const cfg = parsed;
|
|
2214
|
+
if (cfg.acpAgents !== void 0) {
|
|
2215
|
+
cfg.acpAgents = sanitizeAcpAgents(cfg.acpAgents, target);
|
|
2216
|
+
}
|
|
2217
|
+
return cfg;
|
|
2218
|
+
}
|
|
2219
|
+
console.warn(
|
|
2220
|
+
`[runtime/config] ${target}: top-level value is not an object \u2014 ignoring`
|
|
2221
|
+
);
|
|
2222
|
+
return {};
|
|
2223
|
+
} catch (err) {
|
|
2224
|
+
const code = err.code;
|
|
2225
|
+
if (code && code !== "ENOENT") {
|
|
2226
|
+
console.warn(
|
|
2227
|
+
`[runtime/config] failed to read ${target}: ${err instanceof Error ? err.message : String(err)}`
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
return {};
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
async function saveConfig(next, path) {
|
|
2234
|
+
const target = path ?? CONFIG_FILE_PATH();
|
|
2235
|
+
const payload = { ...next, version: CONFIG_VERSION };
|
|
2236
|
+
const dir = dirname(target);
|
|
2237
|
+
await promises.mkdir(dir, { recursive: true });
|
|
2238
|
+
const tmp = `${target}.tmp`;
|
|
2239
|
+
await promises.writeFile(tmp, JSON.stringify(payload, null, 2) + "\n", "utf8");
|
|
2240
|
+
await promises.rename(tmp, target);
|
|
2241
|
+
}
|
|
2242
|
+
function getConfigKey(cfg, dotted) {
|
|
2243
|
+
let cur = cfg;
|
|
2244
|
+
for (const part of dotted.split(".")) {
|
|
2245
|
+
if (cur == null || typeof cur !== "object") return void 0;
|
|
2246
|
+
cur = cur[part];
|
|
2247
|
+
}
|
|
2248
|
+
return cur;
|
|
2249
|
+
}
|
|
2250
|
+
function setConfigKey(cfg, dotted, value) {
|
|
2251
|
+
const parts = dotted.split(".");
|
|
2252
|
+
const out = { ...cfg };
|
|
2253
|
+
let cur = out;
|
|
2254
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
2255
|
+
const k = parts[i];
|
|
2256
|
+
const next = cur[k];
|
|
2257
|
+
if (next && typeof next === "object" && !Array.isArray(next)) {
|
|
2258
|
+
cur[k] = { ...next };
|
|
2259
|
+
} else {
|
|
2260
|
+
cur[k] = {};
|
|
2261
|
+
}
|
|
2262
|
+
cur = cur[k];
|
|
2263
|
+
}
|
|
2264
|
+
const leaf = parts[parts.length - 1];
|
|
2265
|
+
if (value === void 0) {
|
|
2266
|
+
delete cur[leaf];
|
|
2267
|
+
} else {
|
|
2268
|
+
cur[leaf] = value;
|
|
2269
|
+
}
|
|
2270
|
+
return out;
|
|
2271
|
+
}
|
|
2272
|
+
var CONFIG_VERSION, CONFIG_FILE_PATH;
|
|
2273
|
+
var init_config = __esm({
|
|
2274
|
+
"src/config.ts"() {
|
|
2275
|
+
CONFIG_VERSION = 1;
|
|
2276
|
+
CONFIG_FILE_PATH = () => join(homedir(), ".agentproto", "config.json");
|
|
2277
|
+
}
|
|
2278
|
+
});
|
|
2048
2279
|
async function writeRuntimeMeta(workspace, meta) {
|
|
2049
2280
|
const dir = join(workspace, ".agentproto");
|
|
2050
2281
|
try {
|
|
@@ -2172,11 +2403,12 @@ var RESUME_STRATEGIES = Object.fromEntries(
|
|
|
2172
2403
|
{
|
|
2173
2404
|
outputHint: store.outputHint,
|
|
2174
2405
|
storeAs: store.storeAs,
|
|
2175
|
-
fsProbe: async (cwd, prevStartedAt, expectedId) => {
|
|
2406
|
+
fsProbe: async (cwd, prevStartedAt, expectedId, configDir) => {
|
|
2176
2407
|
const candidates = await s.discover({
|
|
2177
2408
|
cwd,
|
|
2178
2409
|
since: prevStartedAt,
|
|
2179
|
-
expectedId
|
|
2410
|
+
expectedId,
|
|
2411
|
+
configDir
|
|
2180
2412
|
});
|
|
2181
2413
|
return candidates[0]?.conversationId ?? null;
|
|
2182
2414
|
},
|
|
@@ -2220,7 +2452,10 @@ async function augmentWithFsResume(prev) {
|
|
|
2220
2452
|
const id = await strategy.fsProbe(
|
|
2221
2453
|
prev.cwd,
|
|
2222
2454
|
prev.startedAt,
|
|
2223
|
-
prev.adapterSessionId
|
|
2455
|
+
prev.adapterSessionId,
|
|
2456
|
+
// A config-dir-isolated session (#824) persisted its transcript under
|
|
2457
|
+
// its own CLAUDE_CONFIG_DIR — probe there, not the global ~/.claude.
|
|
2458
|
+
prev.adapterConfigDir
|
|
2224
2459
|
);
|
|
2225
2460
|
if (!id) return prev;
|
|
2226
2461
|
return {
|
|
@@ -3075,8 +3310,8 @@ async function enrichWithRemainingQuota(rollup, opts) {
|
|
|
3075
3310
|
})
|
|
3076
3311
|
);
|
|
3077
3312
|
const capMs = opts.timeoutMs ?? DEFAULT_ENRICH_CAP_MS;
|
|
3078
|
-
const cap = new Promise((
|
|
3079
|
-
setTimeout(() =>
|
|
3313
|
+
const cap = new Promise((resolve27) => {
|
|
3314
|
+
setTimeout(() => resolve27(null), capMs);
|
|
3080
3315
|
});
|
|
3081
3316
|
const settled = await Promise.race([work, cap]);
|
|
3082
3317
|
if (settled === null) return rollup;
|
|
@@ -3140,8 +3375,8 @@ async function enrichWithAccountCredits(rollup, opts) {
|
|
|
3140
3375
|
})
|
|
3141
3376
|
);
|
|
3142
3377
|
const capMs = opts.timeoutMs ?? DEFAULT_ENRICH_CAP_MS;
|
|
3143
|
-
const cap = new Promise((
|
|
3144
|
-
setTimeout(() =>
|
|
3378
|
+
const cap = new Promise((resolve27) => {
|
|
3379
|
+
setTimeout(() => resolve27(null), capMs);
|
|
3145
3380
|
});
|
|
3146
3381
|
const settled = await Promise.race([work, cap]);
|
|
3147
3382
|
if (settled === null) return rollup;
|
|
@@ -3480,52 +3715,9 @@ function normalizeConfig(parsed) {
|
|
|
3480
3715
|
if (active !== void 0) out.active = active;
|
|
3481
3716
|
return out;
|
|
3482
3717
|
}
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
console.warn(
|
|
3487
|
-
`[runtime/config] ${target}: 'acpAgents' is not an object \u2014 ignoring`
|
|
3488
|
-
);
|
|
3489
|
-
return void 0;
|
|
3490
|
-
}
|
|
3491
|
-
const out = {};
|
|
3492
|
-
for (const [slug, value] of Object.entries(raw)) {
|
|
3493
|
-
if (value && typeof value === "object" && !Array.isArray(value) && typeof value.bin === "string" && value.bin.length > 0) {
|
|
3494
|
-
out[slug] = value;
|
|
3495
|
-
} else {
|
|
3496
|
-
console.warn(
|
|
3497
|
-
`[runtime/config] ${target}: acpAgents.${slug} is missing a string 'bin' \u2014 ignoring`
|
|
3498
|
-
);
|
|
3499
|
-
}
|
|
3500
|
-
}
|
|
3501
|
-
return Object.keys(out).length > 0 ? out : void 0;
|
|
3502
|
-
}
|
|
3503
|
-
async function loadConfig(path) {
|
|
3504
|
-
const target = path ?? CONFIG_FILE_PATH();
|
|
3505
|
-
try {
|
|
3506
|
-
const raw = await promises.readFile(target, "utf8");
|
|
3507
|
-
const parsed = JSON.parse(raw);
|
|
3508
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
3509
|
-
const cfg = parsed;
|
|
3510
|
-
if (cfg.acpAgents !== void 0) {
|
|
3511
|
-
cfg.acpAgents = sanitizeAcpAgents(cfg.acpAgents, target);
|
|
3512
|
-
}
|
|
3513
|
-
return cfg;
|
|
3514
|
-
}
|
|
3515
|
-
console.warn(
|
|
3516
|
-
`[runtime/config] ${target}: top-level value is not an object \u2014 ignoring`
|
|
3517
|
-
);
|
|
3518
|
-
return {};
|
|
3519
|
-
} catch (err) {
|
|
3520
|
-
const code = err.code;
|
|
3521
|
-
if (code && code !== "ENOENT") {
|
|
3522
|
-
console.warn(
|
|
3523
|
-
`[runtime/config] failed to read ${target}: ${err instanceof Error ? err.message : String(err)}`
|
|
3524
|
-
);
|
|
3525
|
-
}
|
|
3526
|
-
return {};
|
|
3527
|
-
}
|
|
3528
|
-
}
|
|
3718
|
+
|
|
3719
|
+
// src/session-spawn.ts
|
|
3720
|
+
init_config();
|
|
3529
3721
|
var markerSchema = z.object({ worktreeId: z.string() });
|
|
3530
3722
|
function statOrUndefined(path) {
|
|
3531
3723
|
try {
|
|
@@ -4111,6 +4303,7 @@ function resolveContextContinuityPolicy(globalDefault, harnessDefault, modelDefa
|
|
|
4111
4303
|
function computeContextPct(contextSize, contextUsed) {
|
|
4112
4304
|
if (contextSize === void 0 || contextSize <= 0) return null;
|
|
4113
4305
|
if (contextUsed === void 0 || contextUsed < 0) return null;
|
|
4306
|
+
if (contextUsed === contextSize) return null;
|
|
4114
4307
|
const used = Math.min(contextUsed, contextSize);
|
|
4115
4308
|
return Math.round(used / contextSize * 100);
|
|
4116
4309
|
}
|
|
@@ -4420,6 +4613,9 @@ function composeRoleContext(role, promptAppend, registry) {
|
|
|
4420
4613
|
return [role.disposition, spawnLine, promptAppend].filter((p) => !!p).join("\n\n");
|
|
4421
4614
|
}
|
|
4422
4615
|
|
|
4616
|
+
// src/role-registry.ts
|
|
4617
|
+
init_config();
|
|
4618
|
+
|
|
4423
4619
|
// src/role-pack.ts
|
|
4424
4620
|
var FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
4425
4621
|
function parseFields(raw) {
|
|
@@ -4642,7 +4838,7 @@ function createSandboxAgentSessionProxy(opts) {
|
|
|
4642
4838
|
`sandbox proxy: ${consecutivePollFailures} consecutive poll failures against the box daemon (session "${remoteSessionId}") \u2014 giving up. Last error: ${pollErr instanceof Error ? pollErr.message : String(pollErr)}`
|
|
4643
4839
|
);
|
|
4644
4840
|
}
|
|
4645
|
-
await new Promise((
|
|
4841
|
+
await new Promise((resolve27) => setTimeout(resolve27, POLL_RETRY_DELAY_MS));
|
|
4646
4842
|
continue;
|
|
4647
4843
|
}
|
|
4648
4844
|
if (result.timedOut) continue;
|
|
@@ -4703,6 +4899,7 @@ function createSandboxAgentSessionProxy(opts) {
|
|
|
4703
4899
|
}
|
|
4704
4900
|
|
|
4705
4901
|
// src/worktree-isolation.ts
|
|
4902
|
+
init_config();
|
|
4706
4903
|
var WORKTREE_ISOLATION_ENV = "AGENTPROTO_WORKTREES_ISOLATION";
|
|
4707
4904
|
var DEFAULT_WORKTREE_ISOLATION = "on-request";
|
|
4708
4905
|
function normalizeWorktreeField(field) {
|
|
@@ -4762,6 +4959,7 @@ async function loadWorktreeIsolation(loadCfg = loadConfig) {
|
|
|
4762
4959
|
}
|
|
4763
4960
|
|
|
4764
4961
|
// src/spawn-attach.ts
|
|
4962
|
+
init_config();
|
|
4765
4963
|
var SPAWN_ATTACH_ENV = "AGENTPROTO_SPAWN_ATTACH";
|
|
4766
4964
|
var DEFAULT_SPAWN_ATTACH = "always";
|
|
4767
4965
|
function normalizeAttachField(field) {
|
|
@@ -4799,6 +4997,9 @@ async function loadSpawnAttach(loadCfg = loadConfig) {
|
|
|
4799
4997
|
}
|
|
4800
4998
|
return DEFAULT_SPAWN_ATTACH;
|
|
4801
4999
|
}
|
|
5000
|
+
|
|
5001
|
+
// src/spawn-dedupe.ts
|
|
5002
|
+
init_config();
|
|
4802
5003
|
var SPAWN_DEDUPE_ENV = "AGENTPROTO_SPAWN_DEDUPE";
|
|
4803
5004
|
var DEFAULT_SPAWN_DEDUPE = "always";
|
|
4804
5005
|
var IMPLICIT_KEY_PREFIX = "\0implicit";
|
|
@@ -4823,6 +5024,205 @@ async function loadSpawnDedupe(loadCfg = loadConfig) {
|
|
|
4823
5024
|
return DEFAULT_SPAWN_DEDUPE;
|
|
4824
5025
|
}
|
|
4825
5026
|
|
|
5027
|
+
// src/gh-provenance-shim.ts
|
|
5028
|
+
init_pr_provenance();
|
|
5029
|
+
var PROVENANCE_WRAP_GH_ENV = "AGENTPROTO_PROVENANCE_WRAP_GH";
|
|
5030
|
+
var DEFAULT_WRAP_GH = false;
|
|
5031
|
+
var GH_PROVENANCE_ENABLE_ENV = "AGENTPROTO_GH_PROVENANCE";
|
|
5032
|
+
var GH_PROVENANCE_ADAPTER_ENV = "AGENTPROTO_ADAPTER";
|
|
5033
|
+
var GH_PROVENANCE_MODEL_ENV = "AGENTPROTO_MODEL";
|
|
5034
|
+
function parseWrapGh(raw) {
|
|
5035
|
+
if (raw === void 0) return void 0;
|
|
5036
|
+
const v = raw.trim().toLowerCase();
|
|
5037
|
+
if (v === "1" || v === "true" || v === "yes" || v === "on") return true;
|
|
5038
|
+
if (v === "0" || v === "false" || v === "no" || v === "off") return false;
|
|
5039
|
+
return void 0;
|
|
5040
|
+
}
|
|
5041
|
+
async function loadProvenanceWrapGh(loadCfg = defaultLoadConfig) {
|
|
5042
|
+
const fromEnv = parseWrapGh(process.env[PROVENANCE_WRAP_GH_ENV]);
|
|
5043
|
+
if (fromEnv !== void 0) return fromEnv;
|
|
5044
|
+
try {
|
|
5045
|
+
const cfg = await loadCfg();
|
|
5046
|
+
if (typeof cfg.provenance?.wrapGh === "boolean") return cfg.provenance.wrapGh;
|
|
5047
|
+
} catch {
|
|
5048
|
+
}
|
|
5049
|
+
return DEFAULT_WRAP_GH;
|
|
5050
|
+
}
|
|
5051
|
+
async function defaultLoadConfig() {
|
|
5052
|
+
const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
5053
|
+
return loadConfig2();
|
|
5054
|
+
}
|
|
5055
|
+
function assembleShimPath(shimDir, basePath, sep2 = delimiter) {
|
|
5056
|
+
const base = basePath ?? "";
|
|
5057
|
+
const entries = base.length > 0 ? base.split(sep2) : [];
|
|
5058
|
+
if (entries[0] === shimDir) return base;
|
|
5059
|
+
return [shimDir, ...entries].join(sep2);
|
|
5060
|
+
}
|
|
5061
|
+
function buildGhShimEnv(input) {
|
|
5062
|
+
const env = {
|
|
5063
|
+
PATH: assembleShimPath(input.shimDir, input.basePath, input.sep),
|
|
5064
|
+
[GH_PROVENANCE_ENABLE_ENV]: "1"
|
|
5065
|
+
};
|
|
5066
|
+
if (input.adapter) env[GH_PROVENANCE_ADAPTER_ENV] = input.adapter;
|
|
5067
|
+
if (input.model) env[GH_PROVENANCE_MODEL_ENV] = input.model;
|
|
5068
|
+
return env;
|
|
5069
|
+
}
|
|
5070
|
+
function renderGhShimScript(opts) {
|
|
5071
|
+
return `#!${opts.nodePath}
|
|
5072
|
+
"use strict"
|
|
5073
|
+
// GENERATED by @agentproto/runtime (gh-provenance-shim.ts). Do not edit by
|
|
5074
|
+
// hand \u2014 the daemon rewrites this on boot. See that module for the rationale.
|
|
5075
|
+
const { spawnSync } = require("node:child_process")
|
|
5076
|
+
const { statSync, realpathSync } = require("node:fs")
|
|
5077
|
+
const path = require("node:path")
|
|
5078
|
+
const os = require("node:os")
|
|
5079
|
+
|
|
5080
|
+
// The real filesystem location of this shim, so we can skip it while scanning
|
|
5081
|
+
// PATH \u2014 compared by realpath so a symlinked temp/home dir (macOS /var ->
|
|
5082
|
+
// /private/var) can't fool the equality check into an infinite recursion.
|
|
5083
|
+
function realOf(p) {
|
|
5084
|
+
try { return realpathSync(p) } catch { return p }
|
|
5085
|
+
}
|
|
5086
|
+
|
|
5087
|
+
const MARKER = ${JSON.stringify(MARKER)}
|
|
5088
|
+
const SESSION_ID_ENV = ${JSON.stringify(SESSION_ID_ENV)}
|
|
5089
|
+
const WORKSPACE_SLUG_ENV = ${JSON.stringify(WORKSPACE_SLUG_ENV)}
|
|
5090
|
+
const ADAPTER_ENV = ${JSON.stringify(GH_PROVENANCE_ADAPTER_ENV)}
|
|
5091
|
+
const MODEL_ENV = ${JSON.stringify(GH_PROVENANCE_MODEL_ENV)}
|
|
5092
|
+
|
|
5093
|
+
const args = process.argv.slice(2)
|
|
5094
|
+
const shimDir = __dirname
|
|
5095
|
+
|
|
5096
|
+
// Resolve the REAL gh: the first executable \`gh\` on PATH that is NOT this
|
|
5097
|
+
// shim's own directory (guarding against infinite recursion).
|
|
5098
|
+
function findRealGh() {
|
|
5099
|
+
const selfReal = realOf(shimDir)
|
|
5100
|
+
const dirs = (process.env.PATH || "").split(path.delimiter)
|
|
5101
|
+
for (const d of dirs) {
|
|
5102
|
+
if (!d) continue
|
|
5103
|
+
const candidate = path.join(d, "gh")
|
|
5104
|
+
try { if (!statSync(candidate).isFile()) continue } catch { continue }
|
|
5105
|
+
if (realOf(d) === selfReal) continue
|
|
5106
|
+
return candidate
|
|
5107
|
+
}
|
|
5108
|
+
return null
|
|
5109
|
+
}
|
|
5110
|
+
|
|
5111
|
+
const realGh = findRealGh()
|
|
5112
|
+
if (!realGh) {
|
|
5113
|
+
// No real gh anywhere \u2014 behave exactly as its absence would: not found.
|
|
5114
|
+
process.stderr.write("agentproto gh-provenance shim: real 'gh' not found on PATH\\n")
|
|
5115
|
+
process.exit(127)
|
|
5116
|
+
}
|
|
5117
|
+
|
|
5118
|
+
// Only \`gh pr create\` is targeted (v1). Everything else passes straight
|
|
5119
|
+
// through to the real gh, exit code and all.
|
|
5120
|
+
const positionals = args.filter(a => !a.startsWith("-"))
|
|
5121
|
+
const isPrCreate = positionals[0] === "pr" && positionals[1] === "create"
|
|
5122
|
+
|
|
5123
|
+
function exitFrom(result) {
|
|
5124
|
+
if (result.error) {
|
|
5125
|
+
process.stderr.write(String(result.error && result.error.message) + "\\n")
|
|
5126
|
+
return 127
|
|
5127
|
+
}
|
|
5128
|
+
return typeof result.status === "number" ? result.status : 1
|
|
5129
|
+
}
|
|
5130
|
+
|
|
5131
|
+
if (!isPrCreate) {
|
|
5132
|
+
const passthrough = spawnSync(realGh, args, { stdio: "inherit" })
|
|
5133
|
+
process.exit(exitFrom(passthrough))
|
|
5134
|
+
}
|
|
5135
|
+
|
|
5136
|
+
// Targeted: run the real create, capture stdout while echoing it through so
|
|
5137
|
+
// the user still sees gh's own output (it prints the PR URL there).
|
|
5138
|
+
const run = spawnSync(realGh, args, { encoding: "utf8", stdio: ["inherit", "pipe", "inherit"] })
|
|
5139
|
+
const stdout = run.stdout || ""
|
|
5140
|
+
if (stdout) process.stdout.write(stdout)
|
|
5141
|
+
const exitCode = exitFrom(run)
|
|
5142
|
+
|
|
5143
|
+
// Footer step is COSMETIC: any failure here must never change the exit code.
|
|
5144
|
+
try {
|
|
5145
|
+
if (exitCode === 0) {
|
|
5146
|
+
const parsed = parsePrUrl(stdout)
|
|
5147
|
+
if (parsed) stampFooter(parsed)
|
|
5148
|
+
}
|
|
5149
|
+
} catch { /* swallow \u2014 stamping never fails the underlying gh */ }
|
|
5150
|
+
process.exit(exitCode)
|
|
5151
|
+
|
|
5152
|
+
// --- pure-ish helpers (mirrors pr-provenance.ts's parseGhPrCreate/buildFooter) ---
|
|
5153
|
+
|
|
5154
|
+
function parsePrUrl(text) {
|
|
5155
|
+
// Take the LAST match so an advisory "Warning: \u2026/pull/\u2026" can't shadow the
|
|
5156
|
+
// real URL gh prints on its own line.
|
|
5157
|
+
const re = /https?:\\/\\/([^\\/\\s]+)\\/([^\\/\\s]+)\\/([^\\/\\s]+)\\/pull\\/(\\d+)/g
|
|
5158
|
+
let m
|
|
5159
|
+
let last = null
|
|
5160
|
+
while ((m = re.exec(text)) !== null) {
|
|
5161
|
+
last = { host: m[1], owner: m[2], repo: m[3], number: Number(m[4]), url: m[0] }
|
|
5162
|
+
}
|
|
5163
|
+
return last
|
|
5164
|
+
}
|
|
5165
|
+
|
|
5166
|
+
function cwdLabel(cwd, ws) {
|
|
5167
|
+
const leaf = path.basename(cwd)
|
|
5168
|
+
if (!ws || ws === leaf) return ws || leaf
|
|
5169
|
+
return ws + "/" + leaf
|
|
5170
|
+
}
|
|
5171
|
+
|
|
5172
|
+
function buildFooter() {
|
|
5173
|
+
const parts = ["\u{1F916} **" + MARKER + "** \u2014 PR"]
|
|
5174
|
+
const sid = process.env[SESSION_ID_ENV]
|
|
5175
|
+
if (sid) parts.push("session \`" + sid + "\`")
|
|
5176
|
+
const adapter = process.env[ADAPTER_ENV]
|
|
5177
|
+
if (adapter) parts.push(adapter)
|
|
5178
|
+
const model = process.env[MODEL_ENV]
|
|
5179
|
+
if (model) parts.push("model \`" + model + "\`")
|
|
5180
|
+
const host = os.hostname()
|
|
5181
|
+
if (host) parts.push("host \`" + host + "\`")
|
|
5182
|
+
parts.push("cwd \`" + cwdLabel(process.cwd(), process.env[WORKSPACE_SLUG_ENV]) + "\`")
|
|
5183
|
+
return "\\n\\n---\\n<sub>" + parts.join(" \xB7 ") + "</sub>"
|
|
5184
|
+
}
|
|
5185
|
+
|
|
5186
|
+
function stampFooter(parsed) {
|
|
5187
|
+
// Read the current body via the real gh; append the footer once (idempotent
|
|
5188
|
+
// by MARKER, so a retry never stacks a second one).
|
|
5189
|
+
const view = spawnSync(realGh, ["pr", "view", parsed.url, "--json", "body", "-q", ".body"], { encoding: "utf8" })
|
|
5190
|
+
if (view.status !== 0) return
|
|
5191
|
+
let body = typeof view.stdout === "string" ? view.stdout : ""
|
|
5192
|
+
if (body.endsWith("\\n")) body = body.slice(0, -1)
|
|
5193
|
+
if (body.includes(MARKER)) return
|
|
5194
|
+
const newBody = body + buildFooter()
|
|
5195
|
+
// Post-create PATCH via \`gh api\` \u2014 never touches the create's own args.
|
|
5196
|
+
const apiPath = "repos/" + parsed.owner + "/" + parsed.repo + "/pulls/" + parsed.number
|
|
5197
|
+
const apiArgs = ["api"]
|
|
5198
|
+
if (parsed.host && parsed.host !== "github.com") apiArgs.push("--hostname", parsed.host)
|
|
5199
|
+
apiArgs.push(apiPath, "-X", "PATCH", "-f", "body=" + newBody)
|
|
5200
|
+
spawnSync(realGh, apiArgs, { stdio: "ignore" })
|
|
5201
|
+
}
|
|
5202
|
+
`;
|
|
5203
|
+
}
|
|
5204
|
+
function defaultGhShimBaseDir() {
|
|
5205
|
+
return join(homedir(), ".agentproto", "shims");
|
|
5206
|
+
}
|
|
5207
|
+
var shimDirCache = /* @__PURE__ */ new Map();
|
|
5208
|
+
function ensureGhShimDir(opts = {}) {
|
|
5209
|
+
const baseDir = opts.baseDir ?? defaultGhShimBaseDir();
|
|
5210
|
+
const nodePath = opts.nodePath ?? process.execPath;
|
|
5211
|
+
const key = `${baseDir}\0${nodePath}`;
|
|
5212
|
+
const cached = shimDirCache.get(key);
|
|
5213
|
+
if (cached) return cached;
|
|
5214
|
+
const task = (async () => {
|
|
5215
|
+
await mkdir(baseDir, { recursive: true });
|
|
5216
|
+
const shimPath = join(baseDir, "gh");
|
|
5217
|
+
await writeFile(shimPath, renderGhShimScript({ nodePath }), "utf8");
|
|
5218
|
+
await chmod(shimPath, 493);
|
|
5219
|
+
return resolve(baseDir);
|
|
5220
|
+
})();
|
|
5221
|
+
shimDirCache.set(key, task);
|
|
5222
|
+
task.catch(() => shimDirCache.delete(key));
|
|
5223
|
+
return task;
|
|
5224
|
+
}
|
|
5225
|
+
|
|
4826
5226
|
// src/session-spawn.ts
|
|
4827
5227
|
var SPAWN_CLAIM_WINDOW_MS = 6e5;
|
|
4828
5228
|
var IMPLICIT_SPAWN_CLAIM_WINDOW_MS = 12e4;
|
|
@@ -4997,6 +5397,11 @@ async function resolveAccessProfileAuth(input) {
|
|
|
4997
5397
|
}
|
|
4998
5398
|
};
|
|
4999
5399
|
}
|
|
5400
|
+
function shouldInjectDaemonSelfMount(adapter, sandbox) {
|
|
5401
|
+
if (adapter === "hermes") return true;
|
|
5402
|
+
if (adapter === "claude-code") return sandbox === void 0;
|
|
5403
|
+
return false;
|
|
5404
|
+
}
|
|
5000
5405
|
function cleanAgentLines(lines) {
|
|
5001
5406
|
return lines.map((l) => l.replace(/\x1b\[[0-9;]*m/g, "")).filter((l) => {
|
|
5002
5407
|
const t = l.trim();
|
|
@@ -5053,7 +5458,8 @@ async function spawnAgentSession(deps2, input) {
|
|
|
5053
5458
|
provisionWorktree,
|
|
5054
5459
|
resolveWorktreeIsolation,
|
|
5055
5460
|
resolveSpawnAttach,
|
|
5056
|
-
resolveSpawnDedupe
|
|
5461
|
+
resolveSpawnDedupe,
|
|
5462
|
+
resolveProvenanceWrapGh
|
|
5057
5463
|
} = deps2;
|
|
5058
5464
|
const explicitCwd = input.cwd !== void 0;
|
|
5059
5465
|
const explicitWorkspaceSlug = input.workspaceSlug !== void 0;
|
|
@@ -5266,7 +5672,7 @@ async function spawnAgentSession(deps2, input) {
|
|
|
5266
5672
|
const delegationDenied = role.toolPolicy.delegation === "deny";
|
|
5267
5673
|
let mcpServers = input.mcpServers;
|
|
5268
5674
|
const mintedSessionId = mintSessionId();
|
|
5269
|
-
if (!mcpServers && input.adapter
|
|
5675
|
+
if (!mcpServers && shouldInjectDaemonSelfMount(input.adapter, input.sandbox) && daemonMcpUrl) {
|
|
5270
5676
|
let ref = delegationDenied ? `${daemonMcpUrl}${daemonMcpUrl.includes("?") ? "&" : "?"}denyTools=${DELEGATION_TOOL_NAMES.join(",")}` : daemonMcpUrl;
|
|
5271
5677
|
ref += `${ref.includes("?") ? "&" : "?"}callerSessionId=${encodeURIComponent(mintedSessionId)}`;
|
|
5272
5678
|
mcpServers = [{ name: "agentproto", transport: "http", ref }];
|
|
@@ -5511,8 +5917,8 @@ async function spawnAgentSession(deps2, input) {
|
|
|
5511
5917
|
}
|
|
5512
5918
|
let resolveClaim;
|
|
5513
5919
|
claims.set(key, {
|
|
5514
|
-
result: new Promise((
|
|
5515
|
-
resolveClaim =
|
|
5920
|
+
result: new Promise((resolve27) => {
|
|
5921
|
+
resolveClaim = resolve27;
|
|
5516
5922
|
}),
|
|
5517
5923
|
// An implicit claim expires sooner than an explicit one — see
|
|
5518
5924
|
// `IMPLICIT_SPAWN_CLAIM_WINDOW_MS`'s docblock.
|
|
@@ -5541,6 +5947,7 @@ async function spawnAgentSession(deps2, input) {
|
|
|
5541
5947
|
workspaceSlug: resolvedSlug,
|
|
5542
5948
|
cwd,
|
|
5543
5949
|
adapterSlug: input.adapter,
|
|
5950
|
+
adapterConfigDir: adapterConfigDirFor(mintedSessionId),
|
|
5544
5951
|
harness: input.harness ?? input.adapter,
|
|
5545
5952
|
...resolved?.routeSelection !== void 0 ? { routeSelection: resolved.routeSelection } : {},
|
|
5546
5953
|
...resolved?.authDescriptor?.provider !== void 0 ? { adapterProvider: resolved.authDescriptor.provider } : {},
|
|
@@ -5615,6 +6022,11 @@ async function spawnAgentSession(deps2, input) {
|
|
|
5615
6022
|
const agentSession2 = await resolved.startSession({
|
|
5616
6023
|
cwd: finalCwd,
|
|
5617
6024
|
...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {},
|
|
6025
|
+
// Persistent isolated-config dir, keyed by this session's id —
|
|
6026
|
+
// recorded on the pending descriptor above so restart/lazy-resume
|
|
6027
|
+
// can hand the respawned adapter the same dir (native-resume
|
|
6028
|
+
// store). Adapters that don't isolate a config dir ignore it.
|
|
6029
|
+
configDir: adapterConfigDirFor(mintedSessionId),
|
|
5618
6030
|
...input.mode ? { mode: input.mode } : {},
|
|
5619
6031
|
...launchConfig.options ? { options: launchConfig.options } : {},
|
|
5620
6032
|
...launchConfig.wireModel ? { model: launchConfig.wireModel } : {},
|
|
@@ -5723,9 +6135,29 @@ ${asyncPrompt}`;
|
|
|
5723
6135
|
sandboxId = booted.sandboxId;
|
|
5724
6136
|
sandboxTeardown = booted.sandboxTeardown;
|
|
5725
6137
|
} else {
|
|
6138
|
+
let ghProvenanceEnv = {};
|
|
6139
|
+
try {
|
|
6140
|
+
const wrapGh = await (resolveProvenanceWrapGh ?? loadProvenanceWrapGh)();
|
|
6141
|
+
if (wrapGh) {
|
|
6142
|
+
const shimDir = await ensureGhShimDir();
|
|
6143
|
+
ghProvenanceEnv = buildGhShimEnv({
|
|
6144
|
+
shimDir,
|
|
6145
|
+
basePath: process.env.PATH ?? "",
|
|
6146
|
+
adapter: input.harness ?? input.adapter,
|
|
6147
|
+
...input.model ?? resolved?.defaultModel ? { model: input.model ?? resolved?.defaultModel } : {}
|
|
6148
|
+
});
|
|
6149
|
+
}
|
|
6150
|
+
} catch {
|
|
6151
|
+
ghProvenanceEnv = {};
|
|
6152
|
+
}
|
|
5726
6153
|
agentSession = await resolved.startSession({
|
|
5727
6154
|
cwd,
|
|
5728
6155
|
...input.resumeSessionId ? { resumeSessionId: input.resumeSessionId } : {},
|
|
6156
|
+
// Persistent isolated-config dir, keyed by this session's id —
|
|
6157
|
+
// recorded on the descriptor below so restart/lazy-resume can hand
|
|
6158
|
+
// the respawned adapter the same dir (native-resume store).
|
|
6159
|
+
// Adapters that don't isolate a config dir ignore it.
|
|
6160
|
+
configDir: adapterConfigDirFor(mintedSessionId),
|
|
5729
6161
|
...input.mode ? { mode: input.mode } : {},
|
|
5730
6162
|
...launchConfig.options ? { options: launchConfig.options } : {},
|
|
5731
6163
|
...launchConfig.wireModel ? { model: launchConfig.wireModel } : {},
|
|
@@ -5742,6 +6174,10 @@ ${asyncPrompt}`;
|
|
|
5742
6174
|
// exec's. `agent_start` has no caller-facing `env` passthrough to
|
|
5743
6175
|
// collide with, so this is the entire env for this spawn.
|
|
5744
6176
|
env: {
|
|
6177
|
+
// Provenance shim env FIRST (PATH + adapter/model) so the identity
|
|
6178
|
+
// vars below always win — they must never be forgeable or shadowed,
|
|
6179
|
+
// and `ghProvenanceEnv` never carries an identity var anyway.
|
|
6180
|
+
...ghProvenanceEnv,
|
|
5745
6181
|
[SESSION_ID_ENV]: mintedSessionId,
|
|
5746
6182
|
[WORKSPACE_SLUG_ENV]: resolvedSlug,
|
|
5747
6183
|
// Lineage (PARENT_SESSION_ID_ENV's doc, sessions.ts) — mirrors
|
|
@@ -5780,6 +6216,7 @@ ${effectivePrompt}`;
|
|
|
5780
6216
|
cwd,
|
|
5781
6217
|
agentSession,
|
|
5782
6218
|
adapterSlug: input.adapter,
|
|
6219
|
+
adapterConfigDir: adapterConfigDirFor(mintedSessionId),
|
|
5783
6220
|
...resolved?.resumable !== void 0 ? { resumable: resolved.resumable } : {},
|
|
5784
6221
|
...resolved?.nativeTerminalResume !== void 0 ? { nativeTerminalResume: resolved.nativeTerminalResume } : {},
|
|
5785
6222
|
harness: input.harness ?? input.adapter,
|
|
@@ -5898,13 +6335,13 @@ async function isSharedDirtyCwd(cwd) {
|
|
|
5898
6335
|
const identity = resolveWorktreeIdentity(cwd);
|
|
5899
6336
|
if (identity?.worktreeId !== void 0) return false;
|
|
5900
6337
|
const { spawn: spawn7 } = await import('child_process');
|
|
5901
|
-
return await new Promise((
|
|
6338
|
+
return await new Promise((resolve27) => {
|
|
5902
6339
|
let stdout = "";
|
|
5903
6340
|
let settled = false;
|
|
5904
6341
|
const done = (v) => {
|
|
5905
6342
|
if (!settled) {
|
|
5906
6343
|
settled = true;
|
|
5907
|
-
|
|
6344
|
+
resolve27(v);
|
|
5908
6345
|
}
|
|
5909
6346
|
};
|
|
5910
6347
|
const child = spawn7("git", ["-C", cwd, "status", "--porcelain"], {
|
|
@@ -5919,15 +6356,15 @@ async function isSharedDirtyCwd(cwd) {
|
|
|
5919
6356
|
}
|
|
5920
6357
|
async function resolveMcpCredentialHeaders(mcpServers) {
|
|
5921
6358
|
if (!mcpServers || mcpServers.length === 0) return mcpServers;
|
|
5922
|
-
const { resolveMcpCredentialHeaders:
|
|
5923
|
-
if (!
|
|
6359
|
+
const { resolveMcpCredentialHeaders: resolve27 } = getMcpCredentialDeps();
|
|
6360
|
+
if (!resolve27) return mcpServers;
|
|
5924
6361
|
return Promise.all(
|
|
5925
6362
|
mcpServers.map(async (entry) => {
|
|
5926
6363
|
const ref = entry.credentialRef;
|
|
5927
6364
|
if (!ref) return entry;
|
|
5928
6365
|
let brokered;
|
|
5929
6366
|
try {
|
|
5930
|
-
brokered = await
|
|
6367
|
+
brokered = await resolve27({ credentialRef: ref });
|
|
5931
6368
|
} catch (err) {
|
|
5932
6369
|
console.warn(
|
|
5933
6370
|
`[agent_start] credentialRef resolution failed for "${entry.name}" (${ref}): ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -6034,10 +6471,10 @@ function sandboxAuthFromResolved(auth) {
|
|
|
6034
6471
|
};
|
|
6035
6472
|
}
|
|
6036
6473
|
async function resolveSandboxSecret(slug) {
|
|
6037
|
-
const { resolveSandboxSecret:
|
|
6038
|
-
if (!
|
|
6474
|
+
const { resolveSandboxSecret: resolve27 } = getMcpCredentialDeps();
|
|
6475
|
+
if (!resolve27) return null;
|
|
6039
6476
|
try {
|
|
6040
|
-
return await
|
|
6477
|
+
return await resolve27(slug);
|
|
6041
6478
|
} catch (err) {
|
|
6042
6479
|
console.warn(
|
|
6043
6480
|
`[agent_start] sandbox secret resolution failed for "${slug}": ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -6491,6 +6928,7 @@ ${contextBlocks.join("\n\n")}` : "";
|
|
|
6491
6928
|
const judgeSessionId = mintSessionId();
|
|
6492
6929
|
const agentSession = await resolved.startSession({
|
|
6493
6930
|
cwd,
|
|
6931
|
+
configDir: adapterConfigDirFor(judgeSessionId),
|
|
6494
6932
|
...spec.model ? { model: spec.model } : {},
|
|
6495
6933
|
env: {
|
|
6496
6934
|
[SESSION_ID_ENV]: judgeSessionId,
|
|
@@ -6505,6 +6943,7 @@ ${contextBlocks.join("\n\n")}` : "";
|
|
|
6505
6943
|
cwd,
|
|
6506
6944
|
agentSession,
|
|
6507
6945
|
adapterSlug: spec.adapter,
|
|
6946
|
+
adapterConfigDir: adapterConfigDirFor(judgeSessionId),
|
|
6508
6947
|
label: `judge:${state.policyId}`,
|
|
6509
6948
|
// PR #800: groups machine-run gate sessions in the VS Code tree.
|
|
6510
6949
|
origin: "gate",
|
|
@@ -7067,11 +7506,13 @@ function listBuckets(root) {
|
|
|
7067
7506
|
}
|
|
7068
7507
|
}
|
|
7069
7508
|
var serialize = (snapshot) => JSON.stringify(snapshot, null, 2) + "\n";
|
|
7509
|
+
var tmpSeq = 0;
|
|
7510
|
+
var tmpPathFor = (target) => `${target}.tmp.${process.pid}.${++tmpSeq}`;
|
|
7070
7511
|
async function writeBucketSnapshot(root, slug, snapshot) {
|
|
7071
7512
|
const dir = bucketDir(root, slug);
|
|
7072
7513
|
await promises.mkdir(dir, { recursive: true });
|
|
7073
7514
|
const target = bucketSessionsFile(root, slug);
|
|
7074
|
-
const tmp =
|
|
7515
|
+
const tmp = tmpPathFor(target);
|
|
7075
7516
|
await promises.writeFile(tmp, serialize(snapshot), "utf8");
|
|
7076
7517
|
await promises.rename(tmp, target);
|
|
7077
7518
|
}
|
|
@@ -7079,7 +7520,7 @@ function writeBucketSnapshotSync(root, slug, snapshot) {
|
|
|
7079
7520
|
const dir = bucketDir(root, slug);
|
|
7080
7521
|
mkdirSync(dir, { recursive: true });
|
|
7081
7522
|
const target = bucketSessionsFile(root, slug);
|
|
7082
|
-
const tmp =
|
|
7523
|
+
const tmp = tmpPathFor(target);
|
|
7083
7524
|
writeFileSync(tmp, serialize(snapshot), "utf8");
|
|
7084
7525
|
renameSync(tmp, target);
|
|
7085
7526
|
}
|
|
@@ -7184,9 +7625,9 @@ async function listClaudeSubagents(projectDir, adapterSessionId) {
|
|
|
7184
7625
|
return entries.filter((e) => e.startsWith("agent-") && e.endsWith(".jsonl")).map((e) => join(dir, e)).sort();
|
|
7185
7626
|
}
|
|
7186
7627
|
async function resolveNativeLink(input) {
|
|
7187
|
-
const { cwd, adapterSlug, adapterSessionId } = input;
|
|
7628
|
+
const { cwd, adapterSlug, adapterSessionId, adapterConfigDir } = input;
|
|
7188
7629
|
if (adapterSlug === "claude-code") {
|
|
7189
|
-
const dir = claudeCodeProjectDir(cwd);
|
|
7630
|
+
const dir = claudeCodeProjectDir(cwd, adapterConfigDir);
|
|
7190
7631
|
const path = join(dir, `${adapterSessionId}.jsonl`);
|
|
7191
7632
|
const subagents = await listClaudeSubagents(dir, adapterSessionId);
|
|
7192
7633
|
return { kind: "claude-jsonl", path, subagents };
|
|
@@ -7293,8 +7734,8 @@ function createTerminalTranscriptWriter(opts) {
|
|
|
7293
7734
|
const stream = streams.get(sessionId);
|
|
7294
7735
|
if (!stream) return Promise.resolve();
|
|
7295
7736
|
streams.delete(sessionId);
|
|
7296
|
-
return new Promise((
|
|
7297
|
-
stream.end(() =>
|
|
7737
|
+
return new Promise((resolve27) => {
|
|
7738
|
+
stream.end(() => resolve27());
|
|
7298
7739
|
});
|
|
7299
7740
|
},
|
|
7300
7741
|
closeAll() {
|
|
@@ -7303,8 +7744,8 @@ function createTerminalTranscriptWriter(opts) {
|
|
|
7303
7744
|
const stream = streams.get(sessionId);
|
|
7304
7745
|
if (!stream) continue;
|
|
7305
7746
|
closings.push(
|
|
7306
|
-
new Promise((
|
|
7307
|
-
stream.end(() =>
|
|
7747
|
+
new Promise((resolve27) => {
|
|
7748
|
+
stream.end(() => resolve27());
|
|
7308
7749
|
})
|
|
7309
7750
|
);
|
|
7310
7751
|
}
|
|
@@ -7322,7 +7763,7 @@ var defaultResolver = (model) => resolvePricing(model);
|
|
|
7322
7763
|
function tokenCost(tokens, pricePer1M) {
|
|
7323
7764
|
return tokens === void 0 ? 0 : tokens * pricePer1M / 1e6;
|
|
7324
7765
|
}
|
|
7325
|
-
function deriveSessionUsage(input,
|
|
7766
|
+
function deriveSessionUsage(input, resolve27 = defaultResolver) {
|
|
7326
7767
|
const contextUsed = plausibleContextUsed(input.contextSize, input.contextUsed);
|
|
7327
7768
|
const base = {
|
|
7328
7769
|
...input.model !== void 0 ? { model: input.model } : {},
|
|
@@ -7336,7 +7777,7 @@ function deriveSessionUsage(input, resolve26 = defaultResolver) {
|
|
|
7336
7777
|
}
|
|
7337
7778
|
const hasTokens = input.tokensIn !== void 0 || input.tokensOut !== void 0;
|
|
7338
7779
|
if (hasTokens) {
|
|
7339
|
-
const pricing = input.model !== void 0 ?
|
|
7780
|
+
const pricing = input.model !== void 0 ? resolve27(input.model) : void 0;
|
|
7340
7781
|
if (!pricing) {
|
|
7341
7782
|
return { ...base, source: "no-pricing" };
|
|
7342
7783
|
}
|
|
@@ -7404,14 +7845,14 @@ async function buildRecentDigest(sessionId) {
|
|
|
7404
7845
|
}
|
|
7405
7846
|
async function captureGitStatus(cwd) {
|
|
7406
7847
|
if (!cwd) return void 0;
|
|
7407
|
-
return new Promise((
|
|
7848
|
+
return new Promise((resolve27) => {
|
|
7408
7849
|
execFile("git", ["status", "--porcelain"], { cwd }, (err, stdout) => {
|
|
7409
7850
|
if (err) {
|
|
7410
|
-
|
|
7851
|
+
resolve27(void 0);
|
|
7411
7852
|
return;
|
|
7412
7853
|
}
|
|
7413
7854
|
const trimmed = stdout.trim();
|
|
7414
|
-
|
|
7855
|
+
resolve27(trimmed || "(working tree clean)");
|
|
7415
7856
|
});
|
|
7416
7857
|
});
|
|
7417
7858
|
}
|
|
@@ -7536,6 +7977,21 @@ function formatAccessForSpawn(desc) {
|
|
|
7536
7977
|
if (!desc.accessProfile?.profileRef) return void 0;
|
|
7537
7978
|
return { profileRef: desc.accessProfile.profileRef };
|
|
7538
7979
|
}
|
|
7980
|
+
function stripOwnCallerStamp(servers, prevId) {
|
|
7981
|
+
if (!servers) return servers;
|
|
7982
|
+
return servers.map((entry) => {
|
|
7983
|
+
if (entry.transport !== "http" || typeof entry.ref !== "string") return entry;
|
|
7984
|
+
let url;
|
|
7985
|
+
try {
|
|
7986
|
+
url = new URL(entry.ref);
|
|
7987
|
+
} catch {
|
|
7988
|
+
return entry;
|
|
7989
|
+
}
|
|
7990
|
+
if (url.searchParams.get("callerSessionId") !== prevId) return entry;
|
|
7991
|
+
url.searchParams.delete("callerSessionId");
|
|
7992
|
+
return { ...entry, ref: url.toString() };
|
|
7993
|
+
});
|
|
7994
|
+
}
|
|
7539
7995
|
function formatPostureForSpawn(desc) {
|
|
7540
7996
|
return desc.posture;
|
|
7541
7997
|
}
|
|
@@ -7565,7 +8021,7 @@ async function continueAgentSessionFresh(deps2, prev, opts = {}) {
|
|
|
7565
8021
|
access: formatAccessForSpawn(prev),
|
|
7566
8022
|
posture: formatPostureForSpawn(prev),
|
|
7567
8023
|
contextProfile: prev.contextProfile,
|
|
7568
|
-
mcpServers: prev.mcpServers,
|
|
8024
|
+
mcpServers: stripOwnCallerStamp(prev.mcpServers, prev.id),
|
|
7569
8025
|
label: prev.label ? `${prev.label} (continued)` : void 0,
|
|
7570
8026
|
title: prev.title ? `${prev.title} (continued)` : void 0,
|
|
7571
8027
|
contextContinuity: opts.contextContinuity ?? prev.contextContinuity,
|
|
@@ -7665,6 +8121,9 @@ var SESSIONS_FILE_PATH = () => resolve(homedir(), ".agentproto", "sessions.json"
|
|
|
7665
8121
|
function mintSessionId() {
|
|
7666
8122
|
return `sess_${randomUUID().slice(0, 8)}`;
|
|
7667
8123
|
}
|
|
8124
|
+
function adapterConfigDirFor(sessionId) {
|
|
8125
|
+
return resolve(homedir(), ".agentproto", "adapter-config", sessionId);
|
|
8126
|
+
}
|
|
7668
8127
|
var SESSION_ID_ENV = "AGENTPROTO_SESSION_ID";
|
|
7669
8128
|
var WORKSPACE_SLUG_ENV = "AGENTPROTO_WORKSPACE_SLUG";
|
|
7670
8129
|
var PARENT_SESSION_ID_ENV = "AGENTPROTO_PARENT_SESSION_ID";
|
|
@@ -7704,6 +8163,7 @@ function toSessionSummary(desc) {
|
|
|
7704
8163
|
activitySummary: desc.activitySummary,
|
|
7705
8164
|
archived: desc.archived,
|
|
7706
8165
|
keepAlive: desc.keepAlive,
|
|
8166
|
+
pinned: desc.pinned,
|
|
7707
8167
|
pty: desc.pty,
|
|
7708
8168
|
name: desc.name,
|
|
7709
8169
|
argv: desc.argv,
|
|
@@ -7727,6 +8187,7 @@ function toSessionSummary(desc) {
|
|
|
7727
8187
|
blockedOn: desc.blockedOn,
|
|
7728
8188
|
stalledSinceMs: desc.stalledSinceMs,
|
|
7729
8189
|
pendingBgTasks: desc.pendingBgTasks,
|
|
8190
|
+
lastTurnErroredAt: desc.lastTurnErroredAt,
|
|
7730
8191
|
origin: desc.origin,
|
|
7731
8192
|
parentSessionId: desc.parentSessionId,
|
|
7732
8193
|
depth: desc.depth,
|
|
@@ -7769,6 +8230,14 @@ function stampProcessAlive(desc) {
|
|
|
7769
8230
|
desc.processAlive = false;
|
|
7770
8231
|
}
|
|
7771
8232
|
}
|
|
8233
|
+
function killChildIfSpawned(child, signal) {
|
|
8234
|
+
if (!child) return;
|
|
8235
|
+
if (typeof child.pid !== "number" || child.pid <= 0) return;
|
|
8236
|
+
try {
|
|
8237
|
+
child.kill(signal);
|
|
8238
|
+
} catch {
|
|
8239
|
+
}
|
|
8240
|
+
}
|
|
7772
8241
|
function stampInterrupted(desc) {
|
|
7773
8242
|
if (desc.killedMidTurn === true && desc.endedReason === "daemon-restart") {
|
|
7774
8243
|
desc.interrupted = true;
|
|
@@ -7894,8 +8363,10 @@ function createSessionsRegistry(opts) {
|
|
|
7894
8363
|
desc.currentPhase = desc.busy ? "thinking" : "idle";
|
|
7895
8364
|
};
|
|
7896
8365
|
const watchersById = /* @__PURE__ */ new Map();
|
|
8366
|
+
const watcherDetailsById = /* @__PURE__ */ new Map();
|
|
7897
8367
|
const stampWatchers = (desc) => {
|
|
7898
8368
|
desc.watchers = watchersById.get(desc.id) ?? 0;
|
|
8369
|
+
desc.watcherDetails = [...watcherDetailsById.get(desc.id) ?? []];
|
|
7899
8370
|
};
|
|
7900
8371
|
const childrenBusyCounts = () => {
|
|
7901
8372
|
const all = Array.from(sessions.values());
|
|
@@ -8136,7 +8607,14 @@ function createSessionsRegistry(opts) {
|
|
|
8136
8607
|
if (!adapterSlug || !adapterSessionId || !cwd) return;
|
|
8137
8608
|
void (async () => {
|
|
8138
8609
|
try {
|
|
8139
|
-
const native = await resolveNativeLink({
|
|
8610
|
+
const native = await resolveNativeLink({
|
|
8611
|
+
cwd,
|
|
8612
|
+
adapterSlug,
|
|
8613
|
+
adapterSessionId,
|
|
8614
|
+
// Config-dir-isolated session (#824): the native transcript lives
|
|
8615
|
+
// under the session's own CLAUDE_CONFIG_DIR, not ~/.claude.
|
|
8616
|
+
...desc.adapterConfigDir ? { adapterConfigDir: desc.adapterConfigDir } : {}
|
|
8617
|
+
});
|
|
8140
8618
|
const registered = readRegisteredSlugs(workspacesConfigPath);
|
|
8141
8619
|
const slug = resolveBucketSlug(desc.workspaceSlug, registered);
|
|
8142
8620
|
const record2 = {
|
|
@@ -8145,6 +8623,7 @@ function createSessionsRegistry(opts) {
|
|
|
8145
8623
|
cwd,
|
|
8146
8624
|
adapterSlug,
|
|
8147
8625
|
adapterSessionId,
|
|
8626
|
+
...desc.adapterConfigDir ? { adapterConfigDir: desc.adapterConfigDir } : {},
|
|
8148
8627
|
...native ? { native } : {},
|
|
8149
8628
|
agentprotoTranscript: sessionEventsPath(desc.id, transcriptBaseDir),
|
|
8150
8629
|
...desc.title ? { title: desc.title } : {},
|
|
@@ -8160,7 +8639,7 @@ function createSessionsRegistry(opts) {
|
|
|
8160
8639
|
})();
|
|
8161
8640
|
};
|
|
8162
8641
|
const schedulePersist = () => {
|
|
8163
|
-
if (!persist) return;
|
|
8642
|
+
if (!persist || shutdownDone) return;
|
|
8164
8643
|
if (persistTimer) clearTimeout(persistTimer);
|
|
8165
8644
|
persistTimer = setTimeout(() => {
|
|
8166
8645
|
void persistSnapshot();
|
|
@@ -8196,11 +8675,13 @@ function createSessionsRegistry(opts) {
|
|
|
8196
8675
|
rows,
|
|
8197
8676
|
heldIdsByBucket.get(slug) ?? EMPTY_ID_SET
|
|
8198
8677
|
);
|
|
8199
|
-
const
|
|
8678
|
+
const persistOnce = async () => {
|
|
8200
8679
|
try {
|
|
8680
|
+
if (shutdownDone) return;
|
|
8201
8681
|
const savedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8202
8682
|
if (partitioned) {
|
|
8203
8683
|
for (const [slug, rows] of groupRowsByBucket()) {
|
|
8684
|
+
if (shutdownDone) return;
|
|
8204
8685
|
await writeBucketSnapshot(bucketsRoot, slug, {
|
|
8205
8686
|
savedAt,
|
|
8206
8687
|
sessions: rowsToWrite(slug, rows)
|
|
@@ -8217,6 +8698,24 @@ function createSessionsRegistry(opts) {
|
|
|
8217
8698
|
);
|
|
8218
8699
|
}
|
|
8219
8700
|
};
|
|
8701
|
+
let persistRunning = false;
|
|
8702
|
+
let persistRerun = false;
|
|
8703
|
+
const persistSnapshot = async () => {
|
|
8704
|
+
if (shutdownDone) return;
|
|
8705
|
+
if (persistRunning) {
|
|
8706
|
+
persistRerun = true;
|
|
8707
|
+
return;
|
|
8708
|
+
}
|
|
8709
|
+
persistRunning = true;
|
|
8710
|
+
try {
|
|
8711
|
+
do {
|
|
8712
|
+
persistRerun = false;
|
|
8713
|
+
await persistOnce();
|
|
8714
|
+
} while (persistRerun && !shutdownDone);
|
|
8715
|
+
} finally {
|
|
8716
|
+
persistRunning = false;
|
|
8717
|
+
}
|
|
8718
|
+
};
|
|
8220
8719
|
const appendLine = (rt, line, stream) => {
|
|
8221
8720
|
rt.recentLines.push(line);
|
|
8222
8721
|
if (rt.recentLines.length > RECENT_LINES_CAP) {
|
|
@@ -8510,11 +9009,11 @@ function createSessionsRegistry(opts) {
|
|
|
8510
9009
|
};
|
|
8511
9010
|
const waitForTurnSettled = (rt, id, caller) => {
|
|
8512
9011
|
if (!rt.busy) return Promise.resolve();
|
|
8513
|
-
return new Promise((
|
|
9012
|
+
return new Promise((resolve27, reject) => {
|
|
8514
9013
|
const onBusy = (busy) => {
|
|
8515
9014
|
if (busy) return;
|
|
8516
9015
|
cleanup();
|
|
8517
|
-
|
|
9016
|
+
resolve27();
|
|
8518
9017
|
};
|
|
8519
9018
|
const cleanup = () => {
|
|
8520
9019
|
clearTimeout(timer);
|
|
@@ -8547,6 +9046,30 @@ function createSessionsRegistry(opts) {
|
|
|
8547
9046
|
}
|
|
8548
9047
|
await waitForTurnSettled(rt, id, caller);
|
|
8549
9048
|
};
|
|
9049
|
+
const dispatchQueuedPrompt = (rt) => {
|
|
9050
|
+
const queue = rt.desc.promptQueue;
|
|
9051
|
+
const next = queue?.[0];
|
|
9052
|
+
if (!queue || !next) return;
|
|
9053
|
+
rt.desc.promptQueue = queue.slice(1);
|
|
9054
|
+
schedulePersist();
|
|
9055
|
+
void (async () => {
|
|
9056
|
+
try {
|
|
9057
|
+
await maybeResumeAgent(rt);
|
|
9058
|
+
const liveRt = validateAgentTurn(rt.desc.id, "queue-drain");
|
|
9059
|
+
await runAgentTurn(
|
|
9060
|
+
liveRt,
|
|
9061
|
+
next.message,
|
|
9062
|
+
next.source ? { promptSource: next.source } : void 0
|
|
9063
|
+
);
|
|
9064
|
+
} catch (err) {
|
|
9065
|
+
appendLine(
|
|
9066
|
+
rt,
|
|
9067
|
+
`[error] queued prompt dropped \u2014 ${err instanceof Error ? err.message : String(err)}`,
|
|
9068
|
+
"stderr"
|
|
9069
|
+
);
|
|
9070
|
+
}
|
|
9071
|
+
})();
|
|
9072
|
+
};
|
|
8550
9073
|
const recordFailedResume = (rt) => {
|
|
8551
9074
|
rt.desc.resumeAttempts = (rt.desc.resumeAttempts ?? 0) + 1;
|
|
8552
9075
|
rt.desc.lastResumeAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -8942,6 +9465,13 @@ ${message}`;
|
|
|
8942
9465
|
delete rt.desc.nextRestartAt;
|
|
8943
9466
|
delete rt.desc.recentRestartAts;
|
|
8944
9467
|
}
|
|
9468
|
+
if (turnEndReason === "error") {
|
|
9469
|
+
rt.desc.lastTurnErroredAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9470
|
+
schedulePersist();
|
|
9471
|
+
} else if (rt.desc.lastTurnErroredAt !== void 0) {
|
|
9472
|
+
delete rt.desc.lastTurnErroredAt;
|
|
9473
|
+
schedulePersist();
|
|
9474
|
+
}
|
|
8945
9475
|
if (rt.readUsage) {
|
|
8946
9476
|
try {
|
|
8947
9477
|
const usage2 = await rt.readUsage();
|
|
@@ -9048,6 +9578,7 @@ ${message}`;
|
|
|
9048
9578
|
});
|
|
9049
9579
|
}
|
|
9050
9580
|
}
|
|
9581
|
+
dispatchQueuedPrompt(rt);
|
|
9051
9582
|
}
|
|
9052
9583
|
};
|
|
9053
9584
|
const wireOutputStreams = (rt) => {
|
|
@@ -9218,6 +9749,9 @@ ${message}`;
|
|
|
9218
9749
|
// Persist the spawn-time MCP mounts so resume re-mounts the same
|
|
9219
9750
|
// toolset (orchestrator WP1). Reference-only shape — no secrets.
|
|
9220
9751
|
...input.mcpServers ? { mcpServers: input.mcpServers } : {},
|
|
9752
|
+
// Persist the isolated-config location so restart/lazy-resume can
|
|
9753
|
+
// hand the respawned adapter the SAME dir (native-resume store).
|
|
9754
|
+
...input.adapterConfigDir ? { adapterConfigDir: input.adapterConfigDir } : {},
|
|
9221
9755
|
// Parent attribution + depth (orchestrator WP4). Depth is always
|
|
9222
9756
|
// recorded (defaults to 0) so subtree/depth logic never has to
|
|
9223
9757
|
// distinguish "absent" from "root".
|
|
@@ -9324,6 +9858,7 @@ ${message}`;
|
|
|
9324
9858
|
...input.title ? { title: input.title } : {},
|
|
9325
9859
|
...input.label ? { renamedByUser: false } : {},
|
|
9326
9860
|
...input.mcpServers ? { mcpServers: input.mcpServers } : {},
|
|
9861
|
+
...input.adapterConfigDir ? { adapterConfigDir: input.adapterConfigDir } : {},
|
|
9327
9862
|
...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
|
|
9328
9863
|
...input.notifyParentOnCrash ? { notifyParentOnCrash: true } : {},
|
|
9329
9864
|
...input.origin ? { origin: input.origin } : {},
|
|
@@ -9695,6 +10230,17 @@ ${message}`;
|
|
|
9695
10230
|
if (opts2?.interrupt && rtPre.busy) {
|
|
9696
10231
|
await interruptInFlightTurn(rtPre, id, "enqueuePrompt");
|
|
9697
10232
|
}
|
|
10233
|
+
if (opts2?.queue && rtPre.busy) {
|
|
10234
|
+
const item = {
|
|
10235
|
+
id: opts2.queueId ?? `q_${randomUUID().slice(0, 8)}`,
|
|
10236
|
+
message,
|
|
10237
|
+
queuedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10238
|
+
...opts2.source ? { source: opts2.source } : {}
|
|
10239
|
+
};
|
|
10240
|
+
rtPre.desc.promptQueue = opts2.force ? [item, ...rtPre.desc.promptQueue ?? []] : [...rtPre.desc.promptQueue ?? [], item];
|
|
10241
|
+
schedulePersist();
|
|
10242
|
+
return;
|
|
10243
|
+
}
|
|
9698
10244
|
await maybeResumeAgent(rtPre);
|
|
9699
10245
|
const rt = validateAgentTurn(id, "enqueuePrompt");
|
|
9700
10246
|
void runAgentTurn(rt, message, opts2?.source ? { promptSource: opts2.source } : void 0).catch((err) => {
|
|
@@ -9705,6 +10251,17 @@ ${message}`;
|
|
|
9705
10251
|
);
|
|
9706
10252
|
});
|
|
9707
10253
|
},
|
|
10254
|
+
removeQueuedPrompt(id, queueId) {
|
|
10255
|
+
const rt = sessions.get(id);
|
|
10256
|
+
if (!rt?.desc.promptQueue?.length) return { removed: false };
|
|
10257
|
+
const next = rt.desc.promptQueue.filter((p) => p.id !== queueId);
|
|
10258
|
+
const removed = next.length !== rt.desc.promptQueue.length;
|
|
10259
|
+
if (removed) {
|
|
10260
|
+
rt.desc.promptQueue = next;
|
|
10261
|
+
schedulePersist();
|
|
10262
|
+
}
|
|
10263
|
+
return { removed };
|
|
10264
|
+
},
|
|
9708
10265
|
async resumeOnBoot(id) {
|
|
9709
10266
|
const rt = sessions.get(id);
|
|
9710
10267
|
if (!rt) return { status: "skipped", reason: "unknown" };
|
|
@@ -9934,13 +10491,26 @@ ${message}`;
|
|
|
9934
10491
|
}
|
|
9935
10492
|
return desc;
|
|
9936
10493
|
},
|
|
9937
|
-
incWatchers(id) {
|
|
10494
|
+
incWatchers(id, detail) {
|
|
9938
10495
|
watchersById.set(id, (watchersById.get(id) ?? 0) + 1);
|
|
10496
|
+
if (detail) {
|
|
10497
|
+
const list = watcherDetailsById.get(id);
|
|
10498
|
+
if (list) list.push(detail);
|
|
10499
|
+
else watcherDetailsById.set(id, [detail]);
|
|
10500
|
+
}
|
|
9939
10501
|
},
|
|
9940
|
-
decWatchers(id) {
|
|
10502
|
+
decWatchers(id, detail) {
|
|
9941
10503
|
const next = (watchersById.get(id) ?? 0) - 1;
|
|
9942
10504
|
if (next > 0) watchersById.set(id, next);
|
|
9943
10505
|
else watchersById.delete(id);
|
|
10506
|
+
if (detail) {
|
|
10507
|
+
const list = watcherDetailsById.get(id);
|
|
10508
|
+
if (list) {
|
|
10509
|
+
const idx = list.indexOf(detail);
|
|
10510
|
+
if (idx >= 0) list.splice(idx, 1);
|
|
10511
|
+
if (list.length === 0) watcherDetailsById.delete(id);
|
|
10512
|
+
}
|
|
10513
|
+
}
|
|
9944
10514
|
},
|
|
9945
10515
|
attach(id, onLine) {
|
|
9946
10516
|
const rt = sessions.get(id);
|
|
@@ -10045,7 +10615,7 @@ ${message}`;
|
|
|
10045
10615
|
}
|
|
10046
10616
|
void terminalTranscriptWriter.close(rt.desc.id);
|
|
10047
10617
|
}
|
|
10048
|
-
rt.child
|
|
10618
|
+
killChildIfSpawned(rt.child, signal);
|
|
10049
10619
|
if (rt.browserStop) {
|
|
10050
10620
|
void rt.browserStop().catch(() => void 0);
|
|
10051
10621
|
}
|
|
@@ -10070,7 +10640,7 @@ ${message}`;
|
|
|
10070
10640
|
tracedSessions.delete(rt.desc.id);
|
|
10071
10641
|
rt.agentSession = void 0;
|
|
10072
10642
|
}
|
|
10073
|
-
rt.child
|
|
10643
|
+
killChildIfSpawned(rt.child, "SIGTERM");
|
|
10074
10644
|
schedulePersist();
|
|
10075
10645
|
const banner = "\u2500\u2500 reaped after being idle past the threshold; the adapter process was freed. Re-prompt to resume this session in place. \u2500\u2500";
|
|
10076
10646
|
appendLine(rt, banner, "stdout");
|
|
@@ -10255,6 +10825,44 @@ ${message}`;
|
|
|
10255
10825
|
stampProcessAlive(rt.desc);
|
|
10256
10826
|
return rt.desc;
|
|
10257
10827
|
},
|
|
10828
|
+
setPinned(id, pinned) {
|
|
10829
|
+
const rt = sessions.get(id);
|
|
10830
|
+
if (!rt) throw new Error(`setPinned: no session "${id}"`);
|
|
10831
|
+
rt.desc.pinned = pinned;
|
|
10832
|
+
schedulePersist();
|
|
10833
|
+
sessionEvents?.emit({
|
|
10834
|
+
type: "session:pinned-changed",
|
|
10835
|
+
sessionId: id,
|
|
10836
|
+
pinned,
|
|
10837
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
10838
|
+
});
|
|
10839
|
+
stampProcessAlive(rt.desc);
|
|
10840
|
+
return rt.desc;
|
|
10841
|
+
},
|
|
10842
|
+
flagAwaitingInput(id, patch) {
|
|
10843
|
+
const rt = sessions.get(id);
|
|
10844
|
+
if (!rt) throw new Error(`flagAwaitingInput: no session "${id}"`);
|
|
10845
|
+
const isAlive = rt.desc.status === "running" || rt.desc.status === "starting";
|
|
10846
|
+
if (!isAlive) {
|
|
10847
|
+
throw new Error(
|
|
10848
|
+
`flagAwaitingInput: session "${id}" is ${rt.desc.status}, not live \u2014 only a running/starting session's awaiting-input classification can be corrected.`
|
|
10849
|
+
);
|
|
10850
|
+
}
|
|
10851
|
+
rt.desc.awaitingInput = patch.awaitingInput;
|
|
10852
|
+
rt.desc.awaitingQuestion = patch.awaitingInput && patch.question !== void 0 ? { text: patch.question, source: "structured" } : void 0;
|
|
10853
|
+
schedulePersist();
|
|
10854
|
+
sessionEvents?.emit({
|
|
10855
|
+
type: "session:awaiting-input-flagged",
|
|
10856
|
+
sessionId: id,
|
|
10857
|
+
awaitingInput: patch.awaitingInput,
|
|
10858
|
+
reason: patch.reason,
|
|
10859
|
+
...rt.desc.awaitingQuestion ? { question: rt.desc.awaitingQuestion } : {},
|
|
10860
|
+
...rt.desc.label ? { label: rt.desc.label } : {},
|
|
10861
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
10862
|
+
});
|
|
10863
|
+
stampProcessAlive(rt.desc);
|
|
10864
|
+
return rt.desc;
|
|
10865
|
+
},
|
|
10258
10866
|
listPendingPermissions(filter) {
|
|
10259
10867
|
const all = Array.from(pendingPermissions.values());
|
|
10260
10868
|
const scoped = filter?.sessionId ? all.filter((p) => p.sessionId === filter.sessionId) : all;
|
|
@@ -10313,7 +10921,7 @@ ${message}`;
|
|
|
10313
10921
|
} catch {
|
|
10314
10922
|
}
|
|
10315
10923
|
}
|
|
10316
|
-
rt.child
|
|
10924
|
+
killChildIfSpawned(rt.child, "SIGTERM");
|
|
10317
10925
|
emitExited(rt);
|
|
10318
10926
|
}
|
|
10319
10927
|
}
|
|
@@ -10359,9 +10967,17 @@ function loadHistorySnapshot(persistPath, sessions, sessionEvents, bucketSlug, s
|
|
|
10359
10967
|
try {
|
|
10360
10968
|
parsed = JSON.parse(raw);
|
|
10361
10969
|
} catch {
|
|
10362
|
-
|
|
10363
|
-
|
|
10364
|
-
|
|
10970
|
+
const quarantine = `${persistPath}.corrupt-${Date.now()}`;
|
|
10971
|
+
try {
|
|
10972
|
+
renameSync(persistPath, quarantine);
|
|
10973
|
+
console.warn(
|
|
10974
|
+
`[sessions] history file ${persistPath} is malformed \u2014 quarantined to ${quarantine} and starting this bucket empty`
|
|
10975
|
+
);
|
|
10976
|
+
} catch {
|
|
10977
|
+
console.warn(
|
|
10978
|
+
`[sessions] history file ${persistPath} is malformed and could not be quarantined \u2014 ignoring`
|
|
10979
|
+
);
|
|
10980
|
+
}
|
|
10365
10981
|
return;
|
|
10366
10982
|
}
|
|
10367
10983
|
if (!Array.isArray(parsed.sessions)) return;
|
|
@@ -10422,109 +11038,9 @@ function quoteArg(arg) {
|
|
|
10422
11038
|
if (/^[a-zA-Z0-9._/=:@,+-]+$/.test(arg)) return arg;
|
|
10423
11039
|
return `"${arg.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
10424
11040
|
}
|
|
10425
|
-
var MARKER = "@agentproto-bot";
|
|
10426
|
-
var fmtTokens = (n) => {
|
|
10427
|
-
if (typeof n !== "number" || !Number.isFinite(n)) return null;
|
|
10428
|
-
return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
|
|
10429
|
-
};
|
|
10430
|
-
function cwdLabel(cwd, workspaceSlug) {
|
|
10431
|
-
const leaf = basename(cwd);
|
|
10432
|
-
if (!workspaceSlug || workspaceSlug === leaf) return workspaceSlug ?? leaf;
|
|
10433
|
-
return `${workspaceSlug}/${leaf}`;
|
|
10434
|
-
}
|
|
10435
|
-
var buildFooter = ({
|
|
10436
|
-
prov,
|
|
10437
|
-
authMode,
|
|
10438
|
-
runId,
|
|
10439
|
-
runUrl,
|
|
10440
|
-
sha,
|
|
10441
|
-
kind = "review"
|
|
10442
|
-
}) => {
|
|
10443
|
-
const parts = [`\u{1F916} **${MARKER}** \u2014 ${kind}`];
|
|
10444
|
-
if (prov?.sessionId) {
|
|
10445
|
-
parts.push(`session \`${prov.sessionId}\`${prov.label ? ` (\`${prov.label}\`)` : ""}`);
|
|
10446
|
-
}
|
|
10447
|
-
if (prov?.adapter) parts.push([prov.adapter, authMode].filter(Boolean).join(" / "));
|
|
10448
|
-
else if (!prov?.sessionId) parts.push(`legacy fallback${authMode ? ` (${authMode})` : ""}`);
|
|
10449
|
-
else if (authMode) parts.push(authMode);
|
|
10450
|
-
if (prov?.authProfile) parts.push(`auth-profile \`${prov.authProfile}\``);
|
|
10451
|
-
if (prov?.model) parts.push(`model \`${prov.model}\``);
|
|
10452
|
-
if (prov?.sandboxId) parts.push(`e2b \`${prov.sandboxId}\``);
|
|
10453
|
-
if (prov?.parentSessionId) parts.push(`supervisor \`${prov.parentSessionId}\``);
|
|
10454
|
-
const tin = fmtTokens(prov?.tokensIn);
|
|
10455
|
-
const tout = fmtTokens(prov?.tokensOut);
|
|
10456
|
-
if (tin || tout) parts.push(`${tin ?? "?"} in / ${tout ?? "?"} out`);
|
|
10457
|
-
if (typeof prov?.costUsd === "number") {
|
|
10458
|
-
parts.push(`$${prov.costUsd.toFixed(4)}${prov.source && prov.source !== "adapter" ? ` (${prov.source})` : ""}`);
|
|
10459
|
-
}
|
|
10460
|
-
const showLocalHostCwd = prov?.source === "local" || prov?.source === "daemon" || !runId;
|
|
10461
|
-
if (runId && !showLocalHostCwd) parts.push(`run [${runId}](${runUrl})`);
|
|
10462
|
-
if (showLocalHostCwd) {
|
|
10463
|
-
if (prov?.host) parts.push(`host \`${prov.host}\``);
|
|
10464
|
-
if (prov?.cwd) parts.push(`cwd \`${cwdLabel(prov.cwd, prov.workspaceSlug)}\``);
|
|
10465
|
-
}
|
|
10466
|
-
if (sha) parts.push(`sha \`${sha.slice(0, 7)}\``);
|
|
10467
|
-
return `
|
|
10468
|
-
|
|
10469
|
-
---
|
|
10470
|
-
<sub>${parts.join(" \xB7 ")}</sub>`;
|
|
10471
|
-
};
|
|
10472
|
-
function sessionFooterProvenance(session, options = {}) {
|
|
10473
|
-
const prov = {
|
|
10474
|
-
sessionId: session.id,
|
|
10475
|
-
label: session.label,
|
|
10476
|
-
adapter: session.harness ?? session.adapterSlug,
|
|
10477
|
-
model: session.model,
|
|
10478
|
-
authProfile: session.accessProfile?.label ?? session.accessProfile?.profileRef,
|
|
10479
|
-
parentSessionId: options.supervisor?.id,
|
|
10480
|
-
costUsd: session.costUsd,
|
|
10481
|
-
tokensIn: session.tokensIn,
|
|
10482
|
-
tokensOut: session.tokensOut,
|
|
10483
|
-
source: options.source ?? "daemon",
|
|
10484
|
-
host: options.host,
|
|
10485
|
-
cwd: session.cwd,
|
|
10486
|
-
workspaceSlug: session.workspaceSlug
|
|
10487
|
-
};
|
|
10488
|
-
return { prov, authMode: session.auth?.mode };
|
|
10489
|
-
}
|
|
10490
|
-
function buildSessionPrFooter(session, options = {}) {
|
|
10491
|
-
const { prov, authMode } = sessionFooterProvenance(session, options);
|
|
10492
|
-
return buildFooter({ prov, authMode, sha: options.sha, kind: "PR" });
|
|
10493
|
-
}
|
|
10494
|
-
function appendFooterOnce(body, footer) {
|
|
10495
|
-
if (body.includes(MARKER)) return body;
|
|
10496
|
-
return `${body}${footer}`;
|
|
10497
|
-
}
|
|
10498
|
-
function parseGhPrCreate(command, args, stdout) {
|
|
10499
|
-
if (basename(command) !== "gh") return null;
|
|
10500
|
-
const positionals = args.filter((a) => !a.startsWith("-"));
|
|
10501
|
-
if (positionals[0] !== "pr" || positionals[1] !== "create") return null;
|
|
10502
|
-
const re = /https?:\/\/\S+?\/pull\/(\d+)/g;
|
|
10503
|
-
let match;
|
|
10504
|
-
let last = null;
|
|
10505
|
-
while ((match = re.exec(stdout)) !== null) {
|
|
10506
|
-
last = { url: match[0], number: Number(match[1]) };
|
|
10507
|
-
}
|
|
10508
|
-
return last;
|
|
10509
|
-
}
|
|
10510
|
-
function cwdRelated(sessionCwd, cwd) {
|
|
10511
|
-
if (sessionCwd === cwd) return true;
|
|
10512
|
-
const sep2 = "/";
|
|
10513
|
-
return cwd.startsWith(sessionCwd + sep2) || sessionCwd.startsWith(cwd + sep2);
|
|
10514
|
-
}
|
|
10515
|
-
function pickExecutorSession(sessions, cwd) {
|
|
10516
|
-
const candidates = sessions.filter(
|
|
10517
|
-
(s) => s.kind === "agent-cli" && typeof s.cwd === "string" && cwdRelated(s.cwd, cwd)
|
|
10518
|
-
);
|
|
10519
|
-
if (candidates.length === 0) return void 0;
|
|
10520
|
-
const alive = (s) => s.status === "running" || s.status === "starting";
|
|
10521
|
-
const byRecency = (a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? "");
|
|
10522
|
-
const live = candidates.filter(alive).sort(byRecency);
|
|
10523
|
-
if (live.length > 0) return live[0];
|
|
10524
|
-
return [...candidates].sort(byRecency)[0];
|
|
10525
|
-
}
|
|
10526
11041
|
|
|
10527
11042
|
// src/pr-provenance-stamp.ts
|
|
11043
|
+
init_pr_provenance();
|
|
10528
11044
|
async function stampPrProvenance(input) {
|
|
10529
11045
|
try {
|
|
10530
11046
|
if (input.exitCode !== 0) return { stamped: false, reason: "command failed" };
|
|
@@ -10558,19 +11074,19 @@ async function stampFooterOnPr(input) {
|
|
|
10558
11074
|
const view = await run(["pr", "view", input.prUrl, "--json", "body", "--jq", ".body"], input.cwd);
|
|
10559
11075
|
if (view.exitCode !== 0) return { stamped: false, reason: `gh pr view exit ${view.exitCode}` };
|
|
10560
11076
|
const body = view.stdout.replace(/\n+$/, "");
|
|
10561
|
-
const alreadyStamped = body
|
|
11077
|
+
const alreadyStamped = hasProvenanceFooter(body);
|
|
10562
11078
|
if (!alreadyStamped) {
|
|
10563
11079
|
const newBody = appendFooterOnce(body, footer);
|
|
10564
11080
|
const edit = await run(["pr", "edit", input.prUrl, "--body", newBody], input.cwd);
|
|
10565
11081
|
if (edit.exitCode !== 0) {
|
|
10566
11082
|
return { stamped: false, reason: `gh pr edit exit ${edit.exitCode}` };
|
|
10567
11083
|
}
|
|
11084
|
+
input.registry.recordOpenedPr(input.session.id, {
|
|
11085
|
+
adapter: input.session.harness ?? input.session.adapterSlug ?? "gh",
|
|
11086
|
+
number: input.prNumber,
|
|
11087
|
+
url: input.prUrl
|
|
11088
|
+
});
|
|
10568
11089
|
}
|
|
10569
|
-
input.registry.recordOpenedPr(input.session.id, {
|
|
10570
|
-
adapter: input.session.harness ?? input.session.adapterSlug ?? "gh",
|
|
10571
|
-
number: input.prNumber,
|
|
10572
|
-
url: input.prUrl
|
|
10573
|
-
});
|
|
10574
11090
|
return {
|
|
10575
11091
|
stamped: true,
|
|
10576
11092
|
url: input.prUrl,
|
|
@@ -10584,14 +11100,14 @@ async function stampFooterOnPr(input) {
|
|
|
10584
11100
|
}
|
|
10585
11101
|
var defaultGhRunner = async (args, cwd) => {
|
|
10586
11102
|
const { spawn: spawn7 } = await import('child_process');
|
|
10587
|
-
return await new Promise((
|
|
11103
|
+
return await new Promise((resolve27) => {
|
|
10588
11104
|
let stdout = "";
|
|
10589
11105
|
const child = spawn7("gh", [...args], { cwd, shell: false });
|
|
10590
11106
|
child.stdout?.on("data", (d) => {
|
|
10591
11107
|
stdout += d.toString("utf8");
|
|
10592
11108
|
});
|
|
10593
|
-
child.on("error", () =>
|
|
10594
|
-
child.on("close", (code) =>
|
|
11109
|
+
child.on("error", () => resolve27({ exitCode: 1, stdout }));
|
|
11110
|
+
child.on("close", (code) => resolve27({ exitCode: code ?? 1, stdout }));
|
|
10595
11111
|
});
|
|
10596
11112
|
};
|
|
10597
11113
|
var ALLOWLIST_REL = ".agentproto/allowed-commands.json";
|
|
@@ -12690,6 +13206,7 @@ function stringifyValues(raw) {
|
|
|
12690
13206
|
}
|
|
12691
13207
|
return out;
|
|
12692
13208
|
}
|
|
13209
|
+
init_config();
|
|
12693
13210
|
var RestartOverrideError = class extends Error {
|
|
12694
13211
|
code = "restart_override_invalid";
|
|
12695
13212
|
status = 400;
|
|
@@ -12960,9 +13477,11 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
|
|
|
12960
13477
|
const message = err instanceof Error ? err.message : String(err);
|
|
12961
13478
|
throw new Error(message);
|
|
12962
13479
|
}
|
|
13480
|
+
const restartConfigDir = prev.adapterConfigDir ?? adapterConfigDirFor(restartedSessionId);
|
|
12963
13481
|
const agentSession = await resolved.startSession({
|
|
12964
13482
|
cwd,
|
|
12965
13483
|
...resumeSessionId ? { resumeSessionId } : {},
|
|
13484
|
+
configDir: restartConfigDir,
|
|
12966
13485
|
...launchConfig.wireModel ? { model: launchConfig.wireModel } : {},
|
|
12967
13486
|
...effEffort ? { effort: effEffort } : {},
|
|
12968
13487
|
...effPosture !== void 0 ? { posture: effPosture } : {},
|
|
@@ -12988,6 +13507,7 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
|
|
|
12988
13507
|
cwd,
|
|
12989
13508
|
agentSession,
|
|
12990
13509
|
adapterSlug,
|
|
13510
|
+
adapterConfigDir: restartConfigDir,
|
|
12991
13511
|
...resolved.resumable !== void 0 ? { resumable: resolved.resumable } : {},
|
|
12992
13512
|
...resolved.nativeTerminalResume !== void 0 ? { nativeTerminalResume: resolved.nativeTerminalResume } : {},
|
|
12993
13513
|
harness: effHarness,
|
|
@@ -13301,7 +13821,10 @@ async function resolveConversationId(desc) {
|
|
|
13301
13821
|
// A live/ongoing session has no endedAt yet and must keep matching
|
|
13302
13822
|
// with no upper bound — only a dead session's endedAt narrows.
|
|
13303
13823
|
...desc.endedAt ? { until: desc.endedAt } : {},
|
|
13304
|
-
...attachmentMode ? { attachmentMode } : {}
|
|
13824
|
+
...attachmentMode ? { attachmentMode } : {},
|
|
13825
|
+
// A config-dir-isolated session's store lives under its own
|
|
13826
|
+
// CLAUDE_CONFIG_DIR (#824), not the provider's global dir.
|
|
13827
|
+
...desc.adapterConfigDir ? { configDir: desc.adapterConfigDir } : {}
|
|
13305
13828
|
});
|
|
13306
13829
|
if (candidates.length === 0) {
|
|
13307
13830
|
return {
|
|
@@ -13360,7 +13883,7 @@ async function readConversation(registry, input) {
|
|
|
13360
13883
|
});
|
|
13361
13884
|
}
|
|
13362
13885
|
try {
|
|
13363
|
-
const session = await store.read(resolved.conversationId, desc.cwd);
|
|
13886
|
+
const session = await store.read(resolved.conversationId, desc.cwd, desc.adapterConfigDir);
|
|
13364
13887
|
const content = format === "json" ? renderJson(session) : renderMarkdown(session);
|
|
13365
13888
|
return {
|
|
13366
13889
|
conversation: session,
|
|
@@ -15000,6 +15523,91 @@ function registerSessionTools(rawServer, opts) {
|
|
|
15000
15523
|
}
|
|
15001
15524
|
}
|
|
15002
15525
|
);
|
|
15526
|
+
server.tool(
|
|
15527
|
+
"session_flag_status",
|
|
15528
|
+
"Manually correct a session's `awaitingInput`/`awaitingQuestion` classification. This is the ONLY write path for that pair besides the daemon's own internal heuristic (which guesses from the tail of the transcript) and a driver-reported structured prompt \u2014 use this when the heuristic missed a real question (set `awaitingInput:true`, optionally attaching `question`) or flagged a false positive (set `awaitingInput:false`, which also clears any attached `awaitingQuestion` \u2014 a question can't outlive its awaiting-input flag). `reason` is required \u2014 a short justification that rides on the emitted `session:awaiting-input-flagged` event, visible via `session_events_poll`, for audit. Only allowed on a LIVE session (running/starting) \u2014 mirrors the inverse of `session_archive`'s terminal-only guard: a terminal session has no turn left to be awaiting anything. The override itself is NOT sticky \u2014 it's cleared automatically like any other awaiting-input signal on the session's next prompt/turn start.",
|
|
15529
|
+
{
|
|
15530
|
+
idOrName: z.string().min(1).describe("Session id or name to flag \u2014 from `session_list`, must be live."),
|
|
15531
|
+
awaitingInput: z.boolean().describe(
|
|
15532
|
+
"New value for the session's awaiting-input classification \u2014 true if it's actually blocked on a question/decision the heuristic missed, false to clear a false positive."
|
|
15533
|
+
),
|
|
15534
|
+
question: z.string().min(1).optional().describe(
|
|
15535
|
+
'The question text to attach when `awaitingInput:true` \u2014 stored as `awaitingQuestion` (`source:"structured"`). Only meaningful alongside `awaitingInput:true`; passing it with `awaitingInput:false` is a validation error.'
|
|
15536
|
+
),
|
|
15537
|
+
reason: z.string().min(1).describe(
|
|
15538
|
+
"Required short justification for this override \u2014 audit/log only, rides on the emitted `session:awaiting-input-flagged` event."
|
|
15539
|
+
)
|
|
15540
|
+
},
|
|
15541
|
+
async (input) => {
|
|
15542
|
+
if (!input.awaitingInput && input.question !== void 0) {
|
|
15543
|
+
return {
|
|
15544
|
+
content: [
|
|
15545
|
+
{
|
|
15546
|
+
type: "text",
|
|
15547
|
+
text: JSON.stringify({
|
|
15548
|
+
error: "session_flag_status: `question` is only meaningful when `awaitingInput:true` (got `awaitingInput:false` with a `question` set)."
|
|
15549
|
+
})
|
|
15550
|
+
}
|
|
15551
|
+
],
|
|
15552
|
+
isError: true
|
|
15553
|
+
};
|
|
15554
|
+
}
|
|
15555
|
+
const prev = registry.findByIdOrName(input.idOrName);
|
|
15556
|
+
if (!prev) {
|
|
15557
|
+
return {
|
|
15558
|
+
content: [
|
|
15559
|
+
{
|
|
15560
|
+
type: "text",
|
|
15561
|
+
text: JSON.stringify({ error: `no session "${input.idOrName}" found` })
|
|
15562
|
+
}
|
|
15563
|
+
],
|
|
15564
|
+
isError: true
|
|
15565
|
+
};
|
|
15566
|
+
}
|
|
15567
|
+
if (callerScope) {
|
|
15568
|
+
const subtree = collectSubtree(
|
|
15569
|
+
callerScope.ownerSessionId,
|
|
15570
|
+
registry.list({ includeArchived: true })
|
|
15571
|
+
);
|
|
15572
|
+
if (!subtree.has(prev.id)) {
|
|
15573
|
+
return {
|
|
15574
|
+
content: [
|
|
15575
|
+
{
|
|
15576
|
+
type: "text",
|
|
15577
|
+
text: JSON.stringify({
|
|
15578
|
+
error: "orchestrator_session_out_of_scope",
|
|
15579
|
+
message: `session_flag_status: session "${prev.id}" is not in your subtree \u2014 a scoped orchestrator can only flag sessions it (transitively) spawned.`,
|
|
15580
|
+
ok: false,
|
|
15581
|
+
sessionId: prev.id
|
|
15582
|
+
})
|
|
15583
|
+
}
|
|
15584
|
+
],
|
|
15585
|
+
isError: true
|
|
15586
|
+
};
|
|
15587
|
+
}
|
|
15588
|
+
}
|
|
15589
|
+
try {
|
|
15590
|
+
const desc = registry.flagAwaitingInput(prev.id, {
|
|
15591
|
+
awaitingInput: input.awaitingInput,
|
|
15592
|
+
...input.question !== void 0 ? { question: input.question } : {},
|
|
15593
|
+
reason: input.reason
|
|
15594
|
+
});
|
|
15595
|
+
return {
|
|
15596
|
+
content: [{ type: "text", text: JSON.stringify(desc, null, 2) }]
|
|
15597
|
+
};
|
|
15598
|
+
} catch (err) {
|
|
15599
|
+
return {
|
|
15600
|
+
content: [
|
|
15601
|
+
{
|
|
15602
|
+
type: "text",
|
|
15603
|
+
text: `session_flag_status: ${err instanceof Error ? err.message : String(err)}`
|
|
15604
|
+
}
|
|
15605
|
+
],
|
|
15606
|
+
isError: true
|
|
15607
|
+
};
|
|
15608
|
+
}
|
|
15609
|
+
}
|
|
15610
|
+
);
|
|
15003
15611
|
server.tool(
|
|
15004
15612
|
"session_rename",
|
|
15005
15613
|
"Set or clear a session's user-facing name \u2014 the label the sessions tree, transcript header, and tab show. `label` out-ranks `title` in that display chain, so a user rename should write `label` (the default a UI picks) to be sure it shows; `title` is the auto-derived first-sentence fallback. For EACH of `title`/`label`: a non-empty string sets it (trimmed + length-capped), an empty string clears it (reverting to the derived title / a friendly `adapter \xB7 id` fallback), and omitting it leaves that field untouched. Persists across daemon restarts. Does NOT rename the adapter-native session or touch the running agent.",
|
|
@@ -15146,6 +15754,68 @@ function registerSessionTools(rawServer, opts) {
|
|
|
15146
15754
|
}
|
|
15147
15755
|
}
|
|
15148
15756
|
);
|
|
15757
|
+
server.tool(
|
|
15758
|
+
"session_set_pinned",
|
|
15759
|
+
"Set or clear a session's list-visibility pin. When `pinned` is true, the session sorts to the top of `agentproto sessions` and the VS Code sessions webview's dedicated Pinned group. Set false to clear it. Persists across daemon restarts. Purely a sort/display flag \u2014 does NOT touch the idle-reaper, keepAlive, or emit any notification, and does NOT touch the running agent.",
|
|
15760
|
+
{
|
|
15761
|
+
idOrName: z.string().min(1).describe("Session id or name to update \u2014 from `session_list`."),
|
|
15762
|
+
pinned: mcpBool2.describe(
|
|
15763
|
+
"true to pin this session to the top of the list, false to unpin it."
|
|
15764
|
+
)
|
|
15765
|
+
},
|
|
15766
|
+
async (input) => {
|
|
15767
|
+
const prev = registry.findByIdOrName(input.idOrName);
|
|
15768
|
+
if (!prev) {
|
|
15769
|
+
return {
|
|
15770
|
+
content: [
|
|
15771
|
+
{
|
|
15772
|
+
type: "text",
|
|
15773
|
+
text: JSON.stringify({ error: `no session "${input.idOrName}" found` })
|
|
15774
|
+
}
|
|
15775
|
+
],
|
|
15776
|
+
isError: true
|
|
15777
|
+
};
|
|
15778
|
+
}
|
|
15779
|
+
if (callerScope) {
|
|
15780
|
+
const subtree = collectSubtree(
|
|
15781
|
+
callerScope.ownerSessionId,
|
|
15782
|
+
registry.list({ includeArchived: true })
|
|
15783
|
+
);
|
|
15784
|
+
if (!subtree.has(prev.id)) {
|
|
15785
|
+
return {
|
|
15786
|
+
content: [
|
|
15787
|
+
{
|
|
15788
|
+
type: "text",
|
|
15789
|
+
text: JSON.stringify({
|
|
15790
|
+
error: "orchestrator_session_out_of_scope",
|
|
15791
|
+
message: `session_set_pinned: session "${prev.id}" is not in your subtree \u2014 a scoped orchestrator can only update sessions it (transitively) spawned.`,
|
|
15792
|
+
ok: false,
|
|
15793
|
+
sessionId: prev.id
|
|
15794
|
+
})
|
|
15795
|
+
}
|
|
15796
|
+
],
|
|
15797
|
+
isError: true
|
|
15798
|
+
};
|
|
15799
|
+
}
|
|
15800
|
+
}
|
|
15801
|
+
try {
|
|
15802
|
+
const desc = registry.setPinned(prev.id, input.pinned);
|
|
15803
|
+
return {
|
|
15804
|
+
content: [{ type: "text", text: JSON.stringify(desc, null, 2) }]
|
|
15805
|
+
};
|
|
15806
|
+
} catch (err) {
|
|
15807
|
+
return {
|
|
15808
|
+
content: [
|
|
15809
|
+
{
|
|
15810
|
+
type: "text",
|
|
15811
|
+
text: `session_set_pinned: ${err instanceof Error ? err.message : String(err)}`
|
|
15812
|
+
}
|
|
15813
|
+
],
|
|
15814
|
+
isError: true
|
|
15815
|
+
};
|
|
15816
|
+
}
|
|
15817
|
+
}
|
|
15818
|
+
);
|
|
15149
15819
|
server.tool(
|
|
15150
15820
|
"terminal_start",
|
|
15151
15821
|
"Spawn a process under a real PTY (node-pty) on the host. Bytes (including ANSI escapes, alt-screen sequences) flow through the daemon's byte ring buffer; subscribers attach via the WS at /sessions/:id/pty. Use for interactive TUIs (claude, vim, htop) or to orchestrate shells from another agent. Returns the session descriptor.",
|
|
@@ -15441,7 +16111,7 @@ function registerBrainTools(server, opts) {
|
|
|
15441
16111
|
);
|
|
15442
16112
|
server.tool(
|
|
15443
16113
|
"workspace_brain_status",
|
|
15444
|
-
"Report how current a workspace's brain is: how many sessions are indexed, how many known-but-pending remain, total bytes, and whether the index is query-ready. Resolves the workspace as the explicit `workspace` argument, else the calling session's own workspace.",
|
|
16114
|
+
"Report how current a workspace's brain is: how many sessions are indexed, how many known-but-pending remain, how many were skipped as permanently unavailable (no transcript ever reachable \u2014 bash PTYs, GC'd transcripts; retry one explicitly with `workspace_brain_ingest`'s `sessionId`), total bytes, and whether the index is query-ready. Resolves the workspace as the explicit `workspace` argument, else the calling session's own workspace.",
|
|
15445
16115
|
{
|
|
15446
16116
|
workspace: z.string().optional().describe("Workspace slug. Omit to use the calling session's workspace.")
|
|
15447
16117
|
},
|
|
@@ -15523,7 +16193,7 @@ async function readSessionForBrain(sessionId) {
|
|
|
15523
16193
|
const store = CONVERSATION_STORES[record2.adapterSlug];
|
|
15524
16194
|
if (store) {
|
|
15525
16195
|
try {
|
|
15526
|
-
const exported = await store.read(record2.adapterSessionId, record2.cwd);
|
|
16196
|
+
const exported = await store.read(record2.adapterSessionId, record2.cwd, record2.adapterConfigDir);
|
|
15527
16197
|
if (exported?.messages?.length) return exported;
|
|
15528
16198
|
} catch {
|
|
15529
16199
|
}
|
|
@@ -18846,9 +19516,11 @@ ${panelBridgeScript("agentproto-live-session")}
|
|
|
18846
19516
|
// ============================================================
|
|
18847
19517
|
// INLINED REDUCER COPY \u2014 hand-kept mirror of live-session-app.logic.ts.
|
|
18848
19518
|
// Plain JS, same semantics: coalesce consecutive text-delta of the same
|
|
18849
|
-
// session
|
|
18850
|
-
//
|
|
18851
|
-
//
|
|
19519
|
+
// session (and rejoin an unterminated mid-line fragment split by an
|
|
19520
|
+
// interleaved record \u2014 see the TS module's text-delta arm), pair
|
|
19521
|
+
// tool-call/tool-result by toolCallId, pass through turn-end/usage_update,
|
|
19522
|
+
// ignore unknown kinds. Keep in sync with the TS module; the TS module is
|
|
19523
|
+
// the one the test suite imports.
|
|
18852
19524
|
// ============================================================
|
|
18853
19525
|
|
|
18854
19526
|
function initialTimelineState() {
|
|
@@ -18859,22 +19531,50 @@ function rowId(record, rows) {
|
|
|
18859
19531
|
return record.seq != null ? (record.kind + '-' + record.seq) : (record.kind + '-' + rows.length);
|
|
18860
19532
|
}
|
|
18861
19533
|
|
|
19534
|
+
// Fold a text-delta record into an existing row (fresh object), keeping the
|
|
19535
|
+
// row's "partial" hint in step with the latest record \u2014 see mergeTextDelta in
|
|
19536
|
+
// the TS module.
|
|
19537
|
+
function mergeTextDelta(row, record) {
|
|
19538
|
+
var merged = Object.assign({}, row, {
|
|
19539
|
+
text: row.text + (record.text || ''),
|
|
19540
|
+
seq: record.seq,
|
|
19541
|
+
ts: record.ts,
|
|
19542
|
+
});
|
|
19543
|
+
if (record.partial === true) merged.partial = true;
|
|
19544
|
+
else delete merged.partial;
|
|
19545
|
+
return merged;
|
|
19546
|
+
}
|
|
19547
|
+
|
|
18862
19548
|
function reduceEvent(state, record) {
|
|
18863
19549
|
switch (record.kind) {
|
|
18864
19550
|
case 'text-delta': {
|
|
18865
19551
|
var last = state.rows[state.rows.length - 1];
|
|
18866
19552
|
if (last && last.kind === 'text' && last.sessionId === record.sessionId) {
|
|
18867
|
-
|
|
18868
|
-
|
|
18869
|
-
|
|
18870
|
-
|
|
18871
|
-
|
|
18872
|
-
|
|
19553
|
+
return { rows: state.rows.slice(0, -1).concat([mergeTextDelta(last, record)]) };
|
|
19554
|
+
}
|
|
19555
|
+
// Debounce can flush an unterminated mid-word fragment, let a tool-call
|
|
19556
|
+
// land, then flush the continuation. Terminated lines carry their
|
|
19557
|
+
// trailing newline (transcript-writer contract) \u2014 look back within the
|
|
19558
|
+
// same turn (bounded by this session's last turn-end) for that
|
|
19559
|
+
// session's most recent text row; if unterminated (or flagged
|
|
19560
|
+
// partial), continue it in place instead of splitting the sentence.
|
|
19561
|
+
for (var i = state.rows.length - 1; i >= 0; i--) {
|
|
19562
|
+
var prior = state.rows[i];
|
|
19563
|
+
if (prior.sessionId !== record.sessionId) continue;
|
|
19564
|
+
if (prior.kind === 'turn-end') break;
|
|
19565
|
+
if (prior.kind !== 'text') continue;
|
|
19566
|
+
if (prior.partial === true || !prior.text.endsWith('\\n')) {
|
|
19567
|
+
var patched = state.rows.slice();
|
|
19568
|
+
patched[i] = mergeTextDelta(prior, record);
|
|
19569
|
+
return { rows: patched };
|
|
19570
|
+
}
|
|
19571
|
+
break;
|
|
18873
19572
|
}
|
|
18874
19573
|
var row = {
|
|
18875
19574
|
kind: 'text', id: rowId(record, state.rows), seq: record.seq, ts: record.ts,
|
|
18876
19575
|
sessionId: record.sessionId, text: record.text || '',
|
|
18877
19576
|
};
|
|
19577
|
+
if (record.partial === true) row.partial = true;
|
|
18878
19578
|
return { rows: state.rows.concat([row]) };
|
|
18879
19579
|
}
|
|
18880
19580
|
case 'tool-call': {
|
|
@@ -19715,7 +20415,7 @@ async function waitForSessionTerminal(registry, sessionId) {
|
|
|
19715
20415
|
for (let attempt = 0; attempt < MAX_SEQUENTIAL_POLLS; attempt++) {
|
|
19716
20416
|
const status = registry.get(sessionId)?.status;
|
|
19717
20417
|
if (isSessionTerminal(status)) return;
|
|
19718
|
-
await new Promise((
|
|
20418
|
+
await new Promise((resolve27) => setTimeout(resolve27, SEQUENTIAL_POLL_INTERVAL_MS));
|
|
19719
20419
|
}
|
|
19720
20420
|
}
|
|
19721
20421
|
function resolveAgentRefsForWorkflow(appRegistry, workflowId) {
|
|
@@ -21205,14 +21905,14 @@ function createSupervisorTaskGateRunner(opts) {
|
|
|
21205
21905
|
const { supervisor, registry, workspace } = opts;
|
|
21206
21906
|
const exec = opts.runCommand ?? runCommand;
|
|
21207
21907
|
const settleTimeoutMs = opts.settleTimeoutMs ?? DEFAULT_VERIFY_SETTLE_TIMEOUT_MS;
|
|
21208
|
-
const waitForSettle = (policyId) => new Promise((
|
|
21908
|
+
const waitForSettle = (policyId) => new Promise((resolve27) => {
|
|
21209
21909
|
let settled = false;
|
|
21210
21910
|
const finish = (outcome) => {
|
|
21211
21911
|
if (settled) return;
|
|
21212
21912
|
settled = true;
|
|
21213
21913
|
clearTimeout(timer);
|
|
21214
21914
|
unsub();
|
|
21215
|
-
|
|
21915
|
+
resolve27(outcome);
|
|
21216
21916
|
};
|
|
21217
21917
|
const read = () => {
|
|
21218
21918
|
const state = supervisor.getStatus(policyId);
|
|
@@ -22357,17 +23057,26 @@ async function monitorSessionWait(opts) {
|
|
|
22357
23057
|
};
|
|
22358
23058
|
}
|
|
22359
23059
|
}
|
|
22360
|
-
return new Promise((
|
|
23060
|
+
return new Promise((resolve27) => {
|
|
22361
23061
|
const unsubs = [];
|
|
22362
23062
|
let settled = false;
|
|
23063
|
+
const watcherLabel = callerScope?.ownerSessionId ? registry.get(callerScope.ownerSessionId)?.label ?? registry.get(callerScope.ownerSessionId)?.title : void 0;
|
|
23064
|
+
const watcherDetail = {
|
|
23065
|
+
...callerScope?.ownerSessionId ? { watcherSessionId: callerScope.ownerSessionId } : {},
|
|
23066
|
+
...watcherLabel ? { watcherLabel } : {},
|
|
23067
|
+
event: targetEvent,
|
|
23068
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
23069
|
+
since: (/* @__PURE__ */ new Date()).toISOString()
|
|
23070
|
+
};
|
|
22363
23071
|
for (const id of resolvedIds) {
|
|
22364
|
-
registry.incWatchers(id);
|
|
23072
|
+
registry.incWatchers(id, watcherDetail);
|
|
22365
23073
|
const watchers = registry.get(id)?.watchers ?? 0;
|
|
22366
23074
|
sessionEvents.emit({
|
|
22367
23075
|
type: "session:watcher-attached",
|
|
22368
23076
|
sessionId: id,
|
|
22369
23077
|
watchers,
|
|
22370
23078
|
...callerScope?.ownerSessionId ? { watcherSessionId: callerScope.ownerSessionId } : {},
|
|
23079
|
+
...watcherLabel ? { label: watcherLabel } : {},
|
|
22371
23080
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
22372
23081
|
});
|
|
22373
23082
|
}
|
|
@@ -22377,17 +23086,18 @@ async function monitorSessionWait(opts) {
|
|
|
22377
23086
|
clearTimeout(timer);
|
|
22378
23087
|
for (const u of unsubs) u();
|
|
22379
23088
|
for (const id of resolvedIds) {
|
|
22380
|
-
registry.decWatchers(id);
|
|
23089
|
+
registry.decWatchers(id, watcherDetail);
|
|
22381
23090
|
const watchers = registry.get(id)?.watchers ?? 0;
|
|
22382
23091
|
sessionEvents.emit({
|
|
22383
23092
|
type: "session:watcher-detached",
|
|
22384
23093
|
sessionId: id,
|
|
22385
23094
|
watchers,
|
|
22386
23095
|
...callerScope?.ownerSessionId ? { watcherSessionId: callerScope.ownerSessionId } : {},
|
|
23096
|
+
...watcherLabel ? { label: watcherLabel } : {},
|
|
22387
23097
|
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
22388
23098
|
});
|
|
22389
23099
|
}
|
|
22390
|
-
|
|
23100
|
+
resolve27(result);
|
|
22391
23101
|
};
|
|
22392
23102
|
const relevantTypes = targetEvent === "any" ? ["session:turn-end", "session:awaiting-input", "session:exited"] : targetEvent === "turn-end" ? ["session:turn-end", "session:awaiting-input"] : targetEvent === "awaiting-input" ? ["session:awaiting-input"] : ["session:exited"];
|
|
22393
23103
|
const idSet = new Set(resolvedIds);
|
|
@@ -22424,13 +23134,13 @@ async function monitorPolicyWait(opts) {
|
|
|
22424
23134
|
if (isSettledStatus(initial.status)) {
|
|
22425
23135
|
return { timedOut: false, state: initial };
|
|
22426
23136
|
}
|
|
22427
|
-
return new Promise((
|
|
23137
|
+
return new Promise((resolve27) => {
|
|
22428
23138
|
let settled = false;
|
|
22429
23139
|
const timer = setTimeout(() => {
|
|
22430
23140
|
if (settled) return;
|
|
22431
23141
|
settled = true;
|
|
22432
23142
|
unsub();
|
|
22433
|
-
|
|
23143
|
+
resolve27({ timedOut: true });
|
|
22434
23144
|
}, timeoutMs);
|
|
22435
23145
|
const unsub = supervisor.onSettle((id) => {
|
|
22436
23146
|
if (id !== policyId) return;
|
|
@@ -22440,7 +23150,7 @@ async function monitorPolicyWait(opts) {
|
|
|
22440
23150
|
settled = true;
|
|
22441
23151
|
clearTimeout(timer);
|
|
22442
23152
|
unsub();
|
|
22443
|
-
|
|
23153
|
+
resolve27({ timedOut: false, state });
|
|
22444
23154
|
});
|
|
22445
23155
|
});
|
|
22446
23156
|
}
|
|
@@ -23804,6 +24514,15 @@ async function startHttpServer(opts) {
|
|
|
23804
24514
|
workspace: opts.meta.workspace,
|
|
23805
24515
|
registered: opts.meta.registered,
|
|
23806
24516
|
uptimeMs: Date.now() - startedAt,
|
|
24517
|
+
startedAt: new Date(startedAt).toISOString(),
|
|
24518
|
+
// What is actually running — version, process, and the exact
|
|
24519
|
+
// node+entry pair launchd (or the shell) exec'd. Lifecycle tooling
|
|
24520
|
+
// (`agentproto daemon start/stop/status`) reports these.
|
|
24521
|
+
version: opts.meta.version ?? null,
|
|
24522
|
+
build: opts.meta.build ?? null,
|
|
24523
|
+
pid: process.pid,
|
|
24524
|
+
node: process.execPath,
|
|
24525
|
+
entry: process.argv[1] ?? null,
|
|
23807
24526
|
resumeSessionsOnBoot: opts.meta.resumeSessionsOnBoot === true,
|
|
23808
24527
|
idleReapAfterMs: opts.meta.idleReapAfterMs ?? 0,
|
|
23809
24528
|
crashDetectIntervalMs: opts.meta.crashDetectIntervalMs ?? 0,
|
|
@@ -24880,16 +25599,16 @@ async function startHttpServer(opts) {
|
|
|
24880
25599
|
});
|
|
24881
25600
|
});
|
|
24882
25601
|
const bind = opts.bind ?? "127.0.0.1";
|
|
24883
|
-
await new Promise((
|
|
25602
|
+
await new Promise((resolve27, reject) => {
|
|
24884
25603
|
server.once("error", reject);
|
|
24885
|
-
server.listen(opts.port, bind, () =>
|
|
25604
|
+
server.listen(opts.port, bind, () => resolve27());
|
|
24886
25605
|
});
|
|
24887
25606
|
return {
|
|
24888
25607
|
url: `http://${bind}:${opts.port}`,
|
|
24889
25608
|
async stop() {
|
|
24890
25609
|
wss.close();
|
|
24891
25610
|
server.closeAllConnections();
|
|
24892
|
-
await new Promise((
|
|
25611
|
+
await new Promise((resolve27) => server.close(() => resolve27()));
|
|
24893
25612
|
}
|
|
24894
25613
|
};
|
|
24895
25614
|
}
|
|
@@ -25539,6 +26258,8 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
25539
26258
|
const body = await readJsonBody(req);
|
|
25540
26259
|
const prompt = body?.prompt;
|
|
25541
26260
|
const interrupt = body?.interrupt === true;
|
|
26261
|
+
const queue = body?.queue === true;
|
|
26262
|
+
const force = body?.force === true;
|
|
25542
26263
|
const validPrompt = typeof prompt === "string" && prompt.length > 0 || Array.isArray(prompt) && prompt.length > 0 && prompt.every((b) => b !== null && typeof b === "object") || prompt !== null && typeof prompt === "object" && !Array.isArray(prompt);
|
|
25543
26264
|
if (!validPrompt) {
|
|
25544
26265
|
json(400, {
|
|
@@ -25553,8 +26274,20 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
25553
26274
|
const fireAndForget = wait === "false" || wait === "0";
|
|
25554
26275
|
try {
|
|
25555
26276
|
if (fireAndForget) {
|
|
25556
|
-
|
|
25557
|
-
|
|
26277
|
+
const queueId = queue ? `q_${randomUUID().slice(0, 8)}` : void 0;
|
|
26278
|
+
await registry.enqueuePrompt(id2, prompt, { interrupt, queue, force, queueId });
|
|
26279
|
+
const promptQueue = queueId ? registry.get(id2)?.promptQueue : void 0;
|
|
26280
|
+
const queuePosition = promptQueue?.findIndex((p) => p.id === queueId) ?? -1;
|
|
26281
|
+
json(202, {
|
|
26282
|
+
ok: true,
|
|
26283
|
+
id: id2,
|
|
26284
|
+
queued: true,
|
|
26285
|
+
// Present only when this prompt actually landed in the FIFO
|
|
26286
|
+
// (busy + `queue: true`) rather than dispatching immediately —
|
|
26287
|
+
// an idle session's `queueId` never appears in `promptQueue`,
|
|
26288
|
+
// so `queuePosition` stays -1 and this is omitted.
|
|
26289
|
+
...queuePosition >= 0 ? { pending: true, queueId, queuePosition: queuePosition + 1 } : {}
|
|
26290
|
+
});
|
|
25558
26291
|
} else {
|
|
25559
26292
|
await registry.sendPrompt(id2, prompt, { interrupt });
|
|
25560
26293
|
json(200, { ok: true, id: id2 });
|
|
@@ -25570,6 +26303,15 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
25570
26303
|
}
|
|
25571
26304
|
return true;
|
|
25572
26305
|
}
|
|
26306
|
+
const queueItemMatch = path.match(/^\/sessions\/([^/]+)\/queue\/([^/]+)$/);
|
|
26307
|
+
if (queueItemMatch && req.method === "DELETE") {
|
|
26308
|
+
const id2 = queueItemMatch[1];
|
|
26309
|
+
const queueId = queueItemMatch[2];
|
|
26310
|
+
if (!id2 || !queueId) return false;
|
|
26311
|
+
const { removed } = registry.removeQueuedPrompt(id2, queueId);
|
|
26312
|
+
json(200, { ok: true, id: id2, queueId, removed });
|
|
26313
|
+
return true;
|
|
26314
|
+
}
|
|
25573
26315
|
const interruptMatch = path.match(/^\/sessions\/([^/]+)\/interrupt$/);
|
|
25574
26316
|
if (interruptMatch && req.method === "POST") {
|
|
25575
26317
|
const id2 = interruptMatch[1];
|
|
@@ -25829,7 +26571,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
25829
26571
|
return true;
|
|
25830
26572
|
}
|
|
25831
26573
|
const idMatch = path.match(
|
|
25832
|
-
/^\/sessions\/([^/]+)(\/events\/stream|\/stream|\/kill|\/preview|\/export|\/conversation|\/events|\/wait)?$/
|
|
26574
|
+
/^\/sessions\/([^/]+)(\/events\/stream|\/stream|\/kill|\/pin|\/preview|\/export|\/conversation|\/events|\/wait)?$/
|
|
25833
26575
|
);
|
|
25834
26576
|
if (!idMatch) return false;
|
|
25835
26577
|
const [, rawIdOrName, suffix] = idMatch;
|
|
@@ -25904,9 +26646,9 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
25904
26646
|
let fileStream;
|
|
25905
26647
|
try {
|
|
25906
26648
|
fileStream = createReadStream(filePath, { encoding: "utf8" });
|
|
25907
|
-
await new Promise((
|
|
26649
|
+
await new Promise((resolve27, reject) => {
|
|
25908
26650
|
fileStream.once("error", reject);
|
|
25909
|
-
fileStream.once("open",
|
|
26651
|
+
fileStream.once("open", resolve27);
|
|
25910
26652
|
});
|
|
25911
26653
|
} catch (err) {
|
|
25912
26654
|
const code = err.code;
|
|
@@ -25962,9 +26704,9 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
25962
26704
|
let fileStream;
|
|
25963
26705
|
try {
|
|
25964
26706
|
fileStream = createReadStream(filePath, { encoding: "utf8" });
|
|
25965
|
-
await new Promise((
|
|
26707
|
+
await new Promise((resolve27, reject) => {
|
|
25966
26708
|
fileStream.once("error", reject);
|
|
25967
|
-
fileStream.once("open",
|
|
26709
|
+
fileStream.once("open", resolve27);
|
|
25968
26710
|
});
|
|
25969
26711
|
} catch (err) {
|
|
25970
26712
|
const code = err.code;
|
|
@@ -26127,6 +26869,27 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
|
|
|
26127
26869
|
json(ok ? 200 : 404, { ok, sessionId: id });
|
|
26128
26870
|
return true;
|
|
26129
26871
|
}
|
|
26872
|
+
if (suffix === "/pin" && req.method === "POST") {
|
|
26873
|
+
if (!resolvedDesc) {
|
|
26874
|
+
json(404, { error: "session_not_found", id: rawIdOrName });
|
|
26875
|
+
return true;
|
|
26876
|
+
}
|
|
26877
|
+
const body = await readJsonBody(req);
|
|
26878
|
+
const b = body && typeof body === "object" ? body : {};
|
|
26879
|
+
const pinned = typeof b.pinned === "boolean" ? b.pinned : b.pinned === "true" ? true : b.pinned === "false" ? false : void 0;
|
|
26880
|
+
if (pinned === void 0) {
|
|
26881
|
+
json(400, { error: "invalid_body", message: "`pinned` must be a boolean" });
|
|
26882
|
+
return true;
|
|
26883
|
+
}
|
|
26884
|
+
try {
|
|
26885
|
+
registry.setPinned(id, pinned);
|
|
26886
|
+
json(200, { ok: true, sessionId: id, pinned });
|
|
26887
|
+
} catch (err) {
|
|
26888
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
26889
|
+
json(msg.includes("no session") ? 404 : 500, { error: "set_pinned_failed", message: msg });
|
|
26890
|
+
}
|
|
26891
|
+
return true;
|
|
26892
|
+
}
|
|
26130
26893
|
if (!suffix && req.method === "GET") {
|
|
26131
26894
|
if (!resolvedDesc) {
|
|
26132
26895
|
json(404, { error: "session_not_found", id: rawIdOrName });
|
|
@@ -27470,6 +28233,9 @@ async function runRestartSweepPass(opts) {
|
|
|
27470
28233
|
}
|
|
27471
28234
|
return summary;
|
|
27472
28235
|
}
|
|
28236
|
+
|
|
28237
|
+
// src/index.ts
|
|
28238
|
+
init_config();
|
|
27473
28239
|
var BINDINGS_FILE_PATH = () => resolve(homedir(), ".agentproto", "transmitter-bindings.json");
|
|
27474
28240
|
var PERSIST_DEBOUNCE_MS4 = 1500;
|
|
27475
28241
|
var keyOf = (alias, source, contactRef) => `${alias}:${source}:${contactRef}`;
|
|
@@ -28791,6 +29557,7 @@ var SessionsRegistryAgentHost = class {
|
|
|
28791
29557
|
const stepSessionId = mintSessionId();
|
|
28792
29558
|
const agentSession = await resolved.startSession({
|
|
28793
29559
|
cwd,
|
|
29560
|
+
configDir: adapterConfigDirFor(stepSessionId),
|
|
28794
29561
|
env: {
|
|
28795
29562
|
[SESSION_ID_ENV]: stepSessionId,
|
|
28796
29563
|
[WORKSPACE_SLUG_ENV]: workspaceSlug
|
|
@@ -28803,6 +29570,7 @@ var SessionsRegistryAgentHost = class {
|
|
|
28803
29570
|
cwd,
|
|
28804
29571
|
agentSession,
|
|
28805
29572
|
adapterSlug: adapter,
|
|
29573
|
+
adapterConfigDir: adapterConfigDirFor(stepSessionId),
|
|
28806
29574
|
label: `agent-step:${adapter}`,
|
|
28807
29575
|
...resolved.commandPreview ? { commandPreview: resolved.commandPreview } : {}
|
|
28808
29576
|
});
|
|
@@ -28892,11 +29660,11 @@ var SessionsRegistryAgentHost = class {
|
|
|
28892
29660
|
}
|
|
28893
29661
|
// ── Internal helpers ──────────────────────────────────────────────────
|
|
28894
29662
|
waitTurnEnd(sessionId) {
|
|
28895
|
-
return new Promise((
|
|
29663
|
+
return new Promise((resolve27, reject) => {
|
|
28896
29664
|
const unsubs = [];
|
|
28897
29665
|
const done = () => {
|
|
28898
29666
|
for (const u of unsubs) u();
|
|
28899
|
-
|
|
29667
|
+
resolve27();
|
|
28900
29668
|
};
|
|
28901
29669
|
const fail = (reason) => {
|
|
28902
29670
|
for (const u of unsubs) u();
|
|
@@ -29140,7 +29908,7 @@ function createOnEscalate(state, persist) {
|
|
|
29140
29908
|
state.run.status = "awaiting-input";
|
|
29141
29909
|
persist();
|
|
29142
29910
|
try {
|
|
29143
|
-
return await new Promise((
|
|
29911
|
+
return await new Promise((resolve27, reject) => {
|
|
29144
29912
|
const timeoutMs = policy.timeoutMs ?? 3e5;
|
|
29145
29913
|
const timer = setTimeout(() => {
|
|
29146
29914
|
state.pendingResolve = void 0;
|
|
@@ -29152,7 +29920,7 @@ function createOnEscalate(state, persist) {
|
|
|
29152
29920
|
resolver: (response) => {
|
|
29153
29921
|
clearTimeout(timer);
|
|
29154
29922
|
state.pendingResolve = void 0;
|
|
29155
|
-
|
|
29923
|
+
resolve27(response);
|
|
29156
29924
|
}
|
|
29157
29925
|
};
|
|
29158
29926
|
});
|
|
@@ -29516,25 +30284,38 @@ function createPrProvenanceReconciler(opts) {
|
|
|
29516
30284
|
if (last !== void 0 && Date.now() - last < POLL_THROTTLE_MS) return;
|
|
29517
30285
|
lastPollAt.set(sessionId, Date.now());
|
|
29518
30286
|
}
|
|
30287
|
+
const shouldStamp = (url) => {
|
|
30288
|
+
if (stampedPrUrls.has(url)) return false;
|
|
30289
|
+
if (desc.openedPrs && desc.openedPrs.some((recorded) => recorded.url === url)) {
|
|
30290
|
+
stampedPrUrls.add(url);
|
|
30291
|
+
return false;
|
|
30292
|
+
}
|
|
30293
|
+
return true;
|
|
30294
|
+
};
|
|
30295
|
+
const supervisor = desc.parentSessionId != null ? opts.registry.get(desc.parentSessionId) ?? { id: desc.parentSessionId } : null;
|
|
30296
|
+
const stamp = async (pr2) => {
|
|
30297
|
+
const outcome = await stampFooterOnPr({
|
|
30298
|
+
registry: opts.registry,
|
|
30299
|
+
session: desc,
|
|
30300
|
+
supervisor,
|
|
30301
|
+
prNumber: pr2.number,
|
|
30302
|
+
prUrl: pr2.url,
|
|
30303
|
+
cwd,
|
|
30304
|
+
...opts.run ? { run: opts.run } : {},
|
|
30305
|
+
...opts.host ? { host: opts.host } : {}
|
|
30306
|
+
});
|
|
30307
|
+
if (outcome.stamped) stampedPrUrls.add(pr2.url);
|
|
30308
|
+
};
|
|
30309
|
+
const records = await (opts.listToolCalls ?? readToolCallRecords)(sessionId);
|
|
30310
|
+
for (const record2 of records) {
|
|
30311
|
+
if (!record2.createdPrUrl || record2.createdPrNumber === void 0) continue;
|
|
30312
|
+
if (!shouldStamp(record2.createdPrUrl)) continue;
|
|
30313
|
+
await stamp({ number: record2.createdPrNumber, url: record2.createdPrUrl });
|
|
30314
|
+
}
|
|
29519
30315
|
const pr = await opts.resolveOpenPr(cwd);
|
|
29520
30316
|
if (!pr) return;
|
|
29521
|
-
if (
|
|
29522
|
-
|
|
29523
|
-
stampedPrUrls.add(pr.url);
|
|
29524
|
-
return;
|
|
29525
|
-
}
|
|
29526
|
-
const supervisor = desc.parentSessionId != null ? opts.registry.get(desc.parentSessionId) ?? { id: desc.parentSessionId } : null;
|
|
29527
|
-
const outcome = await stampFooterOnPr({
|
|
29528
|
-
registry: opts.registry,
|
|
29529
|
-
session: desc,
|
|
29530
|
-
supervisor,
|
|
29531
|
-
prNumber: pr.number,
|
|
29532
|
-
prUrl: pr.url,
|
|
29533
|
-
cwd,
|
|
29534
|
-
...opts.run ? { run: opts.run } : {},
|
|
29535
|
-
...opts.host ? { host: opts.host } : {}
|
|
29536
|
-
});
|
|
29537
|
-
if (outcome.stamped) stampedPrUrls.add(pr.url);
|
|
30317
|
+
if (!shouldStamp(pr.url)) return;
|
|
30318
|
+
await stamp(pr);
|
|
29538
30319
|
};
|
|
29539
30320
|
const safeReconcile = (sessionId, terminal) => {
|
|
29540
30321
|
void reconcile(sessionId, terminal).catch(() => {
|
|
@@ -29706,14 +30487,14 @@ function createActivityProjector(opts) {
|
|
|
29706
30487
|
if (current && isTerminalActivityState(current.state)) {
|
|
29707
30488
|
return Promise.resolve(current);
|
|
29708
30489
|
}
|
|
29709
|
-
return new Promise((
|
|
30490
|
+
return new Promise((resolve27) => {
|
|
29710
30491
|
let settled = false;
|
|
29711
30492
|
const finish = (value) => {
|
|
29712
30493
|
if (settled) return;
|
|
29713
30494
|
settled = true;
|
|
29714
30495
|
clearTimeout(timer);
|
|
29715
30496
|
unsubscribeWait();
|
|
29716
|
-
|
|
30497
|
+
resolve27(value);
|
|
29717
30498
|
};
|
|
29718
30499
|
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
29719
30500
|
const unsubscribeWait = opts.sessionEvents.on("activity:changed", (ev) => {
|
|
@@ -29828,6 +30609,7 @@ function createInboundWatcher(opts) {
|
|
|
29828
30609
|
const contactSessionId = mintSessionId();
|
|
29829
30610
|
const agentSession = await resolved.startSession({
|
|
29830
30611
|
cwd: state.input.cwd,
|
|
30612
|
+
configDir: adapterConfigDirFor(contactSessionId),
|
|
29831
30613
|
...mcpServers ? { mcpServers } : {},
|
|
29832
30614
|
env: {
|
|
29833
30615
|
[SESSION_ID_ENV]: contactSessionId,
|
|
@@ -29842,6 +30624,7 @@ function createInboundWatcher(opts) {
|
|
|
29842
30624
|
cwd: state.input.cwd,
|
|
29843
30625
|
agentSession,
|
|
29844
30626
|
adapterSlug: state.input.adapter,
|
|
30627
|
+
adapterConfigDir: adapterConfigDirFor(contactSessionId),
|
|
29845
30628
|
origin: "webhook",
|
|
29846
30629
|
initialPrompt: prompt,
|
|
29847
30630
|
label: labelParts.join(":"),
|
|
@@ -30215,6 +30998,7 @@ function createCronScheduler(opts) {
|
|
|
30215
30998
|
const agentSessionId = mintSessionId();
|
|
30216
30999
|
const agentSession = await resolved.startSession({
|
|
30217
31000
|
cwd,
|
|
31001
|
+
configDir: adapterConfigDirFor(agentSessionId),
|
|
30218
31002
|
...action.model ? { model: action.model } : {},
|
|
30219
31003
|
...action.mode ? { mode: action.mode } : {},
|
|
30220
31004
|
...action.permissionHold ? { permissionHold: true } : {},
|
|
@@ -30230,6 +31014,7 @@ function createCronScheduler(opts) {
|
|
|
30230
31014
|
cwd,
|
|
30231
31015
|
agentSession,
|
|
30232
31016
|
adapterSlug: action.adapter,
|
|
31017
|
+
adapterConfigDir: adapterConfigDirFor(agentSessionId),
|
|
30233
31018
|
origin: "cron",
|
|
30234
31019
|
label: `cron:${job.id}`,
|
|
30235
31020
|
...action.mode ? { mode: action.mode } : {},
|
|
@@ -30777,7 +31562,7 @@ async function registerBuiltinRoutes(opts) {
|
|
|
30777
31562
|
console.log(`[runtime] loaded custom routes: ${loadedIds.join(", ")}`);
|
|
30778
31563
|
}
|
|
30779
31564
|
}
|
|
30780
|
-
function makeBrowserHandle(entry,
|
|
31565
|
+
function makeBrowserHandle(entry, resolve27) {
|
|
30781
31566
|
return {
|
|
30782
31567
|
slug: entry.id,
|
|
30783
31568
|
name: entry.name,
|
|
@@ -30788,7 +31573,7 @@ function makeBrowserHandle(entry, resolve26) {
|
|
|
30788
31573
|
// check() is available for on-demand health probes but is never called
|
|
30789
31574
|
// by the lister (kit invariant OQ-5). Returns true when the adapter
|
|
30790
31575
|
// is present in the injected resolver map, false otherwise.
|
|
30791
|
-
check: async () =>
|
|
31576
|
+
check: async () => resolve27 ? resolve27(entry.id) != null : true
|
|
30792
31577
|
};
|
|
30793
31578
|
}
|
|
30794
31579
|
var noopLedger = {
|
|
@@ -30860,7 +31645,7 @@ var localSandboxProvider = {
|
|
|
30860
31645
|
}
|
|
30861
31646
|
};
|
|
30862
31647
|
async function getFreePort() {
|
|
30863
|
-
return new Promise((
|
|
31648
|
+
return new Promise((resolve27, reject) => {
|
|
30864
31649
|
const srv = createServer$1();
|
|
30865
31650
|
srv.once("error", reject);
|
|
30866
31651
|
srv.listen(0, "127.0.0.1", () => {
|
|
@@ -30870,7 +31655,7 @@ async function getFreePort() {
|
|
|
30870
31655
|
return;
|
|
30871
31656
|
}
|
|
30872
31657
|
const { port } = address;
|
|
30873
|
-
srv.close(() =>
|
|
31658
|
+
srv.close(() => resolve27(port));
|
|
30874
31659
|
});
|
|
30875
31660
|
});
|
|
30876
31661
|
}
|
|
@@ -30883,7 +31668,7 @@ async function probeHealth(url, timeoutMs) {
|
|
|
30883
31668
|
} catch {
|
|
30884
31669
|
}
|
|
30885
31670
|
if (Date.now() >= deadline) return false;
|
|
30886
|
-
await new Promise((
|
|
31671
|
+
await new Promise((resolve27) => setTimeout(resolve27, POLL_INTERVAL_MS));
|
|
30887
31672
|
}
|
|
30888
31673
|
}
|
|
30889
31674
|
|
|
@@ -31291,7 +32076,7 @@ async function spawnCloudflaredUntil(argv, opts) {
|
|
|
31291
32076
|
};
|
|
31292
32077
|
let settled = false;
|
|
31293
32078
|
let forwardedLen = 0;
|
|
31294
|
-
return await new Promise((
|
|
32079
|
+
return await new Promise((resolve27, reject) => {
|
|
31295
32080
|
const poll = setInterval(() => {
|
|
31296
32081
|
const text10 = readAll();
|
|
31297
32082
|
if (opts.onLog) {
|
|
@@ -31309,7 +32094,7 @@ async function spawnCloudflaredUntil(argv, opts) {
|
|
|
31309
32094
|
if (m) {
|
|
31310
32095
|
settled = true;
|
|
31311
32096
|
clearTimeout(timer);
|
|
31312
|
-
|
|
32097
|
+
resolve27({ proc, match: m[0], stopTail: () => clearInterval(poll) });
|
|
31313
32098
|
}
|
|
31314
32099
|
}, POLL_INTERVAL_MS2);
|
|
31315
32100
|
if (poll.unref) poll.unref();
|
|
@@ -31443,15 +32228,15 @@ function quickTunnelProvider() {
|
|
|
31443
32228
|
}
|
|
31444
32229
|
}, 3e3);
|
|
31445
32230
|
timer.unref();
|
|
31446
|
-
await new Promise((
|
|
32231
|
+
await new Promise((resolve27) => {
|
|
31447
32232
|
if (proc.exitCode !== null) {
|
|
31448
32233
|
clearTimeout(timer);
|
|
31449
|
-
|
|
32234
|
+
resolve27();
|
|
31450
32235
|
return;
|
|
31451
32236
|
}
|
|
31452
32237
|
proc.once("exit", () => {
|
|
31453
32238
|
clearTimeout(timer);
|
|
31454
|
-
|
|
32239
|
+
resolve27();
|
|
31455
32240
|
});
|
|
31456
32241
|
});
|
|
31457
32242
|
}
|
|
@@ -31788,7 +32573,9 @@ function registerDaemonHealthTools(server, opts) {
|
|
|
31788
32573
|
idleReapAfterMs: opts.idleReapAfterMs,
|
|
31789
32574
|
crashDetectIntervalMs: opts.crashDetectIntervalMs ?? 0,
|
|
31790
32575
|
restartSweepIntervalMs: opts.restartSweepIntervalMs ?? 0,
|
|
31791
|
-
turnStallAfterMs: opts.turnStallAfterMs ?? 0
|
|
32576
|
+
turnStallAfterMs: opts.turnStallAfterMs ?? 0,
|
|
32577
|
+
version: opts.version ?? null,
|
|
32578
|
+
build: opts.build ?? null
|
|
31792
32579
|
});
|
|
31793
32580
|
}
|
|
31794
32581
|
);
|
|
@@ -31923,15 +32710,15 @@ function namedTunnelProvider(cfg) {
|
|
|
31923
32710
|
}
|
|
31924
32711
|
}, 3e3);
|
|
31925
32712
|
timer.unref();
|
|
31926
|
-
await new Promise((
|
|
32713
|
+
await new Promise((resolve27) => {
|
|
31927
32714
|
if (proc.exitCode !== null) {
|
|
31928
32715
|
clearTimeout(timer);
|
|
31929
|
-
|
|
32716
|
+
resolve27();
|
|
31930
32717
|
return;
|
|
31931
32718
|
}
|
|
31932
32719
|
proc.once("exit", () => {
|
|
31933
32720
|
clearTimeout(timer);
|
|
31934
|
-
|
|
32721
|
+
resolve27();
|
|
31935
32722
|
});
|
|
31936
32723
|
});
|
|
31937
32724
|
}
|
|
@@ -32055,7 +32842,7 @@ function ngrokTunnelProvider(opts) {
|
|
|
32055
32842
|
};
|
|
32056
32843
|
proc.stderr?.on("data", forwardLogs);
|
|
32057
32844
|
proc.stdout?.on("data", forwardLogs);
|
|
32058
|
-
const url = await new Promise((
|
|
32845
|
+
const url = await new Promise((resolve27, reject) => {
|
|
32059
32846
|
let settled = false;
|
|
32060
32847
|
const timer = setTimeout(() => {
|
|
32061
32848
|
if (settled) return;
|
|
@@ -32083,7 +32870,7 @@ function ngrokTunnelProvider(opts) {
|
|
|
32083
32870
|
settled = true;
|
|
32084
32871
|
clearTimeout(timer);
|
|
32085
32872
|
if (apiPollHandle) clearInterval(apiPollHandle);
|
|
32086
|
-
|
|
32873
|
+
resolve27(pubUrl);
|
|
32087
32874
|
}
|
|
32088
32875
|
} catch {
|
|
32089
32876
|
}
|
|
@@ -32098,7 +32885,7 @@ function ngrokTunnelProvider(opts) {
|
|
|
32098
32885
|
settled = true;
|
|
32099
32886
|
clearTimeout(timer);
|
|
32100
32887
|
if (apiPollHandle) clearInterval(apiPollHandle);
|
|
32101
|
-
|
|
32888
|
+
resolve27(reMatch[1]);
|
|
32102
32889
|
return;
|
|
32103
32890
|
}
|
|
32104
32891
|
for (const line of text10.split(/\r?\n/)) {
|
|
@@ -32109,7 +32896,7 @@ function ngrokTunnelProvider(opts) {
|
|
|
32109
32896
|
settled = true;
|
|
32110
32897
|
clearTimeout(timer);
|
|
32111
32898
|
if (apiPollHandle) clearInterval(apiPollHandle);
|
|
32112
|
-
|
|
32899
|
+
resolve27(parsed.url);
|
|
32113
32900
|
return;
|
|
32114
32901
|
}
|
|
32115
32902
|
} catch {
|
|
@@ -32156,15 +32943,15 @@ function ngrokTunnelProvider(opts) {
|
|
|
32156
32943
|
}
|
|
32157
32944
|
}, 3e3);
|
|
32158
32945
|
timer.unref();
|
|
32159
|
-
await new Promise((
|
|
32946
|
+
await new Promise((resolve27) => {
|
|
32160
32947
|
if (proc.exitCode !== null) {
|
|
32161
32948
|
clearTimeout(timer);
|
|
32162
|
-
|
|
32949
|
+
resolve27();
|
|
32163
32950
|
return;
|
|
32164
32951
|
}
|
|
32165
32952
|
proc.once("exit", () => {
|
|
32166
32953
|
clearTimeout(timer);
|
|
32167
|
-
|
|
32954
|
+
resolve27();
|
|
32168
32955
|
});
|
|
32169
32956
|
});
|
|
32170
32957
|
}
|
|
@@ -33827,16 +34614,16 @@ function dialUrl(rendezvousUrl, token) {
|
|
|
33827
34614
|
return `${rendezvousUrl}${sep2}side=daemon&t=${encodeURIComponent(token)}`;
|
|
33828
34615
|
}
|
|
33829
34616
|
function waitClosed(sink, signal) {
|
|
33830
|
-
return new Promise((
|
|
34617
|
+
return new Promise((resolve27) => {
|
|
33831
34618
|
if (!sink.isOpen) {
|
|
33832
|
-
|
|
34619
|
+
resolve27();
|
|
33833
34620
|
return;
|
|
33834
34621
|
}
|
|
33835
34622
|
let settled = false;
|
|
33836
34623
|
const finish = () => {
|
|
33837
34624
|
if (settled) return;
|
|
33838
34625
|
settled = true;
|
|
33839
|
-
|
|
34626
|
+
resolve27();
|
|
33840
34627
|
};
|
|
33841
34628
|
sink.onClose(() => finish());
|
|
33842
34629
|
signal.addEventListener("abort", () => {
|
|
@@ -33846,13 +34633,13 @@ function waitClosed(sink, signal) {
|
|
|
33846
34633
|
});
|
|
33847
34634
|
}
|
|
33848
34635
|
function sleep(ms, signal) {
|
|
33849
|
-
return new Promise((
|
|
33850
|
-
if (signal.aborted) return
|
|
33851
|
-
const timer = setTimeout(
|
|
34636
|
+
return new Promise((resolve27) => {
|
|
34637
|
+
if (signal.aborted) return resolve27();
|
|
34638
|
+
const timer = setTimeout(resolve27, ms);
|
|
33852
34639
|
if (typeof timer.unref === "function") timer.unref();
|
|
33853
34640
|
signal.addEventListener("abort", () => {
|
|
33854
34641
|
clearTimeout(timer);
|
|
33855
|
-
|
|
34642
|
+
resolve27();
|
|
33856
34643
|
});
|
|
33857
34644
|
});
|
|
33858
34645
|
}
|
|
@@ -33907,6 +34694,9 @@ function composeMode(cfg, modes) {
|
|
|
33907
34694
|
|
|
33908
34695
|
// src/index.ts
|
|
33909
34696
|
init_conversation_store();
|
|
34697
|
+
|
|
34698
|
+
// src/auth-probe.ts
|
|
34699
|
+
init_config();
|
|
33910
34700
|
async function isAgentCliAuthConfigured(slug, descriptor, model) {
|
|
33911
34701
|
const config = await loadConfig();
|
|
33912
34702
|
const spawnDefaults = resolveSpawnDefaults(config.defaults, slug, {});
|
|
@@ -34062,6 +34852,14 @@ async function createGateway(opts) {
|
|
|
34062
34852
|
return await adapter.startSession({
|
|
34063
34853
|
cwd,
|
|
34064
34854
|
resumeSessionId,
|
|
34855
|
+
// Point the respawned adapter at the SAME persistent
|
|
34856
|
+
// isolated-config dir the original spawn used — the
|
|
34857
|
+
// provider's conversation store lives inside it, so this is
|
|
34858
|
+
// what makes `resumeSessionId` restore full context instead
|
|
34859
|
+
// of degrading to the daemon-transcript digest. Absent on
|
|
34860
|
+
// legacy rows spawned before adapterConfigDir existed (those
|
|
34861
|
+
// keep today's digest-fallback behaviour).
|
|
34862
|
+
...descriptor.adapterConfigDir ? { configDir: descriptor.adapterConfigDir } : {},
|
|
34065
34863
|
// Re-mount the persisted spawn-time toolset on resume
|
|
34066
34864
|
// (orchestrator WP1) — closes the gap where re-spawn
|
|
34067
34865
|
// dropped mcpServers.
|
|
@@ -34283,7 +35081,9 @@ async function createGateway(opts) {
|
|
|
34283
35081
|
idleReapAfterMs,
|
|
34284
35082
|
crashDetectIntervalMs,
|
|
34285
35083
|
restartSweepIntervalMs,
|
|
34286
|
-
turnStallAfterMs
|
|
35084
|
+
turnStallAfterMs,
|
|
35085
|
+
...opts.version ? { version: opts.version } : {},
|
|
35086
|
+
...opts.build ? { build: opts.build } : {}
|
|
34287
35087
|
});
|
|
34288
35088
|
registerCommandTools(server, {
|
|
34289
35089
|
workspace,
|
|
@@ -34586,6 +35386,8 @@ async function createGateway(opts) {
|
|
|
34586
35386
|
workspace,
|
|
34587
35387
|
registered,
|
|
34588
35388
|
startedAt,
|
|
35389
|
+
...opts.version ? { version: opts.version } : {},
|
|
35390
|
+
...opts.build ? { build: opts.build } : {},
|
|
34589
35391
|
resumeSessionsOnBoot: opts.resumeSessionsOnBoot === true,
|
|
34590
35392
|
idleReapAfterMs,
|
|
34591
35393
|
crashDetectIntervalMs,
|