@agentproto/runtime 3.3.0 → 3.5.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
@@ -2232,7 +2232,40 @@ async function readPi(conversationId) {
2232
2232
  const { exportPiSession: exportPiSession2 } = await Promise.resolve().then(() => (init_transcript_export(), transcript_export_exports));
2233
2233
  return exportPiSession2(conversationId);
2234
2234
  }
2235
- var CONVERSATION_STORES;
2235
+ function argvBasename(path) {
2236
+ const idx = path.lastIndexOf("/");
2237
+ return idx === -1 ? path : path.slice(idx + 1);
2238
+ }
2239
+ function conversationTerminalSlugFor(subject) {
2240
+ if (subject.adapterSlug && (CONVERSATION_STORES[subject.adapterSlug] || NATIVE_LAUNCH_ARGV[subject.adapterSlug])) {
2241
+ return subject.adapterSlug;
2242
+ }
2243
+ const argv = subject.argv;
2244
+ if (!argv || argv.length === 0 || !argv[0]) return void 0;
2245
+ const bin = argvBasename(argv[0]);
2246
+ for (const [slug, launch] of Object.entries(NATIVE_LAUNCH_ARGV)) {
2247
+ const launchBin = launch[0];
2248
+ if (!launchBin || argvBasename(launchBin) !== bin) continue;
2249
+ if (launchBin === "npx") {
2250
+ if (launch.every((arg, i) => argv[i] === arg)) return slug;
2251
+ continue;
2252
+ }
2253
+ return slug;
2254
+ }
2255
+ return void 0;
2256
+ }
2257
+ function isConversationTerminal(subject) {
2258
+ if (subject.kind !== "terminal") return false;
2259
+ if (conversationTerminalSlugFor(subject) !== void 0) return true;
2260
+ const meta = subject.resumeMetadata;
2261
+ if (meta) {
2262
+ for (const key of CONVERSATION_RESUME_KEYS) {
2263
+ if (meta[key]) return true;
2264
+ }
2265
+ }
2266
+ return false;
2267
+ }
2268
+ var CONVERSATION_STORES, NATIVE_LAUNCH_ARGV, CONVERSATION_RESUME_KEYS;
2236
2269
  var init_conversation_store = __esm({
2237
2270
  "src/conversation-store.ts"() {
2238
2271
  CONVERSATION_STORES = {
@@ -2242,6 +2275,7 @@ var init_conversation_store = __esm({
2242
2275
  // (default). Example: `claude --resume 0e483f81-1a44-4bec-9667-b37158450296`
2243
2276
  outputHint: /claude\s+--resume\s+([0-9a-f-]{8,})/i,
2244
2277
  attachArgv: (conversationId) => ["claude", "--resume", conversationId],
2278
+ transcriptPath: (cwd, conversationId, configDir) => join(claudeCodeProjectDir(cwd, configDir), `${conversationId}.jsonl`),
2245
2279
  configDirEnvVar: "CLAUDE_CONFIG_DIR",
2246
2280
  discover: discoverClaudeCode,
2247
2281
  read: readClaudeCode
@@ -2280,6 +2314,32 @@ var init_conversation_store = __esm({
2280
2314
  read: readPi
2281
2315
  }
2282
2316
  };
2317
+ NATIVE_LAUNCH_ARGV = {
2318
+ "claude-code": ["claude"],
2319
+ hermes: ["hermes", "--tui"],
2320
+ // Bare arm of the same npx package the adapter spawns IS the TUI.
2321
+ opencode: ["npx", "-y", "opencode-ai"],
2322
+ mastracode: ["npx", "-y", "mastracode"],
2323
+ // The adapter bundles the codex-acp wrapper (ACP-only); the interactive
2324
+ // Codex TUI lives in @openai/codex, fetched by npx on first use.
2325
+ codex: ["npx", "-y", "@openai/codex"],
2326
+ // ACP arms add flags/subcommands to these binaries; bare is interactive.
2327
+ gemini: ["gemini"],
2328
+ "grok-cli": ["grok"],
2329
+ pi: ["pi"],
2330
+ jcode: ["jcode"],
2331
+ // `openclaw chat` = "Open a local terminal UI (alias for tui --local)".
2332
+ openclaw: ["openclaw", "chat"],
2333
+ // Generic ACP catalog CLIs whose ACP arm is a separate binary/subcommand;
2334
+ // the bare bin is the interactive TUI.
2335
+ "gemini-cli": ["gemini"],
2336
+ "qwen-code": ["qwen"],
2337
+ "mistral-vibe": ["vibe"],
2338
+ "kimi-cli": ["kimi"]
2339
+ };
2340
+ CONVERSATION_RESUME_KEYS = new Set(
2341
+ Object.values(CONVERSATION_STORES).map((s) => s.storeAs)
2342
+ );
2283
2343
  }
2284
2344
  });
2285
2345
 
@@ -2512,6 +2572,7 @@ var RESUME_STRATEGIES = Object.fromEntries(
2512
2572
  outputHint: store.outputHint,
2513
2573
  storeAs: store.storeAs,
2514
2574
  ...store.configDirEnvVar ? { configDirEnvVar: store.configDirEnvVar } : {},
2575
+ ...store.transcriptPath ? { transcriptPath: store.transcriptPath } : {},
2515
2576
  fsProbe: async (cwd, prevStartedAt, expectedId, configDir) => {
2516
2577
  const candidates = await s.discover({
2517
2578
  cwd,
@@ -2583,6 +2644,21 @@ async function augmentWithFsResume(prev) {
2583
2644
  }
2584
2645
  };
2585
2646
  }
2647
+ async function probeNativeTranscript(prev) {
2648
+ if (!prev.adapterSlug || !prev.cwd) return void 0;
2649
+ const strategy = RESUME_STRATEGIES[prev.adapterSlug];
2650
+ if (!strategy?.transcriptPath) return void 0;
2651
+ const id = prev.resumeMetadata?.[strategy.storeAs] ?? prev.adapterSessionId;
2652
+ if (!id) return void 0;
2653
+ const path = strategy.transcriptPath(prev.cwd, id, prev.adapterConfigDir);
2654
+ let exists = false;
2655
+ try {
2656
+ exists = (await promises.stat(path)).isFile();
2657
+ } catch {
2658
+ exists = false;
2659
+ }
2660
+ return { dir: dirname(path), path, exists };
2661
+ }
2586
2662
  function describeResumePath(prev, opts = {}) {
2587
2663
  const mayPreferNative = prev.pty === true || opts.preferNativeTerminal === true;
2588
2664
  if (prev.adapterSlug && mayPreferNative) {
@@ -2628,6 +2704,9 @@ function tokenizeCommand(s) {
2628
2704
  return out;
2629
2705
  }
2630
2706
 
2707
+ // src/sessions.ts
2708
+ init_conversation_store();
2709
+
2631
2710
  // src/command-log.ts
2632
2711
  init_transcript_writer();
2633
2712
  function writeCommandLogEntry(sessionId, entry, baseDir) {
@@ -4036,7 +4115,13 @@ function resolveWorktreeIdentity(cwd) {
4036
4115
  dir = parent;
4037
4116
  }
4038
4117
  }
4039
- var WIDENING_ROUTES = ["openrouter", "requesty", "huggingface"];
4118
+ var WIDENING_ROUTES = [
4119
+ "openrouter",
4120
+ "requesty",
4121
+ "huggingface",
4122
+ "opencode-go",
4123
+ "opencode"
4124
+ ];
4040
4125
  var VENDOR_COMPATIBILITY_ROUTES = {
4041
4126
  xai: ["xai", "xai-anthropic"]
4042
4127
  };
@@ -4375,9 +4460,12 @@ function reconcileModelRoute(input) {
4375
4460
  }
4376
4461
  function serviceableModelRoutes(model) {
4377
4462
  const routes = /* @__PURE__ */ new Set();
4378
- const provider = getModelProvider(model);
4379
- if (provider) routes.add(provider);
4380
4463
  const parsed = tryParseModelRef(normalizeRouterPrefixedId(model));
4464
+ const selfRouted = parsed !== void 0 && parsed.route === parsed.vendor && WIDENING_ROUTES.includes(parsed.vendor) && tryResolveLlmModelRoute(`${parsed.vendor}/${parsed.product}`)?.pricing.provider === parsed.vendor;
4465
+ if (!selfRouted) {
4466
+ const provider = getModelProvider(model);
4467
+ if (provider) routes.add(provider);
4468
+ }
4381
4469
  if (parsed) {
4382
4470
  if (parsed.route !== parsed.vendor) routes.add(parsed.route);
4383
4471
  if (resolvePricingExact(parsed.product)?.provider === parsed.vendor) {
@@ -8252,8 +8340,10 @@ function registerAgentTools(server, opts) {
8252
8340
  resolveSandboxProvider: resolveSandboxProvider2,
8253
8341
  provisionWorktree,
8254
8342
  resolveWorktreeIsolation,
8255
- supervisor
8343
+ supervisor,
8344
+ defaultAgentPromptInterrupt
8256
8345
  } = opts;
8346
+ const interruptDefault = defaultAgentPromptInterrupt ?? false;
8257
8347
  server.registerTool(
8258
8348
  "agent_start",
8259
8349
  {
@@ -8489,14 +8579,20 @@ function registerAgentTools(server, opts) {
8489
8579
  "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, async }` to pin either, or opt into an early return (`async`, see that field's own description). 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; an EXPLICIT `worktree` on such a nested spawn is REJECTED, not silently ignored \u2014 use `sandbox` to isolate a child) 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`."
8490
8580
  )
8491
8581
  },
8492
- // Live-session widget: rendering agent_start's result auto-mounts the
8493
- // live widget for the new session (ext-apps `_meta.ui.resourceUri` at
8494
- // the tool-definition level — same mechanism as the panel apps in
8495
- // mcp-apps-adapter.ts). `visibility:["model","app"]` keeps agent_start
8496
- // fully usable by the model AND lets the widget re-call it if needed.
8582
+ // Session-chat widget: rendering agent_start's result auto-mounts the
8583
+ // session-chat launcher for the new session (ext-apps
8584
+ // `_meta.ui.resourceUri` at the tool-definition level — same mechanism
8585
+ // as the panel apps in mcp-apps-adapter.ts). The widget reads the
8586
+ // spawned `{ id: "sess_…" }` off the host's tool-result notification
8587
+ // and deep-links the installed `@agentik/session-chat` app straight
8588
+ // into that session (apps/src/session-chat/panel.ts). The older
8589
+ // `ui://live_session/view` resource stays registered for its own
8590
+ // `live_session` tool and other consumers — only this binding moved.
8591
+ // `visibility:["model","app"]` keeps agent_start fully usable by the
8592
+ // model AND lets the widget re-call it if needed.
8497
8593
  _meta: {
8498
8594
  ui: {
8499
- resourceUri: "ui://live_session/view",
8595
+ resourceUri: "ui://agentproto_session_chat/view",
8500
8596
  visibility: ["model", "app"]
8501
8597
  }
8502
8598
  }
@@ -8609,7 +8705,7 @@ function registerAgentTools(server, opts) {
8609
8705
  id: sessionIdAliasField,
8610
8706
  prompt: z.string().min(1).describe("The next user turn (plain text)."),
8611
8707
  interrupt: z.boolean().optional().describe(
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)."
8708
+ "When true and the session is mid-turn, cancel the in-flight turn and deliver this prompt on the same session immediately instead of queueing it behind the current turn. No-op when the session is already idle. UNSET falls back to the daemon default `defaults.agentPromptInterrupt` in config.json (false unless an operator changed it) \u2014 so a mid-turn target queues by default; pass `interrupt: true` explicitly to cut now."
8613
8709
  ),
8614
8710
  queue: z.boolean().optional().describe(
8615
8711
  "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."
@@ -8620,8 +8716,9 @@ function registerAgentTools(server, opts) {
8620
8716
  if (!sessionId) return missingSessionIdError("agent_prompt");
8621
8717
  try {
8622
8718
  const promptSource = callerScope?.ownerSessionId ?? callerSessionId;
8623
- await registry.enqueuePrompt(sessionId, input.prompt, {
8624
- interrupt: input.interrupt,
8719
+ const effectiveInterrupt = input.interrupt ?? interruptDefault;
8720
+ const { queued } = await registry.enqueuePrompt(sessionId, input.prompt, {
8721
+ interrupt: effectiveInterrupt,
8625
8722
  // Queue by default: a mid-turn session holds the prompt in its
8626
8723
  // FIFO queue and dispatches it at turn end, so callers never
8627
8724
  // lose a prompt to the busy rejection. Explicit `queue: false`
@@ -8629,12 +8726,21 @@ function registerAgentTools(server, opts) {
8629
8726
  queue: input.queue ?? true,
8630
8727
  ...promptSource ? { source: `agent:${promptSource}` } : {}
8631
8728
  });
8729
+ const hintQueued = queued && input.interrupt === void 0;
8632
8730
  return {
8633
8731
  content: [
8634
8732
  {
8635
8733
  type: "text",
8636
8734
  text: JSON.stringify(
8637
- { ok: true, sessionId, queued: true },
8735
+ {
8736
+ ok: true,
8737
+ sessionId,
8738
+ queued: true,
8739
+ ...hintQueued ? {
8740
+ delivery: "queued-mid-turn",
8741
+ hint: "Message queued \u2014 it will only be delivered when the target's CURRENT turn ends. If it's urgent, re-send with interrupt: true (cancels the in-flight turn and redirects the session onto this prompt now)."
8742
+ } : {}
8743
+ },
8638
8744
  null,
8639
8745
  2
8640
8746
  )
@@ -8663,9 +8769,12 @@ function registerAgentTools(server, opts) {
8663
8769
  );
8664
8770
  server.tool(
8665
8771
  "message_parent",
8666
- "Report a message UP to the session that spawned you (your parent/supervisor) \u2014 a result, a progress update, or a blocker. No session id needed: the daemon resolves your recorded parent from your own session identity (also visible as the AGENTPROTO_PARENT_SESSION_ID env var). Delivered as a prompt when the parent is idle, or queued onto its next turn when it's mid-turn (never interrupts). Errors if this session has no recorded parent or the parent is gone.",
8772
+ "Report a message UP to the session that spawned you (your parent/supervisor) \u2014 a result, a progress update, or a blocker. No session id needed: the daemon resolves your recorded parent from your own session identity (also visible as the AGENTPROTO_PARENT_SESSION_ID env var). Delivered as a prompt when the parent is idle, or queued onto its next turn when it's mid-turn (never interrupts, by default). Pass `interrupt: true` to CUT a mid-turn parent immediately \u2014 cancel its in-flight turn and redirect it onto this message now (same-context cancel, like `agent_prompt`'s `interrupt`) \u2014 for a genuinely urgent report the parent must act on before it finishes what it's doing. Errors if this session has no recorded parent or the parent is gone.",
8667
8773
  {
8668
- message: z.string().min(1).describe("The message to deliver to your parent session (plain text).")
8774
+ message: z.string().min(1).describe("The message to deliver to your parent session (plain text)."),
8775
+ interrupt: z.boolean().optional().describe(
8776
+ "When true and the parent is mid-turn, cancel its in-flight turn and deliver this message immediately instead of queueing it onto the parent's next turn. No-op when the parent is idle (delivered as a normal prompt either way). UNSET falls back to the daemon default `defaults.agentPromptInterrupt` in config.json (false unless an operator changed it) \u2014 so a report never interrupts by default; pass `interrupt: true` explicitly to cut."
8777
+ )
8669
8778
  },
8670
8779
  async (input) => {
8671
8780
  const fail = (text10) => ({
@@ -8699,11 +8808,12 @@ function registerAgentTools(server, opts) {
8699
8808
  }
8700
8809
  const who = self.label ?? selfId;
8701
8810
  const notice = `[child-message] ${who} (${selfId}): ${input.message}`;
8702
- const done = (delivery) => ({
8811
+ const effectiveInterrupt = input.interrupt ?? interruptDefault;
8812
+ const done = (delivery, extra) => ({
8703
8813
  content: [
8704
8814
  {
8705
8815
  type: "text",
8706
- text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery })
8816
+ text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery, ...extra })
8707
8817
  }
8708
8818
  ]
8709
8819
  });
@@ -8714,12 +8824,27 @@ function registerAgentTools(server, opts) {
8714
8824
  } catch {
8715
8825
  }
8716
8826
  }
8827
+ if (effectiveInterrupt) {
8828
+ try {
8829
+ await registry.enqueuePrompt(parentId, notice, {
8830
+ interrupt: true,
8831
+ origin: `child:${who}`
8832
+ });
8833
+ return done("interrupted");
8834
+ } catch {
8835
+ }
8836
+ }
8717
8837
  if (!registry.stampPendingChildCrashNotice(parentId, notice)) {
8718
8838
  return fail(
8719
8839
  `message_parent: parent session "${parentId}" vanished mid-delivery.`
8720
8840
  );
8721
8841
  }
8722
- return done("queued-next-turn");
8842
+ return done(
8843
+ "queued-next-turn",
8844
+ input.interrupt === void 0 ? {
8845
+ hint: "Message queued \u2014 it will only reach the parent when its CURRENT turn ends. If it's urgent, re-send with interrupt: true (cancels the parent's in-flight turn and redirects it onto this message now)."
8846
+ } : void 0
8847
+ );
8723
8848
  }
8724
8849
  );
8725
8850
  server.tool(
@@ -10906,6 +11031,9 @@ function promptOriginLabel(item) {
10906
11031
  if (code.startsWith("child:")) return `child ${code.slice("child:".length)}`;
10907
11032
  return code;
10908
11033
  }
11034
+ function isShellOnlyRow(desc) {
11035
+ return desc.kind === "command" || desc.kind === "terminal" && !isConversationTerminal(desc);
11036
+ }
10909
11037
  function toSessionSummary(desc) {
10910
11038
  return {
10911
11039
  id: desc.id,
@@ -11091,6 +11219,14 @@ function stampInterrupted(desc) {
11091
11219
  delete desc.interrupted;
11092
11220
  }
11093
11221
  }
11222
+ function stampLiveModes(desc, rt) {
11223
+ const modes = rt.agentSession?.availableModes;
11224
+ if (modes && modes.length > 0) {
11225
+ desc.availableModes = [...modes];
11226
+ } else {
11227
+ delete desc.availableModes;
11228
+ }
11229
+ }
11094
11230
  function isResumable(desc) {
11095
11231
  return desc.kind === "agent-cli" && !!desc.adapterSlug && !!desc.adapterSessionId && !!desc.cwd && !desc.archived;
11096
11232
  }
@@ -11291,6 +11427,7 @@ function createSessionsRegistry(opts) {
11291
11427
  }
11292
11428
  const emitExited = (rt) => {
11293
11429
  if (rt.exitedEmitted) return;
11430
+ rt.linkProbeStop?.();
11294
11431
  cancelPendingPermissionsForSession(rt);
11295
11432
  if (runWorktreeAutoReclaim && rt.desc.worktreeAutoProvisioned && rt.desc.worktreePath) {
11296
11433
  const worktreePath = rt.desc.worktreePath;
@@ -11472,8 +11609,9 @@ function createSessionsRegistry(opts) {
11472
11609
  const recordConversationLink = (rt, adapterSessionIdOverride) => {
11473
11610
  if (!persist || !partitioned) return;
11474
11611
  const desc = rt.desc;
11475
- if (desc.kind !== "agent-cli") return;
11476
- const adapterSlug = desc.adapterSlug;
11612
+ const isConvTerminal = desc.kind === "terminal" && isConversationTerminal(desc);
11613
+ if (desc.kind !== "agent-cli" && !isConvTerminal) return;
11614
+ const adapterSlug = desc.adapterSlug ?? (isConvTerminal ? conversationTerminalSlugFor(desc) : void 0);
11477
11615
  const adapterSessionId = adapterSessionIdOverride ?? desc.adapterSessionId;
11478
11616
  const cwd = desc.cwd;
11479
11617
  if (!adapterSlug || !adapterSessionId || !cwd) return;
@@ -11517,6 +11655,81 @@ function createSessionsRegistry(opts) {
11517
11655
  void persistSnapshot();
11518
11656
  }, PERSIST_DEBOUNCE_MS2);
11519
11657
  };
11658
+ const conversationLinkProbeMs = opts?.conversationLinkProbeMs ?? {
11659
+ initialMs: 3e3,
11660
+ intervalMs: 15e3
11661
+ };
11662
+ const startConversationTerminalLinkProbe = (rt) => {
11663
+ const desc = rt.desc;
11664
+ const slug = desc.adapterSlug ?? conversationTerminalSlugFor(desc);
11665
+ const store = slug ? CONVERSATION_STORES[slug] : void 0;
11666
+ const cwd = desc.cwd;
11667
+ if (!store || !cwd) return;
11668
+ let inFlight = false;
11669
+ let stopped = false;
11670
+ const timers = [];
11671
+ const stop = () => {
11672
+ stopped = true;
11673
+ for (const t of timers) clearTimeout(t);
11674
+ timers.length = 0;
11675
+ };
11676
+ rt.linkProbeStop = stop;
11677
+ const tick = async () => {
11678
+ if (stopped || inFlight) return;
11679
+ if (desc.status !== "running") {
11680
+ stop();
11681
+ return;
11682
+ }
11683
+ if (!desc.lastOutputAt) return;
11684
+ if (desc.adapterSessionId) {
11685
+ stop();
11686
+ return;
11687
+ }
11688
+ inFlight = true;
11689
+ try {
11690
+ const candidates = await store.discover({
11691
+ cwd,
11692
+ since: desc.startedAt,
11693
+ // A PTY writes through the provider's native arm, never ACP.
11694
+ attachmentMode: "native"
11695
+ // No configDir on purpose: nothing threaded CLAUDE_CONFIG_DIR
11696
+ // into this PTY, so the global store is where the file is.
11697
+ });
11698
+ if (candidates.length === 1) {
11699
+ const candidate = candidates[0];
11700
+ desc.adapterSessionId = candidate.conversationId;
11701
+ desc.resumeMetadata = {
11702
+ ...desc.resumeMetadata ?? {},
11703
+ [store.storeAs]: candidate.conversationId
11704
+ };
11705
+ delete desc.linkStatus;
11706
+ if (!desc.title && candidate.preview) {
11707
+ const derived = deriveSessionTitle(candidate.preview);
11708
+ if (derived) desc.title = derived;
11709
+ }
11710
+ schedulePersist();
11711
+ recordConversationLink(rt, candidate.conversationId);
11712
+ stop();
11713
+ } else if (candidates.length > 1) {
11714
+ desc.linkStatus = "ambiguous";
11715
+ schedulePersist();
11716
+ }
11717
+ } catch {
11718
+ } finally {
11719
+ inFlight = false;
11720
+ }
11721
+ };
11722
+ const schedule = (delayMs) => {
11723
+ if (stopped) return;
11724
+ const t = setTimeout(() => {
11725
+ void tick().finally(() => schedule(conversationLinkProbeMs.intervalMs));
11726
+ }, delayMs);
11727
+ t.unref?.();
11728
+ timers.length = 0;
11729
+ timers.push(t);
11730
+ };
11731
+ schedule(conversationLinkProbeMs.initialMs);
11732
+ };
11520
11733
  const snapshotRows = () => Array.from(sessions.values()).map((s) => {
11521
11734
  const {
11522
11735
  processAlive: _processAlive,
@@ -11525,6 +11738,7 @@ function createSessionsRegistry(opts) {
11525
11738
  secondsSinceLastActivity: _secondsSinceLastActivity,
11526
11739
  toolCallsThisTurn: _toolCallsThisTurn,
11527
11740
  eventsPath: _eventsPath,
11741
+ availableModes: _availableModes,
11528
11742
  ...rest
11529
11743
  } = s.desc;
11530
11744
  return rest;
@@ -11599,8 +11813,9 @@ function createSessionsRegistry(opts) {
11599
11813
  rt.emitter.emit("line", { line, stream });
11600
11814
  };
11601
11815
  const sniffResumeHints = (rt, line) => {
11602
- if (rt.desc.kind !== "agent-cli") return;
11603
- const strategy = rt.desc.adapterSlug ? RESUME_STRATEGIES[rt.desc.adapterSlug] : void 0;
11816
+ if (rt.desc.kind !== "agent-cli" && rt.conversationTerminal !== true) return;
11817
+ const strategySlug = rt.desc.adapterSlug ?? conversationTerminalSlugFor(rt.desc);
11818
+ const strategy = strategySlug ? RESUME_STRATEGIES[strategySlug] : void 0;
11604
11819
  if (!strategy?.outputHint) return;
11605
11820
  const plain = stripAnsiCodes(line);
11606
11821
  const m = plain.match(strategy.outputHint);
@@ -11613,6 +11828,14 @@ function createSessionsRegistry(opts) {
11613
11828
  recordConversationLink(rt, m[1]);
11614
11829
  }
11615
11830
  };
11831
+ const sniffConversationTerminalChunk = (rt, chunk) => {
11832
+ const text10 = (rt.sniffCarry ?? "") + chunk.toString("utf8");
11833
+ const lines = text10.split(/\r?\n|\r/);
11834
+ rt.sniffCarry = (lines.pop() ?? "").slice(-4096);
11835
+ for (const line of lines) {
11836
+ if (line) sniffResumeHints(rt, line);
11837
+ }
11838
+ };
11616
11839
  const appendBytes = (rt, chunk) => {
11617
11840
  terminalTranscriptWriter.appendChunk(rt.desc.id, chunk);
11618
11841
  rt.recentBytes.push(chunk);
@@ -11622,6 +11845,7 @@ function createSessionsRegistry(opts) {
11622
11845
  if (dropped) rt.recentBytesSize -= dropped.byteLength;
11623
11846
  }
11624
11847
  rt.desc.lastOutputAt = (/* @__PURE__ */ new Date()).toISOString();
11848
+ if (rt.conversationTerminal === true) sniffConversationTerminalChunk(rt, chunk);
11625
11849
  rt.emitter.emit("data", chunk);
11626
11850
  };
11627
11851
  const reconcilePtySize = (rt) => {
@@ -12992,6 +13216,19 @@ ${message}`;
12992
13216
  rows: input.rows
12993
13217
  });
12994
13218
  const priorCommandSessionId = findPriorCommandSessionId(sessions, input.cwd);
13219
+ const conversationSlug = conversationTerminalSlugFor({ argv: input.argv });
13220
+ const conversationStore = conversationSlug ? CONVERSATION_STORES[conversationSlug] : void 0;
13221
+ const resumeArgvId = (() => {
13222
+ if (!conversationStore) return void 0;
13223
+ for (let i = 0; i < input.argv.length - 1; i++) {
13224
+ if (input.argv[i] !== "--resume" && input.argv[i] !== "-r") continue;
13225
+ const raw = input.argv[i + 1];
13226
+ const base = raw.slice(raw.lastIndexOf("/") + 1);
13227
+ return base.endsWith(".jsonl") ? base.slice(0, -".jsonl".length) : raw;
13228
+ }
13229
+ return void 0;
13230
+ })();
13231
+ const conversationConfigDir = conversationStore?.configDirEnvVar && input.env ? input.env[conversationStore.configDirEnvVar] : void 0;
12995
13232
  const desc = {
12996
13233
  id,
12997
13234
  kind: "terminal",
@@ -13006,6 +13243,16 @@ ${message}`;
13006
13243
  ...worktreeFields(input.cwd),
13007
13244
  ...input.name ? { name: input.name } : {},
13008
13245
  ...input.label ? { label: input.label } : {},
13246
+ // Same rule as spawnAgent: a NEW spawn's label is a slug, not a
13247
+ // human rename — let a later derived title outrank it.
13248
+ ...input.label ? { renamedByUser: false } : {},
13249
+ ...conversationSlug ? { adapterSlug: conversationSlug } : {},
13250
+ ...conversationSlug && RESUME_STRATEGIES[conversationSlug]?.spawnArgs ? { nativeTerminalResume: true } : {},
13251
+ ...conversationConfigDir ? { adapterConfigDir: conversationConfigDir } : {},
13252
+ ...resumeArgvId && conversationStore ? {
13253
+ adapterSessionId: resumeArgvId,
13254
+ resumeMetadata: { [conversationStore.storeAs]: resumeArgvId }
13255
+ } : {},
13009
13256
  ...priorCommandSessionId ? { priorCommandSessionId } : {},
13010
13257
  // Parent attribution + depth (orchestrator WP4) — same recording
13011
13258
  // rule as spawnAgent above: depth always set so subtree/depth
@@ -13032,7 +13279,8 @@ ${message}`;
13032
13279
  emitter: new EventEmitter(),
13033
13280
  busy: false,
13034
13281
  textBuf: "",
13035
- thoughtBuf: ""
13282
+ thoughtBuf: "",
13283
+ ...isConversationTerminal(desc) ? { conversationTerminal: true } : {}
13036
13284
  };
13037
13285
  rt.emitter.setMaxListeners(50);
13038
13286
  sessions.set(id, rt);
@@ -13044,6 +13292,10 @@ ${message}`;
13044
13292
  depth: desc.depth ?? 0,
13045
13293
  ts: (/* @__PURE__ */ new Date()).toISOString()
13046
13294
  });
13295
+ if (rt.conversationTerminal) {
13296
+ if (desc.adapterSessionId) recordConversationLink(rt);
13297
+ else startConversationTerminalLinkProbe(rt);
13298
+ }
13047
13299
  pty.onData((chunk) => {
13048
13300
  const buf = Buffer.from(chunk, "utf8");
13049
13301
  rt.bracketedPaste = scanBracketedPaste(
@@ -13254,14 +13506,14 @@ ${message}`;
13254
13506
  };
13255
13507
  rtPre.desc.promptQueue = opts2.force ? [item, ...rtPre.desc.promptQueue ?? []] : [...rtPre.desc.promptQueue ?? [], item];
13256
13508
  schedulePersist();
13257
- return;
13509
+ return { queued: true };
13258
13510
  }
13259
13511
  await maybeResumeAgent(rtPre);
13260
13512
  const rt = validateAgentTurn(id, "enqueuePrompt");
13261
13513
  const structuredAnswer = matchStructuredQuestionAnswer(rt, message);
13262
13514
  if (structuredAnswer) {
13263
13515
  await answerStructuredQuestion(rt, structuredAnswer);
13264
- return;
13516
+ return { queued: false };
13265
13517
  }
13266
13518
  void runAgentTurn(rt, message, opts2?.source ? { promptSource: opts2.source } : void 0).catch((err) => {
13267
13519
  appendLine(
@@ -13270,6 +13522,7 @@ ${message}`;
13270
13522
  "stderr"
13271
13523
  );
13272
13524
  });
13525
+ return { queued: false };
13273
13526
  },
13274
13527
  removeQueuedPrompt(id, queueId) {
13275
13528
  const rt = sessions.get(id);
@@ -13529,6 +13782,7 @@ ${message}`;
13529
13782
  stampInterrupted(desc);
13530
13783
  stampCurrentStatus(rt);
13531
13784
  stampWatchers(desc);
13785
+ stampLiveModes(desc, rt);
13532
13786
  desc.childrenBusy = childrenBusy.get(desc.id) ?? 0;
13533
13787
  desc.queuedPrompts = desc.promptQueue?.length ?? 0;
13534
13788
  return desc;
@@ -13536,10 +13790,30 @@ ${message}`;
13536
13790
  },
13537
13791
  listSummaries(opts2) {
13538
13792
  const includeArchived = opts2?.includeArchived ?? false;
13793
+ const lane = opts2?.lane;
13539
13794
  const limit = Math.max(1, Math.min(200, opts2?.limit ?? 50));
13540
13795
  const offset = Math.max(0, opts2?.offset ?? 0);
13541
13796
  const childrenBusy = childrenBusyCounts();
13542
- const all = Array.from(sessions.values()).filter((rt) => includeArchived || !rt.desc.archived).sort((a, b) => b.desc.startedAt.localeCompare(a.desc.startedAt));
13797
+ const hasMachineLineage = (rt) => {
13798
+ const ownMachine = (desc) => desc.origin === "cron" || desc.origin === "gate";
13799
+ if (ownMachine(rt.desc)) return true;
13800
+ const seen = /* @__PURE__ */ new Set([rt.desc.id]);
13801
+ let current = rt.desc;
13802
+ while (current.parentSessionId) {
13803
+ const parent = sessions.get(current.parentSessionId)?.desc;
13804
+ if (!parent || seen.has(parent.id)) return true;
13805
+ if (parent.kind === "terminal" || parent.kind === "command") return true;
13806
+ seen.add(parent.id);
13807
+ current = parent;
13808
+ }
13809
+ return ownMachine(current);
13810
+ };
13811
+ const all = Array.from(sessions.values()).filter((rt) => includeArchived || !rt.desc.archived).filter((rt) => {
13812
+ if (!lane) return true;
13813
+ if (isShellOnlyRow(rt.desc)) return false;
13814
+ const machine = hasMachineLineage(rt);
13815
+ return lane === "auto" ? machine : !machine;
13816
+ }).sort((a, b) => b.desc.startedAt.localeCompare(a.desc.startedAt));
13543
13817
  const slice = all.slice(offset, offset + limit);
13544
13818
  const summaries = slice.map((rt) => {
13545
13819
  const desc = rt.desc;
@@ -13549,7 +13823,11 @@ ${message}`;
13549
13823
  stampWatchers(desc);
13550
13824
  desc.childrenBusy = childrenBusy.get(desc.id) ?? 0;
13551
13825
  desc.queuedPrompts = desc.promptQueue?.length ?? 0;
13552
- return toSessionSummary(desc);
13826
+ const summary = toSessionSummary(desc);
13827
+ if (!isShellOnlyRow(desc)) {
13828
+ summary.lane = hasMachineLineage(rt) ? "auto" : "agents";
13829
+ }
13830
+ return summary;
13553
13831
  });
13554
13832
  return { summaries, total: all.length };
13555
13833
  },
@@ -13561,6 +13839,7 @@ ${message}`;
13561
13839
  stampInterrupted(desc);
13562
13840
  stampCurrentStatus(rt);
13563
13841
  stampWatchers(desc);
13842
+ stampLiveModes(desc, rt);
13564
13843
  desc.childrenBusy = childrenBusyCounts().get(desc.id) ?? 0;
13565
13844
  desc.queuedPrompts = desc.promptQueue?.length ?? 0;
13566
13845
  }
@@ -17859,9 +18138,27 @@ function registerSessionTools(rawServer, opts) {
17859
18138
  try {
17860
18139
  if (strategy.kind === "pty-native" || strategy.kind === "pty-plain") {
17861
18140
  if (!ptyEnabled) return ptyNotConfigured("session_restart");
17862
- const argv = strategy.kind === "pty-native" ? strategy.argv : Array.isArray(prev.argv) && prev.argv.length > 0 ? [...prev.argv] : tokenizeCommand(prev.command);
18141
+ let argv = strategy.kind === "pty-native" ? strategy.argv : Array.isArray(prev.argv) && prev.argv.length > 0 ? [...prev.argv] : tokenizeCommand(prev.command);
18142
+ if (strategy.kind === "pty-native" && prev.adapterSlug) {
18143
+ const probe = await probeNativeTranscript(augmented);
18144
+ if (probe?.exists) {
18145
+ const upgraded = RESUME_STRATEGIES[prev.adapterSlug]?.spawnArgs?.(probe.path);
18146
+ if (upgraded) {
18147
+ argv = upgraded;
18148
+ console.log(
18149
+ `[session_restart] ${prev.id} pty-native resume upgraded to absolute transcript path ${probe.path}`
18150
+ );
18151
+ }
18152
+ }
18153
+ }
17863
18154
  const envVarName = strategy.kind === "pty-native" && prev.adapterSlug ? RESUME_STRATEGIES[prev.adapterSlug]?.configDirEnvVar : void 0;
17864
- const ptyEnv = strategy.kind === "pty-native" ? envVarName && augmented.adapterConfigDir ? { [envVarName]: augmented.adapterConfigDir } : void 0 : prev.ptyResumeEnv;
18155
+ const ptyEnv = strategy.kind === "pty-native" ? envVarName && augmented.adapterConfigDir ? { [envVarName]: augmented.adapterConfigDir } : (
18156
+ // A pty-native restart of a row that is ITSELF a restarted
18157
+ // PTY (conversation terminal): no adapterConfigDir, but the
18158
+ // env the earlier hop recorded still names the isolated
18159
+ // store — replay it rather than dropping to the global one.
18160
+ prev.ptyResumeEnv
18161
+ ) : prev.ptyResumeEnv;
17865
18162
  if (strategy.kind === "pty-native") {
17866
18163
  console.log(
17867
18164
  `[session_restart] ${prev.id} pty-native env: ` + (ptyEnv ? `${Object.keys(ptyEnv).join(",")} threaded from adapterConfigDir` : envVarName ? "no adapterConfigDir on descriptor \u2014 resume may miss the isolated store" : `adapter "${prev.adapterSlug}" declares no configDirEnvVar \u2014 resume uses the global store`)
@@ -17943,6 +18240,15 @@ function registerSessionTools(rawServer, opts) {
17943
18240
  isError: true
17944
18241
  };
17945
18242
  }
18243
+ let nativeResumeDecline;
18244
+ if (input.preferNativeTerminal === true && prev.adapterSlug && RESUME_STRATEGIES[prev.adapterSlug]?.spawnArgs) {
18245
+ if (prev.nativeTerminalResume !== true) {
18246
+ nativeResumeDecline = { reason: "capability-missing" };
18247
+ } else {
18248
+ const probe = await probeNativeTranscript(augmented);
18249
+ nativeResumeDecline = probe ? { reason: "transcript-not-found", probedDir: probe.dir } : { reason: "no-resume-id" };
18250
+ }
18251
+ }
17946
18252
  const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev, {
17947
18253
  ...opts.resolveSandboxProvider ? { resolveSandboxProvider: opts.resolveSandboxProvider } : {}
17948
18254
  });
@@ -17955,7 +18261,8 @@ function registerSessionTools(rawServer, opts) {
17955
18261
  ...restarted.desc,
17956
18262
  resumedFrom: restarted.resumedFrom,
17957
18263
  resumeVia: restarted.resumeVia,
17958
- ...restarted.resumeFallback ? { resumeFallback: true } : {}
18264
+ ...restarted.resumeFallback ? { resumeFallback: true } : {},
18265
+ ...nativeResumeDecline ? { nativeResumeDecline } : {}
17959
18266
  },
17960
18267
  null,
17961
18268
  2
@@ -20249,13 +20556,34 @@ function registerWebSearchTools(server, opts = {}) {
20249
20556
  transformers: [catchErrors(), enrichResolverRejections()]
20250
20557
  });
20251
20558
  }
20559
+ var embedTokens = /* @__PURE__ */ new Map();
20560
+ function mintAppEmbedToken(appId) {
20561
+ const token = randomBytes(24).toString("base64url");
20562
+ embedTokens.set(token, appId);
20563
+ return token;
20564
+ }
20565
+ var stableTokens = /* @__PURE__ */ new Map();
20566
+ function stableAppEmbedToken(appId) {
20567
+ const existing = stableTokens.get(appId);
20568
+ if (existing) return existing;
20569
+ const token = mintAppEmbedToken(appId);
20570
+ stableTokens.set(appId, token);
20571
+ return token;
20572
+ }
20573
+ function isValidAppEmbedToken(token) {
20574
+ return typeof token === "string" && token.length > 0 && embedTokens.has(token);
20575
+ }
20252
20576
 
20253
20577
  // src/mcp-apps-adapter.ts
20254
20578
  var MIME_TYPE = "text/html;profile=mcp-app";
20579
+ var EMBED_TOKEN_PLACEHOLDER = '"__AGENPROTO_EMBED_TOKEN__"';
20255
20580
  function registerMcpApps(server, apps) {
20256
20581
  for (const app of apps) {
20257
20582
  const resourceUri = `ui://${app.id}/view`;
20258
- const html = typeof app.html === "string" ? app.html : app.html({});
20583
+ let html = typeof app.html === "string" ? app.html : app.html({});
20584
+ if (html.includes(EMBED_TOKEN_PLACEHOLDER)) {
20585
+ html = html.split(EMBED_TOKEN_PLACEHOLDER).join(JSON.stringify(mintAppEmbedToken(app.id)));
20586
+ }
20259
20587
  const resourceMeta = {
20260
20588
  ui: {
20261
20589
  prefersBorder: true,
@@ -20326,7 +20654,11 @@ function makeBuiltinPanelApps(ops) {
20326
20654
  // installed, install notice otherwise; see apps/src/session-chat).
20327
20655
  makeSessionChatApp({
20328
20656
  httpBaseUrl: ops.httpBaseUrl,
20329
- isSessionChatInstalled: ops.isSessionChatInstalled
20657
+ isSessionChatInstalled: ops.isSessionChatInstalled,
20658
+ // Hand every result a live embed token so a host-cached widget
20659
+ // (Claude Desktop's per-conversation srcdoc) re-arms itself after a
20660
+ // daemon restart instead of 403ing on its dead baked token.
20661
+ mintEmbedToken: () => stableAppEmbedToken("agentproto_session_chat")
20330
20662
  }),
20331
20663
  // Work-board widget — kanban over the Task ledger (see apps/src/
20332
20664
  // work-board). Read path only; writes go through task_claim/
@@ -22891,7 +23223,7 @@ var STANDALONE_REST_BRIDGE_SCRIPT = `<script>
22891
23223
  return res.json().catch(function () {
22892
23224
  throw new Error("tool-call failed: HTTP " + res.status);
22893
23225
  }).then(function (body) {
22894
- if (!res.ok) throw new Error((body && body.error) || ("tool-call failed: HTTP " + res.status));
23226
+ if (!res.ok) throw new Error((body && (body.message || body.error)) || ("tool-call failed: HTTP " + res.status));
22895
23227
  return body;
22896
23228
  });
22897
23229
  });
@@ -23201,6 +23533,22 @@ async function sendOutbound(provider, input, deps2) {
23201
23533
  return { ok: false, error: "unsupported_provider" };
23202
23534
  }
23203
23535
  }
23536
+ function unwrapMcpToolResult(result) {
23537
+ if (!result || typeof result !== "object") return {};
23538
+ const envelope = result;
23539
+ if (envelope.structuredContent && typeof envelope.structuredContent === "object") {
23540
+ return envelope.structuredContent;
23541
+ }
23542
+ const text10 = envelope.content?.find((c) => c.type === "text")?.text;
23543
+ if (typeof text10 === "string") {
23544
+ try {
23545
+ const parsed = JSON.parse(text10);
23546
+ if (parsed && typeof parsed === "object") return parsed;
23547
+ } catch {
23548
+ }
23549
+ }
23550
+ return result;
23551
+ }
23204
23552
  async function sendAgentpush(input, deps2) {
23205
23553
  const alias = input.alias;
23206
23554
  if (!alias) {
@@ -23230,7 +23578,7 @@ async function sendAgentpush(input, deps2) {
23230
23578
  if (!upload.ok) {
23231
23579
  return { ok: false, error: `upload_media_failed: ${upload.error}` };
23232
23580
  }
23233
- const result = typeof upload.result === "object" && upload.result !== null ? upload.result : {};
23581
+ const result = unwrapMcpToolResult(upload.result);
23234
23582
  const providerMediaId = typeof result.media_id === "string" ? result.media_id : typeof result.id === "string" ? result.id : void 0;
23235
23583
  if (!providerMediaId) {
23236
23584
  return { ok: false, error: "upload_media_missing_media_id" };
@@ -23255,7 +23603,18 @@ async function sendAgentpush(input, deps2) {
23255
23603
  if (!out.ok) {
23256
23604
  return { ok: false, error: out.error };
23257
23605
  }
23258
- const providerMessageId = extractProviderMessageId(out.result);
23606
+ const payload = unwrapMcpToolResult(out.result);
23607
+ const status = typeof payload.status === "string" ? payload.status : void 0;
23608
+ if (status === "blocked") {
23609
+ const blockedReason = typeof payload.blocked_reason === "string" ? payload.blocked_reason : "blocked";
23610
+ const suggestion = typeof payload.suggestion === "string" ? payload.suggestion : void 0;
23611
+ return { ok: false, error: blockedReason, blockedReason, ...suggestion ? { suggestion } : {} };
23612
+ }
23613
+ if (status === "failed") {
23614
+ const error = typeof payload.error === "string" ? payload.error : "failed";
23615
+ return { ok: false, error };
23616
+ }
23617
+ const providerMessageId = extractProviderMessageId(payload);
23259
23618
  return { ok: true, providerMessageId };
23260
23619
  }
23261
23620
  async function sendTelegram(input, deps2) {
@@ -23280,12 +23639,7 @@ async function sendTelegram(input, deps2) {
23280
23639
  parse_mode: "MarkdownV2"
23281
23640
  })
23282
23641
  });
23283
- if (!res2.ok) {
23284
- const body2 = await res2.text().catch(() => "");
23285
- return { ok: false, error: `telegram_http_${res2.status}: ${body2}` };
23286
- }
23287
- const json2 = await res2.json().catch(() => ({}));
23288
- return { ok: true, providerMessageId: extractTelegramMessageId(json2) };
23642
+ return parseTelegramSendResponse(res2);
23289
23643
  }
23290
23644
  const files = [];
23291
23645
  for (const att of input.attachments) {
@@ -23319,12 +23673,7 @@ async function sendTelegram(input, deps2) {
23319
23673
  headers: { "Content-Type": contentType2 },
23320
23674
  body: toFetchBody(body2)
23321
23675
  });
23322
- if (!res2.ok) {
23323
- const bodyText = await res2.text().catch(() => "");
23324
- return { ok: false, error: `telegram_http_${res2.status}: ${bodyText}` };
23325
- }
23326
- const json2 = await res2.json().catch(() => ({}));
23327
- return { ok: true, providerMessageId: extractTelegramMessageId(json2) };
23676
+ return parseTelegramSendResponse(res2);
23328
23677
  }
23329
23678
  const mediaJson = input.attachments.map((att, index) => {
23330
23679
  const item = {
@@ -23357,11 +23706,18 @@ async function sendTelegram(input, deps2) {
23357
23706
  headers: { "Content-Type": contentType },
23358
23707
  body: toFetchBody(body)
23359
23708
  });
23709
+ return parseTelegramSendResponse(res);
23710
+ }
23711
+ async function parseTelegramSendResponse(res) {
23360
23712
  if (!res.ok) {
23361
- const bodyText = await res.text().catch(() => "");
23362
- return { ok: false, error: `telegram_http_${res.status}: ${bodyText}` };
23713
+ const body = await res.text().catch(() => "");
23714
+ return { ok: false, error: `telegram_http_${res.status}: ${body}` };
23363
23715
  }
23364
23716
  const json = await res.json().catch(() => ({}));
23717
+ if (json.ok !== true) {
23718
+ const description = typeof json.description === "string" ? json.description : "unknown_error";
23719
+ return { ok: false, error: `telegram_error: ${description}` };
23720
+ }
23365
23721
  return { ok: true, providerMessageId: extractTelegramMessageId(json) };
23366
23722
  }
23367
23723
  function extractProviderMessageId(result) {
@@ -26308,7 +26664,16 @@ function registerOrchestrationTools(rawServer, opts) {
26308
26664
  if (!out.ok) {
26309
26665
  return {
26310
26666
  content: [
26311
- { type: "text", text: JSON.stringify({ sent: false, bound: false, error: out.error }) }
26667
+ {
26668
+ type: "text",
26669
+ text: JSON.stringify({
26670
+ sent: false,
26671
+ bound: false,
26672
+ error: out.error,
26673
+ ...out.blockedReason ? { blocked_reason: out.blockedReason } : {},
26674
+ ...out.suggestion ? { suggestion: out.suggestion } : {}
26675
+ })
26676
+ }
26312
26677
  ],
26313
26678
  isError: true
26314
26679
  };
@@ -26324,7 +26689,18 @@ function registerOrchestrationTools(rawServer, opts) {
26324
26689
  provider
26325
26690
  });
26326
26691
  }
26327
- return { content: [{ type: "text", text: JSON.stringify({ sent: true, bound }) }] };
26692
+ return {
26693
+ content: [
26694
+ {
26695
+ type: "text",
26696
+ text: JSON.stringify({
26697
+ sent: true,
26698
+ bound,
26699
+ ...out.providerMessageId ? { message_id: out.providerMessageId } : {}
26700
+ })
26701
+ }
26702
+ ]
26703
+ };
26328
26704
  }
26329
26705
  );
26330
26706
  }
@@ -26758,7 +27134,7 @@ async function startHttpServer(opts) {
26758
27134
  }
26759
27135
  function authorizeMcp(req, res) {
26760
27136
  const origin = req.headers.origin;
26761
- if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin)) {
27137
+ if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin) && !embedTokenTrusted(req)) {
26762
27138
  const auth = readAuth();
26763
27139
  const header = req.headers.authorization;
26764
27140
  if (auth.mode === "bearer" && header === `Bearer ${auth.token}`) {
@@ -26788,8 +27164,12 @@ async function startHttpServer(opts) {
26788
27164
  }
26789
27165
  const origin = req.headers.origin;
26790
27166
  if (typeof origin === "string" && originAllowed(origin)) return "ok";
27167
+ if (embedTokenTrusted(req)) return "ok";
26791
27168
  return header ? "bad" : "missing";
26792
27169
  }
27170
+ function embedTokenTrusted(req) {
27171
+ return isValidAppEmbedToken(requestEmbedToken(req));
27172
+ }
26793
27173
  function originAllowed(origin) {
26794
27174
  const list = opts.strictOrigins ? opts.allowedOrigins ?? [] : [...DEFAULT_ALLOWED_ORIGINS, ...opts.allowedOrigins ?? []];
26795
27175
  for (const pattern of list) {
@@ -27075,7 +27455,7 @@ async function startHttpServer(opts) {
27075
27455
  }
27076
27456
  function guardBrowserOrigin(req, res) {
27077
27457
  const origin = req.headers.origin;
27078
- if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin)) {
27458
+ if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin) && !embedTokenTrusted(req)) {
27079
27459
  const auth = readAuth();
27080
27460
  const header = req.headers.authorization;
27081
27461
  if (auth.mode === "bearer" && header === `Bearer ${auth.token}`) {
@@ -27094,10 +27474,14 @@ async function startHttpServer(opts) {
27094
27474
  }
27095
27475
  function applyCors(req, res) {
27096
27476
  const origin = req.headers.origin;
27097
- const trusted = typeof origin === "string" && origin.length > 0 && originAllowed(origin);
27098
- if (trusted) {
27477
+ const tokenTrusted = embedTokenTrusted(req);
27478
+ const originTrusted = typeof origin === "string" && origin.length > 0 && originAllowed(origin);
27479
+ const trusted = originTrusted || tokenTrusted;
27480
+ if (originTrusted) {
27099
27481
  res.setHeader("Access-Control-Allow-Origin", origin);
27100
27482
  res.setHeader("Access-Control-Allow-Credentials", "true");
27483
+ } else if (tokenTrusted && origin && origin !== "null") {
27484
+ res.setHeader("Access-Control-Allow-Origin", origin);
27101
27485
  } else {
27102
27486
  res.setHeader("Access-Control-Allow-Origin", "*");
27103
27487
  }
@@ -28065,7 +28449,9 @@ async function startHttpServer(opts) {
28065
28449
  const uiMatch = path.match(/^\/apps\/(.+)\/ui$/);
28066
28450
  if (uiMatch && req.method === "GET") {
28067
28451
  const uiApp = opts.appRegistry.getApp(decodeURIComponent(uiMatch[1]));
28068
- if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && guardBrowserOrigin(req, res)) return;
28452
+ if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && !isValidAppEmbedToken(requestEmbedToken(req)) && guardBrowserOrigin(req, res)) {
28453
+ return;
28454
+ }
28069
28455
  if (!authorize(req, res)) return;
28070
28456
  await handleAppUiPage(
28071
28457
  req,
@@ -28819,10 +29205,13 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
28819
29205
  const queryString = reqUrl.includes("?") ? reqUrl.slice(reqUrl.indexOf("?") + 1) : "";
28820
29206
  const params = new URLSearchParams(queryString);
28821
29207
  const includeArchived = params.get("includeArchived") === "true";
29208
+ const laneParam = params.get("lane");
29209
+ const lane = laneParam === "agents" || laneParam === "auto" ? laneParam : void 0;
28822
29210
  const limit = Number.parseInt(params.get("limit") ?? "", 10);
28823
29211
  const offset = Number.parseInt(params.get("offset") ?? "", 10);
28824
29212
  const result = registry.listSummaries({
28825
29213
  includeArchived,
29214
+ lane,
28826
29215
  limit: Number.isNaN(limit) ? void 0 : limit,
28827
29216
  offset: Number.isNaN(offset) ? void 0 : offset
28828
29217
  });
@@ -30810,6 +31199,16 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
30810
31199
  if (frameAncestors.length === 0) {
30811
31200
  headers["x-frame-options"] = "SAMEORIGIN";
30812
31201
  }
31202
+ if (embedRequested && !isValidAppEmbedToken(requestEmbedToken(req))) {
31203
+ console.warn(
31204
+ `[app-ui] embed refused for "${appId}": ` + JSON.stringify({
31205
+ secFetchDest: req.headers["sec-fetch-dest"] ?? null,
31206
+ origin: req.headers.origin ?? null,
31207
+ referer: req.headers.referer ?? null,
31208
+ embedToken: requestEmbedToken(req) != null
31209
+ })
31210
+ );
31211
+ }
30813
31212
  }
30814
31213
  res.writeHead(200, headers);
30815
31214
  res.end(injectStandaloneAppBridge(raw));
@@ -30817,6 +31216,7 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
30817
31216
  function iframeEmbedAllowed(req, app) {
30818
31217
  const secFetchDest = req.headers["sec-fetch-dest"];
30819
31218
  if (secFetchDest !== void 0 && secFetchDest !== "iframe") return false;
31219
+ if (isValidAppEmbedToken(requestEmbedToken(req))) return true;
30820
31220
  const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
30821
31221
  const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
30822
31222
  if (!origin && !referer) return false;
@@ -30837,6 +31237,13 @@ function iframeEmbedAllowed(req, app) {
30837
31237
  }
30838
31238
  return false;
30839
31239
  }
31240
+ function requestEmbedToken(req) {
31241
+ try {
31242
+ return new URL(req.url ?? "/", "http://localhost").searchParams.get("et");
31243
+ } catch {
31244
+ return null;
31245
+ }
31246
+ }
30840
31247
  function iframeEmbedOriginAllowed(req, app) {
30841
31248
  const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
30842
31249
  const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
@@ -34795,6 +35202,7 @@ function createOrchestratorMcpServerFactory(deps2) {
34795
35202
  ...deps2.listHarnessCapabilities ? { listHarnessCapabilities: deps2.listHarnessCapabilities } : {},
34796
35203
  ...deps2.webhookNotifier ? { webhookNotifier: deps2.webhookNotifier } : {},
34797
35204
  ...deps2.resolveSandboxProvider ? { resolveSandboxProvider: deps2.resolveSandboxProvider } : {},
35205
+ ...deps2.defaultAgentPromptInterrupt != null ? { defaultAgentPromptInterrupt: deps2.defaultAgentPromptInterrupt } : {},
34798
35206
  daemonMcpUrl: deps2.daemonMcpUrl
34799
35207
  });
34800
35208
  registerOrchestrationTools(server, {
@@ -38235,6 +38643,10 @@ async function createGateway(opts) {
38235
38643
  // can declare `sandbox` and resolve a provider, instead of throwing
38236
38644
  // `sandbox_provider_not_found`.
38237
38645
  resolveSandboxProvider: resolveSandboxProviderResolved,
38646
+ // config.json `defaults.agentPromptInterrupt` — same unset-default the
38647
+ // root /mcp surface uses, so a child driving/reporting through the scoped
38648
+ // orchestrator gateway honours it too.
38649
+ ...configDefaults?.agentPromptInterrupt != null ? { defaultAgentPromptInterrupt: configDefaults.agentPromptInterrupt } : {},
38238
38650
  ...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
38239
38651
  ...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
38240
38652
  ...opts.listHarnessCapabilities ? { listHarnessCapabilities: opts.listHarnessCapabilities } : {}
@@ -38295,6 +38707,9 @@ async function createGateway(opts) {
38295
38707
  webhookNotifier,
38296
38708
  daemonMcpUrl,
38297
38709
  resolveSandboxProvider: resolveSandboxProviderResolved,
38710
+ // config.json `defaults.agentPromptInterrupt` — the unset-default for
38711
+ // `interrupt` on agent_prompt / message_parent. Omitted ⇒ false.
38712
+ ...configDefaults?.agentPromptInterrupt != null ? { defaultAgentPromptInterrupt: configDefaults.agentPromptInterrupt } : {},
38298
38713
  ...opts.provisionWorktree ? { provisionWorktree: opts.provisionWorktree } : {},
38299
38714
  ...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
38300
38715
  ...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},