@agentproto/runtime 0.7.0 → 1.0.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,4 +1,4 @@
1
- import { createReadStream, existsSync, readdirSync, readFileSync, mkdirSync, writeFileSync, renameSync, promises, createWriteStream, statSync, openSync, closeSync } from 'fs';
1
+ import { createReadStream, existsSync, promises, readdirSync, readFileSync, mkdirSync, writeFileSync, renameSync, createWriteStream, statSync, openSync, closeSync } from 'fs';
2
2
  import { homedir, tmpdir } from 'os';
3
3
  import { join, resolve, dirname, isAbsolute, normalize, relative, basename } from 'path';
4
4
  import { createInterface } from 'readline';
@@ -12,10 +12,12 @@ import { getModelProvider, resolvePricing } from '@agentproto/model-catalog/llm'
12
12
  import * as providers_store_star from '@agentproto/providers-store';
13
13
  import { makeAdapterLister, makeAdapterResolver, makeCredsStore, discoverAdapterPackages, makeSetupLedger, makeListTool, makeSetupTool } from '@agentproto/provider-kit';
14
14
  import { SandboxSpecSchema, resolveLifecyclePolicy, createSandboxAgentSessionHost } from '@agentproto/sandbox';
15
+ import { eligibleProfiles, getAuthProfile, KeychainStore } from '@agentproto/auth';
15
16
  import matter from 'gray-matter';
16
17
  import { createServer } from 'http';
17
18
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
18
19
  import { WebSocketServer } from 'ws';
20
+ import { resolveLlmModelRoute, formatModelRef, tryParseModelRef } from '@agentproto/model-catalog/route-identity';
19
21
  import { anthropicGatewayPresetList } from '@agentproto/provider-presets';
20
22
  import { createIngestionClient } from '@agentproto/telemetry-langfuse';
21
23
  import { resolveRedactor } from '@agentproto/redaction';
@@ -33,6 +35,7 @@ import { createServer as createServer$1 } from 'net';
33
35
  import { daemonHandshakeOverSink } from '@agentproto/acp/tunnel';
34
36
  import { PairingError, encodeOfferUrl, HOSTED_RENDEZVOUS_URL, decodePairingHello, respondToHandshake, encodePairingMessage, derivePairRoot, deriveEpochRoutingToken, currentEpoch } from '@agentproto/secrets/pairing';
35
37
  import { identityFingerprint } from '@agentproto/secrets/identity';
38
+ import { inferLegacyModeKind } from '@agentproto/driver-agent-cli';
36
39
 
37
40
  /**
38
41
  * @agentproto/runtime v0.1.0-alpha
@@ -445,6 +448,156 @@ var init_transcript_writer = __esm({
445
448
  DEBOUNCE_MS = 250;
446
449
  }
447
450
  });
451
+ function claudeProjectSlug(cwd) {
452
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
453
+ }
454
+ function claudeCodeProjectDir(cwd) {
455
+ return resolve(homedir(), ".claude", "projects", claudeProjectSlug(cwd));
456
+ }
457
+ function extractFirstText(content) {
458
+ if (typeof content === "string") {
459
+ const t = content.trim();
460
+ return t || void 0;
461
+ }
462
+ if (Array.isArray(content)) {
463
+ for (const block of content) {
464
+ if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
465
+ const t = block.text.trim();
466
+ if (t) return t;
467
+ }
468
+ }
469
+ }
470
+ return void 0;
471
+ }
472
+ async function scanClaudeJsonl(filePath) {
473
+ const stream = createReadStream(filePath, { encoding: "utf8" });
474
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
475
+ let startedAt;
476
+ let lastActivityAt;
477
+ let messageCount = 0;
478
+ let preview2;
479
+ let lastWriter;
480
+ for await (const line of rl) {
481
+ const trimmed = line.trim();
482
+ if (!trimmed) continue;
483
+ let entry;
484
+ try {
485
+ entry = JSON.parse(trimmed);
486
+ } catch {
487
+ continue;
488
+ }
489
+ if (typeof entry.timestamp === "string") {
490
+ if (!startedAt) startedAt = entry.timestamp;
491
+ lastActivityAt = entry.timestamp;
492
+ }
493
+ if (typeof entry.entrypoint === "string") {
494
+ lastWriter = entry.entrypoint;
495
+ }
496
+ if (entry.type === "user" || entry.type === "assistant") {
497
+ messageCount += 1;
498
+ if (preview2 === void 0 && entry.type === "user") {
499
+ const text6 = extractFirstText(entry.message?.content);
500
+ if (text6 !== void 0) {
501
+ preview2 = text6.length > 120 ? text6.slice(0, 120) : text6;
502
+ }
503
+ }
504
+ }
505
+ }
506
+ return { startedAt, lastActivityAt, messageCount, preview: preview2, lastWriter };
507
+ }
508
+ async function buildClaudeCandidate(filePath, conversationId) {
509
+ const scanned = await scanClaudeJsonl(filePath);
510
+ return { conversationId, ...scanned };
511
+ }
512
+ function claudeEntrypointFor(mode) {
513
+ return mode === "native" ? "cli" : "sdk-ts";
514
+ }
515
+ async function discoverClaudeCode(input) {
516
+ const { cwd, since, until, attachmentMode, expectedId } = input;
517
+ const dir = claudeCodeProjectDir(cwd);
518
+ if (expectedId) {
519
+ const filePath = join(dir, `${expectedId}.jsonl`);
520
+ try {
521
+ await promises.stat(filePath);
522
+ } catch {
523
+ return [];
524
+ }
525
+ return [await buildClaudeCandidate(filePath, expectedId)];
526
+ }
527
+ let entries;
528
+ try {
529
+ entries = await promises.readdir(dir);
530
+ } catch {
531
+ return [];
532
+ }
533
+ const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
534
+ if (jsonlFiles.length === 0) return [];
535
+ const sinceMs = since ? Date.parse(since) : NaN;
536
+ const untilMs = until ? Date.parse(until) : NaN;
537
+ const wantEntrypoint = attachmentMode ? claudeEntrypointFor(attachmentMode) : void 0;
538
+ const scored = [];
539
+ for (const f of jsonlFiles) {
540
+ const filePath = join(dir, f);
541
+ let mtimeMs;
542
+ try {
543
+ mtimeMs = (await promises.stat(filePath)).mtimeMs;
544
+ } catch {
545
+ continue;
546
+ }
547
+ if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1e3) continue;
548
+ const conversationId = f.replace(/\.jsonl$/, "");
549
+ const candidate = await buildClaudeCandidate(filePath, conversationId);
550
+ if (Number.isFinite(untilMs) && candidate.startedAt !== void 0) {
551
+ const startedMs = Date.parse(candidate.startedAt);
552
+ if (Number.isFinite(startedMs) && startedMs > untilMs) continue;
553
+ }
554
+ if (wantEntrypoint !== void 0 && candidate.lastWriter !== void 0 && candidate.lastWriter !== wantEntrypoint) {
555
+ continue;
556
+ }
557
+ scored.push({ candidate, mtimeMs });
558
+ }
559
+ scored.sort((a, b) => b.mtimeMs - a.mtimeMs);
560
+ return scored.map((s) => s.candidate);
561
+ }
562
+ async function readClaudeCode(conversationId, cwd) {
563
+ const { exportClaudeCodeSession: exportClaudeCodeSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
564
+ return exportClaudeCodeSession2(conversationId, cwd);
565
+ }
566
+ async function discoverHermes(input) {
567
+ const { discoverHermesSessions: discoverHermesSessions2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
568
+ return discoverHermesSessions2(input.cwd, input.since, input.expectedId);
569
+ }
570
+ async function readHermes(conversationId) {
571
+ const { exportHermesSession: exportHermesSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
572
+ return exportHermesSession2(conversationId);
573
+ }
574
+ var CONVERSATION_STORES;
575
+ var init_conversation_store = __esm({
576
+ "src/conversation-store.ts"() {
577
+ CONVERSATION_STORES = {
578
+ "claude-code": {
579
+ storeAs: "claudeResumeId",
580
+ // Printed by claude on graceful exit when session persistence is on
581
+ // (default). Example: `claude --resume 0e483f81-1a44-4bec-9667-b37158450296`
582
+ outputHint: /claude\s+--resume\s+([0-9a-f-]{8,})/i,
583
+ attachArgv: (conversationId) => ["claude", "--resume", conversationId],
584
+ discover: discoverClaudeCode,
585
+ read: readClaudeCode
586
+ },
587
+ hermes: {
588
+ storeAs: "hermesResumeId",
589
+ // `hermes acp` is the ACP arm (bin_args in adapters/hermes/src/index.ts);
590
+ // `--resume SESSION --tui` is the native TUI resume path — same binary,
591
+ // different flags, unlike claude-code where the two arms are different
592
+ // binaries. Verified via `hermes --help`: `--resume SESSION, -r` =
593
+ // "Resume a previous session by ID or title", `--tui` = the real TUI.
594
+ attachArgv: (conversationId) => ["hermes", "--resume", conversationId, "--tui"],
595
+ discover: discoverHermes,
596
+ read: readHermes
597
+ }
598
+ };
599
+ }
600
+ });
448
601
 
449
602
  // src/transcript-export.ts
450
603
  var transcript_export_exports = {};
@@ -549,7 +702,7 @@ async function exportClaudeCodeSession(adapterSessionId, cwd) {
549
702
  "claude-code exporter: cwd is required to locate the JSONL file.\nPass cwd explicitly or use a session id that is in the registry."
550
703
  );
551
704
  }
552
- const encoded = cwd.replace(/\//g, "-");
705
+ const encoded = claudeProjectSlug(cwd);
553
706
  const filePath = join(homedir(), ".claude", "projects", encoded, `${adapterSessionId}.jsonl`);
554
707
  let stream;
555
708
  try {
@@ -1057,6 +1210,7 @@ var init_transcript_export = __esm({
1057
1210
  "src/transcript-export.ts"() {
1058
1211
  init_tool_presenter();
1059
1212
  init_transcript_writer();
1213
+ init_conversation_store();
1060
1214
  ROLE_ICON = {
1061
1215
  user: "\u{1F9D1} User",
1062
1216
  assistant: "\u{1F916} Assistant",
@@ -1743,6 +1897,61 @@ function loadWorkspacesConfigSync(path = DEFAULT_CONFIG_PATH()) {
1743
1897
  }
1744
1898
  return normalizeConfig(parsed);
1745
1899
  }
1900
+ async function saveWorkspacesConfig(config, path = DEFAULT_CONFIG_PATH()) {
1901
+ const normalized = normalizeConfig(config);
1902
+ await promises.mkdir(dirname(path), { recursive: true });
1903
+ const tmp = `${path}.tmp.${process.pid}`;
1904
+ await promises.writeFile(tmp, JSON.stringify(normalized, null, 2) + "\n", "utf8");
1905
+ await promises.rename(tmp, path);
1906
+ }
1907
+ function addWorkspace(config, input) {
1908
+ if (!isAbsolute(input.path)) {
1909
+ throw new Error(
1910
+ `addWorkspace: path must be absolute, got "${input.path}".`
1911
+ );
1912
+ }
1913
+ const slug = sanitizeSlug(input.slug);
1914
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1915
+ const existingIdx = config.workspaces.findIndex((w) => w.slug === slug);
1916
+ const next = existingIdx >= 0 ? {
1917
+ ...config.workspaces[existingIdx],
1918
+ path: input.path,
1919
+ ...input.label !== void 0 ? { label: input.label } : {},
1920
+ updatedAt: now
1921
+ } : {
1922
+ slug,
1923
+ path: input.path,
1924
+ ...input.label ? { label: input.label } : {},
1925
+ addedAt: now,
1926
+ updatedAt: now
1927
+ };
1928
+ const workspaces = [...config.workspaces];
1929
+ if (existingIdx >= 0) workspaces[existingIdx] = next;
1930
+ else workspaces.push(next);
1931
+ const active = config.active ?? slug;
1932
+ return { ...config, workspaces, active };
1933
+ }
1934
+ function removeWorkspace(config, slug) {
1935
+ const sanitised = sanitizeSlug(slug);
1936
+ const workspaces = config.workspaces.filter((w) => w.slug !== sanitised);
1937
+ let active = config.active;
1938
+ if (active === sanitised) {
1939
+ active = workspaces[0]?.slug;
1940
+ }
1941
+ const next = { ...config, workspaces };
1942
+ if (active !== void 0) next.active = active;
1943
+ else delete next.active;
1944
+ return next;
1945
+ }
1946
+ function setActiveWorkspace(config, slug) {
1947
+ const sanitised = sanitizeSlug(slug);
1948
+ if (!config.workspaces.some((w) => w.slug === sanitised)) {
1949
+ throw new Error(
1950
+ `setActiveWorkspace: no workspace registered with slug "${sanitised}". Run \`agentproto workspace add <path> --slug ${sanitised}\` first.`
1951
+ );
1952
+ }
1953
+ return { ...config, active: sanitised };
1954
+ }
1746
1955
  function findWorkspace(config, slug) {
1747
1956
  return config.workspaces.find((w) => w.slug === sanitizeSlug(slug));
1748
1957
  }
@@ -1809,7 +2018,7 @@ function sanitizeAcpAgents(raw, target) {
1809
2018
  return Object.keys(out).length > 0 ? out : void 0;
1810
2019
  }
1811
2020
  async function loadConfig(path) {
1812
- const target = CONFIG_FILE_PATH();
2021
+ const target = path ?? CONFIG_FILE_PATH();
1813
2022
  try {
1814
2023
  const raw = await promises.readFile(target, "utf8");
1815
2024
  const parsed = JSON.parse(raw);
@@ -1900,6 +2109,7 @@ function resolveAuthSpec(input) {
1900
2109
  const subCredAvailable = input.subscriptionCredential !== void 0;
1901
2110
  const apiCredAvailable = input.apiKeyConfigCredential !== void 0 || input.apiKeyStoreCredential !== void 0;
1902
2111
  let mode;
2112
+ let neitherConfigured = false;
1903
2113
  if (input.requestedMode) {
1904
2114
  if (input.requestedMode === "subscription" && !supportsSub) {
1905
2115
  throw new AuthResolutionError(
@@ -1909,7 +2119,11 @@ function resolveAuthSpec(input) {
1909
2119
  mode = input.requestedMode;
1910
2120
  } else {
1911
2121
  const preference = supportsSub ? ["subscription", "api-key"] : ["api-key"];
1912
- mode = preference.find((m) => m === "subscription" ? subCredAvailable : apiCredAvailable) ?? preference[0];
2122
+ const available = preference.find(
2123
+ (m) => m === "subscription" ? subCredAvailable : apiCredAvailable
2124
+ );
2125
+ mode = available ?? preference[0];
2126
+ neitherConfigured = available === void 0;
1913
2127
  }
1914
2128
  let setEnv;
1915
2129
  let credential;
@@ -1945,7 +2159,8 @@ function resolveAuthSpec(input) {
1945
2159
  setEnv,
1946
2160
  unsetEnv,
1947
2161
  explicit: input.explicit,
1948
- enforce
2162
+ enforce,
2163
+ ...neitherConfigured ? { neitherConfigured } : {}
1949
2164
  };
1950
2165
  const echo = {
1951
2166
  provider,
@@ -2209,6 +2424,8 @@ function getMcpCredentialDeps() {
2209
2424
  // src/sandbox-agent-session-proxy.ts
2210
2425
  var MAX_POLL_MS = 49e3;
2211
2426
  var MAX_OUTPUT_LINES = 500;
2427
+ var MAX_CONSECUTIVE_POLL_FAILURES = 6;
2428
+ var POLL_RETRY_DELAY_MS = 5e3;
2212
2429
  function extractPromptText(message) {
2213
2430
  if (typeof message === "string") return message;
2214
2431
  if (message && typeof message === "object" && "text" in message) {
@@ -2226,29 +2443,63 @@ function createSandboxAgentSessionProxy(opts) {
2226
2443
  async *send(message) {
2227
2444
  const prompt = extractPromptText(message);
2228
2445
  lastPrompt = prompt;
2229
- await host.prompt(remoteSessionId, prompt);
2230
2446
  let seenLength = 0;
2231
- for (; ; ) {
2232
- const result = await host.waitForAny([remoteSessionId], {
2233
- event: "any",
2234
- timeoutMs: MAX_POLL_MS
2235
- });
2236
- if (result.timedOut) continue;
2237
- const tail = await host.output(remoteSessionId, MAX_OUTPUT_LINES);
2238
- if (tail.length > seenLength) {
2239
- yield { kind: "text-delta", text: tail.slice(seenLength) };
2447
+ try {
2448
+ await host.prompt(remoteSessionId, prompt);
2449
+ let consecutivePollFailures = 0;
2450
+ for (; ; ) {
2451
+ let result;
2452
+ try {
2453
+ result = await host.waitForAny([remoteSessionId], {
2454
+ event: "any",
2455
+ timeoutMs: MAX_POLL_MS
2456
+ });
2457
+ consecutivePollFailures = 0;
2458
+ } catch (pollErr) {
2459
+ consecutivePollFailures++;
2460
+ if (consecutivePollFailures >= MAX_CONSECUTIVE_POLL_FAILURES) {
2461
+ throw new Error(
2462
+ `sandbox proxy: ${consecutivePollFailures} consecutive poll failures against the box daemon (session "${remoteSessionId}") \u2014 giving up. Last error: ${pollErr instanceof Error ? pollErr.message : String(pollErr)}`
2463
+ );
2464
+ }
2465
+ await new Promise((resolve14) => setTimeout(resolve14, POLL_RETRY_DELAY_MS));
2466
+ continue;
2467
+ }
2468
+ if (result.timedOut) continue;
2469
+ let tail;
2470
+ try {
2471
+ tail = await host.output(remoteSessionId, MAX_OUTPUT_LINES);
2472
+ } catch {
2473
+ try {
2474
+ tail = await host.output(remoteSessionId, MAX_OUTPUT_LINES);
2475
+ } catch {
2476
+ tail = void 0;
2477
+ }
2478
+ }
2479
+ if (tail !== void 0 && tail.length > seenLength) {
2480
+ yield { kind: "text-delta", text: tail.slice(seenLength) };
2481
+ }
2482
+ if (tail !== void 0) seenLength = tail.length;
2483
+ if (result.event === "exited") {
2484
+ throw new Error(
2485
+ `sandbox proxy: remote session "${remoteSessionId}" exited \u2014 the box's own agentproto daemon ended this session (crash, OOM, or an out-of-band kill).`
2486
+ );
2487
+ }
2488
+ yield {
2489
+ kind: "turn-end",
2490
+ reason: result.event === "awaiting-input" ? "awaiting-input" : "completed"
2491
+ };
2492
+ return;
2240
2493
  }
2241
- seenLength = tail.length;
2242
- if (result.event === "exited") {
2243
- throw new Error(
2244
- `sandbox proxy: remote session "${remoteSessionId}" exited \u2014 the box's own agentproto daemon ended this session (crash, OOM, or an out-of-band kill).`
2245
- );
2494
+ } catch (err) {
2495
+ try {
2496
+ const tail = await host.output(remoteSessionId, MAX_OUTPUT_LINES);
2497
+ if (tail.length > seenLength) {
2498
+ yield { kind: "text-delta", text: tail.slice(seenLength) };
2499
+ }
2500
+ } catch {
2246
2501
  }
2247
- yield {
2248
- kind: "turn-end",
2249
- reason: result.event === "awaiting-input" ? "awaiting-input" : "completed"
2250
- };
2251
- return;
2502
+ throw err;
2252
2503
  }
2253
2504
  },
2254
2505
  async cancel() {
@@ -2271,6 +2522,50 @@ function createSandboxAgentSessionProxy(opts) {
2271
2522
  };
2272
2523
  }
2273
2524
 
2525
+ // src/worktree-isolation.ts
2526
+ var WORKTREE_ISOLATION_ENV = "AGENTPROTO_WORKTREES_ISOLATION";
2527
+ var DEFAULT_WORKTREE_ISOLATION = "on-request";
2528
+ function normalizeWorktreeField(field) {
2529
+ if (field === void 0 || field === false) return void 0;
2530
+ if (field === true) return {};
2531
+ const request = {};
2532
+ if (field.slug !== void 0) request.slug = field.slug;
2533
+ if (field.base !== void 0) request.base = field.base;
2534
+ return request;
2535
+ }
2536
+ function decideWorktreeIsolation(input) {
2537
+ const request = normalizeWorktreeField(input.field);
2538
+ if (input.depth > 0) return { action: "spawn-in-place" };
2539
+ switch (input.mode) {
2540
+ case "never":
2541
+ if (request !== void 0) {
2542
+ return {
2543
+ action: "reject",
2544
+ message: `agent_start: \`worktree\` was requested but this daemon's \`worktrees.isolation\` policy is set to "never". Remove the \`worktree\` field, or change the policy (config \`worktrees.isolation\` / env ${WORKTREE_ISOLATION_ENV}).`
2545
+ };
2546
+ }
2547
+ return { action: "spawn-in-place" };
2548
+ case "on-request":
2549
+ return request !== void 0 ? { action: "provision", request } : { action: "spawn-in-place" };
2550
+ case "always":
2551
+ return { action: "provision", request: request ?? {} };
2552
+ }
2553
+ }
2554
+ function parseWorktreeIsolationMode(raw) {
2555
+ return raw === "always" || raw === "on-request" || raw === "never" ? raw : void 0;
2556
+ }
2557
+ async function loadWorktreeIsolation(loadCfg = loadConfig) {
2558
+ const fromEnv = parseWorktreeIsolationMode(process.env[WORKTREE_ISOLATION_ENV]);
2559
+ if (fromEnv) return fromEnv;
2560
+ try {
2561
+ const cfg = await loadCfg();
2562
+ const fromCfg = parseWorktreeIsolationMode(cfg.worktrees?.isolation);
2563
+ if (fromCfg) return fromCfg;
2564
+ } catch {
2565
+ }
2566
+ return DEFAULT_WORKTREE_ISOLATION;
2567
+ }
2568
+
2274
2569
  // src/session-spawn.ts
2275
2570
  var SPAWN_CLAIM_WINDOW_MS = 3e4;
2276
2571
  var spawnClaimsByRegistry = /* @__PURE__ */ new WeakMap();
@@ -2298,8 +2593,20 @@ async function spawnAgentSession(deps2, input) {
2298
2593
  callerScope,
2299
2594
  webhookNotifier,
2300
2595
  loadDefaultsConfig,
2301
- resolveSandboxProvider: resolveSandboxProvider2
2596
+ resolveSandboxProvider: resolveSandboxProvider2,
2597
+ provisionWorktree,
2598
+ resolveWorktreeIsolation
2302
2599
  } = deps2;
2600
+ const explicitCwd = input.cwd !== void 0;
2601
+ const explicitWorkspaceSlug = input.workspaceSlug !== void 0;
2602
+ const childDepth = callerScope ? callerScope.depth + 1 : 0;
2603
+ if (childDepth === 0 && !explicitCwd && !explicitWorkspaceSlug && normalizeWorktreeField(input.worktree) !== void 0) {
2604
+ return {
2605
+ ok: false,
2606
+ code: "worktree_requires_explicit_repo",
2607
+ message: "agent_start: `worktree` isolation was requested but neither `cwd` nor `workspaceSlug` was passed \u2014 refusing to guess the base repo from the daemon's active workspace. Pass `cwd` (an explicit path inside the repo to worktree from) or `workspaceSlug` (a slug from `agentproto workspace list`) to `agent_start`."
2608
+ };
2609
+ }
2303
2610
  let cwd = input.cwd;
2304
2611
  let resolvedSlug = input.workspaceSlug;
2305
2612
  if (!cwd || !resolvedSlug) {
@@ -2336,7 +2643,6 @@ async function spawnAgentSession(deps2, input) {
2336
2643
  message: `agent_start: adapter "${input.adapter}" could not be resolved. If it was working a moment ago, something may be mid-rebuild \u2014 wait and retry. If it has never been installed, run \`agentproto install ${input.adapter}\` first.`
2337
2644
  };
2338
2645
  }
2339
- const childDepth = callerScope ? callerScope.depth + 1 : 0;
2340
2646
  const parentSessionId = callerScope?.ownerSessionId;
2341
2647
  if (callerScope) {
2342
2648
  if (childDepth > callerScope.maxDepth) {
@@ -2366,6 +2672,35 @@ async function spawnAgentSession(deps2, input) {
2366
2672
  };
2367
2673
  }
2368
2674
  }
2675
+ let worktreeRequest;
2676
+ if (input.sandbox === void 0) {
2677
+ const mode = resolveWorktreeIsolation ? await resolveWorktreeIsolation() : await loadWorktreeIsolation();
2678
+ const decision = decideWorktreeIsolation({
2679
+ mode,
2680
+ field: input.worktree,
2681
+ depth: childDepth
2682
+ });
2683
+ if (decision.action === "reject") {
2684
+ return { ok: false, code: "worktree_disabled", message: decision.message };
2685
+ }
2686
+ if (decision.action === "provision") {
2687
+ if (!explicitCwd && !explicitWorkspaceSlug) {
2688
+ return {
2689
+ ok: false,
2690
+ code: "worktree_requires_explicit_repo",
2691
+ message: "agent_start: `worktree` isolation was requested but neither `cwd` nor `workspaceSlug` was passed \u2014 refusing to guess the base repo from the daemon's active workspace. Pass `cwd` (an explicit path inside the repo to worktree from) or `workspaceSlug` (a slug from `agentproto workspace list`) to `agent_start`."
2692
+ };
2693
+ }
2694
+ if (!provisionWorktree) {
2695
+ return {
2696
+ ok: false,
2697
+ code: "worktree_provisioner_not_enabled",
2698
+ message: `agent_start: \`worktree\` isolation is required (by request or the "${mode}" policy) but this daemon has no worktree provisioner wired (createGateway needs \`provisionWorktree\`, injected by the CLI over @agentproto/worktree).`
2699
+ };
2700
+ }
2701
+ worktreeRequest = decision.request;
2702
+ }
2703
+ }
2369
2704
  const configDefaults = loadDefaultsConfig ? await loadDefaultsConfig() : (await loadConfig()).defaults;
2370
2705
  const roleRegistry = deps2.loadRoleRegistry ? await deps2.loadRoleRegistry() : await loadDefaultRoleRegistry();
2371
2706
  let role;
@@ -2443,9 +2778,10 @@ async function spawnAgentSession(deps2, input) {
2443
2778
  options: input.options,
2444
2779
  auth: input.auth
2445
2780
  });
2781
+ const hasGatewayBaseUrlOption = typeof spawnDefaults.options?.base_url === "string" && spawnDefaults.options.base_url.length > 0;
2446
2782
  let authSpec;
2447
2783
  let authEcho;
2448
- if (resolved && input.sandbox === void 0 && resolved.authDescriptor) {
2784
+ if (resolved && input.sandbox === void 0 && resolved.authDescriptor && !hasGatewayBaseUrlOption) {
2449
2785
  const authModel = input.model ?? resolved.defaultModel;
2450
2786
  const pinnedProvider = spawnDefaults.auth.provider;
2451
2787
  const resolvedProvider = pinnedProvider ?? resolved.authDescriptor.provider ?? (authModel ? getModelProvider(authModel) : void 0);
@@ -2476,6 +2812,12 @@ async function spawnAgentSession(deps2, input) {
2476
2812
  }
2477
2813
  throw err;
2478
2814
  }
2815
+ if (authSpec && authSpec.enforce === "always" && authSpec.credential === void 0 && !spawnDefaults.auth.explicit && resolvedProvider !== void 0) {
2816
+ const ignored = await (0, providers_store_exports.getProviderKey)(resolvedProvider);
2817
+ if (ignored !== void 0) {
2818
+ authSpec = { ...authSpec, ignoredApiKeyInStore: true };
2819
+ }
2820
+ }
2479
2821
  }
2480
2822
  const effectiveOptions = normalizeSkillsOption(
2481
2823
  spawnDefaults.skills,
@@ -2520,6 +2862,24 @@ ${input.prompt}` : input.prompt;
2520
2862
  return result;
2521
2863
  };
2522
2864
  try {
2865
+ if (worktreeRequest && provisionWorktree) {
2866
+ let outcome;
2867
+ try {
2868
+ outcome = await provisionWorktree({
2869
+ cwd,
2870
+ ...worktreeRequest.slug ? { slug: worktreeRequest.slug } : {},
2871
+ ...worktreeRequest.base ? { base: worktreeRequest.base } : {},
2872
+ ...input.label ? { labelHint: input.label } : {}
2873
+ });
2874
+ } catch (err) {
2875
+ return finish({
2876
+ ok: false,
2877
+ code: "worktree_provision_failed",
2878
+ message: `agent_start: worktree provisioning failed \u2014 ${err instanceof Error ? err.message : String(err)}`
2879
+ });
2880
+ }
2881
+ if (outcome.isolated) cwd = outcome.cwd;
2882
+ }
2523
2883
  const resolvedMcpServers = await resolveMcpCredentialHeaders(mcpServers);
2524
2884
  let liveSessionId;
2525
2885
  let agentSession;
@@ -2544,7 +2904,14 @@ ${input.prompt}` : input.prompt;
2544
2904
  ...resolvedMcpServers ? { mcpServers: resolvedMcpServers } : {},
2545
2905
  ...input.model ? { model: input.model } : {},
2546
2906
  ...input.effort ? { effort: input.effort } : {},
2547
- ...input.label ? { label: input.label } : {}
2907
+ ...input.label ? { label: input.label } : {},
2908
+ // Explicit billing-auth for the box's OWN agent_start. A fresh box
2909
+ // has no ~/.agentproto/config.json and claude-code never inherits
2910
+ // subscription auth from the shell env — the credential must ride the
2911
+ // spawn call itself. Only the caller's EXPLICIT `auth` is forwarded
2912
+ // (host config defaults stay host-scoped; the box resolves its own
2913
+ // defaults otherwise).
2914
+ ...input.auth ? { auth: input.auth } : {}
2548
2915
  });
2549
2916
  if (!booted.ok) return booted;
2550
2917
  agentSession = booted.agentSession;
@@ -2578,6 +2945,7 @@ ${input.prompt}` : input.prompt;
2578
2945
  agentSession,
2579
2946
  adapterSlug: input.adapter,
2580
2947
  ...input.model ? { model: input.model } : {},
2948
+ ...input.mode ? { mode: input.mode } : {},
2581
2949
  ...input.wait && effectivePrompt ? {} : effectivePrompt ? { initialPrompt: effectivePrompt } : {},
2582
2950
  ...input.label ? { label: input.label } : {},
2583
2951
  ...resolvedMcpServers ? { mcpServers: resolvedMcpServers } : {},
@@ -2728,7 +3096,8 @@ async function bootSandboxAgentSession(opts) {
2728
3096
  ...opts.mcpServers ? { mcpServers: toMcpServerMounts(opts.mcpServers) } : {},
2729
3097
  ...opts.model ? { model: opts.model } : {},
2730
3098
  ...opts.effort ? { effort: opts.effort } : {},
2731
- ...opts.label ? { label: opts.label } : {}
3099
+ ...opts.label ? { label: opts.label } : {},
3100
+ ...opts.auth ? { auth: opts.auth } : {}
2732
3101
  });
2733
3102
  remoteSessionId = remoteDesc.id;
2734
3103
  } catch (err) {
@@ -2759,6 +3128,60 @@ async function resolveSandboxSecret(slug) {
2759
3128
  return null;
2760
3129
  }
2761
3130
  }
3131
+
3132
+ // src/canonical-posture.ts
3133
+ var POSTURE_PREAMBLES = {
3134
+ plan: "You are in PLAN mode. Investigate and propose a concrete plan for the user to approve; do NOT edit files, run commands, or make any other changes until the plan is explicitly approved. Reading and searching are fine.",
3135
+ "accept-edits": "You are in ACCEPT-EDITS mode. File edits are auto-approved, so apply them directly without pausing for confirmation on each one; commands and other actions still warrant the usual care.",
3136
+ bypass: "You are in BYPASS-PERMISSIONS mode. No approval prompts will interrupt you \u2014 every file edit and command runs without confirmation. Be deliberate and careful: there is no safety prompt between you and a destructive action.",
3137
+ "read-only": "You are in READ-ONLY mode. You may read, search, and analyze, but you must NOT edit files, run commands that mutate state, or make any other changes. Answer and advise only."
3138
+ };
3139
+ var POSTURE_NATIVE_ALIASES = {
3140
+ default: ["default", "build", "normal", "standard"],
3141
+ plan: ["plan", "planning", "plan-mode"],
3142
+ "accept-edits": ["accept-edits", "auto-accept", "auto-edit"],
3143
+ bypass: ["bypass", "bypass-permissions", "full-access", "yolo", "dangerously-skip-permissions"],
3144
+ "read-only": ["read-only", "chat", "ask"]
3145
+ };
3146
+ var CANONICAL_POSTURES = Object.keys(
3147
+ POSTURE_NATIVE_ALIASES
3148
+ );
3149
+ var CANONICAL_POSTURE_SET = new Set(CANONICAL_POSTURES);
3150
+ function isCanonicalPosture(value) {
3151
+ return CANONICAL_POSTURE_SET.has(value);
3152
+ }
3153
+ function parsePostureInput(raw) {
3154
+ return isCanonicalPosture(raw) ? raw : { harnessModeId: raw };
3155
+ }
3156
+ function normalizeModeId(id) {
3157
+ return id.toLowerCase().replace(/[^a-z0-9]/g, "");
3158
+ }
3159
+ var CANONICAL_BY_NORMALIZED_ALIAS = (() => {
3160
+ const index = /* @__PURE__ */ new Map();
3161
+ for (const [posture, aliases] of Object.entries(POSTURE_NATIVE_ALIASES)) {
3162
+ for (const alias of aliases) index.set(normalizeModeId(alias), posture);
3163
+ }
3164
+ return index;
3165
+ })();
3166
+ function canonicalForModeId(modeId) {
3167
+ return CANONICAL_BY_NORMALIZED_ALIAS.get(normalizeModeId(modeId));
3168
+ }
3169
+ function findNativeMode(posture, availableModes) {
3170
+ if (typeof posture === "object") {
3171
+ const target = normalizeModeId(posture.harnessModeId);
3172
+ return availableModes.find((mode) => normalizeModeId(mode.id) === target);
3173
+ }
3174
+ return availableModes.find((mode) => canonicalForModeId(mode.id) === posture);
3175
+ }
3176
+ function resolvePosture(posture, availableModes) {
3177
+ const native = findNativeMode(posture, availableModes);
3178
+ if (native) return { kind: "native", mode: native };
3179
+ if (typeof posture === "object") {
3180
+ return { kind: "unavailable", requestedModeId: posture.harnessModeId };
3181
+ }
3182
+ if (posture === "default") return { kind: "noop", posture: "default" };
3183
+ return { kind: "prompt", posture, preamble: POSTURE_PREAMBLES[posture] };
3184
+ }
2762
3185
  var sandboxSpecWithReuseSchema = z.object({
2763
3186
  ...SandboxSpecSchema.shape,
2764
3187
  reuse: z.string().min(1).optional().describe(
@@ -2799,12 +3222,15 @@ function registerAgentTools(server, opts) {
2799
3222
  registry,
2800
3223
  resolveAgentAdapter,
2801
3224
  listAgentAdapters,
3225
+ listCatalogModels,
2802
3226
  buildOrchestratorMcp,
2803
3227
  callerScope,
2804
3228
  webhookNotifier,
2805
3229
  daemonMcpUrl,
2806
3230
  loadRoleRegistry: loadRoleRegistry2,
2807
- resolveSandboxProvider: resolveSandboxProvider2
3231
+ resolveSandboxProvider: resolveSandboxProvider2,
3232
+ provisionWorktree,
3233
+ resolveWorktreeIsolation
2808
3234
  } = opts;
2809
3235
  server.tool(
2810
3236
  "agent_start",
@@ -2919,6 +3345,22 @@ function registerAgentTools(server, opts) {
2919
3345
  ])
2920
3346
  ).optional().describe(
2921
3347
  "Run this session inside a sandbox instead of on the host \u2014 pass a provider slug (see `list_sandbox_providers`) or an inline AIP-36 SandboxDefinition object. The daemon boots the sandbox, spawns `adapter` on the box's OWN agentproto daemon, and proxies the conversation back onto this session \u2014 `agent_prompt`/`agent_output`/`agent_kill` behave exactly as they do for a local spawn, and the transcript stays readable here even after the box is torn down. Omit to run locally (default). Pass an inline spec with `reuse: \"<sandboxId>\"` (from a prior session's `sandboxId`) to reconnect to an existing box instead \u2014 by default such a box is PAUSED (not killed) on session close so it stays reusable; set `lifecycle.destroy_on` to always kill it."
3348
+ ),
3349
+ worktree: jsonTolerant(
3350
+ z.union([
3351
+ mcpBool,
3352
+ z.object({
3353
+ slug: z.string().regex(
3354
+ /^[a-z0-9][a-z0-9-]*$/,
3355
+ "slug must be lowercase kebab-case (letters, digits, hyphens)"
3356
+ ).optional().describe(
3357
+ "Pin the worktree's slug (names its branch `wt/<slug>` and its directory). Omit to auto-mint a collision-free one from the label."
3358
+ ),
3359
+ base: z.string().min(1).optional().describe("Git ref the worktree branch is cut from. Default 'origin/main'.")
3360
+ }).strict()
3361
+ ])
3362
+ ).optional().describe(
3363
+ "Isolate this session in its OWN git worktree instead of spawning directly in `cwd` \u2014 so a parallel agent can't collide on the working tree. `true` provisions a worktree on a fresh branch `wt/<slug>` cut from origin/main (slug auto-minted from `label`); pass `{ slug, base }` to pin either. The daemon boots the worktree (git worktree add + the repo's agentproto.json setup hooks) and spawns `adapter` THERE; the session's cwd, and every path it edits, live inside the worktree. Honoured only for a ROOT spawn (a spawn made THROUGH an orchestrator inherits its parent's tree \u2014 no second worktree) and only when `cwd` is inside a git repo (nothing to isolate otherwise \u21D2 spawns plain, no error). The daemon's `worktrees.isolation` policy may force this ON for every root spawn (`always`) or OFF (`never`, which REJECTS an explicit `worktree`). Ignored for a `sandbox` spawn (the box already isolates). The worktree is NOT auto-removed on session close \u2014 it holds the agent's work; tear it down with `agentproto worktree rm|archive|gc`."
2922
3364
  )
2923
3365
  },
2924
3366
  async (input) => {
@@ -2942,7 +3384,9 @@ function registerAgentTools(server, opts) {
2942
3384
  callerScope,
2943
3385
  webhookNotifier,
2944
3386
  loadRoleRegistry: loadRoleRegistry2,
2945
- resolveSandboxProvider: resolveSandboxProvider2
3387
+ resolveSandboxProvider: resolveSandboxProvider2,
3388
+ ...provisionWorktree ? { provisionWorktree } : {},
3389
+ ...resolveWorktreeIsolation ? { resolveWorktreeIsolation } : {}
2946
3390
  },
2947
3391
  input
2948
3392
  );
@@ -3091,7 +3535,7 @@ function registerAgentTools(server, opts) {
3091
3535
  if (callerScope) {
3092
3536
  const subtree = collectSubtree(
3093
3537
  callerScope.ownerSessionId,
3094
- registry.list()
3538
+ registry.list({ includeArchived: true })
3095
3539
  );
3096
3540
  if (!subtree.has(sessionId)) {
3097
3541
  return {
@@ -3159,35 +3603,142 @@ function registerAgentTools(server, opts) {
3159
3603
  }
3160
3604
  );
3161
3605
  server.tool(
3162
- "agent_sessions_list",
3163
- "List agent-CLI sessions tracked by the daemon. Equivalent to `session_list({kind: 'agent-cli'})`. Each entry includes `kind`, `status`, age, etc. Use this when you only want the agent-CLI subset.",
3606
+ "agent_set_model",
3607
+ "Switch the model on a LIVE agent-cli session without restarting it \u2014 the mid-session counterpart to picking a model at `agent_start` time. Dispatches on the adapter's own apply strategy: a session whose adapter selects models via ACP session config or a `/model` control turn switches live; one that takes its model as a spawn-time CLI argument (e.g. codex) can't, and reports `{applied:false, reason:\"requires-restart\"}` instead of failing. Never throws on a rejected switch \u2014 check `applied` in the result.",
3164
3608
  {
3165
- kind: z.enum(["terminal", "agent-cli", "command", "all"]).optional().describe(
3166
- "Optional override of the default `agent-cli` filter. `all` returns every kind."
3167
- ),
3168
- onlyAlive: z.boolean().optional().describe("When true, only running/starting sessions. Default false."),
3169
- status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive).")
3609
+ sessionId: sessionIdField,
3610
+ id: sessionIdAliasField,
3611
+ model: z.string().describe("Model id to switch to.")
3170
3612
  },
3171
3613
  async (input) => {
3172
- let rows = registry.list();
3173
- if (callerScope) {
3174
- const subtree = collectSubtree(callerScope.ownerSessionId, rows);
3175
- rows = rows.filter((s) => subtree.has(s.id));
3176
- }
3177
- const kind = input.kind ?? "agent-cli";
3178
- if (kind !== "all") {
3179
- rows = rows.filter((s) => s.kind === kind);
3180
- }
3181
- if (input.status) {
3182
- rows = rows.filter((s) => s.status === input.status);
3183
- } else if (input.onlyAlive) {
3184
- rows = rows.filter(
3185
- (s) => s.status === "running" || s.status === "starting"
3186
- );
3187
- }
3188
- return {
3189
- content: [
3190
- { type: "text", text: JSON.stringify({ sessions: rows }, null, 2) }
3614
+ const sessionId = resolveSessionIdArg(input);
3615
+ if (!sessionId) return missingSessionIdError("agent_set_model");
3616
+ try {
3617
+ const result = await registry.setModel(sessionId, input.model);
3618
+ return {
3619
+ content: [
3620
+ {
3621
+ type: "text",
3622
+ text: JSON.stringify({ ok: true, sessionId, ...result }, null, 2)
3623
+ }
3624
+ ]
3625
+ };
3626
+ } catch (err) {
3627
+ return {
3628
+ content: [
3629
+ {
3630
+ type: "text",
3631
+ text: `agent_set_model: ${err instanceof Error ? err.message : String(err)}`
3632
+ }
3633
+ ],
3634
+ isError: true
3635
+ };
3636
+ }
3637
+ }
3638
+ );
3639
+ server.tool(
3640
+ "agent_set_effort",
3641
+ "Switch the reasoning/compute budget (effort) on a LIVE agent-cli session without restarting it \u2014 the effort-axis counterpart to `agent_set_model`. Applied via the adapter's ACP session config. Effort is model-dependent: the same label means a different budget across models and some labels are model-gated (opus offers `ultracode`, haiku doesn't), so a label the current model rejects reports `{applied:false, reason}` instead of failing. Never throws on a rejected switch \u2014 check `applied` in the result.",
3642
+ {
3643
+ sessionId: sessionIdField,
3644
+ id: sessionIdAliasField,
3645
+ effort: z.string().describe(
3646
+ "Effort label to switch to (e.g. low/medium/high/xhigh/max/ultracode; the accepted set is model-dependent)."
3647
+ )
3648
+ },
3649
+ async (input) => {
3650
+ const sessionId = resolveSessionIdArg(input);
3651
+ if (!sessionId) return missingSessionIdError("agent_set_effort");
3652
+ try {
3653
+ const result = await registry.setEffort(sessionId, input.effort);
3654
+ return {
3655
+ content: [
3656
+ {
3657
+ type: "text",
3658
+ text: JSON.stringify({ ok: true, sessionId, ...result }, null, 2)
3659
+ }
3660
+ ]
3661
+ };
3662
+ } catch (err) {
3663
+ return {
3664
+ content: [
3665
+ {
3666
+ type: "text",
3667
+ text: `agent_set_effort: ${err instanceof Error ? err.message : String(err)}`
3668
+ }
3669
+ ],
3670
+ isError: true
3671
+ };
3672
+ }
3673
+ }
3674
+ );
3675
+ server.tool(
3676
+ "agent_set_posture",
3677
+ 'Switch the posture (what the agent may DO \u2014 plan / accept-edits / bypass / read-only, or a raw harness mode id) on a LIVE agent-cli session. When the posture maps to a NATIVE mode the harness advertises, it switches live (`applied:true`). When there is no native mode (the posture would have to be prompt-injected or applied at spawn), it is NOT forced live \u2014 the result is `{applied:false, reason:"requires-restart"}` so the caller can re-apply it through a session restart instead. Never throws on a rejected switch \u2014 check `applied`.',
3678
+ {
3679
+ sessionId: sessionIdField,
3680
+ id: sessionIdAliasField,
3681
+ posture: z.string().describe(
3682
+ "Posture to switch to: a canonical value (default/plan/accept-edits/bypass/read-only) or a raw harness mode id."
3683
+ )
3684
+ },
3685
+ async (input) => {
3686
+ const sessionId = resolveSessionIdArg(input);
3687
+ if (!sessionId) return missingSessionIdError("agent_set_posture");
3688
+ try {
3689
+ const result = await registry.setPosture(sessionId, parsePostureInput(input.posture));
3690
+ return {
3691
+ content: [
3692
+ {
3693
+ type: "text",
3694
+ text: JSON.stringify({ ok: true, sessionId, ...result }, null, 2)
3695
+ }
3696
+ ]
3697
+ };
3698
+ } catch (err) {
3699
+ return {
3700
+ content: [
3701
+ {
3702
+ type: "text",
3703
+ text: `agent_set_posture: ${err instanceof Error ? err.message : String(err)}`
3704
+ }
3705
+ ],
3706
+ isError: true
3707
+ };
3708
+ }
3709
+ }
3710
+ );
3711
+ server.tool(
3712
+ "agent_sessions_list",
3713
+ "List agent-CLI sessions tracked by the daemon. Equivalent to `session_list({kind: 'agent-cli'})`. Each entry includes `kind`, `status`, age, etc. Use this when you only want the agent-CLI subset.",
3714
+ {
3715
+ kind: z.enum(["terminal", "agent-cli", "command", "all"]).optional().describe(
3716
+ "Optional override of the default `agent-cli` filter. `all` returns every kind."
3717
+ ),
3718
+ onlyAlive: z.boolean().optional().describe("When true, only running/starting sessions. Default false."),
3719
+ status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive).")
3720
+ },
3721
+ async (input) => {
3722
+ let rows = registry.list({ includeArchived: true });
3723
+ if (callerScope) {
3724
+ const subtree = collectSubtree(callerScope.ownerSessionId, rows);
3725
+ rows = rows.filter((s) => subtree.has(s.id));
3726
+ }
3727
+ rows = rows.filter((s) => !s.archived);
3728
+ const kind = input.kind ?? "agent-cli";
3729
+ if (kind !== "all") {
3730
+ rows = rows.filter((s) => s.kind === kind);
3731
+ }
3732
+ if (input.status) {
3733
+ rows = rows.filter((s) => s.status === input.status);
3734
+ } else if (input.onlyAlive) {
3735
+ rows = rows.filter(
3736
+ (s) => s.status === "running" || s.status === "starting"
3737
+ );
3738
+ }
3739
+ return {
3740
+ content: [
3741
+ { type: "text", text: JSON.stringify({ sessions: rows }, null, 2) }
3191
3742
  ]
3192
3743
  };
3193
3744
  }
@@ -3226,6 +3777,50 @@ function registerAgentTools(server, opts) {
3226
3777
  }
3227
3778
  }
3228
3779
  );
3780
+ server.tool(
3781
+ "catalog_models",
3782
+ "Read-only vendor/product/route catalog (SPEC \xA75) \u2014 every model this host can reach, widened beyond any one adapter's model list via OpenRouter/Requesty/HuggingFace routing, with a profile-aware `runnable` flag per route. Use before `agent_start` to see what's actually spawnable given the auth profiles configured on this host.",
3783
+ {
3784
+ adapter: z.string().optional().describe("Keep only routes reachable via this adapter slug."),
3785
+ vendor: z.string().optional().describe("Keep only this vendor's entry."),
3786
+ route: z.string().optional().describe("Keep only routes with this route id."),
3787
+ runnableOnly: mcpBool.optional().describe("Drop every route with runnable:false.")
3788
+ },
3789
+ async ({ adapter, vendor, route, runnableOnly }) => {
3790
+ if (!listCatalogModels) {
3791
+ return {
3792
+ content: [
3793
+ {
3794
+ type: "text",
3795
+ text: "catalog_models is not enabled \u2014 the daemon was started without a catalog lister. Wire `buildCatalogModels` via `createGateway({ listCatalogModels })`."
3796
+ }
3797
+ ],
3798
+ isError: true
3799
+ };
3800
+ }
3801
+ try {
3802
+ const catalog = await listCatalogModels({
3803
+ ...adapter ? { adapter } : {},
3804
+ ...vendor ? { vendor } : {},
3805
+ ...route ? { route } : {},
3806
+ ...runnableOnly ? { runnableOnly: true } : {}
3807
+ });
3808
+ return {
3809
+ content: [{ type: "text", text: JSON.stringify(catalog, null, 2) }]
3810
+ };
3811
+ } catch (err) {
3812
+ return {
3813
+ content: [
3814
+ {
3815
+ type: "text",
3816
+ text: `catalog_models failed: ${err instanceof Error ? err.message : String(err)}`
3817
+ }
3818
+ ],
3819
+ isError: true
3820
+ };
3821
+ }
3822
+ }
3823
+ );
3229
3824
  server.tool(
3230
3825
  "role_list",
3231
3826
  "Enumerate every spawn-time role known to the daemon \u2014 the two built-ins (executor, supervisor) plus any custom role installed as a role pack. Read-only: pure visibility into the same registry `agent_start`'s `role` field and privilege-lattice spawn gate use \u2014 this tool never itself grants or denies a spawn. Use before `agent_start` with `orchestrator` to discover which roles this session may in turn spawn.",
@@ -3537,151 +4132,9 @@ function stringifyValues(raw) {
3537
4132
  }
3538
4133
  return out;
3539
4134
  }
3540
- function claudeCodeProjectDir(cwd) {
3541
- const encoded = cwd.replace(/\//g, "-");
3542
- return resolve(homedir(), ".claude", "projects", encoded);
3543
- }
3544
- function extractFirstText(content) {
3545
- if (typeof content === "string") {
3546
- const t = content.trim();
3547
- return t || void 0;
3548
- }
3549
- if (Array.isArray(content)) {
3550
- for (const block of content) {
3551
- if (block && typeof block === "object" && block.type === "text" && typeof block.text === "string") {
3552
- const t = block.text.trim();
3553
- if (t) return t;
3554
- }
3555
- }
3556
- }
3557
- return void 0;
3558
- }
3559
- async function scanClaudeJsonl(filePath) {
3560
- const stream = createReadStream(filePath, { encoding: "utf8" });
3561
- const rl = createInterface({ input: stream, crlfDelay: Infinity });
3562
- let startedAt;
3563
- let lastActivityAt;
3564
- let messageCount = 0;
3565
- let preview2;
3566
- let lastWriter;
3567
- for await (const line of rl) {
3568
- const trimmed = line.trim();
3569
- if (!trimmed) continue;
3570
- let entry;
3571
- try {
3572
- entry = JSON.parse(trimmed);
3573
- } catch {
3574
- continue;
3575
- }
3576
- if (typeof entry.timestamp === "string") {
3577
- if (!startedAt) startedAt = entry.timestamp;
3578
- lastActivityAt = entry.timestamp;
3579
- }
3580
- if (typeof entry.entrypoint === "string") {
3581
- lastWriter = entry.entrypoint;
3582
- }
3583
- if (entry.type === "user" || entry.type === "assistant") {
3584
- messageCount += 1;
3585
- if (preview2 === void 0 && entry.type === "user") {
3586
- const text6 = extractFirstText(entry.message?.content);
3587
- if (text6 !== void 0) {
3588
- preview2 = text6.length > 120 ? text6.slice(0, 120) : text6;
3589
- }
3590
- }
3591
- }
3592
- }
3593
- return { startedAt, lastActivityAt, messageCount, preview: preview2, lastWriter };
3594
- }
3595
- async function buildClaudeCandidate(filePath, conversationId) {
3596
- const scanned = await scanClaudeJsonl(filePath);
3597
- return { conversationId, ...scanned };
3598
- }
3599
- function claudeEntrypointFor(mode) {
3600
- return mode === "native" ? "cli" : "sdk-ts";
3601
- }
3602
- async function discoverClaudeCode(input) {
3603
- const { cwd, since, until, attachmentMode, expectedId } = input;
3604
- const dir = claudeCodeProjectDir(cwd);
3605
- if (expectedId) {
3606
- const filePath = join(dir, `${expectedId}.jsonl`);
3607
- try {
3608
- await promises.stat(filePath);
3609
- } catch {
3610
- return [];
3611
- }
3612
- return [await buildClaudeCandidate(filePath, expectedId)];
3613
- }
3614
- let entries;
3615
- try {
3616
- entries = await promises.readdir(dir);
3617
- } catch {
3618
- return [];
3619
- }
3620
- const jsonlFiles = entries.filter((e) => e.endsWith(".jsonl"));
3621
- if (jsonlFiles.length === 0) return [];
3622
- const sinceMs = since ? Date.parse(since) : NaN;
3623
- const untilMs = until ? Date.parse(until) : NaN;
3624
- const wantEntrypoint = attachmentMode ? claudeEntrypointFor(attachmentMode) : void 0;
3625
- const scored = [];
3626
- for (const f of jsonlFiles) {
3627
- const filePath = join(dir, f);
3628
- let mtimeMs;
3629
- try {
3630
- mtimeMs = (await promises.stat(filePath)).mtimeMs;
3631
- } catch {
3632
- continue;
3633
- }
3634
- if (Number.isFinite(sinceMs) && mtimeMs < sinceMs - 1e3) continue;
3635
- const conversationId = f.replace(/\.jsonl$/, "");
3636
- const candidate = await buildClaudeCandidate(filePath, conversationId);
3637
- if (Number.isFinite(untilMs) && candidate.startedAt !== void 0) {
3638
- const startedMs = Date.parse(candidate.startedAt);
3639
- if (Number.isFinite(startedMs) && startedMs > untilMs) continue;
3640
- }
3641
- if (wantEntrypoint !== void 0 && candidate.lastWriter !== void 0 && candidate.lastWriter !== wantEntrypoint) {
3642
- continue;
3643
- }
3644
- scored.push({ candidate, mtimeMs });
3645
- }
3646
- scored.sort((a, b) => b.mtimeMs - a.mtimeMs);
3647
- return scored.map((s) => s.candidate);
3648
- }
3649
- async function readClaudeCode(conversationId, cwd) {
3650
- const { exportClaudeCodeSession: exportClaudeCodeSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
3651
- return exportClaudeCodeSession2(conversationId, cwd);
3652
- }
3653
- async function discoverHermes(input) {
3654
- const { discoverHermesSessions: discoverHermesSessions2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
3655
- return discoverHermesSessions2(input.cwd, input.since, input.expectedId);
3656
- }
3657
- async function readHermes(conversationId) {
3658
- const { exportHermesSession: exportHermesSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
3659
- return exportHermesSession2(conversationId);
3660
- }
3661
- var CONVERSATION_STORES = {
3662
- "claude-code": {
3663
- storeAs: "claudeResumeId",
3664
- // Printed by claude on graceful exit when session persistence is on
3665
- // (default). Example: `claude --resume 0e483f81-1a44-4bec-9667-b37158450296`
3666
- outputHint: /claude\s+--resume\s+([0-9a-f-]{8,})/i,
3667
- attachArgv: (conversationId) => ["claude", "--resume", conversationId],
3668
- discover: discoverClaudeCode,
3669
- read: readClaudeCode
3670
- },
3671
- hermes: {
3672
- storeAs: "hermesResumeId",
3673
- // `hermes acp` is the ACP arm (bin_args in adapters/hermes/src/index.ts);
3674
- // `--resume SESSION --tui` is the native TUI resume path — same binary,
3675
- // different flags, unlike claude-code where the two arms are different
3676
- // binaries. Verified via `hermes --help`: `--resume SESSION, -r` =
3677
- // "Resume a previous session by ID or title", `--tui` = the real TUI.
3678
- attachArgv: (conversationId) => ["hermes", "--resume", conversationId, "--tui"],
3679
- discover: discoverHermes,
3680
- read: readHermes
3681
- }
3682
- };
3683
4135
 
3684
4136
  // src/resume-strategies.ts
4137
+ init_conversation_store();
3685
4138
  var claudeCodeStore = CONVERSATION_STORES["claude-code"];
3686
4139
  var RESUME_STRATEGIES = {
3687
4140
  "claude-code": {
@@ -3789,6 +4242,46 @@ function tokenizeCommand(s) {
3789
4242
  if (buf) out.push(buf);
3790
4243
  return out;
3791
4244
  }
4245
+ var RestartOverrideError = class extends Error {
4246
+ code = "restart_override_invalid";
4247
+ status = 400;
4248
+ constructor(message) {
4249
+ super(message);
4250
+ this.name = "RestartOverrideError";
4251
+ }
4252
+ };
4253
+ async function resolveAccessProfileFromStore(profileRef) {
4254
+ const profile = await getAuthProfile(profileRef);
4255
+ if (!profile) return void 0;
4256
+ const stored = await new KeychainStore().read({ path: profile.credentialRef });
4257
+ return { profile, ...stored?.value !== void 0 ? { credential: stored.value } : {} };
4258
+ }
4259
+ function methodToMode(method) {
4260
+ return method === "oauth-bearer" ? "subscription" : "api-key";
4261
+ }
4262
+ function directMethods(descriptor) {
4263
+ const methods = [];
4264
+ if (descriptor?.authSubscription) methods.push("oauth-bearer");
4265
+ if (descriptor?.provider) methods.push("api-key");
4266
+ return methods;
4267
+ }
4268
+ function eligibilityManifest(adapterSlug, descriptor, route, model) {
4269
+ const baseVendor = descriptor?.provider ?? (model ? getModelProvider(model) : void 0);
4270
+ const gateway = route?.gateway;
4271
+ const routeId = gateway ?? baseVendor;
4272
+ if (routeId === void 0) return void 0;
4273
+ const isDirect = baseVendor !== void 0 && routeId === baseVendor;
4274
+ const billedVendor2 = isDirect ? baseVendor : routeId;
4275
+ const methods = isDirect ? directMethods(descriptor) : ["api-key"];
4276
+ return {
4277
+ manifest: {
4278
+ id: adapterSlug,
4279
+ vendorByRoute: { [routeId]: billedVendor2 },
4280
+ methodsByRoute: { [routeId]: methods }
4281
+ },
4282
+ routeId
4283
+ };
4284
+ }
3792
4285
  async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {}) {
3793
4286
  const augmented = opts.forceAgentResume ? prev : await augmentWithFsResume(prev);
3794
4287
  const strategy = opts.forceAgentResume ? { kind: "agent", resumeSessionId: prev.adapterSessionId } : decideRestartStrategy(augmented);
@@ -3810,15 +4303,80 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
3810
4303
  let cwd = prev.cwd;
3811
4304
  if (!cwd) console.warn(`[restartAgentSession] no cwd on prior descriptor ${prev.id} \u2014 falling back to daemon's cwd ${process.cwd()}`);
3812
4305
  cwd ??= process.cwd();
4306
+ const overrides = opts.overrides ?? {};
4307
+ const effModel = overrides.model ?? prev.model;
4308
+ const effEffort = overrides.effort ?? prev.effort;
4309
+ const effRoute = overrides.route ?? prev.route;
4310
+ const effPosture = overrides.posture ?? prev.posture;
4311
+ const effContextProfile = overrides.contextProfile ?? prev.contextProfile;
4312
+ const effMode = overrides.mode ?? prev.mode;
4313
+ const accessOverrideRef = overrides.access?.profileRef;
3813
4314
  let authSpec;
3814
4315
  let authEcho;
3815
- if (resolved.authDescriptor) {
4316
+ let accessProfileEcho = prev.accessProfile;
4317
+ if (accessOverrideRef !== void 0) {
4318
+ const resolveProfile = opts.resolveAccessProfile ?? resolveAccessProfileFromStore;
4319
+ const found = await resolveProfile(accessOverrideRef);
4320
+ if (!found) {
4321
+ throw new RestartOverrideError(
4322
+ `restart access override: no auth profile "${accessOverrideRef}" found.`
4323
+ );
4324
+ }
4325
+ const { profile, credential } = found;
4326
+ if (!resolved.authDescriptor) {
4327
+ throw new RestartOverrideError(
4328
+ `restart access override: adapter "${adapterSlug}" presents no billing-auth, so profile "${profile.id}" cannot be attached.`
4329
+ );
4330
+ }
4331
+ const projected = eligibilityManifest(
4332
+ adapterSlug,
4333
+ resolved.authDescriptor,
4334
+ effRoute,
4335
+ effModel ?? resolved.defaultModel
4336
+ );
4337
+ if (!projected) {
4338
+ throw new RestartOverrideError(
4339
+ `restart access override: cannot resolve a billing vendor for adapter "${adapterSlug}" (no fixed provider, no model) \u2014 profile "${profile.id}" eligibility is unverifiable, refusing to spawn.`
4340
+ );
4341
+ }
4342
+ const { manifest, routeId } = projected;
4343
+ if (eligibleProfiles([profile], manifest, routeId).length === 0) {
4344
+ const billed = manifest.vendorByRoute[routeId];
4345
+ const methods = manifest.methodsByRoute[routeId] ?? [];
4346
+ throw new RestartOverrideError(
4347
+ `restart access override: profile "${profile.id}" (${profile.vendor}/${profile.method}) is not eligible for adapter "${adapterSlug}" on route "${routeId}" \u2014 that endpoint bills "${billed}" via [${methods.join(", ") || "no presentable methods"}]. Attach a profile whose vendor + method match the route.`
4348
+ );
4349
+ }
4350
+ const mode = methodToMode(profile.method);
4351
+ const result = resolveAuthSpec({
4352
+ descriptor: resolved.authDescriptor,
4353
+ ...effModel ? { model: effModel } : {},
4354
+ requestedProvider: profile.vendor,
4355
+ requestedMode: mode,
4356
+ // Attaching a named profile is always an EXPLICIT billing choice — so a
4357
+ // missing credential fails loud (driver `missing_auth_credential`) rather
4358
+ // than falling back to ambient env or the prior credential.
4359
+ explicit: true,
4360
+ ...mode === "subscription" && credential !== void 0 ? { subscriptionCredential: credential } : {},
4361
+ ...mode === "api-key" && credential !== void 0 ? { apiKeyConfigCredential: credential } : {}
4362
+ });
4363
+ if (result) {
4364
+ authSpec = result.spec;
4365
+ authEcho = result.echo;
4366
+ }
4367
+ accessProfileEcho = {
4368
+ profileRef: profile.id,
4369
+ ...profile.label !== void 0 ? { label: profile.label } : {},
4370
+ vendor: profile.vendor,
4371
+ method: profile.method
4372
+ };
4373
+ } else if (resolved.authDescriptor) {
3816
4374
  const configDefaults = opts.loadDefaultsConfig ? await opts.loadDefaultsConfig() : (await loadConfig()).defaults;
3817
4375
  const explicitAuthInput = prev.auth ? { mode: prev.auth.mode } : void 0;
3818
4376
  const spawnDefaults = resolveSpawnDefaults(configDefaults, adapterSlug, {
3819
4377
  auth: explicitAuthInput
3820
4378
  });
3821
- const authModel = prev.model ?? resolved.defaultModel;
4379
+ const authModel = effModel ?? resolved.defaultModel;
3822
4380
  const pinnedProvider = spawnDefaults.auth.provider;
3823
4381
  const resolvedProvider = pinnedProvider ?? resolved.authDescriptor.provider ?? (authModel ? getModelProvider(authModel) : void 0);
3824
4382
  const apiKeyStoreCredential = resolvedProvider && spawnDefaults.auth.explicit && spawnDefaults.auth.apiKeyCredential === void 0 ? await (0, providers_store_exports.getProviderKey)(resolvedProvider) : void 0;
@@ -3836,19 +4394,32 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
3836
4394
  authSpec = result.spec;
3837
4395
  authEcho = result.echo;
3838
4396
  }
4397
+ if (authSpec && authSpec.enforce === "always" && authSpec.credential === void 0 && !spawnDefaults.auth.explicit && resolvedProvider !== void 0) {
4398
+ const ignored = await (0, providers_store_exports.getProviderKey)(resolvedProvider);
4399
+ if (ignored !== void 0) {
4400
+ authSpec = { ...authSpec, ignoredApiKeyInStore: true };
4401
+ }
4402
+ }
3839
4403
  }
3840
4404
  const spawnWithResume = async (resumeSessionId) => {
3841
4405
  let liveSessionId;
3842
4406
  const agentSession = await resolved.startSession({
3843
4407
  cwd,
3844
4408
  ...resumeSessionId ? { resumeSessionId } : {},
3845
- ...prev.model ? { model: prev.model } : {},
4409
+ ...effModel ? { model: effModel } : {},
4410
+ ...effEffort ? { effort: effEffort } : {},
4411
+ // Legacy AIP-45 mode override only — the decomposed route/posture/
4412
+ // contextProfile spawn-env apply-path rides the driver's mode
4413
+ // decomposition (build step 2), out of scope here; they still round-trip
4414
+ // on the descriptor below.
4415
+ ...overrides.mode ? { mode: overrides.mode } : {},
3846
4416
  ...prev.mcpServers ? { mcpServers: prev.mcpServers } : {},
3847
4417
  ...authSpec ? { auth: authSpec } : {},
3848
4418
  onActivity: () => {
3849
4419
  if (liveSessionId) registry.pulseActivity(liveSessionId);
3850
4420
  }
3851
4421
  });
4422
+ const resumeVia = !resumeSessionId ? "" : opts.forceAgentResume ? "resumed via ACP" : describeResumePath(augmented);
3852
4423
  const desc2 = registry.spawnAgent({
3853
4424
  workspaceSlug: prev.workspaceSlug,
3854
4425
  cwd,
@@ -3856,7 +4427,17 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
3856
4427
  adapterSlug,
3857
4428
  ...prev.label ? { label: prev.label } : {},
3858
4429
  ...prev.mcpServers ? { mcpServers: prev.mcpServers } : {},
3859
- ...prev.model ? { model: prev.model } : {},
4430
+ ...effModel ? { model: effModel } : {},
4431
+ // Decomposed config-axis echoes (SPEC §3.7) — carried forward from `prev`
4432
+ // and overlaid with any override so every axis round-trips onto the fresh
4433
+ // descriptor and the picker re-opens on it (SPEC §3.8), even the axes
4434
+ // whose spawn-env apply-path isn't wired here yet (route/posture/context).
4435
+ ...effEffort ? { effort: effEffort } : {},
4436
+ ...effPosture !== void 0 ? { posture: effPosture } : {},
4437
+ ...effRoute ? { route: effRoute } : {},
4438
+ ...effContextProfile ? { contextProfile: effContextProfile } : {},
4439
+ ...accessProfileEcho ? { accessProfile: accessProfileEcho } : {},
4440
+ ...effMode ? { mode: effMode } : {},
3860
4441
  ...resolved.commandPreview ? { commandPreview: resolved.commandPreview } : {},
3861
4442
  // Verifiability echo (never the credential) — see the auth
3862
4443
  // resolution block above. Absent when no credential resolved,
@@ -3869,31 +4450,53 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
3869
4450
  credentialSource: authEcho.credentialSource,
3870
4451
  setEnv: authEcho.setEnv
3871
4452
  }
3872
- } : {}
4453
+ } : {},
4454
+ resumedFrom: prev.id,
4455
+ resumeVia
3873
4456
  });
3874
4457
  liveSessionId = desc2.id;
3875
4458
  return desc2;
3876
4459
  };
3877
4460
  let desc;
3878
4461
  let resumeFallback = false;
3879
- let usedResumeSessionId = strategy.resumeSessionId;
3880
4462
  try {
3881
4463
  desc = await spawnWithResume(strategy.resumeSessionId);
3882
4464
  } catch (err) {
3883
4465
  const msg = err instanceof Error ? err.message : String(err);
3884
4466
  if (strategy.resumeSessionId && /not found|Resource not found/i.test(msg)) {
3885
4467
  desc = await spawnWithResume(void 0);
3886
- usedResumeSessionId = void 0;
3887
4468
  resumeFallback = true;
3888
4469
  } else {
3889
4470
  throw err;
3890
4471
  }
3891
4472
  }
3892
- const resumeVia = !usedResumeSessionId ? "" : opts.forceAgentResume ? "resumed via ACP" : describeResumePath(augmented);
4473
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
4474
+ const emit = (axis, value) => {
4475
+ registry.emitConfigChanged({
4476
+ type: "session:config-changed",
4477
+ sessionId: desc.id,
4478
+ axis,
4479
+ value,
4480
+ ...desc.label ? { label: desc.label } : {},
4481
+ ts
4482
+ });
4483
+ };
4484
+ if (overrides.model !== void 0) emit("model", overrides.model);
4485
+ if (overrides.effort !== void 0) emit("effort", overrides.effort);
4486
+ if (accessOverrideRef !== void 0) emit("access", { profileRef: accessOverrideRef });
4487
+ if (overrides.route !== void 0) emit("route", overrides.route);
4488
+ if (overrides.posture !== void 0) emit("posture", overrides.posture);
4489
+ if (overrides.contextProfile !== void 0) emit("contextProfile", overrides.contextProfile);
3893
4490
  return {
3894
4491
  desc,
3895
4492
  resumedFrom: prev.id,
3896
- resumeVia,
4493
+ // `spawnWithResume` already computed + persisted this onto `desc` (the
4494
+ // fix this module carries — see `SessionDescriptor.resumedFrom`'s doc);
4495
+ // reading it back here rather than recomputing keeps the RESULT and the
4496
+ // STORED descriptor from ever being able to diverge. Never actually
4497
+ // undefined — every `spawnWithResume` call sets it, `?? ""` is just
4498
+ // satisfying the optional field's type.
4499
+ resumeVia: desc.resumeVia ?? "",
3897
4500
  ...resumeFallback ? { resumeFallback: true } : {}
3898
4501
  };
3899
4502
  }
@@ -4037,6 +4640,9 @@ function withToolExclusion(server, excluded) {
4037
4640
  }
4038
4641
  });
4039
4642
  }
4643
+
4644
+ // src/conversation-read.ts
4645
+ init_conversation_store();
4040
4646
  init_transcript_export();
4041
4647
  var BINARY_TO_STORE_KEY = {
4042
4648
  claude: "claude-code",
@@ -4216,7 +4822,7 @@ function buildSessionTree(sessions) {
4216
4822
  });
4217
4823
  return sessions.filter((s) => !s.parentSessionId || !idSet.has(s.parentSessionId)).sort((a, b) => a.startedAt.localeCompare(b.startedAt)).map(toNode);
4218
4824
  }
4219
- z.preprocess(
4825
+ var mcpBool2 = z.preprocess(
4220
4826
  (v) => v === "true" ? true : v === "false" ? false : v,
4221
4827
  z.boolean()
4222
4828
  );
@@ -4238,14 +4844,20 @@ function registerSessionTools(rawServer, opts) {
4238
4844
  "Filter by session kind. `all` (default) returns every kind. Use `terminal` to list only PTY sessions, `agent-cli` for structured ACP agents."
4239
4845
  ),
4240
4846
  onlyAlive: z.boolean().optional().describe("When true, only running/starting sessions. Default false."),
4241
- status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive).")
4847
+ status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive)."),
4848
+ includeArchived: z.boolean().optional().describe(
4849
+ "When true, also include archived sessions (hidden from every other view by `session_archive`). Default false."
4850
+ )
4242
4851
  },
4243
4852
  async (input) => {
4244
- let rows = registry.list();
4853
+ let rows = registry.list({ includeArchived: true });
4245
4854
  if (callerScope) {
4246
4855
  const subtree = collectSubtree(callerScope.ownerSessionId, rows);
4247
4856
  rows = rows.filter((s) => subtree.has(s.id));
4248
4857
  }
4858
+ if (!input.includeArchived) {
4859
+ rows = rows.filter((s) => !s.archived);
4860
+ }
4249
4861
  if (input.kind && input.kind !== "all") {
4250
4862
  rows = rows.filter((s) => s.kind === input.kind);
4251
4863
  }
@@ -4283,7 +4895,10 @@ function registerSessionTools(rawServer, opts) {
4283
4895
  };
4284
4896
  }
4285
4897
  if (callerScope) {
4286
- const subtree = collectSubtree(callerScope.ownerSessionId, registry.list());
4898
+ const subtree = collectSubtree(
4899
+ callerScope.ownerSessionId,
4900
+ registry.list({ includeArchived: true })
4901
+ );
4287
4902
  if (!subtree.has(desc.id)) {
4288
4903
  return {
4289
4904
  content: [
@@ -4319,11 +4934,12 @@ function registerSessionTools(rawServer, opts) {
4319
4934
  status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive).")
4320
4935
  },
4321
4936
  async (input) => {
4322
- let rows = registry.list();
4937
+ let rows = registry.list({ includeArchived: true });
4323
4938
  if (callerScope) {
4324
4939
  const subtree = collectSubtree(callerScope.ownerSessionId, rows);
4325
4940
  rows = rows.filter((s) => subtree.has(s.id));
4326
4941
  }
4942
+ rows = rows.filter((s) => !s.archived);
4327
4943
  const kind = input.kind ?? "terminal";
4328
4944
  if (kind !== "all") {
4329
4945
  rows = rows.filter((s) => s.kind === kind);
@@ -4353,11 +4969,12 @@ function registerSessionTools(rawServer, opts) {
4353
4969
  status: z.enum(["starting", "running", "exited", "killed", "error"]).optional().describe("Filter by exact status (overrides onlyAlive).")
4354
4970
  },
4355
4971
  async (input) => {
4356
- let rows = registry.list();
4972
+ let rows = registry.list({ includeArchived: true });
4357
4973
  if (callerScope) {
4358
4974
  const subtree = collectSubtree(callerScope.ownerSessionId, rows);
4359
4975
  rows = rows.filter((s) => subtree.has(s.id));
4360
4976
  }
4977
+ rows = rows.filter((s) => !s.archived);
4361
4978
  const kind = input.kind ?? "command";
4362
4979
  if (kind !== "all") {
4363
4980
  rows = rows.filter((s) => s.kind === kind);
@@ -4641,11 +5258,12 @@ function registerSessionTools(rawServer, opts) {
4641
5258
  )
4642
5259
  },
4643
5260
  async (input) => {
4644
- let rows = registry.list();
5261
+ let rows = registry.list({ includeArchived: true });
4645
5262
  if (callerScope) {
4646
5263
  const subtree = collectSubtree(callerScope.ownerSessionId, rows);
4647
5264
  rows = rows.filter((s) => subtree.has(s.id));
4648
5265
  }
5266
+ rows = rows.filter((s) => !s.archived);
4649
5267
  if (input.onlyAlive) {
4650
5268
  rows = rows.filter(
4651
5269
  (s) => s.status === "running" || s.status === "starting"
@@ -4678,7 +5296,25 @@ function registerSessionTools(rawServer, opts) {
4678
5296
  cols: z.number().int().min(1).max(500).optional().describe(
4679
5297
  "PTY cols \u2014 only used when the restart resolves to a provider-native or plain PTY resume. Default 80."
4680
5298
  ),
4681
- rows: z.number().int().min(1).max(200).optional().describe("PTY rows \u2014 same case as `cols`. Default 24.")
5299
+ rows: z.number().int().min(1).max(200).optional().describe("PTY rows \u2014 same case as `cols`. Default 24."),
5300
+ // ── Restart-with-override axes (SPEC §4.3, step 6) — the single path
5301
+ // for all four restart-only axes. Each optional; an omitted axis is
5302
+ // carried forward from the prior session, an axis set here wins.
5303
+ model: z.string().min(1).optional().describe("Override the model on restart (route-identity ref)."),
5304
+ effort: z.enum(["low", "medium", "high", "xhigh", "max", "ultracode"]).optional().describe("Override the reasoning-effort level on restart."),
5305
+ access: z.object({
5306
+ profileRef: z.string().min(1).describe("Attach this NAMED auth profile (SPEC \xA71c). Rejected 400 if it's not eligible for the resolved (adapter \xD7 route).")
5307
+ }).optional().describe("Switch the session's billing wallet to a named auth profile."),
5308
+ route: z.object({
5309
+ gateway: z.string().min(1).describe("Endpoint/gateway id (anthropic|moonshot|\u2026)."),
5310
+ baseUrl: z.string().url().optional().describe("Explicit base URL for a custom gateway.")
5311
+ }).optional().describe("Override the endpoint/gateway rail on restart (access is downstream)."),
5312
+ posture: z.union([
5313
+ z.enum(["default", "plan", "accept-edits", "bypass", "read-only"]),
5314
+ z.object({ harnessModeId: z.string().min(1) })
5315
+ ]).optional().describe("Override the posture (what the agent may DO) on restart."),
5316
+ contextProfile: z.string().min(1).optional().describe("Override what enters context (full|lean|\u2026) on restart."),
5317
+ mode: z.string().min(1).optional().describe("Legacy AIP-45 mode id override, forwarded verbatim to the driver at spawn.")
4682
5318
  },
4683
5319
  async (input) => {
4684
5320
  const prev = registry.findByIdOrName(input.idOrName);
@@ -4694,7 +5330,10 @@ function registerSessionTools(rawServer, opts) {
4694
5330
  };
4695
5331
  }
4696
5332
  if (callerScope) {
4697
- const subtree = collectSubtree(callerScope.ownerSessionId, registry.list());
5333
+ const subtree = collectSubtree(
5334
+ callerScope.ownerSessionId,
5335
+ registry.list({ includeArchived: true })
5336
+ );
4698
5337
  if (!subtree.has(prev.id)) {
4699
5338
  return {
4700
5339
  content: [
@@ -4712,6 +5351,84 @@ function registerSessionTools(rawServer, opts) {
4712
5351
  };
4713
5352
  }
4714
5353
  }
5354
+ const overrides = {
5355
+ ...input.model !== void 0 ? { model: input.model } : {},
5356
+ ...input.effort !== void 0 ? { effort: input.effort } : {},
5357
+ ...input.access !== void 0 ? { access: input.access } : {},
5358
+ ...input.route !== void 0 ? { route: input.route } : {},
5359
+ ...input.posture !== void 0 ? { posture: input.posture } : {},
5360
+ ...input.contextProfile !== void 0 ? { contextProfile: input.contextProfile } : {},
5361
+ ...input.mode !== void 0 ? { mode: input.mode } : {}
5362
+ };
5363
+ if (Object.keys(overrides).length > 0) {
5364
+ if (!prev.adapterSlug || !resolveAgentAdapter) {
5365
+ return {
5366
+ content: [
5367
+ {
5368
+ type: "text",
5369
+ text: JSON.stringify({
5370
+ error: "restart_override_invalid",
5371
+ status: 400,
5372
+ message: "session_restart: restart-with-override only applies to agent-cli sessions (a PTY/command session has no config axes to override).",
5373
+ ok: false,
5374
+ sessionId: prev.id
5375
+ })
5376
+ }
5377
+ ],
5378
+ isError: true
5379
+ };
5380
+ }
5381
+ try {
5382
+ const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev, {
5383
+ forceAgentResume: true,
5384
+ overrides
5385
+ });
5386
+ return {
5387
+ content: [
5388
+ {
5389
+ type: "text",
5390
+ text: JSON.stringify(
5391
+ {
5392
+ ...restarted.desc,
5393
+ resumedFrom: restarted.resumedFrom,
5394
+ resumeVia: restarted.resumeVia,
5395
+ ...restarted.resumeFallback ? { resumeFallback: true } : {}
5396
+ },
5397
+ null,
5398
+ 2
5399
+ )
5400
+ }
5401
+ ]
5402
+ };
5403
+ } catch (err) {
5404
+ if (err instanceof RestartOverrideError) {
5405
+ return {
5406
+ content: [
5407
+ {
5408
+ type: "text",
5409
+ text: JSON.stringify({
5410
+ error: err.code,
5411
+ status: err.status,
5412
+ message: err.message,
5413
+ ok: false,
5414
+ sessionId: prev.id
5415
+ })
5416
+ }
5417
+ ],
5418
+ isError: true
5419
+ };
5420
+ }
5421
+ return {
5422
+ content: [
5423
+ {
5424
+ type: "text",
5425
+ text: `session_restart: ${err instanceof Error ? err.message : String(err)}`
5426
+ }
5427
+ ],
5428
+ isError: true
5429
+ };
5430
+ }
5431
+ }
4715
5432
  const augmented = await augmentWithFsResume(prev);
4716
5433
  const strategy = decideRestartStrategy(augmented);
4717
5434
  if (strategy.kind === "unsupported") {
@@ -4743,17 +5460,15 @@ function registerSessionTools(rawServer, opts) {
4743
5460
  cols: input.cols ?? 80,
4744
5461
  rows: input.rows ?? 24,
4745
5462
  ...prev.name ? { name: prev.name } : {},
4746
- ...prev.label ? { label: prev.label } : {}
5463
+ ...prev.label ? { label: prev.label } : {},
5464
+ resumedFrom: prev.id,
5465
+ resumeVia: describeResumePath(augmented)
4747
5466
  });
4748
5467
  return {
4749
5468
  content: [
4750
5469
  {
4751
5470
  type: "text",
4752
- text: JSON.stringify(
4753
- { ...desc, resumedFrom: prev.id, resumeVia: describeResumePath(augmented) },
4754
- null,
4755
- 2
4756
- )
5471
+ text: JSON.stringify(desc, null, 2)
4757
5472
  }
4758
5473
  ]
4759
5474
  };
@@ -4774,36 +5489,158 @@ function registerSessionTools(rawServer, opts) {
4774
5489
  content: [
4775
5490
  {
4776
5491
  type: "text",
4777
- text: "session_restart: agent_start is not enabled \u2014 the daemon was started without an adapter resolver."
5492
+ text: "session_restart: agent_start is not enabled \u2014 the daemon was started without an adapter resolver."
5493
+ }
5494
+ ],
5495
+ isError: true
5496
+ };
5497
+ }
5498
+ const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev);
5499
+ return {
5500
+ content: [
5501
+ {
5502
+ type: "text",
5503
+ text: JSON.stringify(
5504
+ {
5505
+ ...restarted.desc,
5506
+ resumedFrom: restarted.resumedFrom,
5507
+ resumeVia: restarted.resumeVia,
5508
+ ...restarted.resumeFallback ? { resumeFallback: true } : {}
5509
+ },
5510
+ null,
5511
+ 2
5512
+ )
5513
+ }
5514
+ ]
5515
+ };
5516
+ } catch (err) {
5517
+ return {
5518
+ content: [
5519
+ {
5520
+ type: "text",
5521
+ text: `session_restart: ${err instanceof Error ? err.message : String(err)}`
5522
+ }
5523
+ ],
5524
+ isError: true
5525
+ };
5526
+ }
5527
+ }
5528
+ );
5529
+ server.tool(
5530
+ "session_archive",
5531
+ "Archive a terminal-status session (exited/killed/error) so it drops out of `session_list`'s / `GET /sessions`'s default view \u2014 a housekeeping flag, not a daemon action: the session's history and transcript are untouched and stay fully readable (`session_usage`, `agent_export`, or `session_list({ includeArchived: true })`). Refuses a still-alive session (running/starting) \u2014 archiving one would hide it from view while it keeps working unattended. Use `session_unarchive` to restore visibility.",
5532
+ {
5533
+ idOrName: z.string().min(1).describe(
5534
+ "Session id or name to archive \u2014 from `session_list`, must be terminal-status."
5535
+ )
5536
+ },
5537
+ async (input) => {
5538
+ const prev = registry.findByIdOrName(input.idOrName);
5539
+ if (!prev) {
5540
+ return {
5541
+ content: [
5542
+ {
5543
+ type: "text",
5544
+ text: JSON.stringify({ error: `no session "${input.idOrName}" found` })
5545
+ }
5546
+ ],
5547
+ isError: true
5548
+ };
5549
+ }
5550
+ if (callerScope) {
5551
+ const subtree = collectSubtree(
5552
+ callerScope.ownerSessionId,
5553
+ registry.list({ includeArchived: true })
5554
+ );
5555
+ if (!subtree.has(prev.id)) {
5556
+ return {
5557
+ content: [
5558
+ {
5559
+ type: "text",
5560
+ text: JSON.stringify({
5561
+ error: "orchestrator_session_out_of_scope",
5562
+ message: `session_archive: session "${prev.id}" is not in your subtree \u2014 a scoped orchestrator can only archive sessions it (transitively) spawned.`,
5563
+ ok: false,
5564
+ sessionId: prev.id
5565
+ })
5566
+ }
5567
+ ],
5568
+ isError: true
5569
+ };
5570
+ }
5571
+ }
5572
+ try {
5573
+ const desc = registry.archiveSession(prev.id);
5574
+ return {
5575
+ content: [{ type: "text", text: JSON.stringify(desc, null, 2) }]
5576
+ };
5577
+ } catch (err) {
5578
+ return {
5579
+ content: [
5580
+ {
5581
+ type: "text",
5582
+ text: `session_archive: ${err instanceof Error ? err.message : String(err)}`
5583
+ }
5584
+ ],
5585
+ isError: true
5586
+ };
5587
+ }
5588
+ }
5589
+ );
5590
+ server.tool(
5591
+ "session_unarchive",
5592
+ "Restore an archived session to `session_list`'s default view \u2014 the inverse of `session_archive`. No status guard: archiving never touches daemon state, so there is nothing to re-validate \u2014 any archived session can be unarchived at any time.",
5593
+ {
5594
+ idOrName: z.string().min(1).describe(
5595
+ "Session id or name to unarchive \u2014 find it via `session_list({ includeArchived: true })`."
5596
+ )
5597
+ },
5598
+ async (input) => {
5599
+ const prev = registry.findByIdOrName(input.idOrName);
5600
+ if (!prev) {
5601
+ return {
5602
+ content: [
5603
+ {
5604
+ type: "text",
5605
+ text: JSON.stringify({ error: `no session "${input.idOrName}" found` })
5606
+ }
5607
+ ],
5608
+ isError: true
5609
+ };
5610
+ }
5611
+ if (callerScope) {
5612
+ const subtree = collectSubtree(
5613
+ callerScope.ownerSessionId,
5614
+ registry.list({ includeArchived: true })
5615
+ );
5616
+ if (!subtree.has(prev.id)) {
5617
+ return {
5618
+ content: [
5619
+ {
5620
+ type: "text",
5621
+ text: JSON.stringify({
5622
+ error: "orchestrator_session_out_of_scope",
5623
+ message: `session_unarchive: session "${prev.id}" is not in your subtree \u2014 a scoped orchestrator can only unarchive sessions it (transitively) spawned.`,
5624
+ ok: false,
5625
+ sessionId: prev.id
5626
+ })
4778
5627
  }
4779
5628
  ],
4780
5629
  isError: true
4781
5630
  };
4782
5631
  }
4783
- const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev);
5632
+ }
5633
+ try {
5634
+ const desc = registry.unarchiveSession(prev.id);
4784
5635
  return {
4785
- content: [
4786
- {
4787
- type: "text",
4788
- text: JSON.stringify(
4789
- {
4790
- ...restarted.desc,
4791
- resumedFrom: restarted.resumedFrom,
4792
- resumeVia: restarted.resumeVia,
4793
- ...restarted.resumeFallback ? { resumeFallback: true } : {}
4794
- },
4795
- null,
4796
- 2
4797
- )
4798
- }
4799
- ]
5636
+ content: [{ type: "text", text: JSON.stringify(desc, null, 2) }]
4800
5637
  };
4801
5638
  } catch (err) {
4802
5639
  return {
4803
5640
  content: [
4804
5641
  {
4805
5642
  type: "text",
4806
- text: `session_restart: ${err instanceof Error ? err.message : String(err)}`
5643
+ text: `session_unarchive: ${err instanceof Error ? err.message : String(err)}`
4807
5644
  }
4808
5645
  ],
4809
5646
  isError: true
@@ -4872,7 +5709,16 @@ function registerSessionTools(rawServer, opts) {
4872
5709
  cols: input.cols ?? 80,
4873
5710
  rows: input.rows ?? 24,
4874
5711
  ...input.name ? { name: input.name } : {},
4875
- ...input.label ? { label: input.label } : {}
5712
+ ...input.label ? { label: input.label } : {},
5713
+ // Parent attribution + depth (orchestrator WP4) — same rule as
5714
+ // `agent_start` (session-spawn.ts): a spawn through a scoped
5715
+ // sub-gateway is attributed to the owning orchestrator so
5716
+ // `session_tree` shows the PTY as its child. Depth caps and
5717
+ // child quotas stay agent_start-only for now.
5718
+ ...callerScope?.ownerSessionId ? {
5719
+ parentSessionId: callerScope.ownerSessionId,
5720
+ depth: callerScope.depth + 1
5721
+ } : {}
4876
5722
  });
4877
5723
  return {
4878
5724
  content: [{ type: "text", text: JSON.stringify(desc, null, 2) }]
@@ -4951,10 +5797,13 @@ function registerSessionTools(rawServer, opts) {
4951
5797
  );
4952
5798
  server.tool(
4953
5799
  "terminal_output",
4954
- "Snapshot the recent byte buffer of a PTY session. Returns base64-encoded bytes (the buffer is RAW including ANSI escapes \u2014 strip with a regex if you want plain text). `lastBytes` caps the read from the tail.",
5800
+ "Snapshot the recent byte buffer of a PTY session. Returns base64-encoded bytes (the buffer is RAW including ANSI escapes) by default; pass `clean: true` for ANSI-stripped plain text instead. `lastBytes` caps the read from the tail.",
4955
5801
  {
4956
5802
  sessionId: z.string().describe("Session id OR name from terminal_start."),
4957
- lastBytes: z.number().int().min(1).max(64 * 1024).optional().describe("Max bytes from the tail. Default: full ring buffer (~64 KiB).")
5803
+ lastBytes: z.number().int().min(1).max(64 * 1024).optional().describe("Max bytes from the tail. Default: full ring buffer (~64 KiB)."),
5804
+ clean: mcpBool2.optional().describe(
5805
+ "Strip ANSI codes, returning human-readable text (as `text` instead of `b64`). Default false = raw base64."
5806
+ )
4958
5807
  },
4959
5808
  async (input) => {
4960
5809
  if (!ptyEnabled) return ptyNotConfigured("terminal_output");
@@ -4994,7 +5843,7 @@ function registerSessionTools(rawServer, opts) {
4994
5843
  sessionId: desc.id,
4995
5844
  status: desc.status,
4996
5845
  bytes: buf.byteLength,
4997
- b64: buf.toString("base64")
5846
+ ...input.clean ? { text: stripAnsi(buf.toString("utf8")) } : { b64: buf.toString("base64") }
4998
5847
  },
4999
5848
  null,
5000
5849
  2
@@ -7811,6 +8660,9 @@ function filterSessionObserver(inner, shouldObserve) {
7811
8660
  // src/sessions.ts
7812
8661
  init_tool_presenter();
7813
8662
  init_transcript_writer();
8663
+
8664
+ // src/conversation-index.ts
8665
+ init_conversation_store();
7814
8666
  var DEFAULT_BUCKET = "default";
7815
8667
  var BUCKETS_ROOT = () => resolve(homedir(), ".agentproto", "workspaces");
7816
8668
  var LEGACY_SESSIONS_FILE = () => resolve(homedir(), ".agentproto", "sessions.json");
@@ -7934,6 +8786,110 @@ function readBucketRows(root, slug) {
7934
8786
  return [];
7935
8787
  }
7936
8788
  }
8789
+ var rowId = (row) => row && typeof row === "object" && "id" in row && typeof row.id === "string" ? row.id : void 0;
8790
+ function mergeBucketRows(onDisk, rows, everHeldIds) {
8791
+ const incomingIds = new Set(rows.map(rowId).filter((id) => id !== void 0));
8792
+ const preserved = onDisk.filter((row) => {
8793
+ const id = rowId(row);
8794
+ return id !== void 0 && !incomingIds.has(id) && !everHeldIds.has(id);
8795
+ });
8796
+ return [...rows, ...preserved];
8797
+ }
8798
+
8799
+ // src/conversation-index.ts
8800
+ function conversationIndexPath(bucketsRoot, slug) {
8801
+ return join(bucketDir(bucketsRoot, slug), "conversations.jsonl");
8802
+ }
8803
+ async function listClaudeSubagents(projectDir, adapterSessionId) {
8804
+ const dir = join(projectDir, adapterSessionId, "subagents");
8805
+ let entries;
8806
+ try {
8807
+ entries = await promises.readdir(dir);
8808
+ } catch {
8809
+ return [];
8810
+ }
8811
+ return entries.filter((e) => e.startsWith("agent-") && e.endsWith(".jsonl")).map((e) => join(dir, e)).sort();
8812
+ }
8813
+ async function resolveNativeLink(input) {
8814
+ const { cwd, adapterSlug, adapterSessionId } = input;
8815
+ if (adapterSlug === "claude-code") {
8816
+ const dir = claudeCodeProjectDir(cwd);
8817
+ const path = join(dir, `${adapterSessionId}.jsonl`);
8818
+ const subagents = await listClaudeSubagents(dir, adapterSessionId);
8819
+ return { kind: "claude-jsonl", path, subagents };
8820
+ }
8821
+ if (adapterSlug === "hermes") {
8822
+ return {
8823
+ kind: "hermes-sqlite",
8824
+ dbPath: join(homedir(), ".hermes", "state.db"),
8825
+ rowId: adapterSessionId
8826
+ };
8827
+ }
8828
+ return void 0;
8829
+ }
8830
+ async function appendConversationRecord(bucketsRoot, slug, record) {
8831
+ const path = conversationIndexPath(bucketsRoot, slug);
8832
+ await promises.mkdir(dirname(path), { recursive: true });
8833
+ await promises.appendFile(path, JSON.stringify(record) + "\n", "utf8");
8834
+ }
8835
+ function isConversationIndexRecord(value) {
8836
+ if (!value || typeof value !== "object") return false;
8837
+ const r = value;
8838
+ return typeof r.sessionId === "string" && typeof r.workspace === "string" && typeof r.cwd === "string" && typeof r.adapterSlug === "string" && typeof r.adapterSessionId === "string" && typeof r.agentprotoTranscript === "string" && typeof r.startedAt === "string";
8839
+ }
8840
+ async function readConversationIndex(bucketsRoot, slug) {
8841
+ const path = conversationIndexPath(bucketsRoot, slug);
8842
+ let raw;
8843
+ try {
8844
+ raw = await promises.readFile(path, "utf8");
8845
+ } catch {
8846
+ return [];
8847
+ }
8848
+ const bySession = /* @__PURE__ */ new Map();
8849
+ for (const line of raw.split("\n")) {
8850
+ const trimmed = line.trim();
8851
+ if (!trimmed) continue;
8852
+ let parsed;
8853
+ try {
8854
+ parsed = JSON.parse(trimmed);
8855
+ } catch {
8856
+ continue;
8857
+ }
8858
+ if (!isConversationIndexRecord(parsed)) continue;
8859
+ bySession.set(parsed.sessionId, parsed);
8860
+ }
8861
+ return Array.from(bySession.values());
8862
+ }
8863
+ async function findConversationRecord(bucketsRoot, slug, sessionId) {
8864
+ const rows = await readConversationIndex(bucketsRoot, slug);
8865
+ return rows.find((r) => r.sessionId === sessionId);
8866
+ }
8867
+ async function locateConversationBySessionId(bucketsRoot, listBuckets2, sessionId) {
8868
+ for (const slug of listBuckets2()) {
8869
+ const record = await findConversationRecord(bucketsRoot, slug, sessionId);
8870
+ if (record) return { workspace: slug, record };
8871
+ }
8872
+ return void 0;
8873
+ }
8874
+ async function locateConversationByNativePath(bucketsRoot, listBuckets2, nativePath) {
8875
+ const target = resolve(nativePath);
8876
+ for (const slug of listBuckets2()) {
8877
+ const rows = await readConversationIndex(bucketsRoot, slug);
8878
+ for (const record of rows) {
8879
+ if (!record.native || record.native.kind !== "claude-jsonl") continue;
8880
+ if (resolve(record.native.path) === target) {
8881
+ return { workspace: slug, record };
8882
+ }
8883
+ const matchedSubagentPath = record.native.subagents.find(
8884
+ (p) => resolve(p) === target
8885
+ );
8886
+ if (matchedSubagentPath) {
8887
+ return { workspace: slug, record, matchedSubagentPath };
8888
+ }
8889
+ }
8890
+ }
8891
+ return void 0;
8892
+ }
7937
8893
 
7938
8894
  // src/terminal-transcript-writer.ts
7939
8895
  init_transcript_writer();
@@ -8111,6 +9067,15 @@ var SessionNotAliveError = class extends Error {
8111
9067
  var RECENT_LINES_CAP = 500;
8112
9068
  var RECENT_BYTES_CAP = 64 * 1024;
8113
9069
  var PERSIST_DEBOUNCE_MS = 1500;
9070
+ var EMPTY_ID_SET = /* @__PURE__ */ new Set();
9071
+ function markHeldId(map, slug, id) {
9072
+ let set = map.get(slug);
9073
+ if (!set) {
9074
+ set = /* @__PURE__ */ new Set();
9075
+ map.set(slug, set);
9076
+ }
9077
+ set.add(id);
9078
+ }
8114
9079
  var HISTORY_CAP = 200;
8115
9080
  var INTERRUPT_SETTLE_TIMEOUT_MS = 6e4;
8116
9081
  function stampProcessAlive(desc) {
@@ -8135,6 +9100,11 @@ function findPriorCommandSessionId(liveSessions, cwd) {
8135
9100
  }
8136
9101
  return best?.desc.id;
8137
9102
  }
9103
+ function currentRouteOf(desc) {
9104
+ if (desc.route?.gateway) return desc.route.gateway;
9105
+ if (desc.model) return tryParseModelRef(desc.model)?.route;
9106
+ return void 0;
9107
+ }
8138
9108
  function worktreeFields(cwd) {
8139
9109
  const identity = resolveWorktreeIdentity(cwd);
8140
9110
  if (!identity) return {};
@@ -8185,6 +9155,8 @@ function createSessionsRegistry(opts) {
8185
9155
  let nextSubId = 1;
8186
9156
  let shutdownDone = false;
8187
9157
  const knownBuckets = /* @__PURE__ */ new Set();
9158
+ const sourceBucketOf = /* @__PURE__ */ new Map();
9159
+ const heldIdsByBucket = /* @__PURE__ */ new Map();
8188
9160
  if (persist) {
8189
9161
  if (partitioned) {
8190
9162
  migrateLegacySessionsFile({
@@ -8197,13 +9169,17 @@ function createSessionsRegistry(opts) {
8197
9169
  loadHistorySnapshot(
8198
9170
  bucketSessionsFile(bucketsRoot, slug),
8199
9171
  sessions,
8200
- sessionEvents
9172
+ sessionEvents,
9173
+ slug,
9174
+ sourceBucketOf,
9175
+ heldIdsByBucket
8201
9176
  );
8202
9177
  }
8203
9178
  } else {
8204
9179
  loadHistorySnapshot(legacyPath, sessions, sessionEvents);
8205
9180
  }
8206
9181
  }
9182
+ const bootLoadedBuckets = new Set(knownBuckets);
8207
9183
  const onProcessExit = () => {
8208
9184
  shutdownImpl();
8209
9185
  };
@@ -8287,6 +9263,39 @@ function createSessionsRegistry(opts) {
8287
9263
  }
8288
9264
  delete rt.desc.awaitingPermission;
8289
9265
  };
9266
+ const recordConversationLink = (rt, adapterSessionIdOverride) => {
9267
+ if (!persist || !partitioned) return;
9268
+ const desc = rt.desc;
9269
+ if (desc.kind !== "agent-cli") return;
9270
+ const adapterSlug = desc.adapterSlug;
9271
+ const adapterSessionId = adapterSessionIdOverride ?? desc.adapterSessionId;
9272
+ const cwd = desc.cwd;
9273
+ if (!adapterSlug || !adapterSessionId || !cwd) return;
9274
+ void (async () => {
9275
+ try {
9276
+ const native = await resolveNativeLink({ cwd, adapterSlug, adapterSessionId });
9277
+ const registered = readRegisteredSlugs(workspacesConfigPath);
9278
+ const slug = resolveBucketSlug(desc.workspaceSlug, registered);
9279
+ const record = {
9280
+ sessionId: desc.id,
9281
+ workspace: slug,
9282
+ cwd,
9283
+ adapterSlug,
9284
+ adapterSessionId,
9285
+ ...native ? { native } : {},
9286
+ agentprotoTranscript: sessionEventsPath(desc.id, transcriptBaseDir),
9287
+ ...desc.title ? { title: desc.title } : {},
9288
+ startedAt: desc.startedAt,
9289
+ ...desc.endedAt ? { endedAt: desc.endedAt } : {}
9290
+ };
9291
+ await appendConversationRecord(bucketsRoot, slug, record);
9292
+ } catch (err) {
9293
+ console.warn(
9294
+ `[sessions] conversation-index write failed for ${desc.id}: ${err instanceof Error ? err.message : String(err)}`
9295
+ );
9296
+ }
9297
+ })();
9298
+ };
8290
9299
  const schedulePersist = () => {
8291
9300
  if (!persist) return;
8292
9301
  if (persistTimer) clearTimeout(persistTimer);
@@ -8303,7 +9312,8 @@ function createSessionsRegistry(opts) {
8303
9312
  const groups = /* @__PURE__ */ new Map();
8304
9313
  for (const slug of knownBuckets) groups.set(slug, []);
8305
9314
  for (const desc of snapshotRows()) {
8306
- const slug = resolveBucketSlug(desc.workspaceSlug, registered);
9315
+ const slug = sourceBucketOf.get(desc.id) ?? resolveBucketSlug(desc.workspaceSlug, registered);
9316
+ markHeldId(heldIdsByBucket, slug, desc.id);
8307
9317
  const list = groups.get(slug);
8308
9318
  if (list) list.push(desc);
8309
9319
  else groups.set(slug, [desc]);
@@ -8311,6 +9321,11 @@ function createSessionsRegistry(opts) {
8311
9321
  for (const slug of groups.keys()) knownBuckets.add(slug);
8312
9322
  return groups;
8313
9323
  };
9324
+ const rowsToWrite = (slug, rows) => bootLoadedBuckets.has(slug) ? rows : mergeBucketRows(
9325
+ readBucketRows(bucketsRoot, slug),
9326
+ rows,
9327
+ heldIdsByBucket.get(slug) ?? EMPTY_ID_SET
9328
+ );
8314
9329
  const persistSnapshot = async () => {
8315
9330
  try {
8316
9331
  const savedAt = (/* @__PURE__ */ new Date()).toISOString();
@@ -8318,7 +9333,7 @@ function createSessionsRegistry(opts) {
8318
9333
  for (const [slug, rows] of groupRowsByBucket()) {
8319
9334
  await writeBucketSnapshot(bucketsRoot, slug, {
8320
9335
  savedAt,
8321
- sessions: rows
9336
+ sessions: rowsToWrite(slug, rows)
8322
9337
  });
8323
9338
  }
8324
9339
  return;
@@ -8353,6 +9368,7 @@ function createSessionsRegistry(opts) {
8353
9368
  [strategy.storeAs]: m[1]
8354
9369
  };
8355
9370
  schedulePersist();
9371
+ recordConversationLink(rt, m[1]);
8356
9372
  }
8357
9373
  };
8358
9374
  const appendBytes = (rt, chunk) => {
@@ -8630,6 +9646,7 @@ function createSessionsRegistry(opts) {
8630
9646
  rt.emitter.emit("status", rt.desc.status);
8631
9647
  }
8632
9648
  schedulePersist();
9649
+ recordConversationLink(rt);
8633
9650
  } catch (err) {
8634
9651
  const msg = err instanceof Error ? err.message : String(err);
8635
9652
  appendLine(rt, `[error] resume failed: ${msg}`, "stderr");
@@ -8974,11 +9991,25 @@ function createSessionsRegistry(opts) {
8974
9991
  ...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
8975
9992
  depth: input.depth ?? 0,
8976
9993
  ...input.model ? { model: input.model } : {},
9994
+ ...input.mode ? { mode: input.mode } : {},
8977
9995
  ...input.auth ? { auth: input.auth } : {},
9996
+ // Decomposed config-axis echoes (SPEC §3.7), same optional-spread
9997
+ // shape as `model`/`mode`/`auth` above.
9998
+ ...input.effort ? { effort: input.effort } : {},
9999
+ ...input.posture !== void 0 ? { posture: input.posture } : {},
10000
+ ...input.route ? { route: input.route } : {},
10001
+ ...input.contextProfile ? { contextProfile: input.contextProfile } : {},
10002
+ ...input.accessProfile ? { accessProfile: input.accessProfile } : {},
8978
10003
  ...priorCommandSessionId ? { priorCommandSessionId } : {},
8979
10004
  ...input.remote ? { remote: true } : {},
8980
10005
  ...input.sandboxId ? { sandboxId: input.sandboxId } : {},
8981
- ...input.sandboxTeardown ? { sandboxTeardown: input.sandboxTeardown } : {}
10006
+ ...input.sandboxTeardown ? { sandboxTeardown: input.sandboxTeardown } : {},
10007
+ // Restart lineage (see SessionDescriptor.resumedFrom's doc). `resumeVia`
10008
+ // can legitimately be "" (a fresh fallback spawn with no continuity),
10009
+ // so it's gated on `!== undefined` rather than truthiness — a truthy
10010
+ // gate would silently drop the empty-string case.
10011
+ ...input.resumedFrom ? { resumedFrom: input.resumedFrom } : {},
10012
+ ...input.resumeVia !== void 0 ? { resumeVia: input.resumeVia } : {}
8982
10013
  };
8983
10014
  if (input.trace ?? opts?.langfuseTracingDefault ?? false) {
8984
10015
  tracedSessions.add(id);
@@ -9006,6 +10037,7 @@ function createSessionsRegistry(opts) {
9006
10037
  "stdout"
9007
10038
  );
9008
10039
  schedulePersist();
10040
+ recordConversationLink(rt);
9009
10041
  if (input.initialPrompt) {
9010
10042
  void runAgentTurn(rt, input.initialPrompt).catch((err) => {
9011
10043
  appendLine(
@@ -9065,7 +10097,16 @@ function createSessionsRegistry(opts) {
9065
10097
  ...worktreeFields(input.cwd),
9066
10098
  ...input.name ? { name: input.name } : {},
9067
10099
  ...input.label ? { label: input.label } : {},
9068
- ...priorCommandSessionId ? { priorCommandSessionId } : {}
10100
+ ...priorCommandSessionId ? { priorCommandSessionId } : {},
10101
+ // Parent attribution + depth (orchestrator WP4) — same recording
10102
+ // rule as spawnAgent above: depth always set so subtree/depth
10103
+ // logic never distinguishes "absent" from "root".
10104
+ ...input.parentSessionId ? { parentSessionId: input.parentSessionId } : {},
10105
+ depth: input.depth ?? 0,
10106
+ // Restart lineage — same gating rule as spawnAgent above (`resumeVia`
10107
+ // can legitimately be "").
10108
+ ...input.resumedFrom ? { resumedFrom: input.resumedFrom } : {},
10109
+ ...input.resumeVia !== void 0 ? { resumeVia: input.resumeVia } : {}
9069
10110
  };
9070
10111
  const rt = {
9071
10112
  desc,
@@ -9231,14 +10272,126 @@ function createSessionsRegistry(opts) {
9231
10272
  await interruptInFlightTurn(rt, id, "interruptSession");
9232
10273
  return { wasBusy: true };
9233
10274
  },
10275
+ async setModel(id, modelId) {
10276
+ const rt = sessions.get(id);
10277
+ if (!rt) throw new Error(`setModel: no session "${id}"`);
10278
+ if (rt.desc.kind !== "agent-cli" || !rt.agentSession) {
10279
+ throw new Error(`setModel: session "${id}" is not an agent-cli session`);
10280
+ }
10281
+ const targetRoute = tryParseModelRef(modelId)?.route;
10282
+ const currentRoute = currentRouteOf(rt.desc);
10283
+ if (targetRoute && currentRoute && targetRoute !== currentRoute) {
10284
+ return {
10285
+ applied: false,
10286
+ reason: "requires-restart",
10287
+ suggestedOverride: { route: { gateway: targetRoute }, model: modelId }
10288
+ };
10289
+ }
10290
+ if (!rt.agentSession.setModel) {
10291
+ return { applied: false, reason: "not-supported" };
10292
+ }
10293
+ const result = await rt.agentSession.setModel(modelId);
10294
+ if (result.applied) {
10295
+ rt.desc.model = result.model ?? modelId;
10296
+ schedulePersist();
10297
+ if (sessionEvents) {
10298
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
10299
+ sessionEvents.emit({
10300
+ type: "session:config-changed",
10301
+ sessionId: id,
10302
+ axis: "model",
10303
+ value: rt.desc.model,
10304
+ label: rt.desc.label,
10305
+ ts
10306
+ });
10307
+ sessionEvents.emit({
10308
+ type: "session:model-changed",
10309
+ sessionId: id,
10310
+ model: rt.desc.model,
10311
+ label: rt.desc.label,
10312
+ ts
10313
+ });
10314
+ }
10315
+ }
10316
+ return result;
10317
+ },
10318
+ emitConfigChanged(ev) {
10319
+ sessionEvents?.emit(ev);
10320
+ },
10321
+ async setEffort(id, effort) {
10322
+ const rt = sessions.get(id);
10323
+ if (!rt) throw new Error(`setEffort: no session "${id}"`);
10324
+ if (rt.desc.kind !== "agent-cli" || !rt.agentSession) {
10325
+ throw new Error(`setEffort: session "${id}" is not an agent-cli session`);
10326
+ }
10327
+ if (!rt.agentSession.setEffort) {
10328
+ return { applied: false, reason: "not-supported" };
10329
+ }
10330
+ const result = await rt.agentSession.setEffort(effort);
10331
+ if (result.applied) {
10332
+ rt.desc.effort = result.effort ?? effort;
10333
+ schedulePersist();
10334
+ if (sessionEvents) {
10335
+ sessionEvents.emit({
10336
+ type: "session:config-changed",
10337
+ sessionId: id,
10338
+ axis: "effort",
10339
+ value: rt.desc.effort,
10340
+ label: rt.desc.label,
10341
+ ts: (/* @__PURE__ */ new Date()).toISOString()
10342
+ });
10343
+ }
10344
+ }
10345
+ return result;
10346
+ },
10347
+ async setPosture(id, posture) {
10348
+ const rt = sessions.get(id);
10349
+ if (!rt) throw new Error(`setPosture: no session "${id}"`);
10350
+ if (rt.desc.kind !== "agent-cli" || !rt.agentSession) {
10351
+ throw new Error(`setPosture: session "${id}" is not an agent-cli session`);
10352
+ }
10353
+ const resolution = resolvePosture(posture, rt.agentSession.availableModes ?? []);
10354
+ if (resolution.kind !== "native") {
10355
+ return {
10356
+ applied: false,
10357
+ reason: "requires-restart",
10358
+ resolution: resolution.kind
10359
+ };
10360
+ }
10361
+ if (!rt.agentSession.setSessionMode) {
10362
+ return { applied: false, reason: "not-supported", resolution: "native" };
10363
+ }
10364
+ const result = await rt.agentSession.setSessionMode(resolution.mode.id);
10365
+ if (!result.applied) {
10366
+ return {
10367
+ applied: false,
10368
+ resolution: "native",
10369
+ ...result.reason ? { reason: result.reason } : {}
10370
+ };
10371
+ }
10372
+ rt.desc.posture = posture;
10373
+ schedulePersist();
10374
+ if (sessionEvents) {
10375
+ sessionEvents.emit({
10376
+ type: "session:config-changed",
10377
+ sessionId: id,
10378
+ axis: "posture",
10379
+ value: posture,
10380
+ label: rt.desc.label,
10381
+ ts: (/* @__PURE__ */ new Date()).toISOString()
10382
+ });
10383
+ }
10384
+ return { applied: true, posture, modeId: resolution.mode.id, resolution: "native" };
10385
+ },
9234
10386
  pulseActivity(id) {
9235
10387
  const rt = sessions.get(id);
9236
10388
  if (!rt) return;
9237
10389
  rt.desc.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
9238
10390
  schedulePersist();
9239
10391
  },
9240
- list() {
9241
- return Array.from(sessions.values()).map((s) => s.desc).sort((a, b) => b.startedAt.localeCompare(a.startedAt)).map((desc) => {
10392
+ list(opts2) {
10393
+ const includeArchived = opts2?.includeArchived ?? false;
10394
+ return Array.from(sessions.values()).map((s) => s.desc).filter((desc) => includeArchived || !desc.archived).sort((a, b) => b.startedAt.localeCompare(a.startedAt)).map((desc) => {
9242
10395
  stampProcessAlive(desc);
9243
10396
  return desc;
9244
10397
  });
@@ -9354,6 +10507,28 @@ function createSessionsRegistry(opts) {
9354
10507
  emitExited(rt);
9355
10508
  return true;
9356
10509
  },
10510
+ archiveSession(id) {
10511
+ const rt = sessions.get(id);
10512
+ if (!rt) throw new Error(`archiveSession: no session "${id}"`);
10513
+ const isAlive = rt.desc.status === "running" || rt.desc.status === "starting";
10514
+ if (isAlive) {
10515
+ throw new Error(
10516
+ `archiveSession: session "${id}" is still ${rt.desc.status} \u2014 only a terminal-status session (exited/killed/error) can be archived.`
10517
+ );
10518
+ }
10519
+ rt.desc.archived = true;
10520
+ schedulePersist();
10521
+ stampProcessAlive(rt.desc);
10522
+ return rt.desc;
10523
+ },
10524
+ unarchiveSession(id) {
10525
+ const rt = sessions.get(id);
10526
+ if (!rt) throw new Error(`unarchiveSession: no session "${id}"`);
10527
+ rt.desc.archived = false;
10528
+ schedulePersist();
10529
+ stampProcessAlive(rt.desc);
10530
+ return rt.desc;
10531
+ },
9357
10532
  listPendingPermissions(filter) {
9358
10533
  const all = Array.from(pendingPermissions.values());
9359
10534
  const scoped = filter?.sessionId ? all.filter((p) => p.sessionId === filter.sessionId) : all;
@@ -9478,7 +10653,7 @@ function createSessionsRegistry(opts) {
9478
10653
  for (const [slug, rows] of groupRowsByBucket()) {
9479
10654
  writeBucketSnapshotSync(bucketsRoot, slug, {
9480
10655
  savedAt: nowIso,
9481
- sessions: rows
10656
+ sessions: rowsToWrite(slug, rows)
9482
10657
  });
9483
10658
  }
9484
10659
  } else {
@@ -9500,7 +10675,7 @@ function clearInFlightFlags(desc) {
9500
10675
  desc.blockedOn = void 0;
9501
10676
  desc.pendingToolCallId = void 0;
9502
10677
  }
9503
- function loadHistorySnapshot(persistPath, sessions, sessionEvents) {
10678
+ function loadHistorySnapshot(persistPath, sessions, sessionEvents, bucketSlug, sourceBucketOf, heldIdsByBucket) {
9504
10679
  let raw;
9505
10680
  try {
9506
10681
  raw = readFileSync(persistPath, "utf8");
@@ -9552,6 +10727,10 @@ function loadHistorySnapshot(persistPath, sessions, sessionEvents) {
9552
10727
  };
9553
10728
  rt.emitter.setMaxListeners(50);
9554
10729
  sessions.set(desc.id, rt);
10730
+ if (bucketSlug !== void 0) {
10731
+ sourceBucketOf?.set(desc.id, bucketSlug);
10732
+ if (heldIdsByBucket) markHeldId(heldIdsByBucket, bucketSlug, desc.id);
10733
+ }
9555
10734
  if (wasAlive) {
9556
10735
  sessionEvents?.emit({
9557
10736
  type: "session:exited",
@@ -10427,7 +11606,7 @@ function registerOrchestrationTools(rawServer, opts) {
10427
11606
  const server = opts.toolSubset ? withToolSubset(rawServer, opts.toolSubset) : rawServer;
10428
11607
  const { registry, sessionEvents, eventRing, callerScope, inboundWatcher } = opts;
10429
11608
  const isPolicyInSubtree = (policy, ownerId) => {
10430
- const subtree = collectSubtree(ownerId, registry.list());
11609
+ const subtree = collectSubtree(ownerId, registry.list({ includeArchived: true }));
10431
11610
  const ids = policy.sessionIds.length > 0 ? policy.sessionIds : [policy.sessionId];
10432
11611
  return ids.every((id) => subtree.has(id));
10433
11612
  };
@@ -10459,7 +11638,10 @@ function registerOrchestrationTools(rawServer, opts) {
10459
11638
  const isSessionInScope = (sessionId) => {
10460
11639
  if (!callerScope) return true;
10461
11640
  if (!callerScope.ownerSessionId) return false;
10462
- return collectSubtree(callerScope.ownerSessionId, registry.list()).has(sessionId);
11641
+ return collectSubtree(
11642
+ callerScope.ownerSessionId,
11643
+ registry.list({ includeArchived: true })
11644
+ ).has(sessionId);
10463
11645
  };
10464
11646
  const enrichPermission2 = (p) => {
10465
11647
  const desc = registry.get(p.sessionId);
@@ -10637,6 +11819,12 @@ function registerOrchestrationTools(rawServer, opts) {
10637
11819
  "Reuse the session spawned by an earlier step (any prior stage), by that step's `label`. Ignored if `adapter` is set. Lets a later-stage step act on an earlier stage's output (e.g. a 'verify' step reusing a 'produce' step's session)."
10638
11820
  ),
10639
11821
  cacheable: z.boolean().optional().describe("Cache this step's output under the run's cacheKey (opt-in; for idempotent/pure steps only)."),
11822
+ sandbox: z.union([
11823
+ z.string().min(1).describe("Sandbox provider slug from `list_sandbox_providers` (e.g. 'local', 'e2b')."),
11824
+ z.object({ provider: z.string().min(1) }).passthrough().describe("Inline AIP-36 SandboxSpec ({ provider, config?, env?, \u2026 }).")
11825
+ ]).optional().describe(
11826
+ "Run this step's session inside a sandbox instead of on the host \u2014 same semantics as `agent_start.sandbox`. Only meaningful with `adapter`."
11827
+ ),
10640
11828
  policy: z.discriminatedUnion("awaiting", [
10641
11829
  z.object({ awaiting: z.literal("auto-allow"), prompt: z.string() }),
10642
11830
  z.object({
@@ -10858,7 +12046,10 @@ function registerOrchestrationTools(rawServer, opts) {
10858
12046
  };
10859
12047
  }
10860
12048
  const targetIds = input.sessionIds && input.sessionIds.length > 0 ? input.sessionIds : input.sessionId ? [input.sessionId] : [];
10861
- const subtree = collectSubtree(callerScope.ownerSessionId, registry.list());
12049
+ const subtree = collectSubtree(
12050
+ callerScope.ownerSessionId,
12051
+ registry.list({ includeArchived: true })
12052
+ );
10862
12053
  const outside = targetIds.filter((id) => !subtree.has(id));
10863
12054
  if (outside.length > 0) {
10864
12055
  return {
@@ -11716,55 +12907,170 @@ async function startHttpServer(opts) {
11716
12907
  await handleHeartbeatTick(req, res);
11717
12908
  return;
11718
12909
  }
11719
- if (path === "/files/upload" && req.method === "POST") {
12910
+ if (path === "/files/upload" && req.method === "POST") {
12911
+ const gate = checkSessionsToken(req);
12912
+ if (gate !== "ok") {
12913
+ rejectUnauthorizedSession(req, res, gate);
12914
+ return;
12915
+ }
12916
+ await handleFileUpload(req, res, url);
12917
+ return;
12918
+ }
12919
+ if (opts.sessions && path.startsWith("/sessions")) {
12920
+ if (isMutatingSessionsRoute(req.method ?? "GET", path)) {
12921
+ const gate = checkSessionsToken(req);
12922
+ if (gate !== "ok") {
12923
+ rejectUnauthorizedSession(req, res, gate);
12924
+ return;
12925
+ }
12926
+ }
12927
+ const handled = await handleSessions(
12928
+ req,
12929
+ res,
12930
+ path,
12931
+ opts.sessions,
12932
+ opts.resolveAgentAdapter,
12933
+ opts.ptyEnabled === true,
12934
+ opts.resolveBrowserAdapter,
12935
+ opts.listBrowserAdapters,
12936
+ opts.sessionEvents,
12937
+ opts.eventRing,
12938
+ opts.buildOrchestratorMcp,
12939
+ opts.daemonMcpUrl,
12940
+ opts.provisionWorktree
12941
+ );
12942
+ if (handled) return;
12943
+ }
12944
+ if (path === "/workspaces" && req.method === "GET") {
12945
+ try {
12946
+ const config = await loadWorkspacesConfig();
12947
+ res.writeHead(200, { "content-type": "application/json" });
12948
+ res.end(JSON.stringify(config));
12949
+ } catch (err) {
12950
+ res.writeHead(500, { "content-type": "application/json" });
12951
+ res.end(
12952
+ JSON.stringify({
12953
+ error: "workspaces_load_failed",
12954
+ message: err instanceof Error ? err.message : String(err)
12955
+ })
12956
+ );
12957
+ }
12958
+ return;
12959
+ }
12960
+ if (path === "/workspaces" && req.method === "POST") {
12961
+ const gate = checkSessionsToken(req);
12962
+ if (gate !== "ok") {
12963
+ rejectUnauthorizedSession(req, res, gate);
12964
+ return;
12965
+ }
12966
+ const body = await readJsonBody(req);
12967
+ if (!body || typeof body.path !== "string" || !body.path) {
12968
+ res.writeHead(400, { "content-type": "application/json" });
12969
+ res.end(JSON.stringify({ error: "missing_path" }));
12970
+ return;
12971
+ }
12972
+ if (!isAbsolute(body.path)) {
12973
+ res.writeHead(400, { "content-type": "application/json" });
12974
+ res.end(
12975
+ JSON.stringify({
12976
+ error: "workspace_path_not_absolute",
12977
+ message: `path must be absolute, got "${body.path}".`
12978
+ })
12979
+ );
12980
+ return;
12981
+ }
12982
+ try {
12983
+ await stat(body.path);
12984
+ } catch {
12985
+ res.writeHead(400, { "content-type": "application/json" });
12986
+ res.end(
12987
+ JSON.stringify({
12988
+ error: "workspace_path_not_found",
12989
+ message: `"${body.path}" doesn't exist.`
12990
+ })
12991
+ );
12992
+ return;
12993
+ }
12994
+ if (body.slug !== void 0 && typeof body.slug !== "string") {
12995
+ res.writeHead(400, { "content-type": "application/json" });
12996
+ res.end(JSON.stringify({ error: "invalid_slug" }));
12997
+ return;
12998
+ }
12999
+ if (body.label !== void 0 && typeof body.label !== "string") {
13000
+ res.writeHead(400, { "content-type": "application/json" });
13001
+ res.end(JSON.stringify({ error: "invalid_label" }));
13002
+ return;
13003
+ }
13004
+ try {
13005
+ const config = await loadWorkspacesConfig();
13006
+ const next = addWorkspace(config, {
13007
+ slug: body.slug || basename(body.path),
13008
+ path: body.path,
13009
+ ...body.label ? { label: body.label } : {}
13010
+ });
13011
+ await saveWorkspacesConfig(next);
13012
+ res.writeHead(201, { "content-type": "application/json" });
13013
+ res.end(JSON.stringify(next));
13014
+ } catch (err) {
13015
+ res.writeHead(400, { "content-type": "application/json" });
13016
+ res.end(
13017
+ JSON.stringify({
13018
+ error: "workspace_add_failed",
13019
+ message: err instanceof Error ? err.message : String(err)
13020
+ })
13021
+ );
13022
+ }
13023
+ return;
13024
+ }
13025
+ if (path === "/workspaces/active" && req.method === "PUT") {
11720
13026
  const gate = checkSessionsToken(req);
11721
13027
  if (gate !== "ok") {
11722
13028
  rejectUnauthorizedSession(req, res, gate);
11723
13029
  return;
11724
13030
  }
11725
- await handleFileUpload(req, res, url);
11726
- return;
11727
- }
11728
- if (opts.sessions && path.startsWith("/sessions")) {
11729
- if (isMutatingSessionsRoute(req.method ?? "GET", path)) {
11730
- const gate = checkSessionsToken(req);
11731
- if (gate !== "ok") {
11732
- rejectUnauthorizedSession(req, res, gate);
11733
- return;
11734
- }
13031
+ const body = await readJsonBody(req);
13032
+ if (!body || typeof body.slug !== "string" || !body.slug) {
13033
+ res.writeHead(400, { "content-type": "application/json" });
13034
+ res.end(JSON.stringify({ error: "missing_slug" }));
13035
+ return;
11735
13036
  }
11736
- const handled = await handleSessions(
11737
- req,
11738
- res,
11739
- path,
11740
- opts.sessions,
11741
- opts.resolveAgentAdapter,
11742
- opts.ptyEnabled === true,
11743
- opts.resolveBrowserAdapter,
11744
- opts.listBrowserAdapters,
11745
- opts.sessionEvents,
11746
- opts.eventRing,
11747
- opts.buildOrchestratorMcp,
11748
- opts.daemonMcpUrl
11749
- );
11750
- if (handled) return;
11751
- }
11752
- if (path === "/workspaces" && req.method === "GET") {
11753
13037
  try {
11754
13038
  const config = await loadWorkspacesConfig();
13039
+ const next = setActiveWorkspace(config, body.slug);
13040
+ await saveWorkspacesConfig(next);
11755
13041
  res.writeHead(200, { "content-type": "application/json" });
11756
- res.end(JSON.stringify(config));
13042
+ res.end(JSON.stringify(next));
11757
13043
  } catch (err) {
11758
- res.writeHead(500, { "content-type": "application/json" });
13044
+ res.writeHead(404, { "content-type": "application/json" });
11759
13045
  res.end(
11760
13046
  JSON.stringify({
11761
- error: "workspaces_load_failed",
13047
+ error: "workspace_not_found",
11762
13048
  message: err instanceof Error ? err.message : String(err)
11763
13049
  })
11764
13050
  );
11765
13051
  }
11766
13052
  return;
11767
13053
  }
13054
+ const workspaceSlugMatch = path.match(/^\/workspaces\/([^/]+)$/);
13055
+ if (workspaceSlugMatch && req.method === "DELETE") {
13056
+ const gate = checkSessionsToken(req);
13057
+ if (gate !== "ok") {
13058
+ rejectUnauthorizedSession(req, res, gate);
13059
+ return;
13060
+ }
13061
+ const slug = decodeURIComponent(workspaceSlugMatch[1] ?? "");
13062
+ const config = await loadWorkspacesConfig();
13063
+ if (!findWorkspace(config, slug)) {
13064
+ res.writeHead(404, { "content-type": "application/json" });
13065
+ res.end(JSON.stringify({ error: "workspace_not_found", slug }));
13066
+ return;
13067
+ }
13068
+ const next = removeWorkspace(config, slug);
13069
+ await saveWorkspacesConfig(next);
13070
+ res.writeHead(200, { "content-type": "application/json" });
13071
+ res.end(JSON.stringify(next));
13072
+ return;
13073
+ }
11768
13074
  if (path === "/mcps/imports" && req.method === "GET") {
11769
13075
  const config = await loadImportedMcps();
11770
13076
  res.writeHead(200, { "content-type": "application/json" });
@@ -11920,6 +13226,41 @@ async function startHttpServer(opts) {
11920
13226
  }
11921
13227
  return;
11922
13228
  }
13229
+ if (path === "/catalog/models" && req.method === "GET") {
13230
+ if (!opts.listCatalogModels) {
13231
+ res.writeHead(501, { "content-type": "application/json" });
13232
+ res.end(
13233
+ JSON.stringify({
13234
+ error: "lister_not_configured",
13235
+ message: "Daemon was started without `listCatalogModels` \u2014 see `buildCatalogModels` in `catalog-models.ts`."
13236
+ })
13237
+ );
13238
+ return;
13239
+ }
13240
+ try {
13241
+ const qs = new URLSearchParams(
13242
+ url.includes("?") ? url.slice(url.indexOf("?") + 1) : ""
13243
+ );
13244
+ const runnableOnlyParam = qs.get("runnableOnly");
13245
+ const catalog = await opts.listCatalogModels({
13246
+ ...qs.get("adapter") ? { adapter: qs.get("adapter") } : {},
13247
+ ...qs.get("vendor") ? { vendor: qs.get("vendor") } : {},
13248
+ ...qs.get("route") ? { route: qs.get("route") } : {},
13249
+ ...runnableOnlyParam === "true" ? { runnableOnly: true } : {}
13250
+ });
13251
+ res.writeHead(200, { "content-type": "application/json" });
13252
+ res.end(JSON.stringify(catalog));
13253
+ } catch (err) {
13254
+ res.writeHead(500, { "content-type": "application/json" });
13255
+ res.end(
13256
+ JSON.stringify({
13257
+ error: "list_failed",
13258
+ message: err instanceof Error ? err.message : String(err)
13259
+ })
13260
+ );
13261
+ }
13262
+ return;
13263
+ }
11923
13264
  if (path === "/presets" && req.method === "GET") {
11924
13265
  const handled = await handlePresets(req, res, path);
11925
13266
  if (handled) return;
@@ -12203,6 +13544,22 @@ function parseOrchestratorField(raw) {
12203
13544
  }
12204
13545
  return void 0;
12205
13546
  }
13547
+ function parseWorktreeField(raw) {
13548
+ const value = typeof raw === "string" ? tryParseJson(raw) ?? raw : raw;
13549
+ if (typeof value === "boolean") return value;
13550
+ if (value === "true") return true;
13551
+ if (value === "false") return false;
13552
+ if (value && typeof value === "object" && !Array.isArray(value)) {
13553
+ const obj = value;
13554
+ const slug = typeof obj.slug === "string" ? obj.slug : void 0;
13555
+ const base = typeof obj.base === "string" ? obj.base : void 0;
13556
+ return {
13557
+ ...slug !== void 0 ? { slug } : {},
13558
+ ...base !== void 0 ? { base } : {}
13559
+ };
13560
+ }
13561
+ return void 0;
13562
+ }
12206
13563
  function parseMcpServersField(raw) {
12207
13564
  const value = typeof raw === "string" ? tryParseJson(raw) : raw;
12208
13565
  if (!Array.isArray(value)) return void 0;
@@ -12253,13 +13610,16 @@ async function resolveSlugFromCwd(cwd) {
12253
13610
  return void 0;
12254
13611
  }
12255
13612
  }
12256
- async function handleSessions(req, res, path, registry, resolveAgentAdapter, ptyEnabled = false, resolveBrowserAdapter, listBrowserAdapters, sessionEvents, eventRing, buildOrchestratorMcp, daemonMcpUrl) {
13613
+ async function handleSessions(req, res, path, registry, resolveAgentAdapter, ptyEnabled = false, resolveBrowserAdapter, listBrowserAdapters, sessionEvents, eventRing, buildOrchestratorMcp, daemonMcpUrl, provisionWorktree) {
12257
13614
  const json = (status, body) => {
12258
13615
  res.writeHead(status, { "content-type": "application/json" });
12259
13616
  res.end(JSON.stringify(body));
12260
13617
  };
12261
13618
  if (path === "/sessions" && req.method === "GET") {
12262
- json(200, { sessions: registry.list() });
13619
+ const reqUrl = req.url ?? "";
13620
+ const queryString = reqUrl.includes("?") ? reqUrl.slice(reqUrl.indexOf("?") + 1) : "";
13621
+ const includeArchived = new URLSearchParams(queryString).get("includeArchived") === "true";
13622
+ json(200, { sessions: registry.list({ includeArchived }) });
12263
13623
  return true;
12264
13624
  }
12265
13625
  if (path === "/sessions/agent" && req.method === "POST") {
@@ -12282,7 +13642,13 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
12282
13642
  return true;
12283
13643
  }
12284
13644
  const result = await spawnAgentSession(
12285
- { registry, resolveAgentAdapter, buildOrchestratorMcp, daemonMcpUrl },
13645
+ {
13646
+ registry,
13647
+ resolveAgentAdapter,
13648
+ buildOrchestratorMcp,
13649
+ daemonMcpUrl,
13650
+ ...provisionWorktree ? { provisionWorktree } : {}
13651
+ },
12286
13652
  {
12287
13653
  adapter,
12288
13654
  ...typeof b.cwd === "string" && b.cwd.length > 0 ? { cwd: b.cwd } : {},
@@ -12326,11 +13692,19 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
12326
13692
  ...b.permissionHold !== void 0 ? (() => {
12327
13693
  const h = typeof b.permissionHold === "boolean" ? b.permissionHold : b.permissionHold === "true" ? true : b.permissionHold === "false" ? false : void 0;
12328
13694
  return h ? { permissionHold: true } : {};
13695
+ })() : {},
13696
+ // Worktree isolation — the HTTP twin of the MCP `agent_start` tool's
13697
+ // `worktree` field. Same `spawnAgentSession` core resolves the
13698
+ // `worktrees.isolation` policy, so `always` bites here too and there's
13699
+ // no policy-bypassing spawn path.
13700
+ ...b.worktree !== void 0 ? (() => {
13701
+ const parsed = parseWorktreeField(b.worktree);
13702
+ return parsed !== void 0 ? { worktree: parsed } : {};
12329
13703
  })() : {}
12330
13704
  }
12331
13705
  );
12332
13706
  if (!result.ok) {
12333
- const status = result.code === "adapter_not_found" || result.code === "no_cwd" ? 404 : result.code === "orchestrator_not_enabled" ? 501 : result.code === "orchestrator_max_depth_exceeded" || result.code === "orchestrator_child_quota_exceeded" || result.code === "role_spawn_denied" ? 409 : result.code === "invalid_role" ? 400 : 500;
13707
+ const status = result.code === "adapter_not_found" || result.code === "no_cwd" ? 404 : result.code === "orchestrator_not_enabled" ? 501 : result.code === "orchestrator_max_depth_exceeded" || result.code === "orchestrator_child_quota_exceeded" || result.code === "role_spawn_denied" ? 409 : result.code === "invalid_role" || result.code === "worktree_requires_explicit_repo" ? 400 : 500;
12334
13708
  json(status, {
12335
13709
  error: result.code,
12336
13710
  message: result.message,
@@ -12549,6 +13923,129 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
12549
13923
  }
12550
13924
  return true;
12551
13925
  }
13926
+ const modelMatch = path.match(/^\/sessions\/([^/]+)\/model$/);
13927
+ if (modelMatch && req.method === "POST") {
13928
+ const id2 = modelMatch[1];
13929
+ if (!id2) return false;
13930
+ const body = await readJsonBody(req);
13931
+ const model = body && typeof body === "object" && typeof body.model === "string" ? body.model : void 0;
13932
+ if (!model) {
13933
+ json(400, { error: "missing_model" });
13934
+ return true;
13935
+ }
13936
+ try {
13937
+ const result = await registry.setModel(id2, model);
13938
+ json(200, { ok: true, id: id2, ...result });
13939
+ } catch (err) {
13940
+ const msg = err instanceof Error ? err.message : String(err);
13941
+ const status = msg.includes("no session") ? 404 : msg.includes("not an agent-cli session") ? 400 : 500;
13942
+ json(status, { error: "set_model_failed", message: msg });
13943
+ }
13944
+ return true;
13945
+ }
13946
+ const restartMatch = path.match(/^\/sessions\/([^/]+)\/restart$/);
13947
+ if (restartMatch && req.method === "POST") {
13948
+ const id2 = restartMatch[1];
13949
+ if (!id2) return false;
13950
+ if (!resolveAgentAdapter) {
13951
+ json(501, {
13952
+ error: "restart_not_enabled",
13953
+ message: "POST /sessions/:id/restart needs the host to inject `resolveAgentAdapter`."
13954
+ });
13955
+ return true;
13956
+ }
13957
+ const prev = registry.findByIdOrName(id2);
13958
+ if (!prev) {
13959
+ json(404, { error: "no_session", message: `no session "${id2}" found` });
13960
+ return true;
13961
+ }
13962
+ if (!prev.adapterSlug) {
13963
+ json(400, {
13964
+ error: "restart_override_invalid",
13965
+ message: "restart-with-override only applies to agent-cli sessions (a PTY/command session has no config axes to override)."
13966
+ });
13967
+ return true;
13968
+ }
13969
+ const body = await readJsonBody(req);
13970
+ const b = body && typeof body === "object" ? body : {};
13971
+ const str = (v) => typeof v === "string" ? v : void 0;
13972
+ const overrides = {
13973
+ ...str(b.model) !== void 0 ? { model: str(b.model) } : {},
13974
+ ...str(b.effort) !== void 0 ? { effort: str(b.effort) } : {},
13975
+ ...b.access && typeof b.access === "object" && str(b.access.profileRef) !== void 0 ? { access: { profileRef: str(b.access.profileRef) } } : {},
13976
+ ...b.route && typeof b.route === "object" && str(b.route.gateway) !== void 0 ? {
13977
+ route: {
13978
+ gateway: str(b.route.gateway),
13979
+ ...str(b.route.baseUrl) !== void 0 ? { baseUrl: str(b.route.baseUrl) } : {}
13980
+ }
13981
+ } : {},
13982
+ ...b.posture !== void 0 ? { posture: b.posture } : {},
13983
+ ...str(b.contextProfile) !== void 0 ? { contextProfile: str(b.contextProfile) } : {},
13984
+ ...str(b.mode) !== void 0 ? { mode: str(b.mode) } : {}
13985
+ };
13986
+ try {
13987
+ const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev, {
13988
+ forceAgentResume: true,
13989
+ overrides
13990
+ });
13991
+ json(200, {
13992
+ ...restarted.desc,
13993
+ resumedFrom: restarted.resumedFrom,
13994
+ resumeVia: restarted.resumeVia,
13995
+ ...restarted.resumeFallback ? { resumeFallback: true } : {}
13996
+ });
13997
+ } catch (err) {
13998
+ if (err instanceof RestartOverrideError) {
13999
+ json(err.status, { error: err.code, message: err.message, sessionId: prev.id });
14000
+ return true;
14001
+ }
14002
+ json(500, {
14003
+ error: "restart_failed",
14004
+ message: err instanceof Error ? err.message : String(err)
14005
+ });
14006
+ }
14007
+ return true;
14008
+ }
14009
+ const effortMatch = path.match(/^\/sessions\/([^/]+)\/effort$/);
14010
+ if (effortMatch && req.method === "POST") {
14011
+ const id2 = effortMatch[1];
14012
+ if (!id2) return false;
14013
+ const body = await readJsonBody(req);
14014
+ const effort = body && typeof body === "object" && typeof body.effort === "string" ? body.effort : void 0;
14015
+ if (!effort) {
14016
+ json(400, { error: "missing_effort" });
14017
+ return true;
14018
+ }
14019
+ try {
14020
+ const result = await registry.setEffort(id2, effort);
14021
+ json(200, { ok: true, id: id2, ...result });
14022
+ } catch (err) {
14023
+ const msg = err instanceof Error ? err.message : String(err);
14024
+ const status = msg.includes("no session") ? 404 : msg.includes("not an agent-cli session") ? 400 : 500;
14025
+ json(status, { error: "set_effort_failed", message: msg });
14026
+ }
14027
+ return true;
14028
+ }
14029
+ const postureMatch = path.match(/^\/sessions\/([^/]+)\/posture$/);
14030
+ if (postureMatch && req.method === "POST") {
14031
+ const id2 = postureMatch[1];
14032
+ if (!id2) return false;
14033
+ const body = await readJsonBody(req);
14034
+ const postureRaw = body && typeof body === "object" && typeof body.posture === "string" ? body.posture : void 0;
14035
+ if (!postureRaw) {
14036
+ json(400, { error: "missing_posture" });
14037
+ return true;
14038
+ }
14039
+ try {
14040
+ const result = await registry.setPosture(id2, parsePostureInput(postureRaw));
14041
+ json(200, { ok: true, id: id2, ...result });
14042
+ } catch (err) {
14043
+ const msg = err instanceof Error ? err.message : String(err);
14044
+ const status = msg.includes("no session") ? 404 : msg.includes("not an agent-cli session") ? 400 : 500;
14045
+ json(status, { error: "set_posture_failed", message: msg });
14046
+ }
14047
+ return true;
14048
+ }
12552
14049
  if (path === "/sessions" && req.method === "POST") {
12553
14050
  const body = await readJsonBody(req);
12554
14051
  if (!body || typeof body !== "object") {
@@ -14389,8 +15886,6 @@ function createRoutineRunner(opts) {
14389
15886
  }
14390
15887
  };
14391
15888
  }
14392
-
14393
- // src/sessions-registry-agent-host.ts
14394
15889
  init_transcript_export();
14395
15890
  var SessionsRegistryAgentHost = class {
14396
15891
  constructor(registry, sessionEvents, resolveAgentAdapter, opts) {
@@ -14405,10 +15900,45 @@ var SessionsRegistryAgentHost = class {
14405
15900
  opts;
14406
15901
  sessionsByLabel = /* @__PURE__ */ new Map();
14407
15902
  async spawn(adapter, opts) {
14408
- const resolved = await this.resolveAgentAdapter(adapter);
14409
- if (!resolved) throw new Error(`adapter '${adapter}' not found`);
14410
15903
  const workspaceSlug = opts.workspaceSlug ?? this.opts?.workspaceSlug ?? "default";
14411
15904
  const cwd = opts.cwd ?? this.opts?.cwd ?? process.cwd();
15905
+ if (opts.sandbox !== void 0) {
15906
+ let sandbox;
15907
+ if (typeof opts.sandbox === "string") {
15908
+ sandbox = opts.sandbox;
15909
+ } else {
15910
+ const parsed = SandboxSpecSchema.safeParse({ config: {}, ...opts.sandbox });
15911
+ if (!parsed.success) {
15912
+ throw new Error(
15913
+ `agent step sandbox spec invalid (provider "${opts.sandbox.provider}"): ${parsed.error.message}`
15914
+ );
15915
+ }
15916
+ sandbox = parsed.data;
15917
+ }
15918
+ const result = await spawnAgentSession(
15919
+ {
15920
+ registry: this.registry,
15921
+ resolveAgentAdapter: this.resolveAgentAdapter,
15922
+ ...this.opts?.resolveSandboxProvider ? { resolveSandboxProvider: this.opts.resolveSandboxProvider } : {}
15923
+ },
15924
+ {
15925
+ adapter,
15926
+ cwd,
15927
+ workspaceSlug,
15928
+ sandbox,
15929
+ label: `agent-step:${adapter}`
15930
+ }
15931
+ );
15932
+ if (!result.ok) {
15933
+ throw new Error(`agent step sandbox spawn failed (${result.code}): ${result.message}`);
15934
+ }
15935
+ if (opts.stepId) {
15936
+ this.sessionsByLabel.set(opts.stepId, result.descriptor.id);
15937
+ }
15938
+ return result.descriptor.id;
15939
+ }
15940
+ const resolved = await this.resolveAgentAdapter(adapter);
15941
+ if (!resolved) throw new Error(`adapter '${adapter}' not found`);
14412
15942
  const agentSession = await resolved.startSession({ cwd });
14413
15943
  const desc = this.registry.spawnAgent({
14414
15944
  workspaceSlug,
@@ -14503,7 +16033,14 @@ var SessionsRegistryAgentHost = class {
14503
16033
  };
14504
16034
  unsubs.push(
14505
16035
  this.sessionEvents.on("session:turn-end", (ev) => {
14506
- if (ev.sessionId === sessionId) done();
16036
+ if (ev.sessionId !== sessionId) return;
16037
+ if (ev.empty === true) {
16038
+ fail(
16039
+ `session ${sessionId} produced an empty turn \u2014 no assistant output or tool call (commonly an auth failure or an invalid model id)`
16040
+ );
16041
+ } else {
16042
+ done();
16043
+ }
14507
16044
  })
14508
16045
  );
14509
16046
  unsubs.push(
@@ -14584,6 +16121,7 @@ function translateStages(stages, workflowId) {
14584
16121
  id: step.label,
14585
16122
  ...step.adapter !== void 0 ? { adapter: step.adapter } : {},
14586
16123
  ...step.sessionRef !== void 0 ? { sessionRef: step.sessionRef } : {},
16124
+ ...step.sandbox !== void 0 ? { sandbox: step.sandbox } : {},
14587
16125
  ...step.cacheable ? { cacheable: true } : {},
14588
16126
  prompt: () => step.prompt ?? "",
14589
16127
  policy: step.policy ?? { awaiting: "fail" }
@@ -14601,6 +16139,45 @@ function translateStages(stages, workflowId) {
14601
16139
  steps
14602
16140
  };
14603
16141
  }
16142
+ function collectAgentSteps(steps) {
16143
+ const collected = [];
16144
+ for (const step of steps) {
16145
+ if (step.kind === "agent") {
16146
+ const adapter = typeof step.adapter === "string" ? step.adapter : void 0;
16147
+ collected.push({ id: step.id, adapter, sessionRef: step.sessionRef });
16148
+ } else if (step.kind === "parallel") {
16149
+ for (const branch of step.branches) collected.push(...collectAgentSteps(branch.steps));
16150
+ } else if (step.kind === "group") {
16151
+ collected.push(...collectAgentSteps(step.steps));
16152
+ } else if (step.kind === "map") ; else if (step.kind === "pipeline") {
16153
+ for (const stage of step.stages) {
16154
+ }
16155
+ } else if (step.kind === "branch") {
16156
+ collected.push(...collectAgentSteps(step.then));
16157
+ if (step.otherwise) collected.push(...collectAgentSteps(step.otherwise));
16158
+ } else if (step.kind === "loop") {
16159
+ collected.push(...collectAgentSteps(step.body));
16160
+ } else if (step.kind === "subworkflow") {
16161
+ collected.push(...collectAgentSteps(step.workflow.steps));
16162
+ }
16163
+ }
16164
+ return collected;
16165
+ }
16166
+ function runtimeWorkflowToStages(workflow) {
16167
+ const agents = collectAgentSteps(workflow.steps);
16168
+ if (agents.length === 0) {
16169
+ return [{ steps: [{ label: "workflow" }] }];
16170
+ }
16171
+ return [
16172
+ {
16173
+ steps: agents.map((a) => ({
16174
+ label: a.id,
16175
+ ...a.adapter !== void 0 ? { adapter: a.adapter } : {},
16176
+ ...a.sessionRef !== void 0 ? { sessionRef: a.sessionRef } : {}
16177
+ }))
16178
+ }
16179
+ ];
16180
+ }
14604
16181
  var DEFAULT_PERSIST_PATH2 = () => join(homedir(), ".agentproto", "workflow-runs.json");
14605
16182
  function loadRuns2(persistPath) {
14606
16183
  const result = /* @__PURE__ */ new Map();
@@ -14658,13 +16235,10 @@ function fireNotifyUrl(run) {
14658
16235
  }).catch(() => void 0);
14659
16236
  }
14660
16237
  function resolveStepSessionId(step, agents) {
14661
- if (step.adapter) {
14662
- return agents.resolveByLabel(step.label);
14663
- }
14664
16238
  if (step.sessionRef) {
14665
16239
  return agents.resolveByLabel(step.sessionRef);
14666
16240
  }
14667
- return void 0;
16241
+ return agents.resolveByLabel(step.label);
14668
16242
  }
14669
16243
  function fillStepStates(stages, defs, agents) {
14670
16244
  const sessionIds = [];
@@ -14724,6 +16298,8 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, cache, cache
14724
16298
  }
14725
16299
  }
14726
16300
  }
16301
+ const sessionIds = fillStepStates(state.run.stages, state.stages, agents);
16302
+ if (sessionIds.length > 0) state.run.result = { sessionIds };
14727
16303
  }
14728
16304
  }
14729
16305
  fireNotifyUrl(state.run);
@@ -14775,7 +16351,8 @@ function createWorkflowRunner(opts) {
14775
16351
  {
14776
16352
  workspaceSlug: input.workspaceSlug,
14777
16353
  cwd: input.cwd,
14778
- notifyUrl: input.notifyUrl
16354
+ notifyUrl: input.notifyUrl,
16355
+ ...opts.resolveSandboxProvider ? { resolveSandboxProvider: opts.resolveSandboxProvider } : {}
14779
16356
  }
14780
16357
  );
14781
16358
  const cache = input.cacheKey ? createFileStepCache(input.cacheKey) : void 0;
@@ -14792,26 +16369,30 @@ function createWorkflowRunner(opts) {
14792
16369
  }
14793
16370
  const handle = await loadWorkflowHandle(args.path);
14794
16371
  const workflow = await compileWorkflow2(handle);
16372
+ const fileStages = runtimeWorkflowToStages(workflow);
14795
16373
  const runId = `wfrun_${randomUUID()}`;
14796
16374
  const run = {
14797
16375
  runId,
14798
16376
  workflowId: handle.id,
14799
16377
  status: "running",
14800
16378
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
14801
- stages: [
14802
- {
14803
- index: 0,
14804
- status: "pending",
14805
- steps: [{ index: 0, label: "workflow", status: "pending" }]
14806
- }
14807
- ]
16379
+ stages: fileStages.map((stage, si) => ({
16380
+ index: si,
16381
+ ...stage.label !== void 0 ? { label: stage.label } : {},
16382
+ status: "pending",
16383
+ steps: stage.steps.map((s, i) => ({
16384
+ index: i,
16385
+ label: s.label,
16386
+ status: "pending"
16387
+ }))
16388
+ }))
14808
16389
  };
14809
16390
  const abort = new AbortController();
14810
16391
  const state = {
14811
16392
  run,
14812
16393
  cancelled: false,
14813
16394
  abort,
14814
- stages: [],
16395
+ stages: fileStages,
14815
16396
  ...args.cwd !== void 0 ? { cwd: args.cwd } : {},
14816
16397
  ...args.workspaceSlug !== void 0 ? { workspaceSlug: args.workspaceSlug } : {}
14817
16398
  };
@@ -14823,7 +16404,8 @@ function createWorkflowRunner(opts) {
14823
16404
  resolveAgentAdapter,
14824
16405
  {
14825
16406
  workspaceSlug: args.workspaceSlug,
14826
- cwd: args.cwd
16407
+ cwd: args.cwd,
16408
+ ...opts.resolveSandboxProvider ? { resolveSandboxProvider: opts.resolveSandboxProvider } : {}
14827
16409
  }
14828
16410
  );
14829
16411
  const cache = args.cacheKey ? createFileStepCache(args.cacheKey) : void 0;
@@ -15588,6 +17170,216 @@ function createOrchestratorInjector(deps2) {
15588
17170
  return { entry, scope, bindLifecycle };
15589
17171
  };
15590
17172
  }
17173
+ var WIDENING_ROUTES = ["openrouter", "requesty", "huggingface"];
17174
+ function normalizeRouterPrefixedId(id) {
17175
+ const firstSlash = id.indexOf("/");
17176
+ if (firstSlash === -1) return id;
17177
+ const head = id.slice(0, firstSlash);
17178
+ if (!WIDENING_ROUTES.includes(head)) return id;
17179
+ const remainder = id.slice(firstSlash + 1);
17180
+ if (!remainder.includes("/") || remainder.includes("@")) return id;
17181
+ return `${remainder}@${head}`;
17182
+ }
17183
+ function tryResolveLlmModelRoute(id) {
17184
+ try {
17185
+ return resolveLlmModelRoute(id);
17186
+ } catch {
17187
+ return void 0;
17188
+ }
17189
+ }
17190
+ function vendorFromIdPrefix(bareId) {
17191
+ if (/^claude[-/]/.test(bareId)) return "anthropic";
17192
+ if (/^(gpt[-/]|o[1-9](-|$)|chatgpt)/.test(bareId)) return "openai";
17193
+ if (/^gemini[-/]/.test(bareId)) return "google";
17194
+ if (/^grok[-/]/.test(bareId)) return "x-ai";
17195
+ if (/^deepseek[-/]/.test(bareId)) return "deepseek";
17196
+ return void 0;
17197
+ }
17198
+ function resolveModelId(id) {
17199
+ const normalized = normalizeRouterPrefixedId(id);
17200
+ const resolved = tryResolveLlmModelRoute(normalized);
17201
+ if (resolved) {
17202
+ return {
17203
+ vendor: resolved.vendor,
17204
+ product: resolved.product,
17205
+ directRoute: resolved.route,
17206
+ ref: formatModelRef(resolved.ref),
17207
+ baseUrl: resolved.transport.baseUrl ?? null,
17208
+ pricing: {
17209
+ inPer1M: resolved.pricing.inputPer1M,
17210
+ outPer1M: resolved.pricing.outputPer1M
17211
+ }
17212
+ };
17213
+ }
17214
+ const parsed = tryParseModelRef(normalized);
17215
+ if (parsed) {
17216
+ return {
17217
+ vendor: parsed.vendor,
17218
+ product: parsed.product,
17219
+ directRoute: parsed.route,
17220
+ ref: formatModelRef(parsed),
17221
+ baseUrl: null,
17222
+ pricing: null
17223
+ };
17224
+ }
17225
+ const vendor = vendorFromIdPrefix(id) ?? "unknown";
17226
+ return {
17227
+ vendor,
17228
+ product: id,
17229
+ directRoute: vendor,
17230
+ ref: `${vendor}/${id}`,
17231
+ baseUrl: null,
17232
+ pricing: null
17233
+ };
17234
+ }
17235
+ function methodsForDirect(descriptor) {
17236
+ const methods = [];
17237
+ if (descriptor?.authSubscription) methods.push("oauth-bearer");
17238
+ if (descriptor?.provider) methods.push("api-key");
17239
+ return methods;
17240
+ }
17241
+ function isDirectRoute(mode, resolved) {
17242
+ return mode === void 0 && resolved.directRoute === resolved.vendor;
17243
+ }
17244
+ function curatedContributions(adapters) {
17245
+ const out = [];
17246
+ for (const adapter of adapters) {
17247
+ for (const model of adapter.models) {
17248
+ const resolved = resolveModelId(model.id);
17249
+ const route = model.mode ?? resolved.directRoute;
17250
+ const methods = isDirectRoute(model.mode, resolved) ? methodsForDirect(adapter.authDescriptor) : ["api-key"];
17251
+ out.push({
17252
+ vendor: resolved.vendor,
17253
+ product: resolved.product,
17254
+ route,
17255
+ ref: resolved.ref,
17256
+ baseUrl: resolved.baseUrl,
17257
+ pricing: resolved.pricing,
17258
+ curated: true,
17259
+ adapterSlug: adapter.slug,
17260
+ ...model.mode ? { adapterMode: model.mode } : {},
17261
+ methods
17262
+ });
17263
+ }
17264
+ }
17265
+ return out;
17266
+ }
17267
+ function widenedContributions(curated) {
17268
+ const seenProducts = /* @__PURE__ */ new Map();
17269
+ for (const c of curated) {
17270
+ const key = `${c.vendor}/${c.product}`;
17271
+ const routes = seenProducts.get(key) ?? /* @__PURE__ */ new Set();
17272
+ routes.add(c.route);
17273
+ seenProducts.set(key, routes);
17274
+ }
17275
+ const out = [];
17276
+ for (const [key, existingRoutes] of seenProducts) {
17277
+ const [vendor, product] = key.split("/", 2);
17278
+ for (const router of WIDENING_ROUTES) {
17279
+ if (existingRoutes.has(router)) continue;
17280
+ const resolved = resolveLlmModelRoute(`${vendor}/${product}@${router}`);
17281
+ if (!resolved) continue;
17282
+ out.push({
17283
+ vendor,
17284
+ product,
17285
+ route: router,
17286
+ ref: formatModelRef(resolved.ref),
17287
+ baseUrl: resolved.transport.baseUrl ?? null,
17288
+ pricing: {
17289
+ inPer1M: resolved.pricing.inputPer1M,
17290
+ outPer1M: resolved.pricing.outputPer1M
17291
+ },
17292
+ curated: false,
17293
+ methods: ["api-key"]
17294
+ });
17295
+ }
17296
+ }
17297
+ return out;
17298
+ }
17299
+ function mergeContributions(contributions) {
17300
+ const rows = /* @__PURE__ */ new Map();
17301
+ for (const c of contributions) {
17302
+ const key = `${c.vendor}\0${c.product}\0${c.route}`;
17303
+ const existing = rows.get(key);
17304
+ if (!existing) {
17305
+ rows.set(key, {
17306
+ vendor: c.vendor,
17307
+ product: c.product,
17308
+ route: c.route,
17309
+ ref: c.ref,
17310
+ baseUrl: c.baseUrl,
17311
+ pricing: c.pricing,
17312
+ curated: c.curated,
17313
+ adapters: c.adapterSlug ? [c.adapterSlug] : [],
17314
+ adapterModes: c.adapterMode ? [c.adapterMode] : [],
17315
+ methods: [...c.methods]
17316
+ });
17317
+ continue;
17318
+ }
17319
+ existing.curated = existing.curated || c.curated;
17320
+ existing.baseUrl = existing.baseUrl ?? c.baseUrl;
17321
+ existing.pricing = existing.pricing ?? c.pricing;
17322
+ if (c.adapterSlug && !existing.adapters.includes(c.adapterSlug)) {
17323
+ existing.adapters.push(c.adapterSlug);
17324
+ }
17325
+ if (c.adapterMode && !existing.adapterModes.includes(c.adapterMode)) {
17326
+ existing.adapterModes.push(c.adapterMode);
17327
+ }
17328
+ for (const m of c.methods) {
17329
+ if (!existing.methods.includes(m)) existing.methods.push(m);
17330
+ }
17331
+ }
17332
+ return [...rows.values()];
17333
+ }
17334
+ function billedVendor(vendor, route) {
17335
+ return route === vendor ? vendor : route;
17336
+ }
17337
+ function buildCatalogModels(input) {
17338
+ const contributions = [
17339
+ ...curatedContributions(input.adapters),
17340
+ ...widenedContributions(curatedContributions(input.adapters))
17341
+ ];
17342
+ const merged = mergeContributions(contributions);
17343
+ const query = input.query ?? {};
17344
+ const vendors = /* @__PURE__ */ new Map();
17345
+ for (const row of merged) {
17346
+ if (query.vendor && row.vendor !== query.vendor) continue;
17347
+ if (query.route && row.route !== query.route) continue;
17348
+ if (query.adapter && !row.adapters.includes(query.adapter)) continue;
17349
+ const manifest = {
17350
+ id: `${row.vendor}/${row.product}@${row.route}`,
17351
+ vendorByRoute: { [row.route]: billedVendor(row.vendor, row.route) },
17352
+ methodsByRoute: { [row.route]: row.methods }
17353
+ };
17354
+ const eligible = eligibleProfiles(input.profiles, manifest, row.route);
17355
+ const runnable = eligible.length > 0;
17356
+ if (query.runnableOnly && !runnable) continue;
17357
+ const route = {
17358
+ route: row.route,
17359
+ ref: row.ref,
17360
+ baseUrl: row.baseUrl,
17361
+ pricing: row.pricing,
17362
+ runnable,
17363
+ eligibleProfiles: eligible.map((p) => p.id),
17364
+ adapterModes: row.adapterModes,
17365
+ adapters: row.adapters,
17366
+ curated: row.curated
17367
+ };
17368
+ const products = vendors.get(row.vendor) ?? /* @__PURE__ */ new Map();
17369
+ vendors.set(row.vendor, products);
17370
+ const routes = products.get(row.product) ?? [];
17371
+ products.set(row.product, routes);
17372
+ routes.push(route);
17373
+ }
17374
+ const result = [...vendors.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([vendor, products]) => ({
17375
+ vendor,
17376
+ products: [...products.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([product, routes]) => ({
17377
+ product,
17378
+ routes: [...routes].sort((a, b) => a.route.localeCompare(b.route))
17379
+ }))
17380
+ }));
17381
+ return { vendors: result };
17382
+ }
15591
17383
  function makeBrowserHandle(entry, resolve14) {
15592
17384
  return {
15593
17385
  slug: entry.id,
@@ -17487,7 +19279,7 @@ var WorkspacePathError = class extends Error {
17487
19279
  };
17488
19280
  function createWorkspaceFs(opts) {
17489
19281
  const root = resolve(opts.workspace);
17490
- function resolvePath4(path) {
19282
+ function resolvePath5(path) {
17491
19283
  if (typeof path !== "string" || path.length === 0) {
17492
19284
  throw new WorkspacePathError("path must be a non-empty string");
17493
19285
  }
@@ -17507,18 +19299,18 @@ function createWorkspaceFs(opts) {
17507
19299
  }
17508
19300
  return {
17509
19301
  async readFile(path) {
17510
- const abs = resolvePath4(path);
19302
+ const abs = resolvePath5(path);
17511
19303
  const buf = await readFile(abs);
17512
19304
  return buf.toString("utf8");
17513
19305
  },
17514
19306
  async writeFile(path, content) {
17515
- const abs = resolvePath4(path);
19307
+ const abs = resolvePath5(path);
17516
19308
  await mkdir(dirname(abs), { recursive: true });
17517
19309
  await writeFile(abs, content);
17518
19310
  },
17519
19311
  async exists(path) {
17520
19312
  try {
17521
- const abs = resolvePath4(path);
19313
+ const abs = resolvePath5(path);
17522
19314
  return existsSync(abs);
17523
19315
  } catch {
17524
19316
  return false;
@@ -17876,6 +19668,62 @@ function isEnoent(err) {
17876
19668
  function errMsg(err) {
17877
19669
  return err instanceof Error ? err.message : String(err);
17878
19670
  }
19671
+ var POSTURE_MODE_VALUES = {
19672
+ default: "default",
19673
+ plan: "plan",
19674
+ "accept-edits": "accept-edits",
19675
+ "bypass-permissions": "bypass",
19676
+ "read-only": "read-only",
19677
+ "full-access": "bypass",
19678
+ build: "default"
19679
+ };
19680
+ function decomposeMode(modes, modeId) {
19681
+ const declared = modes.find((mode) => mode.id === modeId);
19682
+ const kind = declared?.kind ?? inferLegacyModeKind(modeId);
19683
+ if (kind === "route") return { route: { gateway: modeId } };
19684
+ if (kind === "posture") return { posture: POSTURE_MODE_VALUES[modeId] ?? "default" };
19685
+ return { contextProfile: modeId };
19686
+ }
19687
+ function decomposedAxisMatches(cfg, decomposed) {
19688
+ if (decomposed.route) return cfg.route?.gateway === decomposed.route.gateway;
19689
+ if (decomposed.posture !== void 0) return cfg.posture === decomposed.posture;
19690
+ if (decomposed.contextProfile !== void 0) {
19691
+ return cfg.contextProfile === decomposed.contextProfile;
19692
+ }
19693
+ return false;
19694
+ }
19695
+ function composeMode(cfg, modes) {
19696
+ for (const mode of modes) {
19697
+ if (decomposedAxisMatches(cfg, decomposeMode(modes, mode.id))) return mode.id;
19698
+ }
19699
+ return void 0;
19700
+ }
19701
+
19702
+ // src/index.ts
19703
+ init_conversation_store();
19704
+ async function isAgentCliAuthConfigured(slug, descriptor, model) {
19705
+ const config = await loadConfig();
19706
+ const spawnDefaults = resolveSpawnDefaults(config.defaults, slug, {});
19707
+ const pinnedProvider = spawnDefaults.auth.provider;
19708
+ const resolvedProvider = pinnedProvider ?? descriptor.provider ?? (model ? getModelProvider(model) : void 0);
19709
+ const apiKeyStoreCredential = resolvedProvider && spawnDefaults.auth.explicit && spawnDefaults.auth.apiKeyCredential === void 0 ? await (0, providers_store_exports.getProviderKey)(resolvedProvider) : void 0;
19710
+ try {
19711
+ const result = resolveAuthSpec({
19712
+ descriptor,
19713
+ ...model ? { model } : {},
19714
+ ...pinnedProvider ? { requestedProvider: pinnedProvider } : {},
19715
+ ...spawnDefaults.auth.requestedMode ? { requestedMode: spawnDefaults.auth.requestedMode } : {},
19716
+ explicit: spawnDefaults.auth.explicit,
19717
+ ...spawnDefaults.auth.subscriptionCredential !== void 0 ? { subscriptionCredential: spawnDefaults.auth.subscriptionCredential } : {},
19718
+ ...spawnDefaults.auth.apiKeyCredential !== void 0 ? { apiKeyConfigCredential: spawnDefaults.auth.apiKeyCredential } : {},
19719
+ ...apiKeyStoreCredential !== void 0 ? { apiKeyStoreCredential } : {}
19720
+ });
19721
+ return result?.spec.credential !== void 0;
19722
+ } catch (err) {
19723
+ if (err instanceof AuthResolutionError) return false;
19724
+ throw err;
19725
+ }
19726
+ }
17879
19727
 
17880
19728
  // src/index.ts
17881
19729
  var DEFAULT_ALWAYS_ON_TOOLS = [
@@ -18025,6 +19873,10 @@ async function createGateway(opts) {
18025
19873
  sessionEvents,
18026
19874
  resolveAgentAdapter: opts.resolveAgentAdapter,
18027
19875
  persist,
19876
+ // Sandbox-capable agent steps (`AgentStep.sandbox` / workflow_start's
19877
+ // step `sandbox`) resolve providers through the same resolver
19878
+ // `agent_start.sandbox` uses.
19879
+ resolveSandboxProvider: resolveSandboxProviderResolved,
18028
19880
  // Compile a loaded WORKFLOW.md handle into a runnable RuntimeWorkflow
18029
19881
  // for `workflow_run_file` / `startFromFile`. The daemon's workflow
18030
19882
  // surface is agent-step based (like the stage primitive), so no tool/
@@ -18086,8 +19938,10 @@ async function createGateway(opts) {
18086
19938
  webhookNotifier,
18087
19939
  daemonMcpUrl,
18088
19940
  resolveSandboxProvider: resolveSandboxProviderResolved,
19941
+ ...opts.provisionWorktree ? { provisionWorktree: opts.provisionWorktree } : {},
18089
19942
  ...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
18090
- ...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {}
19943
+ ...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
19944
+ ...opts.listCatalogModels ? { listCatalogModels: opts.listCatalogModels } : {}
18091
19945
  });
18092
19946
  registerBrowserTools(server, {
18093
19947
  registry: sessions,
@@ -18241,7 +20095,9 @@ async function createGateway(opts) {
18241
20095
  // `agent_start` tool gets (session-spawn.ts is the shared logic).
18242
20096
  buildOrchestratorMcp: orchestratorInjector,
18243
20097
  daemonMcpUrl,
20098
+ ...opts.provisionWorktree ? { provisionWorktree: opts.provisionWorktree } : {},
18244
20099
  ...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
20100
+ ...opts.listCatalogModels ? { listCatalogModels: opts.listCatalogModels } : {},
18245
20101
  ...opts.resolveBrowserAdapter ? { resolveBrowserAdapter: opts.resolveBrowserAdapter } : {},
18246
20102
  ...opts.listBrowserAdapters ? { listBrowserAdapters: opts.listBrowserAdapters } : {},
18247
20103
  meta: { workspace, registered, startedAt },
@@ -18328,6 +20184,6 @@ var export_providersPath = providers_store_exports.providersPath;
18328
20184
  var export_removeProviderKey = providers_store_exports.removeProviderKey;
18329
20185
  var export_setProviderKey = providers_store_exports.setProviderKey;
18330
20186
 
18331
- export { AuthResolutionError, BUCKETS_ROOT, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, LEGACY_SESSIONS_FILE, PAIRINGS_VERSION, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, bucketDir, bucketSessionsFile, bucketTranscriptDir, composeSessionObservers, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createScopeTokenRegistry, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, declaredPresetToProviderPreset, deriveSessionUsage, fileConversationStore, formatToolCall, formatToolResult, getMcpCredentialDeps, export_getProviderKey as getProviderKey, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isSafeBucketSlug, listBuckets, listPresets, export_loadProviders as loadProviders, makeBrowserAdapterLister, migrateLegacySessionsFile, migrationMarkerPath, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeSkillsOption, parseDuration, policyWatchesSession, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readDaemonRegistry, readRegisteredSlugs, readRuntimeMeta, registerPairingTools, export_removeProviderKey as removeProviderKey, resolveAuthSpec, resolveBucketSlug, resolveSpawnDefaults, setMcpCredentialDeps, export_setProviderKey as setProviderKey, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
20187
+ export { AuthResolutionError, BUCKETS_ROOT, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, LEGACY_SESSIONS_FILE, PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, WORKTREE_ISOLATION_ENV, appendConversationRecord, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, canonicalForModeId, claudeProjectSlug, composeMode, composeSessionObservers, conversationIndexPath, createFileStepCache, createGateway, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createScopeTokenRegistry, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decomposeMode, deriveSessionUsage, fileConversationStore, findConversationRecord, findNativeMode, formatToolCall, formatToolResult, getMcpCredentialDeps, export_getProviderKey as getProviderKey, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isSafeBucketSlug, listBuckets, listPresets, export_loadProviders as loadProviders, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, migrateLegacySessionsFile, migrationMarkerPath, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeModeId, normalizeSkillsOption, normalizeWorktreeField, parseDuration, parseWorktreeIsolationMode, policyWatchesSession, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readConversationIndex, readDaemonRegistry, readRegisteredSlugs, readRuntimeMeta, registerPairingTools, export_removeProviderKey as removeProviderKey, resolveAuthSpec, resolveBucketSlug, resolveNativeLink, resolvePosture, resolveSpawnDefaults, setMcpCredentialDeps, export_setProviderKey as setProviderKey, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, writeDaemonRegistryEntry };
18332
20188
  //# sourceMappingURL=index.mjs.map
18333
20189
  //# sourceMappingURL=index.mjs.map