@agentproto/runtime 2.7.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, dirname, resolve, basename, isAbsolute, normalize, relative, extname, delimiter, 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
  }
@@ -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,14 +761,13 @@ 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" });
@@ -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));
@@ -2274,11 +2403,12 @@ var RESUME_STRATEGIES = Object.fromEntries(
2274
2403
  {
2275
2404
  outputHint: store.outputHint,
2276
2405
  storeAs: store.storeAs,
2277
- fsProbe: async (cwd, prevStartedAt, expectedId) => {
2406
+ fsProbe: async (cwd, prevStartedAt, expectedId, configDir) => {
2278
2407
  const candidates = await s.discover({
2279
2408
  cwd,
2280
2409
  since: prevStartedAt,
2281
- expectedId
2410
+ expectedId,
2411
+ configDir
2282
2412
  });
2283
2413
  return candidates[0]?.conversationId ?? null;
2284
2414
  },
@@ -2322,7 +2452,10 @@ async function augmentWithFsResume(prev) {
2322
2452
  const id = await strategy.fsProbe(
2323
2453
  prev.cwd,
2324
2454
  prev.startedAt,
2325
- 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
2326
2459
  );
2327
2460
  if (!id) return prev;
2328
2461
  return {
@@ -4890,109 +5023,9 @@ async function loadSpawnDedupe(loadCfg = loadConfig) {
4890
5023
  }
4891
5024
  return DEFAULT_SPAWN_DEDUPE;
4892
5025
  }
4893
- var MARKER = "@agentproto-bot";
4894
- var fmtTokens = (n) => {
4895
- if (typeof n !== "number" || !Number.isFinite(n)) return null;
4896
- return n >= 1e3 ? `${(n / 1e3).toFixed(1)}k` : String(n);
4897
- };
4898
- function cwdLabel(cwd, workspaceSlug) {
4899
- const leaf = basename(cwd);
4900
- if (!workspaceSlug || workspaceSlug === leaf) return workspaceSlug ?? leaf;
4901
- return `${workspaceSlug}/${leaf}`;
4902
- }
4903
- var buildFooter = ({
4904
- prov,
4905
- authMode,
4906
- runId,
4907
- runUrl,
4908
- sha,
4909
- kind = "review"
4910
- }) => {
4911
- const parts = [`\u{1F916} **${MARKER}** \u2014 ${kind}`];
4912
- if (prov?.sessionId) {
4913
- parts.push(`session \`${prov.sessionId}\`${prov.label ? ` (\`${prov.label}\`)` : ""}`);
4914
- }
4915
- if (prov?.adapter) parts.push([prov.adapter, authMode].filter(Boolean).join(" / "));
4916
- else if (!prov?.sessionId) parts.push(`legacy fallback${authMode ? ` (${authMode})` : ""}`);
4917
- else if (authMode) parts.push(authMode);
4918
- if (prov?.authProfile) parts.push(`auth-profile \`${prov.authProfile}\``);
4919
- if (prov?.model) parts.push(`model \`${prov.model}\``);
4920
- if (prov?.sandboxId) parts.push(`e2b \`${prov.sandboxId}\``);
4921
- if (prov?.parentSessionId) parts.push(`supervisor \`${prov.parentSessionId}\``);
4922
- const tin = fmtTokens(prov?.tokensIn);
4923
- const tout = fmtTokens(prov?.tokensOut);
4924
- if (tin || tout) parts.push(`${tin ?? "?"} in / ${tout ?? "?"} out`);
4925
- if (typeof prov?.costUsd === "number") {
4926
- parts.push(`$${prov.costUsd.toFixed(4)}${prov.source && prov.source !== "adapter" ? ` (${prov.source})` : ""}`);
4927
- }
4928
- const showLocalHostCwd = prov?.source === "local" || prov?.source === "daemon" || !runId;
4929
- if (runId && !showLocalHostCwd) parts.push(`run [${runId}](${runUrl})`);
4930
- if (showLocalHostCwd) {
4931
- if (prov?.host) parts.push(`host \`${prov.host}\``);
4932
- if (prov?.cwd) parts.push(`cwd \`${cwdLabel(prov.cwd, prov.workspaceSlug)}\``);
4933
- }
4934
- if (sha) parts.push(`sha \`${sha.slice(0, 7)}\``);
4935
- return `
4936
-
4937
- ---
4938
- <sub>${parts.join(" \xB7 ")}</sub>`;
4939
- };
4940
- function sessionFooterProvenance(session, options = {}) {
4941
- const prov = {
4942
- sessionId: session.id,
4943
- label: session.label,
4944
- adapter: session.harness ?? session.adapterSlug,
4945
- model: session.model,
4946
- authProfile: session.accessProfile?.label ?? session.accessProfile?.profileRef,
4947
- parentSessionId: options.supervisor?.id,
4948
- costUsd: session.costUsd,
4949
- tokensIn: session.tokensIn,
4950
- tokensOut: session.tokensOut,
4951
- source: options.source ?? "daemon",
4952
- host: options.host,
4953
- cwd: session.cwd,
4954
- workspaceSlug: session.workspaceSlug
4955
- };
4956
- return { prov, authMode: session.auth?.mode };
4957
- }
4958
- function buildSessionPrFooter(session, options = {}) {
4959
- const { prov, authMode } = sessionFooterProvenance(session, options);
4960
- return buildFooter({ prov, authMode, sha: options.sha, kind: "PR" });
4961
- }
4962
- function appendFooterOnce(body, footer) {
4963
- if (body.includes(MARKER)) return body;
4964
- return `${body}${footer}`;
4965
- }
4966
- function parseGhPrCreate(command, args, stdout) {
4967
- if (basename(command) !== "gh") return null;
4968
- const positionals = args.filter((a) => !a.startsWith("-"));
4969
- if (positionals[0] !== "pr" || positionals[1] !== "create") return null;
4970
- const re = /https?:\/\/\S+?\/pull\/(\d+)/g;
4971
- let match;
4972
- let last = null;
4973
- while ((match = re.exec(stdout)) !== null) {
4974
- last = { url: match[0], number: Number(match[1]) };
4975
- }
4976
- return last;
4977
- }
4978
- function cwdRelated(sessionCwd, cwd) {
4979
- if (sessionCwd === cwd) return true;
4980
- const sep2 = "/";
4981
- return cwd.startsWith(sessionCwd + sep2) || sessionCwd.startsWith(cwd + sep2);
4982
- }
4983
- function pickExecutorSession(sessions, cwd) {
4984
- const candidates = sessions.filter(
4985
- (s) => s.kind === "agent-cli" && typeof s.cwd === "string" && cwdRelated(s.cwd, cwd)
4986
- );
4987
- if (candidates.length === 0) return void 0;
4988
- const alive = (s) => s.status === "running" || s.status === "starting";
4989
- const byRecency = (a, b) => (b.startedAt ?? "").localeCompare(a.startedAt ?? "");
4990
- const live = candidates.filter(alive).sort(byRecency);
4991
- if (live.length > 0) return live[0];
4992
- return [...candidates].sort(byRecency)[0];
4993
- }
4994
5026
 
4995
5027
  // src/gh-provenance-shim.ts
5028
+ init_pr_provenance();
4996
5029
  var PROVENANCE_WRAP_GH_ENV = "AGENTPROTO_PROVENANCE_WRAP_GH";
4997
5030
  var DEFAULT_WRAP_GH = false;
4998
5031
  var GH_PROVENANCE_ENABLE_ENV = "AGENTPROTO_GH_PROVENANCE";
@@ -5364,6 +5397,11 @@ async function resolveAccessProfileAuth(input) {
5364
5397
  }
5365
5398
  };
5366
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
+ }
5367
5405
  function cleanAgentLines(lines) {
5368
5406
  return lines.map((l) => l.replace(/\x1b\[[0-9;]*m/g, "")).filter((l) => {
5369
5407
  const t = l.trim();
@@ -5634,7 +5672,7 @@ async function spawnAgentSession(deps2, input) {
5634
5672
  const delegationDenied = role.toolPolicy.delegation === "deny";
5635
5673
  let mcpServers = input.mcpServers;
5636
5674
  const mintedSessionId = mintSessionId();
5637
- if (!mcpServers && input.adapter === "hermes" && daemonMcpUrl) {
5675
+ if (!mcpServers && shouldInjectDaemonSelfMount(input.adapter, input.sandbox) && daemonMcpUrl) {
5638
5676
  let ref = delegationDenied ? `${daemonMcpUrl}${daemonMcpUrl.includes("?") ? "&" : "?"}denyTools=${DELEGATION_TOOL_NAMES.join(",")}` : daemonMcpUrl;
5639
5677
  ref += `${ref.includes("?") ? "&" : "?"}callerSessionId=${encodeURIComponent(mintedSessionId)}`;
5640
5678
  mcpServers = [{ name: "agentproto", transport: "http", ref }];
@@ -7468,11 +7506,13 @@ function listBuckets(root) {
7468
7506
  }
7469
7507
  }
7470
7508
  var serialize = (snapshot) => JSON.stringify(snapshot, null, 2) + "\n";
7509
+ var tmpSeq = 0;
7510
+ var tmpPathFor = (target) => `${target}.tmp.${process.pid}.${++tmpSeq}`;
7471
7511
  async function writeBucketSnapshot(root, slug, snapshot) {
7472
7512
  const dir = bucketDir(root, slug);
7473
7513
  await promises.mkdir(dir, { recursive: true });
7474
7514
  const target = bucketSessionsFile(root, slug);
7475
- const tmp = `${target}.tmp.${process.pid}`;
7515
+ const tmp = tmpPathFor(target);
7476
7516
  await promises.writeFile(tmp, serialize(snapshot), "utf8");
7477
7517
  await promises.rename(tmp, target);
7478
7518
  }
@@ -7480,7 +7520,7 @@ function writeBucketSnapshotSync(root, slug, snapshot) {
7480
7520
  const dir = bucketDir(root, slug);
7481
7521
  mkdirSync(dir, { recursive: true });
7482
7522
  const target = bucketSessionsFile(root, slug);
7483
- const tmp = `${target}.tmp.${process.pid}`;
7523
+ const tmp = tmpPathFor(target);
7484
7524
  writeFileSync(tmp, serialize(snapshot), "utf8");
7485
7525
  renameSync(tmp, target);
7486
7526
  }
@@ -7585,9 +7625,9 @@ async function listClaudeSubagents(projectDir, adapterSessionId) {
7585
7625
  return entries.filter((e) => e.startsWith("agent-") && e.endsWith(".jsonl")).map((e) => join(dir, e)).sort();
7586
7626
  }
7587
7627
  async function resolveNativeLink(input) {
7588
- const { cwd, adapterSlug, adapterSessionId } = input;
7628
+ const { cwd, adapterSlug, adapterSessionId, adapterConfigDir } = input;
7589
7629
  if (adapterSlug === "claude-code") {
7590
- const dir = claudeCodeProjectDir(cwd);
7630
+ const dir = claudeCodeProjectDir(cwd, adapterConfigDir);
7591
7631
  const path = join(dir, `${adapterSessionId}.jsonl`);
7592
7632
  const subagents = await listClaudeSubagents(dir, adapterSessionId);
7593
7633
  return { kind: "claude-jsonl", path, subagents };
@@ -7937,6 +7977,21 @@ function formatAccessForSpawn(desc) {
7937
7977
  if (!desc.accessProfile?.profileRef) return void 0;
7938
7978
  return { profileRef: desc.accessProfile.profileRef };
7939
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
+ }
7940
7995
  function formatPostureForSpawn(desc) {
7941
7996
  return desc.posture;
7942
7997
  }
@@ -7966,7 +8021,7 @@ async function continueAgentSessionFresh(deps2, prev, opts = {}) {
7966
8021
  access: formatAccessForSpawn(prev),
7967
8022
  posture: formatPostureForSpawn(prev),
7968
8023
  contextProfile: prev.contextProfile,
7969
- mcpServers: prev.mcpServers,
8024
+ mcpServers: stripOwnCallerStamp(prev.mcpServers, prev.id),
7970
8025
  label: prev.label ? `${prev.label} (continued)` : void 0,
7971
8026
  title: prev.title ? `${prev.title} (continued)` : void 0,
7972
8027
  contextContinuity: opts.contextContinuity ?? prev.contextContinuity,
@@ -8175,6 +8230,14 @@ function stampProcessAlive(desc) {
8175
8230
  desc.processAlive = false;
8176
8231
  }
8177
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
+ }
8178
8241
  function stampInterrupted(desc) {
8179
8242
  if (desc.killedMidTurn === true && desc.endedReason === "daemon-restart") {
8180
8243
  desc.interrupted = true;
@@ -8544,7 +8607,14 @@ function createSessionsRegistry(opts) {
8544
8607
  if (!adapterSlug || !adapterSessionId || !cwd) return;
8545
8608
  void (async () => {
8546
8609
  try {
8547
- 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
+ });
8548
8618
  const registered = readRegisteredSlugs(workspacesConfigPath);
8549
8619
  const slug = resolveBucketSlug(desc.workspaceSlug, registered);
8550
8620
  const record2 = {
@@ -8553,6 +8623,7 @@ function createSessionsRegistry(opts) {
8553
8623
  cwd,
8554
8624
  adapterSlug,
8555
8625
  adapterSessionId,
8626
+ ...desc.adapterConfigDir ? { adapterConfigDir: desc.adapterConfigDir } : {},
8556
8627
  ...native ? { native } : {},
8557
8628
  agentprotoTranscript: sessionEventsPath(desc.id, transcriptBaseDir),
8558
8629
  ...desc.title ? { title: desc.title } : {},
@@ -8568,7 +8639,7 @@ function createSessionsRegistry(opts) {
8568
8639
  })();
8569
8640
  };
8570
8641
  const schedulePersist = () => {
8571
- if (!persist) return;
8642
+ if (!persist || shutdownDone) return;
8572
8643
  if (persistTimer) clearTimeout(persistTimer);
8573
8644
  persistTimer = setTimeout(() => {
8574
8645
  void persistSnapshot();
@@ -8604,11 +8675,13 @@ function createSessionsRegistry(opts) {
8604
8675
  rows,
8605
8676
  heldIdsByBucket.get(slug) ?? EMPTY_ID_SET
8606
8677
  );
8607
- const persistSnapshot = async () => {
8678
+ const persistOnce = async () => {
8608
8679
  try {
8680
+ if (shutdownDone) return;
8609
8681
  const savedAt = (/* @__PURE__ */ new Date()).toISOString();
8610
8682
  if (partitioned) {
8611
8683
  for (const [slug, rows] of groupRowsByBucket()) {
8684
+ if (shutdownDone) return;
8612
8685
  await writeBucketSnapshot(bucketsRoot, slug, {
8613
8686
  savedAt,
8614
8687
  sessions: rowsToWrite(slug, rows)
@@ -8625,6 +8698,24 @@ function createSessionsRegistry(opts) {
8625
8698
  );
8626
8699
  }
8627
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
+ };
8628
8719
  const appendLine = (rt, line, stream) => {
8629
8720
  rt.recentLines.push(line);
8630
8721
  if (rt.recentLines.length > RECENT_LINES_CAP) {
@@ -10524,7 +10615,7 @@ ${message}`;
10524
10615
  }
10525
10616
  void terminalTranscriptWriter.close(rt.desc.id);
10526
10617
  }
10527
- rt.child?.kill(signal);
10618
+ killChildIfSpawned(rt.child, signal);
10528
10619
  if (rt.browserStop) {
10529
10620
  void rt.browserStop().catch(() => void 0);
10530
10621
  }
@@ -10549,7 +10640,7 @@ ${message}`;
10549
10640
  tracedSessions.delete(rt.desc.id);
10550
10641
  rt.agentSession = void 0;
10551
10642
  }
10552
- rt.child?.kill("SIGTERM");
10643
+ killChildIfSpawned(rt.child, "SIGTERM");
10553
10644
  schedulePersist();
10554
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";
10555
10646
  appendLine(rt, banner, "stdout");
@@ -10830,7 +10921,7 @@ ${message}`;
10830
10921
  } catch {
10831
10922
  }
10832
10923
  }
10833
- rt.child?.kill("SIGTERM");
10924
+ killChildIfSpawned(rt.child, "SIGTERM");
10834
10925
  emitExited(rt);
10835
10926
  }
10836
10927
  }
@@ -10876,9 +10967,17 @@ function loadHistorySnapshot(persistPath, sessions, sessionEvents, bucketSlug, s
10876
10967
  try {
10877
10968
  parsed = JSON.parse(raw);
10878
10969
  } catch {
10879
- console.warn(
10880
- `[sessions] history file ${persistPath} is malformed \u2014 ignoring`
10881
- );
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
+ }
10882
10981
  return;
10883
10982
  }
10884
10983
  if (!Array.isArray(parsed.sessions)) return;
@@ -10939,6 +11038,9 @@ function quoteArg(arg) {
10939
11038
  if (/^[a-zA-Z0-9._/=:@,+-]+$/.test(arg)) return arg;
10940
11039
  return `"${arg.replace(/(["\\$`])/g, "\\$1")}"`;
10941
11040
  }
11041
+
11042
+ // src/pr-provenance-stamp.ts
11043
+ init_pr_provenance();
10942
11044
  async function stampPrProvenance(input) {
10943
11045
  try {
10944
11046
  if (input.exitCode !== 0) return { stamped: false, reason: "command failed" };
@@ -10972,19 +11074,19 @@ async function stampFooterOnPr(input) {
10972
11074
  const view = await run(["pr", "view", input.prUrl, "--json", "body", "--jq", ".body"], input.cwd);
10973
11075
  if (view.exitCode !== 0) return { stamped: false, reason: `gh pr view exit ${view.exitCode}` };
10974
11076
  const body = view.stdout.replace(/\n+$/, "");
10975
- const alreadyStamped = body.includes(MARKER);
11077
+ const alreadyStamped = hasProvenanceFooter(body);
10976
11078
  if (!alreadyStamped) {
10977
11079
  const newBody = appendFooterOnce(body, footer);
10978
11080
  const edit = await run(["pr", "edit", input.prUrl, "--body", newBody], input.cwd);
10979
11081
  if (edit.exitCode !== 0) {
10980
11082
  return { stamped: false, reason: `gh pr edit exit ${edit.exitCode}` };
10981
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
+ });
10982
11089
  }
10983
- input.registry.recordOpenedPr(input.session.id, {
10984
- adapter: input.session.harness ?? input.session.adapterSlug ?? "gh",
10985
- number: input.prNumber,
10986
- url: input.prUrl
10987
- });
10988
11090
  return {
10989
11091
  stamped: true,
10990
11092
  url: input.prUrl,
@@ -13719,7 +13821,10 @@ async function resolveConversationId(desc) {
13719
13821
  // A live/ongoing session has no endedAt yet and must keep matching
13720
13822
  // with no upper bound — only a dead session's endedAt narrows.
13721
13823
  ...desc.endedAt ? { until: desc.endedAt } : {},
13722
- ...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 } : {}
13723
13828
  });
13724
13829
  if (candidates.length === 0) {
13725
13830
  return {
@@ -13778,7 +13883,7 @@ async function readConversation(registry, input) {
13778
13883
  });
13779
13884
  }
13780
13885
  try {
13781
- const session = await store.read(resolved.conversationId, desc.cwd);
13886
+ const session = await store.read(resolved.conversationId, desc.cwd, desc.adapterConfigDir);
13782
13887
  const content = format === "json" ? renderJson(session) : renderMarkdown(session);
13783
13888
  return {
13784
13889
  conversation: session,
@@ -16006,7 +16111,7 @@ function registerBrainTools(server, opts) {
16006
16111
  );
16007
16112
  server.tool(
16008
16113
  "workspace_brain_status",
16009
- "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.",
16010
16115
  {
16011
16116
  workspace: z.string().optional().describe("Workspace slug. Omit to use the calling session's workspace.")
16012
16117
  },
@@ -16088,7 +16193,7 @@ async function readSessionForBrain(sessionId) {
16088
16193
  const store = CONVERSATION_STORES[record2.adapterSlug];
16089
16194
  if (store) {
16090
16195
  try {
16091
- const exported = await store.read(record2.adapterSessionId, record2.cwd);
16196
+ const exported = await store.read(record2.adapterSessionId, record2.cwd, record2.adapterConfigDir);
16092
16197
  if (exported?.messages?.length) return exported;
16093
16198
  } catch {
16094
16199
  }
@@ -19411,9 +19516,11 @@ ${panelBridgeScript("agentproto-live-session")}
19411
19516
  // ============================================================
19412
19517
  // INLINED REDUCER COPY \u2014 hand-kept mirror of live-session-app.logic.ts.
19413
19518
  // Plain JS, same semantics: coalesce consecutive text-delta of the same
19414
- // session, pair tool-call/tool-result by toolCallId, pass through
19415
- // turn-end/usage_update, ignore unknown kinds. Keep in sync with the
19416
- // 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.
19417
19524
  // ============================================================
19418
19525
 
19419
19526
  function initialTimelineState() {
@@ -19424,22 +19531,50 @@ function rowId(record, rows) {
19424
19531
  return record.seq != null ? (record.kind + '-' + record.seq) : (record.kind + '-' + rows.length);
19425
19532
  }
19426
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
+
19427
19548
  function reduceEvent(state, record) {
19428
19549
  switch (record.kind) {
19429
19550
  case 'text-delta': {
19430
19551
  var last = state.rows[state.rows.length - 1];
19431
19552
  if (last && last.kind === 'text' && last.sessionId === record.sessionId) {
19432
- var merged = Object.assign({}, last, {
19433
- text: last.text + (record.text || ''),
19434
- seq: record.seq,
19435
- ts: record.ts,
19436
- });
19437
- 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;
19438
19572
  }
19439
19573
  var row = {
19440
19574
  kind: 'text', id: rowId(record, state.rows), seq: record.seq, ts: record.ts,
19441
19575
  sessionId: record.sessionId, text: record.text || '',
19442
19576
  };
19577
+ if (record.partial === true) row.partial = true;
19443
19578
  return { rows: state.rows.concat([row]) };
19444
19579
  }
19445
19580
  case 'tool-call': {
@@ -24384,6 +24519,7 @@ async function startHttpServer(opts) {
24384
24519
  // node+entry pair launchd (or the shell) exec'd. Lifecycle tooling
24385
24520
  // (`agentproto daemon start/stop/status`) reports these.
24386
24521
  version: opts.meta.version ?? null,
24522
+ build: opts.meta.build ?? null,
24387
24523
  pid: process.pid,
24388
24524
  node: process.execPath,
24389
24525
  entry: process.argv[1] ?? null,
@@ -30148,25 +30284,38 @@ function createPrProvenanceReconciler(opts) {
30148
30284
  if (last !== void 0 && Date.now() - last < POLL_THROTTLE_MS) return;
30149
30285
  lastPollAt.set(sessionId, Date.now());
30150
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
+ }
30151
30315
  const pr = await opts.resolveOpenPr(cwd);
30152
30316
  if (!pr) return;
30153
- if (stampedPrUrls.has(pr.url)) return;
30154
- if (desc.openedPrs && desc.openedPrs.some((recorded) => recorded.url === pr.url)) {
30155
- stampedPrUrls.add(pr.url);
30156
- return;
30157
- }
30158
- const supervisor = desc.parentSessionId != null ? opts.registry.get(desc.parentSessionId) ?? { id: desc.parentSessionId } : null;
30159
- const outcome = await stampFooterOnPr({
30160
- registry: opts.registry,
30161
- session: desc,
30162
- supervisor,
30163
- prNumber: pr.number,
30164
- prUrl: pr.url,
30165
- cwd,
30166
- ...opts.run ? { run: opts.run } : {},
30167
- ...opts.host ? { host: opts.host } : {}
30168
- });
30169
- if (outcome.stamped) stampedPrUrls.add(pr.url);
30317
+ if (!shouldStamp(pr.url)) return;
30318
+ await stamp(pr);
30170
30319
  };
30171
30320
  const safeReconcile = (sessionId, terminal) => {
30172
30321
  void reconcile(sessionId, terminal).catch(() => {
@@ -32424,7 +32573,9 @@ function registerDaemonHealthTools(server, opts) {
32424
32573
  idleReapAfterMs: opts.idleReapAfterMs,
32425
32574
  crashDetectIntervalMs: opts.crashDetectIntervalMs ?? 0,
32426
32575
  restartSweepIntervalMs: opts.restartSweepIntervalMs ?? 0,
32427
- turnStallAfterMs: opts.turnStallAfterMs ?? 0
32576
+ turnStallAfterMs: opts.turnStallAfterMs ?? 0,
32577
+ version: opts.version ?? null,
32578
+ build: opts.build ?? null
32428
32579
  });
32429
32580
  }
32430
32581
  );
@@ -34930,7 +35081,9 @@ async function createGateway(opts) {
34930
35081
  idleReapAfterMs,
34931
35082
  crashDetectIntervalMs,
34932
35083
  restartSweepIntervalMs,
34933
- turnStallAfterMs
35084
+ turnStallAfterMs,
35085
+ ...opts.version ? { version: opts.version } : {},
35086
+ ...opts.build ? { build: opts.build } : {}
34934
35087
  });
34935
35088
  registerCommandTools(server, {
34936
35089
  workspace,
@@ -35234,6 +35387,7 @@ async function createGateway(opts) {
35234
35387
  registered,
35235
35388
  startedAt,
35236
35389
  ...opts.version ? { version: opts.version } : {},
35390
+ ...opts.build ? { build: opts.build } : {},
35237
35391
  resumeSessionsOnBoot: opts.resumeSessionsOnBoot === true,
35238
35392
  idleReapAfterMs,
35239
35393
  crashDetectIntervalMs,