@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/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((resolve26) => {
491
- state.stream.end(() => resolve26());
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((resolve26) => state.stream.end(() => resolve26())));
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 encoded = claudeProjectSlug(cwd);
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((resolve26, reject) => {
774
+ await new Promise((resolve27, reject) => {
649
775
  stream.once("error", reject);
650
- stream.once("open", resolve26);
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((resolve26, reject) => {
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
- resolve26(Buffer.concat(chunks).toString("utf8"));
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((resolve26, reject) => {
1106
+ await new Promise((resolve27, reject) => {
981
1107
  stream.once("error", reject);
982
- stream.once("open", resolve26);
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
- return resolve(homedir(), ".claude", "projects", claudeProjectSlug(cwd));
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((resolve26) => {
3079
- setTimeout(() => resolve26(null), capMs);
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((resolve26) => {
3144
- setTimeout(() => resolve26(null), capMs);
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
- var CONFIG_FILE_PATH = () => join(homedir(), ".agentproto", "config.json");
3484
- function sanitizeAcpAgents(raw, target) {
3485
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
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((resolve26) => setTimeout(resolve26, POLL_RETRY_DELAY_MS));
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 === "hermes" && daemonMcpUrl) {
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((resolve26) => {
5515
- resolveClaim = resolve26;
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((resolve26) => {
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
- resolve26(v);
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: resolve26 } = getMcpCredentialDeps();
5923
- if (!resolve26) return mcpServers;
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 resolve26({ credentialRef: ref });
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: resolve26 } = getMcpCredentialDeps();
6038
- if (!resolve26) return null;
6474
+ const { resolveSandboxSecret: resolve27 } = getMcpCredentialDeps();
6475
+ if (!resolve27) return null;
6039
6476
  try {
6040
- return await resolve26(slug);
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 = `${target}.tmp.${process.pid}`;
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 = `${target}.tmp.${process.pid}`;
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((resolve26) => {
7297
- stream.end(() => resolve26());
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((resolve26) => {
7307
- stream.end(() => resolve26());
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, resolve26 = defaultResolver) {
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 ? resolve26(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((resolve26) => {
7848
+ return new Promise((resolve27) => {
7408
7849
  execFile("git", ["status", "--porcelain"], { cwd }, (err, stdout) => {
7409
7850
  if (err) {
7410
- resolve26(void 0);
7851
+ resolve27(void 0);
7411
7852
  return;
7412
7853
  }
7413
7854
  const trimmed = stdout.trim();
7414
- resolve26(trimmed || "(working tree clean)");
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({ cwd, adapterSlug, adapterSessionId });
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 persistSnapshot = async () => {
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((resolve26, reject) => {
9012
+ return new Promise((resolve27, reject) => {
8514
9013
  const onBusy = (busy) => {
8515
9014
  if (busy) return;
8516
9015
  cleanup();
8517
- resolve26();
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?.kill(signal);
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?.kill("SIGTERM");
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?.kill("SIGTERM");
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
- console.warn(
10363
- `[sessions] history file ${persistPath} is malformed \u2014 ignoring`
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.includes(MARKER);
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((resolve26) => {
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", () => resolve26({ exitCode: 1, stdout }));
10594
- child.on("close", (code) => resolve26({ exitCode: code ?? 1, stdout }));
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, pair tool-call/tool-result by toolCallId, pass through
18850
- // turn-end/usage_update, ignore unknown kinds. Keep in sync with the
18851
- // TS module; the TS module is the one the test suite imports.
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
- var merged = Object.assign({}, last, {
18868
- text: last.text + (record.text || ''),
18869
- seq: record.seq,
18870
- ts: record.ts,
18871
- });
18872
- return { rows: state.rows.slice(0, -1).concat([merged]) };
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((resolve26) => setTimeout(resolve26, SEQUENTIAL_POLL_INTERVAL_MS));
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((resolve26) => {
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
- resolve26(outcome);
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((resolve26) => {
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
- resolve26(result);
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((resolve26) => {
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
- resolve26({ timedOut: true });
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
- resolve26({ timedOut: false, state });
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((resolve26, reject) => {
25602
+ await new Promise((resolve27, reject) => {
24884
25603
  server.once("error", reject);
24885
- server.listen(opts.port, bind, () => resolve26());
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((resolve26) => server.close(() => resolve26()));
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
- await registry.enqueuePrompt(id2, prompt, { interrupt });
25557
- json(202, { ok: true, id: id2, queued: true });
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((resolve26, reject) => {
26649
+ await new Promise((resolve27, reject) => {
25908
26650
  fileStream.once("error", reject);
25909
- fileStream.once("open", resolve26);
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((resolve26, reject) => {
26707
+ await new Promise((resolve27, reject) => {
25966
26708
  fileStream.once("error", reject);
25967
- fileStream.once("open", resolve26);
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((resolve26, reject) => {
29663
+ return new Promise((resolve27, reject) => {
28896
29664
  const unsubs = [];
28897
29665
  const done = () => {
28898
29666
  for (const u of unsubs) u();
28899
- resolve26();
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((resolve26, reject) => {
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
- resolve26(response);
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 (stampedPrUrls.has(pr.url)) return;
29522
- if (desc.openedPrs && desc.openedPrs.some((recorded) => recorded.url === pr.url)) {
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((resolve26) => {
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
- resolve26(value);
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, resolve26) {
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 () => resolve26 ? resolve26(entry.id) != null : true
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((resolve26, reject) => {
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(() => resolve26(port));
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((resolve26) => setTimeout(resolve26, POLL_INTERVAL_MS));
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((resolve26, reject) => {
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
- resolve26({ proc, match: m[0], stopTail: () => clearInterval(poll) });
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((resolve26) => {
32231
+ await new Promise((resolve27) => {
31447
32232
  if (proc.exitCode !== null) {
31448
32233
  clearTimeout(timer);
31449
- resolve26();
32234
+ resolve27();
31450
32235
  return;
31451
32236
  }
31452
32237
  proc.once("exit", () => {
31453
32238
  clearTimeout(timer);
31454
- resolve26();
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((resolve26) => {
32713
+ await new Promise((resolve27) => {
31927
32714
  if (proc.exitCode !== null) {
31928
32715
  clearTimeout(timer);
31929
- resolve26();
32716
+ resolve27();
31930
32717
  return;
31931
32718
  }
31932
32719
  proc.once("exit", () => {
31933
32720
  clearTimeout(timer);
31934
- resolve26();
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((resolve26, reject) => {
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
- resolve26(pubUrl);
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
- resolve26(reMatch[1]);
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
- resolve26(parsed.url);
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((resolve26) => {
32946
+ await new Promise((resolve27) => {
32160
32947
  if (proc.exitCode !== null) {
32161
32948
  clearTimeout(timer);
32162
- resolve26();
32949
+ resolve27();
32163
32950
  return;
32164
32951
  }
32165
32952
  proc.once("exit", () => {
32166
32953
  clearTimeout(timer);
32167
- resolve26();
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((resolve26) => {
34617
+ return new Promise((resolve27) => {
33831
34618
  if (!sink.isOpen) {
33832
- resolve26();
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
- resolve26();
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((resolve26) => {
33850
- if (signal.aborted) return resolve26();
33851
- const timer = setTimeout(resolve26, ms);
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
- resolve26();
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,