@agentproto/runtime 3.1.0 → 3.2.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,5 +1,5 @@
1
1
  import { join, resolve, dirname, basename, isAbsolute, normalize, relative, extname, sep, delimiter } from 'path';
2
- import { createReadStream, promises, readdirSync, readFileSync, mkdirSync, writeFileSync, renameSync, existsSync, chmodSync, openSync, closeSync, realpathSync, statSync, createWriteStream } from 'fs';
2
+ import { createReadStream, promises, readFileSync, mkdirSync, writeFileSync, renameSync, readdirSync, existsSync, chmodSync, openSync, closeSync, realpathSync, statSync, createWriteStream } from 'fs';
3
3
  import { homedir, hostname, tmpdir } from 'os';
4
4
  import { createInterface } from 'readline';
5
5
  import { timingSafeEqual, createHmac, randomBytes, randomUUID, createHash } from 'crypto';
@@ -15,7 +15,7 @@ import { resolveCustomRoute, registerCustomRoute, formatModelRef, resolveLlmMode
15
15
  import { findAnthropicGatewayPreset, anthropicGatewayPresetList, getAnthropicGatewayPreset } from '@agentproto/provider-presets';
16
16
  import * as providers_store_star from '@agentproto/providers-store';
17
17
  import { makeCredsStore, makeAdapterResolver, makeAdapterLister, discoverAdapterPackages, makeSetupLedger, makeListTool, makeSetupTool } from '@agentproto/provider-kit';
18
- import { SandboxSpecSchema, resolveLifecyclePolicy, createSandboxAgentSessionHost } from '@agentproto/sandbox';
18
+ import { SandboxSpecSchema, resolveLifecyclePolicy, createSandboxAgentSessionHost, isSandboxBoxGoneError, SandboxHostBootFailedError } from '@agentproto/sandbox';
19
19
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
20
20
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
21
21
  import { createServer as createServer$1 } from 'net';
@@ -25,7 +25,7 @@ import { inferLegacyModeKind, parseModelSwitchCommand, isModelSwitchAcknowledgem
25
25
  import { loadSandboxConfig, resolveCommandSandbox, COMMAND_SANDBOX_MODE_ENV } from '@agentproto/command-sandbox';
26
26
  import { createBrainManager, parseKnowledgeConfig } from '@agentproto/workspace-brain';
27
27
  import { defineHttpDriver } from '@agentproto/driver-http';
28
- import { makeSessionsPanelApp, makeAgentsOverviewApp, makeBureauSessionsApp, makeSessionStoryPanelApp, makeLiveSessionApp, sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, liveSessionApp } from '@agentproto/apps';
28
+ import { makeSessionsPanelApp, makeAgentsOverviewApp, makeBureauSessionsApp, makeSessionStoryPanelApp, makeLiveSessionApp, makeSessionChatApp, makeWorkBoardApp, SESSION_CHAT_APP_ID, sessionsPanelApp, agentsOverviewApp, bureauSessionsApp, sessionStoryApp, liveSessionApp, sessionChatApp, workBoardApp } from '@agentproto/apps';
29
29
  import matter2 from 'gray-matter';
30
30
  import { createServer } from 'http';
31
31
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
@@ -2647,6 +2647,135 @@ async function readCommandLogEntry(sessionId, baseDir) {
2647
2647
  return null;
2648
2648
  }
2649
2649
  }
2650
+ var sandboxLedgerPath = () => process.env.AGENTPROTO_SANDBOX_LEDGER ?? resolve(homedir(), ".agentproto", "sandboxes.json");
2651
+ var tmpSeq = 0;
2652
+ var serialize = (snapshot) => JSON.stringify(snapshot, null, 2) + "\n";
2653
+ var isLedgerState = (value) => value === "booted" || value === "paused" || value === "connected" || value === "stopped" || value === "gone";
2654
+ var isEntry = (value) => {
2655
+ if (typeof value !== "object" || value === null) return false;
2656
+ if (!("sandboxId" in value) || !("provider" in value) || !("state" in value) || !("createdAt" in value) || !("updatedAt" in value)) {
2657
+ return false;
2658
+ }
2659
+ return typeof value.sandboxId === "string" && value.sandboxId.length > 0 && typeof value.provider === "string" && isLedgerState(value.state) && typeof value.createdAt === "string" && typeof value.updatedAt === "string";
2660
+ };
2661
+ function readSandboxLedger(path) {
2662
+ try {
2663
+ const parsed = JSON.parse(
2664
+ readFileSync(path ?? sandboxLedgerPath(), "utf8")
2665
+ );
2666
+ if (!Array.isArray(parsed.sandboxes)) return [];
2667
+ return parsed.sandboxes.filter(isEntry);
2668
+ } catch {
2669
+ return [];
2670
+ }
2671
+ }
2672
+ function upsertSandboxLedger(entry, path) {
2673
+ try {
2674
+ const target = path ?? sandboxLedgerPath();
2675
+ const dir = join(target, "..");
2676
+ mkdirSync(dir, { recursive: true });
2677
+ const existing = readSandboxLedger(target);
2678
+ const merged = [entry, ...existing.filter((e) => e.sandboxId !== entry.sandboxId)];
2679
+ const snapshot = {
2680
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
2681
+ sandboxes: merged
2682
+ };
2683
+ const tmp = `${target}.tmp.${process.pid}.${++tmpSeq}`;
2684
+ writeFileSync(tmp, serialize(snapshot), "utf8");
2685
+ renameSync(tmp, target);
2686
+ } catch {
2687
+ }
2688
+ }
2689
+ function removeSandboxLedgerEntry(sandboxId, path) {
2690
+ try {
2691
+ const target = path ?? sandboxLedgerPath();
2692
+ const existing = readSandboxLedger(target);
2693
+ if (!existing.some((e) => e.sandboxId === sandboxId)) return false;
2694
+ const snapshot = {
2695
+ savedAt: (/* @__PURE__ */ new Date()).toISOString(),
2696
+ sandboxes: existing.filter((e) => e.sandboxId !== sandboxId)
2697
+ };
2698
+ const tmp = `${target}.tmp.${process.pid}.${++tmpSeq}`;
2699
+ writeFileSync(tmp, serialize(snapshot), "utf8");
2700
+ renameSync(tmp, target);
2701
+ return true;
2702
+ } catch {
2703
+ return false;
2704
+ }
2705
+ }
2706
+ var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
2707
+ function recordSandboxBoot(opts) {
2708
+ try {
2709
+ const target = opts.path ?? sandboxLedgerPath();
2710
+ const existing = readSandboxLedger(target);
2711
+ const prior = existing.find((e) => e.sandboxId === opts.sandboxId);
2712
+ const ts = nowIso();
2713
+ const entry = {
2714
+ sandboxId: opts.sandboxId,
2715
+ provider: opts.provider,
2716
+ state: opts.state,
2717
+ createdAt: prior?.createdAt ?? ts,
2718
+ updatedAt: ts,
2719
+ ...opts.label ? { label: opts.label } : prior?.label ? { label: prior.label } : {},
2720
+ ...opts.cwd ? { cwd: opts.cwd } : prior?.cwd ? { cwd: prior.cwd } : {},
2721
+ ...opts.originSessionId ? { originSessionId: opts.originSessionId } : prior?.originSessionId ? { originSessionId: prior.originSessionId } : {},
2722
+ ...opts.expiresAt ? { expiresAt: opts.expiresAt } : {}
2723
+ };
2724
+ upsertSandboxLedger(entry, target);
2725
+ } catch {
2726
+ }
2727
+ }
2728
+ function recordSandboxState(sandboxId, state, path) {
2729
+ try {
2730
+ const target = path ?? sandboxLedgerPath();
2731
+ const prior = readSandboxLedger(target).find((e) => e.sandboxId === sandboxId);
2732
+ if (!prior) return;
2733
+ upsertSandboxLedger(
2734
+ { ...prior, state, updatedAt: nowIso() },
2735
+ target
2736
+ );
2737
+ } catch {
2738
+ }
2739
+ }
2740
+ function recordSandboxLiveness(sandboxId, alive, path) {
2741
+ try {
2742
+ const target = path ?? sandboxLedgerPath();
2743
+ const prior = readSandboxLedger(target).find((e) => e.sandboxId === sandboxId);
2744
+ if (!prior) return;
2745
+ upsertSandboxLedger(
2746
+ {
2747
+ ...prior,
2748
+ ...alive ? {} : { state: "gone" },
2749
+ sandboxAlive: alive,
2750
+ sandboxCheckedAt: nowIso(),
2751
+ updatedAt: nowIso()
2752
+ },
2753
+ target
2754
+ );
2755
+ } catch {
2756
+ }
2757
+ }
2758
+ function recordSandboxOrigin(sandboxId, originSessionId, path) {
2759
+ try {
2760
+ const target = path ?? sandboxLedgerPath();
2761
+ const prior = readSandboxLedger(target).find((e) => e.sandboxId === sandboxId);
2762
+ if (!prior) return;
2763
+ upsertSandboxLedger(
2764
+ { ...prior, originSessionId, updatedAt: nowIso() },
2765
+ target
2766
+ );
2767
+ } catch {
2768
+ }
2769
+ }
2770
+ function resolveReuseFromLedger(reuse, entries) {
2771
+ const matches = entries.filter(
2772
+ (e) => e.label === reuse || e.sandboxId.startsWith(reuse)
2773
+ );
2774
+ const sole = matches.length === 1 ? matches[0] : void 0;
2775
+ if (sole) return { kind: "resolved", sandboxId: sole.sandboxId, entry: sole };
2776
+ if (matches.length > 1) return { kind: "ambiguous", candidates: matches };
2777
+ return { kind: "unresolved" };
2778
+ }
2650
2779
 
2651
2780
  // src/tool-call-log.ts
2652
2781
  init_transcript_writer();
@@ -4997,15 +5126,15 @@ function listBuckets(root) {
4997
5126
  return [];
4998
5127
  }
4999
5128
  }
5000
- var serialize = (snapshot) => JSON.stringify(snapshot, null, 2) + "\n";
5001
- var tmpSeq = 0;
5002
- var tmpPathFor = (target) => `${target}.tmp.${process.pid}.${++tmpSeq}`;
5129
+ var serialize2 = (snapshot) => JSON.stringify(snapshot, null, 2) + "\n";
5130
+ var tmpSeq2 = 0;
5131
+ var tmpPathFor = (target) => `${target}.tmp.${process.pid}.${++tmpSeq2}`;
5003
5132
  async function writeBucketSnapshot(root, slug, snapshot) {
5004
5133
  const dir = bucketDir(root, slug);
5005
5134
  await promises.mkdir(dir, { recursive: true });
5006
5135
  const target = bucketSessionsFile(root, slug);
5007
5136
  const tmp = tmpPathFor(target);
5008
- await promises.writeFile(tmp, serialize(snapshot), "utf8");
5137
+ await promises.writeFile(tmp, serialize2(snapshot), "utf8");
5009
5138
  await promises.rename(tmp, target);
5010
5139
  }
5011
5140
  function writeBucketSnapshotSync(root, slug, snapshot) {
@@ -5013,7 +5142,7 @@ function writeBucketSnapshotSync(root, slug, snapshot) {
5013
5142
  mkdirSync(dir, { recursive: true });
5014
5143
  const target = bucketSessionsFile(root, slug);
5015
5144
  const tmp = tmpPathFor(target);
5016
- writeFileSync(tmp, serialize(snapshot), "utf8");
5145
+ writeFileSync(tmp, serialize2(snapshot), "utf8");
5017
5146
  renameSync(tmp, target);
5018
5147
  }
5019
5148
  function migrateLegacySessionsFile(opts) {
@@ -5181,117 +5310,6 @@ function setMcpCredentialDeps(d) {
5181
5310
  function getMcpCredentialDeps() {
5182
5311
  return deps;
5183
5312
  }
5184
- var sandboxLedgerPath = () => process.env.AGENTPROTO_SANDBOX_LEDGER ?? resolve(homedir(), ".agentproto", "sandboxes.json");
5185
- var tmpSeq2 = 0;
5186
- var serialize2 = (snapshot) => JSON.stringify(snapshot, null, 2) + "\n";
5187
- var isLedgerState = (value) => value === "booted" || value === "paused" || value === "connected" || value === "stopped";
5188
- var isEntry = (value) => {
5189
- if (typeof value !== "object" || value === null) return false;
5190
- if (!("sandboxId" in value) || !("provider" in value) || !("state" in value) || !("createdAt" in value) || !("updatedAt" in value)) {
5191
- return false;
5192
- }
5193
- return typeof value.sandboxId === "string" && value.sandboxId.length > 0 && typeof value.provider === "string" && isLedgerState(value.state) && typeof value.createdAt === "string" && typeof value.updatedAt === "string";
5194
- };
5195
- function readSandboxLedger(path) {
5196
- try {
5197
- const parsed = JSON.parse(
5198
- readFileSync(path ?? sandboxLedgerPath(), "utf8")
5199
- );
5200
- if (!Array.isArray(parsed.sandboxes)) return [];
5201
- return parsed.sandboxes.filter(isEntry);
5202
- } catch {
5203
- return [];
5204
- }
5205
- }
5206
- function upsertSandboxLedger(entry, path) {
5207
- try {
5208
- const target = path ?? sandboxLedgerPath();
5209
- const dir = join(target, "..");
5210
- mkdirSync(dir, { recursive: true });
5211
- const existing = readSandboxLedger(target);
5212
- const merged = [entry, ...existing.filter((e) => e.sandboxId !== entry.sandboxId)];
5213
- const snapshot = {
5214
- savedAt: (/* @__PURE__ */ new Date()).toISOString(),
5215
- sandboxes: merged
5216
- };
5217
- const tmp = `${target}.tmp.${process.pid}.${++tmpSeq2}`;
5218
- writeFileSync(tmp, serialize2(snapshot), "utf8");
5219
- renameSync(tmp, target);
5220
- } catch {
5221
- }
5222
- }
5223
- function removeSandboxLedgerEntry(sandboxId, path) {
5224
- try {
5225
- const target = path ?? sandboxLedgerPath();
5226
- const existing = readSandboxLedger(target);
5227
- if (!existing.some((e) => e.sandboxId === sandboxId)) return false;
5228
- const snapshot = {
5229
- savedAt: (/* @__PURE__ */ new Date()).toISOString(),
5230
- sandboxes: existing.filter((e) => e.sandboxId !== sandboxId)
5231
- };
5232
- const tmp = `${target}.tmp.${process.pid}.${++tmpSeq2}`;
5233
- writeFileSync(tmp, serialize2(snapshot), "utf8");
5234
- renameSync(tmp, target);
5235
- return true;
5236
- } catch {
5237
- return false;
5238
- }
5239
- }
5240
- var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
5241
- function recordSandboxBoot(opts) {
5242
- try {
5243
- const target = opts.path ?? sandboxLedgerPath();
5244
- const existing = readSandboxLedger(target);
5245
- const prior = existing.find((e) => e.sandboxId === opts.sandboxId);
5246
- const ts = nowIso();
5247
- const entry = {
5248
- sandboxId: opts.sandboxId,
5249
- provider: opts.provider,
5250
- state: opts.state,
5251
- createdAt: prior?.createdAt ?? ts,
5252
- updatedAt: ts,
5253
- ...opts.label ? { label: opts.label } : prior?.label ? { label: prior.label } : {},
5254
- ...opts.cwd ? { cwd: opts.cwd } : prior?.cwd ? { cwd: prior.cwd } : {},
5255
- ...opts.originSessionId ? { originSessionId: opts.originSessionId } : prior?.originSessionId ? { originSessionId: prior.originSessionId } : {},
5256
- ...opts.expiresAt ? { expiresAt: opts.expiresAt } : {}
5257
- };
5258
- upsertSandboxLedger(entry, target);
5259
- } catch {
5260
- }
5261
- }
5262
- function recordSandboxState(sandboxId, state, path) {
5263
- try {
5264
- const target = path ?? sandboxLedgerPath();
5265
- const prior = readSandboxLedger(target).find((e) => e.sandboxId === sandboxId);
5266
- if (!prior) return;
5267
- upsertSandboxLedger(
5268
- { ...prior, state, updatedAt: nowIso() },
5269
- target
5270
- );
5271
- } catch {
5272
- }
5273
- }
5274
- function recordSandboxOrigin(sandboxId, originSessionId, path) {
5275
- try {
5276
- const target = path ?? sandboxLedgerPath();
5277
- const prior = readSandboxLedger(target).find((e) => e.sandboxId === sandboxId);
5278
- if (!prior) return;
5279
- upsertSandboxLedger(
5280
- { ...prior, originSessionId, updatedAt: nowIso() },
5281
- target
5282
- );
5283
- } catch {
5284
- }
5285
- }
5286
- function resolveReuseFromLedger(reuse, entries) {
5287
- const matches = entries.filter(
5288
- (e) => e.label === reuse || e.sandboxId.startsWith(reuse)
5289
- );
5290
- const sole = matches.length === 1 ? matches[0] : void 0;
5291
- if (sole) return { kind: "resolved", sandboxId: sole.sandboxId, entry: sole };
5292
- if (matches.length > 1) return { kind: "ambiguous", candidates: matches };
5293
- return { kind: "unresolved" };
5294
- }
5295
5313
 
5296
5314
  // src/sandbox-agent-session-proxy.ts
5297
5315
  var MAX_POLL_MS = 49e3;
@@ -5648,6 +5666,24 @@ async function installApp(client, dir) {
5648
5666
  }
5649
5667
  return appId;
5650
5668
  }
5669
+ function serveLogPath(dir) {
5670
+ return `${dir.replace(/\/+$/, "")}/.agentproto/app-serve.log`;
5671
+ }
5672
+ function extractServeError(logText) {
5673
+ const lines = logText.split("\n").map((l) => l.trimEnd()).filter((l) => l.includes("agentproto app serve:") && !l.includes("serving "));
5674
+ if (lines.length === 0) return void 0;
5675
+ return lines[lines.length - 1];
5676
+ }
5677
+ async function readServeError(client, dir) {
5678
+ try {
5679
+ const res = await client.callTool("file_read", { path: serveLogPath(dir) });
5680
+ const text10 = firstText(res);
5681
+ if (text10 === void 0) return void 0;
5682
+ return extractServeError(text10);
5683
+ } catch {
5684
+ return void 0;
5685
+ }
5686
+ }
5651
5687
  async function launchServeProcess(client, dir, port) {
5652
5688
  const script = buildServeLaunchScript(dir, port);
5653
5689
  const res = await client.callTool("command_execute", {
@@ -5703,9 +5739,13 @@ async function startSandboxAppServe(host, req, opts) {
5703
5739
  ...opts?.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
5704
5740
  ...opts?.intervalMs !== void 0 ? { intervalMs: opts.intervalMs } : {}
5705
5741
  });
5742
+ let message;
5743
+ if (!ready) {
5744
+ message = await readServeError(client, req.dir);
5745
+ }
5706
5746
  return {
5707
5747
  ok: true,
5708
- appServe: { appId, dir: req.dir, port, url, ready }
5748
+ appServe: { appId, dir: req.dir, port, url, ready, ...message !== void 0 ? { message } : {} }
5709
5749
  };
5710
5750
  } catch (err) {
5711
5751
  return {
@@ -6452,6 +6492,12 @@ function gcSpawnClaims(claims, now) {
6452
6492
  resolved.sort((a, b) => a[1].resolvedAt - b[1].resolvedAt);
6453
6493
  for (const [k] of resolved.slice(0, excess)) claims.delete(k);
6454
6494
  }
6495
+ function findWorktreeLabelCwdCollision(registry, excludeId, label, cwd) {
6496
+ if (!label) return void 0;
6497
+ return registry.list().find(
6498
+ (s) => s.id !== excludeId && s.label === label && s.cwd === cwd && (s.status === "running" || s.status === "starting")
6499
+ );
6500
+ }
6455
6501
  function profileMethodToAuthMode(method) {
6456
6502
  return method === "oauth-bearer" ? "subscription" : "api-key";
6457
6503
  }
@@ -6844,6 +6890,9 @@ async function spawnAgentSession(deps2, input) {
6844
6890
  };
6845
6891
  }
6846
6892
  worktreeRequest = decision.request;
6893
+ if (worktreeRequest.async === void 0) {
6894
+ worktreeRequest = { ...worktreeRequest, async: !input.wait };
6895
+ }
6847
6896
  worktreeAutoProvisioned = decision.implicit;
6848
6897
  }
6849
6898
  }
@@ -7347,6 +7396,16 @@ async function spawnAgentSession(deps2, input) {
7347
7396
  return;
7348
7397
  }
7349
7398
  const finalCwd = outcome.isolated ? outcome.cwd : baseCwd;
7399
+ if (outcome.isolated) {
7400
+ const dupe = findWorktreeLabelCwdCollision(registry, pendingDesc.id, input.label, finalCwd);
7401
+ if (dupe) {
7402
+ registry.settlePendingAgent(pendingDesc.id, {
7403
+ ok: false,
7404
+ message: `agent_start: refused \u2014 another LIVE session ("${dupe.id}") already has the same label ("${input.label}") and cwd ("${finalCwd}") as this worktree spawn. Not starting a second agent in the same worktree.`
7405
+ });
7406
+ return;
7407
+ }
7408
+ }
7350
7409
  try {
7351
7410
  const agentSession2 = await resolved.startSession({
7352
7411
  cwd: finalCwd,
@@ -7382,16 +7441,6 @@ ${asyncPrompt}`;
7382
7441
  }
7383
7442
  const commandPreview2 = resolved.commandPreview;
7384
7443
  const readUsage2 = resolved.readUsage ? () => resolved.readUsage(agentSession2.sessionId) : void 0;
7385
- if (pendingDesc.label) {
7386
- const dupe = registry.list().find(
7387
- (s) => s.id !== pendingDesc.id && s.label === pendingDesc.label && s.cwd === finalCwd && (s.status === "running" || s.status === "starting")
7388
- );
7389
- if (dupe) {
7390
- console.warn(
7391
- `[agent_start] another LIVE session ("${dupe.id}") already has the same label ("${pendingDesc.label}") and cwd ("${finalCwd}") as this one. If this is a retried spawn rather than a deliberate parallel run, both are now editing the same working directory concurrently \u2014 check before proceeding.`
7392
- );
7393
- }
7394
- }
7395
7444
  registry.settlePendingAgent(pendingDesc.id, {
7396
7445
  ok: true,
7397
7446
  agentSession: agentSession2,
@@ -7428,13 +7477,26 @@ ${asyncPrompt}`;
7428
7477
  message: `agent_start: worktree provisioning failed \u2014 ${err instanceof Error ? err.message : String(err)}`
7429
7478
  });
7430
7479
  }
7431
- if (outcome.isolated) cwd = outcome.cwd;
7480
+ if (outcome.isolated) {
7481
+ cwd = outcome.cwd;
7482
+ const dupe = findWorktreeLabelCwdCollision(registry, mintedSessionId, input.label, cwd);
7483
+ if (dupe) {
7484
+ return finish({
7485
+ ok: true,
7486
+ descriptor: dupe,
7487
+ deduped: true,
7488
+ dedupeSource: "worktree-cwd",
7489
+ ...spawnWarnings.length ? { warnings: spawnWarnings } : {}
7490
+ });
7491
+ }
7492
+ }
7432
7493
  }
7433
7494
  let liveSessionId;
7434
7495
  let agentSession;
7435
7496
  let commandPreview;
7436
7497
  let readUsage;
7437
7498
  let sandboxId;
7499
+ let sandboxProvider;
7438
7500
  let sandboxTeardown;
7439
7501
  let sandboxPorts;
7440
7502
  let appServe;
@@ -7471,6 +7533,7 @@ ${asyncPrompt}`;
7471
7533
  agentSession = booted.agentSession;
7472
7534
  commandPreview = booted.commandPreview;
7473
7535
  sandboxId = booted.sandboxId;
7536
+ sandboxProvider = booted.provider;
7474
7537
  sandboxTeardown = booted.sandboxTeardown;
7475
7538
  sandboxPorts = booted.sandboxPorts;
7476
7539
  appServe = booted.appServe;
@@ -7635,6 +7698,7 @@ ${effectivePrompt}`;
7635
7698
  }
7636
7699
  } : {},
7637
7700
  ...sandboxId ? { remote: true, sandboxId } : {},
7701
+ ...sandboxProvider ? { sandboxProvider } : {},
7638
7702
  ...sandboxTeardown ? { sandboxTeardown } : {},
7639
7703
  ...sandboxPorts ? { sandboxPorts } : {},
7640
7704
  ...appServe ? { appServe } : {},
@@ -7864,6 +7928,18 @@ async function bootSandboxAgentSession(opts) {
7864
7928
  secrets: { slugs, resolver: resolveSandboxSecret }
7865
7929
  });
7866
7930
  } catch (err) {
7931
+ if (reuseSandboxId !== void 0 && isSandboxBoxGoneError(err)) {
7932
+ recordSandboxLiveness(reuseSandboxId, false);
7933
+ }
7934
+ if (err instanceof SandboxHostBootFailedError) {
7935
+ markFailedBootLedger({
7936
+ sandboxId: err.sandboxId,
7937
+ provider: providerSlug,
7938
+ state: err.cleanedUp,
7939
+ ...opts.label ? { label: opts.label } : {},
7940
+ ...opts.cwd ? { cwd: opts.cwd } : {}
7941
+ });
7942
+ }
7867
7943
  return reuseSandboxId !== void 0 ? {
7868
7944
  ok: false,
7869
7945
  code: "sandbox_reconnect_failed",
@@ -7897,6 +7973,7 @@ async function bootSandboxAgentSession(opts) {
7897
7973
  remoteSessionId = remoteDesc.id;
7898
7974
  } catch (err) {
7899
7975
  await host.stop().catch(() => void 0);
7976
+ recordSandboxState(host.sandboxId, "stopped");
7900
7977
  return {
7901
7978
  ok: false,
7902
7979
  code: "sandbox_proxy_failed",
@@ -7908,6 +7985,7 @@ async function bootSandboxAgentSession(opts) {
7908
7985
  const serve = await startSandboxAppServe(host, opts.appServe);
7909
7986
  if (!serve.ok) {
7910
7987
  await host.stop().catch(() => void 0);
7988
+ recordSandboxState(host.sandboxId, "stopped");
7911
7989
  return {
7912
7990
  ok: false,
7913
7991
  code: "sandbox_app_serve_failed",
@@ -7926,6 +8004,7 @@ async function bootSandboxAgentSession(opts) {
7926
8004
  }),
7927
8005
  commandPreview: `sandbox:${providerSlug} \u2192 ${opts.adapter}`,
7928
8006
  sandboxId: host.sandboxId,
8007
+ provider: providerSlug,
7929
8008
  sandboxTeardown: lifecyclePolicy.teardown,
7930
8009
  ...host.ports && Object.keys(host.ports).length > 0 ? { sandboxPorts: host.ports } : {},
7931
8010
  ...appServe ? { appServe } : {},
@@ -7955,6 +8034,27 @@ function sandboxAuthFromResolved(auth) {
7955
8034
  ...auth.mode === "api-key" ? auth.credential !== void 0 ? { apiKey: auth.credential } : {} : auth.credential !== void 0 ? { token: auth.credential } : {}
7956
8035
  };
7957
8036
  }
8037
+ function markFailedBootLedger(opts) {
8038
+ try {
8039
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
8040
+ const prior = readSandboxLedger().find((e) => e.sandboxId === opts.sandboxId);
8041
+ if (prior) {
8042
+ recordSandboxState(opts.sandboxId, opts.state);
8043
+ return;
8044
+ }
8045
+ const entry = {
8046
+ sandboxId: opts.sandboxId,
8047
+ provider: opts.provider,
8048
+ state: opts.state,
8049
+ createdAt: ts,
8050
+ updatedAt: ts,
8051
+ ...opts.label ? { label: opts.label } : {},
8052
+ ...opts.cwd ? { cwd: opts.cwd } : {}
8053
+ };
8054
+ upsertSandboxLedger(entry);
8055
+ } catch {
8056
+ }
8057
+ }
7958
8058
  async function resolveSandboxSecret(slug) {
7959
8059
  const { resolveSandboxSecret: resolve31 } = getMcpCredentialDeps();
7960
8060
  if (!resolve31) return null;
@@ -8201,7 +8301,7 @@ function registerAgentTools(server, opts) {
8201
8301
  "Caller-declared 'this is the same logical spawn' token \u2014 a PROMISE, not a guess. A retried agent_start call (e.g. after a slow/lost response) that repeats the same `idempotencyKey` for the same `adapter`+`cwd` within ~10min of a successful spawn gets that SAME session's descriptor back instead of forking a second process \u2014 the response carries `deduped: true` and `dedupeSource: \"explicit\"` so you can tell. Always wins over the daemon's own derived key (see `dedupe` below) when both would apply. Omitting this does NOT mean 'spawn unconditionally' \u2014 see `dedupe`."
8202
8302
  ),
8203
8303
  dedupe: mcpBool.optional().describe(
8204
- 'Per-call override for the daemon\'s `spawn.dedupe` policy \u2014 what happens when NO `idempotencyKey` is supplied. By DEFAULT (`spawn.dedupe: "always"`) a spawn that carries a `label` gets an IMPLICIT key derived from that label plus a hash of `prompt`, and dedupes against it exactly like an explicit key \u2014 set `dedupeSource: "implicit"` on the response (alongside `deduped: true`) so you can tell it wasn\'t your own promise that matched. A spawn with no `label` is never touched by this \u2014 deliberate parallel fan-out into one cwd (a real, exercised pattern here) needs no label and stays exactly as many sessions as you asked for. Pass `dedupe: false` to opt this ONE spawn out of implicit derivation regardless of policy \u2014 the escape hatch, mirroring `attach: false` / `worktree: false`. `dedupe: true` forces derivation even under an `"on-request"` daemon policy, mirroring `attach: true`.'
8304
+ 'Per-call override for the daemon\'s `spawn.dedupe` policy \u2014 what happens when NO `idempotencyKey` is supplied. By DEFAULT (`spawn.dedupe: "always"`) a spawn that carries a `label` gets an IMPLICIT key derived from that label plus a hash of `prompt`, and dedupes against it exactly like an explicit key \u2014 set `dedupeSource: "implicit"` on the response (alongside `deduped: true`) so you can tell it wasn\'t your own promise that matched. A spawn with no `label` is never touched by this \u2014 deliberate parallel fan-out into one cwd (a real, exercised pattern here) needs no label and stays exactly as many sessions as you asked for. Pass `dedupe: false` to opt this ONE spawn out of implicit derivation regardless of policy \u2014 the escape hatch, mirroring `attach: false` / `worktree: false`. `dedupe: true` forces derivation even under an `"on-request"` daemon policy, mirroring `attach: true`. Unrelated and NOT covered by this flag: a `worktree` spawn that lands in a worktree another LIVE session already occupies under the same `label` is always refused (`dedupeSource: "worktree-cwd"`) \u2014 a shared worktree, unlike a shared plain cwd, is never a legitimate fan-out.'
8205
8305
  ),
8206
8306
  permissionHold: mcpBool.optional().describe(
8207
8307
  "Start the session in permission-hold mode: every ACP permission request the agent raises (Write, Bash, \u2026) is SURFACED and HELD in the cross-session inbox (`permissions_list` / `permissions_respond`) instead of auto-answered, and the agent blocks until a human/orchestrator approves or denies it. Default false = today's auto-answer behaviour. ACP adapters only; others ignore it."
@@ -8381,7 +8481,7 @@ function registerAgentTools(server, opts) {
8381
8481
  ),
8382
8482
  base: z.string().min(1).optional().describe("Git ref the worktree branch is cut from. Default 'origin/main'."),
8383
8483
  async: z.boolean().optional().describe(
8384
- 'Return a real, registered session as soon as it\'s minted (status "starting") instead of blocking `agent_start`\'s response on `git worktree add` + the repo\'s setup hooks, which can run minutes. Provisioning + the driver spawn continue in the background; poll the session\'s `status` (flips to "running" on success, "error" with a readable `lastError` on failure \u2014 it never sits in "starting" forever). Any `prompt` is held and dispatched only once the tree and the driver session both exist. Incompatible with `wait` (there is no first-turn output to block on yet) \u2014 combining the two is rejected. Default false (synchronous, today\'s behaviour).'
8484
+ 'Return a real, registered session as soon as it\'s minted (status "starting") instead of blocking `agent_start`\'s response on `git worktree add` + the repo\'s setup hooks, which can run minutes. Provisioning + the driver spawn continue in the background; poll the session\'s `status` (flips to "running" on success, "error" with a readable `lastError` on failure \u2014 it never sits in "starting" forever). Any `prompt` is held and dispatched only once the tree and the driver session both exist. Incompatible with `wait` (there is no first-turn output to block on yet) \u2014 combining the two is rejected. Defaults to true for any spawn that provisions a worktree, UNLESS this call also sets `wait` (which falls back to the old synchronous path instead of conflicting). Pass `false` explicitly to force the old blocking ok/fail contract even without `wait`.'
8385
8485
  )
8386
8486
  }).strict()
8387
8487
  ])
@@ -8503,13 +8603,16 @@ function registerAgentTools(server, opts) {
8503
8603
  );
8504
8604
  server.tool(
8505
8605
  "agent_prompt",
8506
- "Send a follow-up prompt to a live agent session \u2014 multi-turn continuity without re-spawning. The session id comes from `agent_start` (or `agent_sessions_list`). Returns immediately; tail output via `agent_output` or the SSE /sessions/:id/stream endpoint. By default, a session mid-turn rejects the new prompt \u2014 pass `interrupt: true` to cancel the in-flight turn and redirect the SAME session onto this prompt instead, without losing its context (unlike `agent_kill`, which ends the session entirely). `interrupt` is a no-op on an already-idle session.",
8606
+ "Send a follow-up prompt to a live agent session \u2014 multi-turn continuity without re-spawning. The session id comes from `agent_start` (or `agent_sessions_list`). Returns immediately; tail output via `agent_output` or the SSE /sessions/:id/stream endpoint. If the session is mid-turn, the prompt is queued (FIFO) and dispatched automatically when the current turn ends \u2014 so fan-in bursts are delivered in order instead of rejected. Pass `interrupt: true` to cancel the in-flight turn and redirect the SAME session onto this prompt instead, without losing its context (unlike `agent_kill`, which ends the session entirely). `interrupt` is a no-op on an already-idle session.",
8507
8607
  {
8508
8608
  sessionId: sessionIdField,
8509
8609
  id: sessionIdAliasField,
8510
8610
  prompt: z.string().min(1).describe("The next user turn (plain text)."),
8511
8611
  interrupt: z.boolean().optional().describe(
8512
8612
  "When true and the session is mid-turn, cancel the in-flight turn and deliver this prompt on the same session instead of rejecting. No-op when the session is already idle. Default false (mid-turn rejects, as today)."
8613
+ ),
8614
+ queue: z.boolean().optional().describe(
8615
+ "When the session is mid-turn, queue this prompt (FIFO) and dispatch it automatically once the current turn ends instead of rejecting. Default true. Explicit false restores the old reject-when-busy behavior."
8513
8616
  )
8514
8617
  },
8515
8618
  async (input) => {
@@ -8519,6 +8622,11 @@ function registerAgentTools(server, opts) {
8519
8622
  const promptSource = callerScope?.ownerSessionId ?? callerSessionId;
8520
8623
  await registry.enqueuePrompt(sessionId, input.prompt, {
8521
8624
  interrupt: input.interrupt,
8625
+ // Queue by default: a mid-turn session holds the prompt in its
8626
+ // FIFO queue and dispatches it at turn end, so callers never
8627
+ // lose a prompt to the busy rejection. Explicit `queue: false`
8628
+ // restores the old reject-when-busy behavior.
8629
+ queue: input.queue ?? true,
8522
8630
  ...promptSource ? { source: `agent:${promptSource}` } : {}
8523
8631
  });
8524
8632
  return {
@@ -8534,11 +8642,18 @@ function registerAgentTools(server, opts) {
8534
8642
  ]
8535
8643
  };
8536
8644
  } catch (err) {
8645
+ let message = err instanceof Error ? err.message : String(err);
8646
+ if (input.queue === false && message.includes("is mid-turn")) {
8647
+ message = message.replace(
8648
+ "wait for it to finish or cancel",
8649
+ "pass queue: true, or use `agentproto sessions prompt`"
8650
+ );
8651
+ }
8537
8652
  return {
8538
8653
  content: [
8539
8654
  {
8540
8655
  type: "text",
8541
- text: `agent_prompt: ${err instanceof Error ? err.message : String(err)}`
8656
+ text: `agent_prompt: ${message}`
8542
8657
  }
8543
8658
  ],
8544
8659
  isError: true
@@ -10861,7 +10976,9 @@ function toSessionSummary(desc) {
10861
10976
  sandboxId: desc.sandboxId,
10862
10977
  sandboxTeardown: desc.sandboxTeardown,
10863
10978
  sandboxPorts: desc.sandboxPorts,
10864
- appServe: desc.appServe
10979
+ appServe: desc.appServe,
10980
+ sandboxAlive: desc.sandboxAlive,
10981
+ sandboxCheckedAt: desc.sandboxCheckedAt
10865
10982
  };
10866
10983
  }
10867
10984
  var BRACKETED_PASTE_ON = "\x1B[?2004h";
@@ -10924,7 +11041,15 @@ function markHeldId(map, slug, id) {
10924
11041
  }
10925
11042
  var HISTORY_CAP = 200;
10926
11043
  var INTERRUPT_SETTLE_TIMEOUT_MS = 6e4;
11044
+ function stampAlive(desc) {
11045
+ desc.alive = desc.status === "running" || desc.status === "starting";
11046
+ }
11047
+ function stampReadLiveness(desc) {
11048
+ stampProcessAlive(desc);
11049
+ stampSandboxLiveness(desc);
11050
+ }
10927
11051
  function stampProcessAlive(desc) {
11052
+ stampAlive(desc);
10928
11053
  if (desc.pid === null || desc.pid === void 0) {
10929
11054
  delete desc.processAlive;
10930
11055
  return;
@@ -10936,6 +11061,21 @@ function stampProcessAlive(desc) {
10936
11061
  desc.processAlive = false;
10937
11062
  }
10938
11063
  }
11064
+ function stampSandboxLiveness(desc) {
11065
+ if (!desc.sandboxId) {
11066
+ delete desc.sandboxAlive;
11067
+ delete desc.sandboxCheckedAt;
11068
+ return;
11069
+ }
11070
+ const row = readSandboxLedger().find((e) => e.sandboxId === desc.sandboxId);
11071
+ if (!row || row.sandboxAlive === void 0) {
11072
+ delete desc.sandboxAlive;
11073
+ delete desc.sandboxCheckedAt;
11074
+ return;
11075
+ }
11076
+ desc.sandboxAlive = row.sandboxAlive;
11077
+ desc.sandboxCheckedAt = row.sandboxCheckedAt;
11078
+ }
10939
11079
  function killChildIfSpawned(child, signal) {
10940
11080
  if (!child) return;
10941
11081
  if (typeof child.pid !== "number" || child.pid <= 0) return;
@@ -12602,6 +12742,7 @@ ${message}`;
12602
12742
  ...priorCommandSessionId ? { priorCommandSessionId } : {},
12603
12743
  ...input.remote ? { remote: true } : {},
12604
12744
  ...input.sandboxId ? { sandboxId: input.sandboxId } : {},
12745
+ ...input.sandboxProvider ? { sandboxProvider: input.sandboxProvider } : {},
12605
12746
  ...input.sandboxTeardown ? { sandboxTeardown: input.sandboxTeardown } : {},
12606
12747
  ...input.sandboxPorts ? { sandboxPorts: input.sandboxPorts } : {},
12607
12748
  ...input.appServe ? { appServe: input.appServe } : {},
@@ -12710,6 +12851,7 @@ ${message}`;
12710
12851
  ...priorCommandSessionId ? { priorCommandSessionId } : {},
12711
12852
  ...input.remote ? { remote: true } : {},
12712
12853
  ...input.sandboxId ? { sandboxId: input.sandboxId } : {},
12854
+ ...input.sandboxProvider ? { sandboxProvider: input.sandboxProvider } : {},
12713
12855
  ...input.sandboxTeardown ? { sandboxTeardown: input.sandboxTeardown } : {},
12714
12856
  ...input.sandboxPorts ? { sandboxPorts: input.sandboxPorts } : {},
12715
12857
  ...input.resumedFrom ? { resumedFrom: input.resumedFrom } : {},
@@ -13383,7 +13525,7 @@ ${message}`;
13383
13525
  const childrenBusy = childrenBusyCounts();
13384
13526
  return Array.from(sessions.values()).filter((rt) => includeArchived || !rt.desc.archived).sort((a, b) => b.desc.startedAt.localeCompare(a.desc.startedAt)).map((rt) => {
13385
13527
  const desc = rt.desc;
13386
- stampProcessAlive(desc);
13528
+ stampReadLiveness(desc);
13387
13529
  stampInterrupted(desc);
13388
13530
  stampCurrentStatus(rt);
13389
13531
  stampWatchers(desc);
@@ -13401,7 +13543,7 @@ ${message}`;
13401
13543
  const slice = all.slice(offset, offset + limit);
13402
13544
  const summaries = slice.map((rt) => {
13403
13545
  const desc = rt.desc;
13404
- stampProcessAlive(desc);
13546
+ stampReadLiveness(desc);
13405
13547
  stampInterrupted(desc);
13406
13548
  stampCurrentStatus(rt);
13407
13549
  stampWatchers(desc);
@@ -13415,7 +13557,7 @@ ${message}`;
13415
13557
  const rt = sessions.get(id);
13416
13558
  const desc = rt?.desc;
13417
13559
  if (rt && desc) {
13418
- stampProcessAlive(desc);
13560
+ stampReadLiveness(desc);
13419
13561
  stampInterrupted(desc);
13420
13562
  stampCurrentStatus(rt);
13421
13563
  stampWatchers(desc);
@@ -13491,14 +13633,14 @@ ${message}`;
13491
13633
  findByIdOrName(query) {
13492
13634
  const direct = sessions.get(query);
13493
13635
  if (direct) {
13494
- stampProcessAlive(direct.desc);
13636
+ stampReadLiveness(direct.desc);
13495
13637
  stampInterrupted(direct.desc);
13496
13638
  stampCurrentStatus(direct);
13497
13639
  return direct.desc;
13498
13640
  }
13499
13641
  for (const rt of sessions.values()) {
13500
13642
  if (rt.desc.name === query) {
13501
- stampProcessAlive(rt.desc);
13643
+ stampReadLiveness(rt.desc);
13502
13644
  stampInterrupted(rt.desc);
13503
13645
  stampCurrentStatus(rt);
13504
13646
  return rt.desc;
@@ -13696,7 +13838,7 @@ ${message}`;
13696
13838
  }
13697
13839
  rt.desc.archived = true;
13698
13840
  schedulePersist();
13699
- stampProcessAlive(rt.desc);
13841
+ stampReadLiveness(rt.desc);
13700
13842
  return rt.desc;
13701
13843
  },
13702
13844
  unarchiveSession(id) {
@@ -13704,7 +13846,7 @@ ${message}`;
13704
13846
  if (!rt) throw new Error(`unarchiveSession: no session "${id}"`);
13705
13847
  rt.desc.archived = false;
13706
13848
  schedulePersist();
13707
- stampProcessAlive(rt.desc);
13849
+ stampReadLiveness(rt.desc);
13708
13850
  return rt.desc;
13709
13851
  },
13710
13852
  gcSessions(opts2) {
@@ -13752,7 +13894,7 @@ ${message}`;
13752
13894
  renamedByUser: true,
13753
13895
  ts: (/* @__PURE__ */ new Date()).toISOString()
13754
13896
  });
13755
- stampProcessAlive(rt.desc);
13897
+ stampReadLiveness(rt.desc);
13756
13898
  return rt.desc;
13757
13899
  },
13758
13900
  setKeepAlive(id, keepAlive) {
@@ -13760,7 +13902,7 @@ ${message}`;
13760
13902
  if (!rt) throw new Error(`setKeepAlive: no session "${id}"`);
13761
13903
  rt.desc.keepAlive = keepAlive;
13762
13904
  schedulePersist();
13763
- stampProcessAlive(rt.desc);
13905
+ stampReadLiveness(rt.desc);
13764
13906
  return rt.desc;
13765
13907
  },
13766
13908
  setPinned(id, pinned) {
@@ -13774,7 +13916,7 @@ ${message}`;
13774
13916
  pinned,
13775
13917
  ts: (/* @__PURE__ */ new Date()).toISOString()
13776
13918
  });
13777
- stampProcessAlive(rt.desc);
13919
+ stampReadLiveness(rt.desc);
13778
13920
  return rt.desc;
13779
13921
  },
13780
13922
  flagAwaitingInput(id, patch) {
@@ -13798,7 +13940,7 @@ ${message}`;
13798
13940
  ...rt.desc.label ? { label: rt.desc.label } : {},
13799
13941
  ts: (/* @__PURE__ */ new Date()).toISOString()
13800
13942
  });
13801
- stampProcessAlive(rt.desc);
13943
+ stampReadLiveness(rt.desc);
13802
13944
  return rt.desc;
13803
13945
  },
13804
13946
  listPendingPermissions(filter) {
@@ -15565,6 +15707,7 @@ async function restartAgentSession(registry, resolveAgentAdapter, prev, opts = {
15565
15707
  } : {},
15566
15708
  remote: true,
15567
15709
  sandboxId: host.sandboxId,
15710
+ sandboxProvider: providerSlug,
15568
15711
  sandboxTeardown: lifecyclePolicy.teardown,
15569
15712
  ...host.ports && Object.keys(host.ports).length > 0 ? { sandboxPorts: host.ports } : {},
15570
15713
  commandPreview: `sandbox:${providerSlug} \u2192 ${adapterSlug}`,
@@ -16255,6 +16398,7 @@ var compactSessionItem = (s) => ({
16255
16398
  name: s.name,
16256
16399
  label: s.label,
16257
16400
  status: s.status,
16401
+ alive: s.alive,
16258
16402
  pty: s.pty,
16259
16403
  command: s.command,
16260
16404
  cwd: s.cwd,
@@ -20176,7 +20320,18 @@ function makeBuiltinPanelApps(ops) {
20176
20320
  // Live-session widget — resource ui://live_session/view, also bound to
20177
20321
  // `agent_start` via _meta.ui.resourceUri (agent-tools.ts) so a launch
20178
20322
  // auto-renders it.
20179
- makeLiveSessionApp({ httpBaseUrl: ops.httpBaseUrl })
20323
+ makeLiveSessionApp({ httpBaseUrl: ops.httpBaseUrl }),
20324
+ // Session-chat widget — thin launcher for the installed
20325
+ // `@agentik/session-chat` app's standalone UI (deep-linked iframe when
20326
+ // installed, install notice otherwise; see apps/src/session-chat).
20327
+ makeSessionChatApp({
20328
+ httpBaseUrl: ops.httpBaseUrl,
20329
+ isSessionChatInstalled: ops.isSessionChatInstalled
20330
+ }),
20331
+ // Work-board widget — kanban over the Task ledger (see apps/src/
20332
+ // work-board). Read path only; writes go through task_claim/
20333
+ // task_update/task_create over the bridge, same as every other caller.
20334
+ makeWorkBoardApp({ listTasks: ops.listTasks })
20180
20335
  ];
20181
20336
  }
20182
20337
  var PANEL_APP_HANDLES = [
@@ -20184,12 +20339,16 @@ var PANEL_APP_HANDLES = [
20184
20339
  agentsOverviewApp,
20185
20340
  bureauSessionsApp,
20186
20341
  sessionStoryApp,
20187
- liveSessionApp
20342
+ liveSessionApp,
20343
+ sessionChatApp,
20344
+ workBoardApp
20188
20345
  ];
20189
20346
  function builtinPanelCatalogEntries() {
20190
20347
  const apps = makeBuiltinPanelApps({
20191
20348
  listSessions: () => [],
20192
- httpBaseUrl: "http://127.0.0.1:0"
20349
+ httpBaseUrl: "http://127.0.0.1:0",
20350
+ isSessionChatInstalled: () => false,
20351
+ listTasks: (boardId) => ({ boardId: boardId ?? "ws:default", tasks: [] })
20193
20352
  });
20194
20353
  return apps.map((app, i) => {
20195
20354
  const handle = PANEL_APP_HANDLES[i];
@@ -22785,7 +22944,8 @@ async function makeInstalledAppUiApps(appRegistry, cache, existingToolNames) {
22785
22944
  ...ui.csp ? {
22786
22945
  csp: {
22787
22946
  ...ui.csp.connectDomains ? { connectDomains: [...ui.csp.connectDomains] } : {},
22788
- ...ui.csp.resourceDomains ? { resourceDomains: [...ui.csp.resourceDomains] } : {}
22947
+ ...ui.csp.resourceDomains ? { resourceDomains: [...ui.csp.resourceDomains] } : {},
22948
+ ...ui.csp.frameDomains ? { frameDomains: [...ui.csp.frameDomains] } : {}
22789
22949
  }
22790
22950
  } : {}
22791
22951
  });
@@ -23414,6 +23574,8 @@ function buildMsg(body, ctx, fields) {
23414
23574
  const source = ctx.sourceOverride ?? (typeof fields.source === "function" ? fields.source() : fields.source);
23415
23575
  const contactRef = typeof fields.contactRef === "function" ? fields.contactRef() : fields.contactRef;
23416
23576
  const text10 = typeof fields.text === "function" ? fields.text() : fields.text;
23577
+ const displayName = typeof fields.displayName === "function" ? fields.displayName() : fields.displayName;
23578
+ const surface = typeof fields.surface === "function" ? fields.surface() : fields.surface;
23417
23579
  const providerMessageId = typeof fields.providerMessageId === "function" ? fields.providerMessageId() : fields.providerMessageId;
23418
23580
  if (!source) return { ok: false, error: "missing_source" };
23419
23581
  if (!contactRef) return { ok: false, error: "missing_contact_ref" };
@@ -23423,6 +23585,10 @@ function buildMsg(body, ctx, fields) {
23423
23585
  source,
23424
23586
  contactRef,
23425
23587
  text: text10,
23588
+ // Opt-in attribution — only set when the dialect knows a sender name,
23589
+ // so 1:1 bindings keep receiving the raw text unprefixed.
23590
+ ...displayName ? { displayName } : {},
23591
+ ...surface ? { surface } : {},
23426
23592
  ...Array.isArray(body) ? { messages: body } : {}
23427
23593
  };
23428
23594
  return { ok: true, msg, providerMessageId };
@@ -23476,10 +23642,13 @@ function normalizeTelegram(body, ctx) {
23476
23642
  const contactRef = chat && typeof chat.id === "number" ? String(chat.id) : void 0;
23477
23643
  const text10 = typeof message.text === "string" ? message.text : void 0;
23478
23644
  const providerMessageId = typeof message.message_id === "number" ? String(message.message_id) : void 0;
23645
+ const displayName = typeof from?.first_name === "string" && from.first_name ? from.first_name : typeof from?.username === "string" && from.username ? from.username : void 0;
23479
23646
  return buildMsg(body, ctx, {
23480
23647
  source,
23481
23648
  contactRef,
23482
23649
  text: text10,
23650
+ displayName,
23651
+ surface: "telegram",
23483
23652
  providerMessageId
23484
23653
  });
23485
23654
  }
@@ -27447,6 +27616,11 @@ async function startHttpServer(opts) {
27447
27616
  return;
27448
27617
  }
27449
27618
  if (path === "/mcps/imports" && req.method === "POST") {
27619
+ const gate = checkSessionsToken(req);
27620
+ if (gate !== "ok") {
27621
+ rejectUnauthorizedSession(req, res, gate);
27622
+ return;
27623
+ }
27450
27624
  const body = await readJsonBody(req);
27451
27625
  if (!body || typeof body.sourceMcpId !== "string") {
27452
27626
  res.writeHead(400, { "content-type": "application/json" });
@@ -27477,6 +27651,11 @@ async function startHttpServer(opts) {
27477
27651
  }
27478
27652
  const importMatch = path.match(/^\/mcps\/imports\/(.+)$/);
27479
27653
  if (importMatch && req.method === "DELETE") {
27654
+ const gate = checkSessionsToken(req);
27655
+ if (gate !== "ok") {
27656
+ rejectUnauthorizedSession(req, res, gate);
27657
+ return;
27658
+ }
27480
27659
  const id = decodeURIComponent(importMatch[1] ?? "");
27481
27660
  const cfg = await loadImportedMcps();
27482
27661
  if (!cfg.imports.some((e) => e.id === id)) {
@@ -27542,6 +27721,11 @@ async function startHttpServer(opts) {
27542
27721
  return;
27543
27722
  }
27544
27723
  if (path === "/mcps/proxy/call" && req.method === "POST") {
27724
+ const gate = checkSessionsToken(req);
27725
+ if (gate !== "ok") {
27726
+ rejectUnauthorizedSession(req, res, gate);
27727
+ return;
27728
+ }
27545
27729
  if (!opts.mcpProxy) {
27546
27730
  res.writeHead(501, { "content-type": "application/json" });
27547
27731
  res.end(JSON.stringify({ error: "mcp_proxy_not_configured" }));
@@ -27815,6 +27999,17 @@ async function startHttpServer(opts) {
27815
27999
  const handled = await handleTasks(req, res, path, opts.taskLedger);
27816
28000
  if (handled) return;
27817
28001
  }
28002
+ if (path.startsWith("/sandboxes")) {
28003
+ if ((req.method ?? "GET") !== "GET") {
28004
+ const gate = checkSessionsToken(req);
28005
+ if (gate !== "ok") {
28006
+ rejectUnauthorizedSession(req, res, gate);
28007
+ return;
28008
+ }
28009
+ }
28010
+ const handled = await handleSandboxes(req, res, path, opts.resolveSandboxProvider);
28011
+ if (handled) return;
28012
+ }
27818
28013
  if (opts.sessions && path.startsWith("/permissions")) {
27819
28014
  if ((req.method ?? "GET") !== "GET") {
27820
28015
  const gate = checkSessionsToken(req);
@@ -27848,9 +28043,15 @@ async function startHttpServer(opts) {
27848
28043
  if (opts.appRegistry && path.startsWith("/apps/")) {
27849
28044
  const uiMatch = path.match(/^\/apps\/(.+)\/ui$/);
27850
28045
  if (uiMatch && req.method === "GET") {
27851
- if (guardBrowserOrigin(req, res)) return;
28046
+ const uiApp = opts.appRegistry.getApp(decodeURIComponent(uiMatch[1]));
28047
+ if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && guardBrowserOrigin(req, res)) return;
27852
28048
  if (!authorize(req, res)) return;
27853
- await handleAppUiPage(res, decodeURIComponent(uiMatch[1]), opts.appRegistry);
28049
+ await handleAppUiPage(
28050
+ req,
28051
+ res,
28052
+ decodeURIComponent(uiMatch[1]),
28053
+ opts.appRegistry
28054
+ );
27854
28055
  return;
27855
28056
  }
27856
28057
  const toolCallMatch = path.match(/^\/apps\/(.+)\/tool-call$/);
@@ -28218,6 +28419,12 @@ async function* transcriptDiskRecords(id) {
28218
28419
  }
28219
28420
  }
28220
28421
  function buildSpawnSessionHttpArgs(b, adapter, preset) {
28422
+ const maxCostUsdCap = b.maxCostUsd !== void 0 ? parseMaxCostUsdField(b.maxCostUsd) : void 0;
28423
+ const costBudgetCap = b.costBudget !== void 0 ? parseCostBudgetField(b.costBudget) : void 0;
28424
+ const spendCaps = {
28425
+ ...maxCostUsdCap !== void 0 ? maxCostUsdCap : {},
28426
+ ...costBudgetCap !== void 0 ? { costBudget: costBudgetCap } : {}
28427
+ };
28221
28428
  return {
28222
28429
  adapter,
28223
28430
  ...typeof b.origin === "string" && b.origin.length > 0 ? { origin: b.origin } : {},
@@ -28247,6 +28454,8 @@ function buildSpawnSessionHttpArgs(b, adapter, preset) {
28247
28454
  const parsed = parseAuthField(b.auth);
28248
28455
  return parsed !== void 0 ? { auth: parsed } : {};
28249
28456
  })() : {},
28457
+ // Spend caps (parsed above) — see `spendCaps`.
28458
+ ...spendCaps,
28250
28459
  ...typeof b.prompt === "string" ? { prompt: b.prompt } : {},
28251
28460
  ...typeof b.label === "string" ? { label: b.label } : {},
28252
28461
  // Explicit title override (SPEC-3 FIX C, `--title`) — wins over the
@@ -28431,6 +28640,20 @@ function parseRouteField(raw) {
28431
28640
  ...typeof obj.baseUrl === "string" && obj.baseUrl.length > 0 ? { baseUrl: obj.baseUrl } : {}
28432
28641
  };
28433
28642
  }
28643
+ function parseMaxCostUsdField(raw) {
28644
+ const n = typeof raw === "string" ? Number(raw) : raw;
28645
+ return typeof n === "number" && Number.isFinite(n) && n > 0 ? { maxCostUsd: n } : void 0;
28646
+ }
28647
+ function parseCostBudgetField(raw) {
28648
+ const value = typeof raw === "string" ? tryParseJson(raw) : raw;
28649
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
28650
+ const obj = value;
28651
+ const usd = typeof obj.maxCostUsd === "string" ? Number(obj.maxCostUsd) : obj.maxCostUsd;
28652
+ if (typeof usd !== "number" || !Number.isFinite(usd) || usd <= 0) return void 0;
28653
+ if (typeof obj.window !== "string" || obj.window.length === 0) return void 0;
28654
+ if (obj.scope !== "session" && obj.scope !== "profile") return void 0;
28655
+ return { maxCostUsd: usd, window: obj.window, scope: obj.scope };
28656
+ }
28434
28657
  function parseAccessField(raw) {
28435
28658
  const value = typeof raw === "string" ? tryParseJson(raw) : raw;
28436
28659
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
@@ -28491,6 +28714,61 @@ async function resolveSlugFromCwd(cwd) {
28491
28714
  return void 0;
28492
28715
  }
28493
28716
  }
28717
+ async function handleSandboxes(_req, res, path, resolveSandboxProvider2) {
28718
+ const json = (status, body) => {
28719
+ res.writeHead(status, { "content-type": "application/json" });
28720
+ res.end(JSON.stringify(body));
28721
+ };
28722
+ const match = path.match(/^\/sandboxes\/([^/]+)\/alive$/);
28723
+ if (!match || _req.method !== "GET") return false;
28724
+ const sandboxId = decodeURIComponent(match[1]);
28725
+ const row = readSandboxLedger().find((e) => e.sandboxId === sandboxId);
28726
+ if (!row) {
28727
+ json(404, {
28728
+ error: `sandbox "${sandboxId}" is not in the sandbox ledger (~/.agentproto/sandboxes.json)`
28729
+ });
28730
+ return true;
28731
+ }
28732
+ const resolver = resolveSandboxProvider2 ?? makeSandboxResolver(makeSandboxCredsStore());
28733
+ let handle;
28734
+ try {
28735
+ handle = await resolver(row.provider);
28736
+ } catch (err) {
28737
+ json(502, {
28738
+ alive: null,
28739
+ state: row.state,
28740
+ error: `sandbox provider "${row.provider}" could not be resolved \u2014 ${err instanceof Error ? err.message : String(err)}`
28741
+ });
28742
+ return true;
28743
+ }
28744
+ if (!handle?.provider?.probe) {
28745
+ json(501, {
28746
+ alive: null,
28747
+ state: row.state,
28748
+ error: `sandbox provider "${row.provider}" has no probe() \u2014 liveness unknown`
28749
+ });
28750
+ return true;
28751
+ }
28752
+ let probe;
28753
+ try {
28754
+ probe = await handle.provider.probe(row.sandboxId);
28755
+ } catch (err) {
28756
+ json(502, {
28757
+ alive: null,
28758
+ state: row.state,
28759
+ error: `liveness probe failed \u2014 ${err instanceof Error ? err.message : String(err)}`
28760
+ });
28761
+ return true;
28762
+ }
28763
+ recordSandboxLiveness(row.sandboxId, probe.alive);
28764
+ const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
28765
+ if (!probe.alive) {
28766
+ json(410, { alive: false, state: "gone", checkedAt });
28767
+ return true;
28768
+ }
28769
+ json(200, { alive: true, ...probe.state ? { state: probe.state } : { state: row.state }, checkedAt });
28770
+ return true;
28771
+ }
28494
28772
  async function handleSessions(req, res, path, registry, resolveAgentAdapter, ptyEnabled = false, resolveBrowserAdapter, listBrowserAdapters, sessionEvents, eventRing, buildOrchestratorMcp, daemonMcpUrl, provisionWorktree, listCatalogModels, resolveSandboxProvider2) {
28495
28773
  const json = (status, body) => {
28496
28774
  res.writeHead(status, { "content-type": "application/json" });
@@ -29187,7 +29465,7 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
29187
29465
  return true;
29188
29466
  }
29189
29467
  const idMatch = path.match(
29190
- /^\/sessions\/([^/]+)(\/events\/stream|\/stream|\/kill|\/pin|\/preview|\/export|\/conversation|\/events|\/wait|\/chat)?$/
29468
+ /^\/sessions\/([^/]+)(\/events\/stream|\/stream|\/kill|\/pin|\/preview|\/export|\/conversation|\/events|\/wait|\/chat|\/alive)?$/
29191
29469
  );
29192
29470
  if (!idMatch) return false;
29193
29471
  const [, rawIdOrName, suffix] = idMatch;
@@ -29546,6 +29824,15 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
29546
29824
  }
29547
29825
  return true;
29548
29826
  }
29827
+ if (suffix === "/alive" && req.method === "GET") {
29828
+ if (!resolvedDesc) {
29829
+ json(404, { error: "session_not_found", id: rawIdOrName });
29830
+ return true;
29831
+ }
29832
+ const alive = resolvedDesc.status === "running" || resolvedDesc.status === "starting";
29833
+ json(alive ? 200 : 410, { alive, status: resolvedDesc.status });
29834
+ return true;
29835
+ }
29549
29836
  if (!suffix && req.method === "GET") {
29550
29837
  if (!resolvedDesc) {
29551
29838
  json(404, { error: "session_not_found", id: rawIdOrName });
@@ -30296,6 +30583,16 @@ async function handleNativeInbound(req, res, deps2) {
30296
30583
  res.end(JSON.stringify({ error: "missing_text" }));
30297
30584
  return;
30298
30585
  }
30586
+ if (body.display_name !== void 0 && typeof body.display_name !== "string") {
30587
+ res.writeHead(400, { "content-type": "application/json" });
30588
+ res.end(JSON.stringify({ error: "invalid_display_name" }));
30589
+ return;
30590
+ }
30591
+ if (body.surface !== void 0 && typeof body.surface !== "string") {
30592
+ res.writeHead(400, { "content-type": "application/json" });
30593
+ res.end(JSON.stringify({ error: "invalid_surface" }));
30594
+ return;
30595
+ }
30299
30596
  let mode = "route-or-spawn";
30300
30597
  if (body.mode !== void 0) {
30301
30598
  if (!isInboundRouteMode(String(body.mode))) {
@@ -30325,6 +30622,10 @@ async function handleNativeInbound(req, res, deps2) {
30325
30622
  source: body.source,
30326
30623
  contactRef: body.contact_ref,
30327
30624
  text: body.text,
30625
+ // Attribution is opt-in — an absent field must not exist on the message
30626
+ // at all, or attributeInboundText would prefix every 1:1 turn.
30627
+ ...typeof body.display_name === "string" ? { displayName: body.display_name } : {},
30628
+ ...typeof body.surface === "string" ? { surface: body.surface } : {},
30328
30629
  ...Array.isArray(body.messages) ? { messages: body.messages } : {}
30329
30630
  };
30330
30631
  const result = await deps2.routeInboundMessage(msg, mode);
@@ -30448,7 +30749,7 @@ async function handleProviderInbound(req, res, slug, deps2) {
30448
30749
  res.writeHead(200, { "content-type": "application/json" });
30449
30750
  res.end(JSON.stringify(result));
30450
30751
  }
30451
- async function handleAppUiPage(res, appId, appRegistry) {
30752
+ async function handleAppUiPage(req, res, appId, appRegistry) {
30452
30753
  const app = appRegistry.getApp(appId);
30453
30754
  if (!app?.ui) {
30454
30755
  res.writeHead(404, { "content-type": "application/json" });
@@ -30467,14 +30768,59 @@ async function handleAppUiPage(res, appId, appRegistry) {
30467
30768
  );
30468
30769
  return;
30469
30770
  }
30470
- res.writeHead(200, {
30771
+ const embedRequested = new URL(req.url ?? "/", "http://localhost").searchParams.get("embed") === "1";
30772
+ const headers = {
30471
30773
  "content-type": "text/html; charset=utf-8",
30472
- "cache-control": "no-store",
30473
- "x-frame-options": "DENY",
30474
- "content-security-policy": "frame-ancestors 'none'"
30475
- });
30774
+ "cache-control": "no-store"
30775
+ };
30776
+ if (!(embedRequested && iframeEmbedAllowed(req, app))) {
30777
+ headers["x-frame-options"] = "DENY";
30778
+ headers["content-security-policy"] = "frame-ancestors 'none'";
30779
+ }
30780
+ res.writeHead(200, headers);
30476
30781
  res.end(injectStandaloneAppBridge(raw));
30477
30782
  }
30783
+ function iframeEmbedAllowed(req, app) {
30784
+ const secFetchDest = req.headers["sec-fetch-dest"];
30785
+ if (secFetchDest !== void 0 && secFetchDest !== "iframe") return false;
30786
+ const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
30787
+ const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
30788
+ if (!origin && !referer) return false;
30789
+ const candidates = /* @__PURE__ */ new Set();
30790
+ if (origin) candidates.add(origin);
30791
+ if (referer) {
30792
+ try {
30793
+ candidates.add(new URL(referer).origin);
30794
+ } catch {
30795
+ }
30796
+ }
30797
+ const host = req.headers.host;
30798
+ const daemonOrigins = host ? [`http://${host}`, `https://${host}`] : [];
30799
+ for (const candidate of candidates) {
30800
+ if (candidate.startsWith("vscode-webview://")) return true;
30801
+ if (daemonOrigins.includes(candidate)) return true;
30802
+ if ((app.ui?.csp?.frameDomains ?? []).includes(candidate)) return true;
30803
+ }
30804
+ return false;
30805
+ }
30806
+ function iframeEmbedOriginAllowed(req, app) {
30807
+ const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
30808
+ const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
30809
+ if (!origin && !referer) return false;
30810
+ const candidates = /* @__PURE__ */ new Set();
30811
+ if (origin) candidates.add(origin);
30812
+ if (referer) {
30813
+ try {
30814
+ candidates.add(new URL(referer).origin);
30815
+ } catch {
30816
+ }
30817
+ }
30818
+ for (const candidate of candidates) {
30819
+ if (candidate.startsWith("vscode-webview://")) return true;
30820
+ if ((app.ui?.csp?.frameDomains ?? []).includes(candidate)) return true;
30821
+ }
30822
+ return false;
30823
+ }
30478
30824
  function outboundKindForExt(ext) {
30479
30825
  const e = ext.toLowerCase();
30480
30826
  if ([".png", ".jpg", ".jpeg", ".gif", ".webp"].includes(e)) return "photo";
@@ -31177,6 +31523,14 @@ function createInboundEndpointStore(opts) {
31177
31523
  }
31178
31524
 
31179
31525
  // src/inbound-router.ts
31526
+ function attributeInboundText(msg) {
31527
+ const displayName = typeof msg.displayName === "string" && msg.displayName.trim() !== "" ? msg.displayName : void 0;
31528
+ const surface = typeof msg.surface === "string" && msg.surface.trim() !== "" ? msg.surface : void 0;
31529
+ if (!displayName && !surface) return msg.text;
31530
+ if (!surface) return `[${displayName}] ${msg.text}`;
31531
+ const name = displayName ?? msg.contactRef;
31532
+ return `[${name} \xB7 ${surface}] ${msg.text}`;
31533
+ }
31180
31534
  async function routeInboundMessage(deps2, msg, mode) {
31181
31535
  const log = deps2.log ?? (() => {
31182
31536
  });
@@ -31199,7 +31553,7 @@ async function routeInboundMessage(deps2, msg, mode) {
31199
31553
  return { action: "skipped" };
31200
31554
  }
31201
31555
  const routeInto = async (sessionId) => {
31202
- await deps2.enqueuePrompt(sessionId, msg.text);
31556
+ await deps2.enqueuePrompt(sessionId, attributeInboundText(msg), { queue: true });
31203
31557
  deps2.bindings.upsert({
31204
31558
  alias: binding.alias,
31205
31559
  source: binding.source,
@@ -34728,6 +35082,63 @@ function registerSandboxAttachTool(server, opts = {}) {
34728
35082
  );
34729
35083
  }
34730
35084
 
35085
+ // src/sandbox-gc.ts
35086
+ var DEAD_SESSION_STATUSES = /* @__PURE__ */ new Set(["error", "killed", "exited"]);
35087
+ function collectGcCandidates(entries, sessionStatuses) {
35088
+ return entries.filter((e) => e.originSessionId !== void 0 && e.state !== "stopped").map((entry) => ({
35089
+ entry,
35090
+ sessionStatus: sessionStatuses.get(entry.originSessionId)
35091
+ })).filter(
35092
+ (c) => c.sessionStatus !== void 0 && DEAD_SESSION_STATUSES.has(c.sessionStatus)
35093
+ );
35094
+ }
35095
+ async function reapGcEntry(entry, opts) {
35096
+ let handle;
35097
+ try {
35098
+ handle = await opts.resolveProvider(entry.provider);
35099
+ } catch (err) {
35100
+ return {
35101
+ ok: false,
35102
+ error: `provider "${entry.provider}" could not be resolved \u2014 ${err instanceof Error ? err.message : String(err)}`
35103
+ };
35104
+ }
35105
+ if (!handle) return { ok: false, error: `provider "${entry.provider}" not found.` };
35106
+ if (!handle.provider.connect) {
35107
+ return {
35108
+ ok: false,
35109
+ error: `provider "${entry.provider}" has no connect() \u2014 cannot reach the existing box.`
35110
+ };
35111
+ }
35112
+ let booted;
35113
+ try {
35114
+ booted = await handle.provider.connect(
35115
+ entry.sandboxId,
35116
+ { provider: entry.provider, config: {} },
35117
+ { env: {} }
35118
+ );
35119
+ } catch (err) {
35120
+ const message = err instanceof Error ? err.message : String(err);
35121
+ if (/\b404\b|doesn't exist|does not exist|not found|no such/i.test(message)) {
35122
+ recordSandboxState(entry.sandboxId, "stopped", opts.ledgerPath);
35123
+ return { ok: true, action: "stopped" };
35124
+ }
35125
+ return { ok: false, error: `connect to "${entry.sandboxId}" failed \u2014 ${message}` };
35126
+ }
35127
+ const action = opts.pause === true && booted.pause ? "paused" : "stopped";
35128
+ try {
35129
+ if (action === "paused") await booted.pause();
35130
+ else await booted.stop();
35131
+ } catch (err) {
35132
+ return {
35133
+ ok: false,
35134
+ error: `tearing down "${entry.sandboxId}" failed \u2014 ${err instanceof Error ? err.message : String(err)}`
35135
+ };
35136
+ }
35137
+ recordSandboxState(entry.sandboxId, action, opts.ledgerPath);
35138
+ return { ok: true, action };
35139
+ }
35140
+ var GC_REAPABLE_STATES = ["booted", "connected", "paused"];
35141
+
34731
35142
  // src/index.ts
34732
35143
  init_tool_presenter();
34733
35144
  var POLL_INTERVAL_MS2 = 200;
@@ -37909,7 +38320,30 @@ async function createGateway(opts) {
37909
38320
  listSessions: listSessionsFiltered,
37910
38321
  // httpBaseUrl = this daemon's own origin (SSE stream + bridge
37911
38322
  // fallback for the live-session widget).
37912
- httpBaseUrl: `http://127.0.0.1:${port}`
38323
+ httpBaseUrl: `http://127.0.0.1:${port}`,
38324
+ // The session-chat widget is a thin launcher for the installed
38325
+ // `@agentik/session-chat` studio app — resolve installed-ness from
38326
+ // the AppRegistry at call time (not boot) so `app_install`/
38327
+ // `app_uninstall` of that app is reflected without a daemon restart.
38328
+ isSessionChatInstalled: () => {
38329
+ try {
38330
+ return appRegistry.getApp(SESSION_CHAT_APP_ID)?.ui != null;
38331
+ } catch {
38332
+ return false;
38333
+ }
38334
+ },
38335
+ // Work-board widget's read path — the root `/mcp` endpoint has no
38336
+ // scope, so this mount is always the operator caller (default
38337
+ // board `ws:<slug>`); `canAccessBoard` lets the operator read any
38338
+ // board (including a `tree:*` one) when an explicit boardId is
38339
+ // passed in from the panel's board switcher.
38340
+ listTasks: (boardId) => ({
38341
+ boardId: taskLedger.resolveBoardId({ kind: "operator" }, boardId),
38342
+ tasks: taskLedger.list(
38343
+ { ...boardId ? { boardId } : {}, includeClosed: true },
38344
+ { kind: "operator" }
38345
+ )
38346
+ })
37913
38347
  }),
37914
38348
  // Same ptyEnabled gate as terminal_start/terminal_input/… in
37915
38349
  // session-tools.ts — the panel would be able to open the WS but
@@ -38312,6 +38746,6 @@ var export_providersPath = providers_store_exports.providersPath;
38312
38746
  var export_removeProviderKey = providers_store_exports.removeProviderKey;
38313
38747
  var export_setProviderKey = providers_store_exports.setProviderKey;
38314
38748
 
38315
- export { AnthropicRemainingQuotaReader, AuthResolutionError, BUCKETS_ROOT, CLAUDE_CODE_OAUTH_SOURCE, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, HarnessPresetValidationError, INBOUND_PROVIDERS, LEGACY_SESSIONS_FILE, PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, ResumeDisabledError, SESSION_ID_ENV, SubscriptionSourceError, TASK_STATUSES, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, activityCounts, addHarnessPreset, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, buildCatalogProviderModels, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, claudeProjectSlug, composeMode, composeSessionObservers, conversationIndexPath, createActivityProjector, createFileStepCache, createGateway, createInboundEndpointStore, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createPrProvenanceReconciler, createReconnectLogGate, createScopeTokenRegistry, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decomposeMode, defineBraveSearchHttpDriver, defineSerperHttpDriver, deleteUserPreset, deriveSessionUsage, enrichRollupWithProviderQuota, enrichWithRemainingQuota, evaluateCostBudget, fileConversationStore, filterActivities, findConversationRecord, findNativeMode, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getMcpCredentialDeps, export_getProviderKey as getProviderKey, getUserPreset, harnessPresetsPath, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isClosedTaskStatus, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listPresets, listUserPresets, loadHarnessPresets, export_loadProviders as loadProviders, loadQuotaStore, loadUserPresets, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, migrateLegacySessionsFile, migrationMarkerPath, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeInbound, normalizeModeId, normalizeSkillsOption, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseDuration, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, policyToActivities, policyWatchesSession, prToActivities, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readConversationIndex, readDaemonRegistry, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readUsageSnapshots, reapOrphanedDescendants, recordProfileQuota, registerBrainTools, registerBuiltinRoutes, registerPairingTools, registerSandboxAttachTool, registerWebSearchTools, removeHarnessPreset, export_removeProviderKey as removeProviderKey, removeSandboxLedgerEntry, resolveAuthSpec, resolveBucketSlug, resolveNativeLink, resolvePosture, resolveReuseFromLedger, resolveSpawnDefaults, resolveSubscriptionCredential, rollupUsage, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runStallWatchdogPass, sandboxLedgerPath, saveUserPreset, setDefaultPreset, setMcpCredentialDeps, export_setProviderKey as setProviderKey, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, userPresetsPath, verifyInboundSignature, webSearchTool, workflowToActivities, writeDaemonRegistryEntry };
38749
+ export { AnthropicRemainingQuotaReader, AuthResolutionError, BUCKETS_ROOT, CLAUDE_CODE_OAUTH_SOURCE, DEAD_SESSION_STATUSES, DEFAULT_BUCKET, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, GC_REAPABLE_STATES, HarnessPresetValidationError, INBOUND_PROVIDERS, LEGACY_SESSIONS_FILE, PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, ResumeDisabledError, SESSION_ID_ENV, SubscriptionSourceError, TASK_STATUSES, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, activityCounts, addHarnessPreset, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, buildCatalogProviderModels, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, claudeProjectSlug, collectGcCandidates, composeMode, composeSessionObservers, conversationIndexPath, createActivityProjector, createFileStepCache, createGateway, createInboundEndpointStore, createOrchestratorInjector, createOrchestratorMcpServerFactory, createPairingRegistry, createPrProvenanceReconciler, createReconnectLogGate, createScopeTokenRegistry, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decomposeMode, defineBraveSearchHttpDriver, defineSerperHttpDriver, deleteUserPreset, deriveSessionUsage, enrichRollupWithProviderQuota, enrichWithRemainingQuota, evaluateCostBudget, fileConversationStore, filterActivities, findConversationRecord, findNativeMode, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getMcpCredentialDeps, export_getProviderKey as getProviderKey, getUserPreset, harnessPresetsPath, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isClosedTaskStatus, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listPresets, listUserPresets, loadHarnessPresets, export_loadProviders as loadProviders, loadQuotaStore, loadUserPresets, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, migrateLegacySessionsFile, migrationMarkerPath, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeInbound, normalizeModeId, normalizeSkillsOption, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseDuration, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, policyToActivities, policyWatchesSession, prToActivities, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readConversationIndex, readDaemonRegistry, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readUsageSnapshots, reapGcEntry, reapOrphanedDescendants, recordProfileQuota, recordSandboxBoot, recordSandboxLiveness, recordSandboxOrigin, recordSandboxState, registerBrainTools, registerBuiltinRoutes, registerPairingTools, registerSandboxAttachTool, registerWebSearchTools, removeHarnessPreset, export_removeProviderKey as removeProviderKey, removeSandboxLedgerEntry, resolveAuthSpec, resolveBucketSlug, resolveNativeLink, resolvePosture, resolveReuseFromLedger, resolveSpawnDefaults, resolveSubscriptionCredential, rollupUsage, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runStallWatchdogPass, sandboxLedgerPath, saveUserPreset, setDefaultPreset, setMcpCredentialDeps, export_setProviderKey as setProviderKey, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, userPresetsPath, verifyInboundSignature, webSearchTool, workflowToActivities, writeDaemonRegistryEntry };
38316
38750
  //# sourceMappingURL=index.mjs.map
38317
38751
  //# sourceMappingURL=index.mjs.map