@agentproto/runtime 3.4.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) {
@@ -8261,8 +8340,10 @@ function registerAgentTools(server, opts) {
8261
8340
  resolveSandboxProvider: resolveSandboxProvider2,
8262
8341
  provisionWorktree,
8263
8342
  resolveWorktreeIsolation,
8264
- supervisor
8343
+ supervisor,
8344
+ defaultAgentPromptInterrupt
8265
8345
  } = opts;
8346
+ const interruptDefault = defaultAgentPromptInterrupt ?? false;
8266
8347
  server.registerTool(
8267
8348
  "agent_start",
8268
8349
  {
@@ -8498,14 +8579,20 @@ function registerAgentTools(server, opts) {
8498
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`."
8499
8580
  )
8500
8581
  },
8501
- // Live-session widget: rendering agent_start's result auto-mounts the
8502
- // live widget for the new session (ext-apps `_meta.ui.resourceUri` at
8503
- // the tool-definition level — same mechanism as the panel apps in
8504
- // mcp-apps-adapter.ts). `visibility:["model","app"]` keeps agent_start
8505
- // 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.
8506
8593
  _meta: {
8507
8594
  ui: {
8508
- resourceUri: "ui://live_session/view",
8595
+ resourceUri: "ui://agentproto_session_chat/view",
8509
8596
  visibility: ["model", "app"]
8510
8597
  }
8511
8598
  }
@@ -8618,7 +8705,7 @@ function registerAgentTools(server, opts) {
8618
8705
  id: sessionIdAliasField,
8619
8706
  prompt: z.string().min(1).describe("The next user turn (plain text)."),
8620
8707
  interrupt: z.boolean().optional().describe(
8621
- "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."
8622
8709
  ),
8623
8710
  queue: z.boolean().optional().describe(
8624
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."
@@ -8629,8 +8716,9 @@ function registerAgentTools(server, opts) {
8629
8716
  if (!sessionId) return missingSessionIdError("agent_prompt");
8630
8717
  try {
8631
8718
  const promptSource = callerScope?.ownerSessionId ?? callerSessionId;
8632
- await registry.enqueuePrompt(sessionId, input.prompt, {
8633
- interrupt: input.interrupt,
8719
+ const effectiveInterrupt = input.interrupt ?? interruptDefault;
8720
+ const { queued } = await registry.enqueuePrompt(sessionId, input.prompt, {
8721
+ interrupt: effectiveInterrupt,
8634
8722
  // Queue by default: a mid-turn session holds the prompt in its
8635
8723
  // FIFO queue and dispatches it at turn end, so callers never
8636
8724
  // lose a prompt to the busy rejection. Explicit `queue: false`
@@ -8638,12 +8726,21 @@ function registerAgentTools(server, opts) {
8638
8726
  queue: input.queue ?? true,
8639
8727
  ...promptSource ? { source: `agent:${promptSource}` } : {}
8640
8728
  });
8729
+ const hintQueued = queued && input.interrupt === void 0;
8641
8730
  return {
8642
8731
  content: [
8643
8732
  {
8644
8733
  type: "text",
8645
8734
  text: JSON.stringify(
8646
- { 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
+ },
8647
8744
  null,
8648
8745
  2
8649
8746
  )
@@ -8672,9 +8769,12 @@ function registerAgentTools(server, opts) {
8672
8769
  );
8673
8770
  server.tool(
8674
8771
  "message_parent",
8675
- "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.",
8676
8773
  {
8677
- 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
+ )
8678
8778
  },
8679
8779
  async (input) => {
8680
8780
  const fail = (text10) => ({
@@ -8708,11 +8808,12 @@ function registerAgentTools(server, opts) {
8708
8808
  }
8709
8809
  const who = self.label ?? selfId;
8710
8810
  const notice = `[child-message] ${who} (${selfId}): ${input.message}`;
8711
- const done = (delivery) => ({
8811
+ const effectiveInterrupt = input.interrupt ?? interruptDefault;
8812
+ const done = (delivery, extra) => ({
8712
8813
  content: [
8713
8814
  {
8714
8815
  type: "text",
8715
- text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery })
8816
+ text: JSON.stringify({ ok: true, parentSessionId: parentId, delivery, ...extra })
8716
8817
  }
8717
8818
  ]
8718
8819
  });
@@ -8723,12 +8824,27 @@ function registerAgentTools(server, opts) {
8723
8824
  } catch {
8724
8825
  }
8725
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
+ }
8726
8837
  if (!registry.stampPendingChildCrashNotice(parentId, notice)) {
8727
8838
  return fail(
8728
8839
  `message_parent: parent session "${parentId}" vanished mid-delivery.`
8729
8840
  );
8730
8841
  }
8731
- 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
+ );
8732
8848
  }
8733
8849
  );
8734
8850
  server.tool(
@@ -10915,6 +11031,9 @@ function promptOriginLabel(item) {
10915
11031
  if (code.startsWith("child:")) return `child ${code.slice("child:".length)}`;
10916
11032
  return code;
10917
11033
  }
11034
+ function isShellOnlyRow(desc) {
11035
+ return desc.kind === "command" || desc.kind === "terminal" && !isConversationTerminal(desc);
11036
+ }
10918
11037
  function toSessionSummary(desc) {
10919
11038
  return {
10920
11039
  id: desc.id,
@@ -11308,6 +11427,7 @@ function createSessionsRegistry(opts) {
11308
11427
  }
11309
11428
  const emitExited = (rt) => {
11310
11429
  if (rt.exitedEmitted) return;
11430
+ rt.linkProbeStop?.();
11311
11431
  cancelPendingPermissionsForSession(rt);
11312
11432
  if (runWorktreeAutoReclaim && rt.desc.worktreeAutoProvisioned && rt.desc.worktreePath) {
11313
11433
  const worktreePath = rt.desc.worktreePath;
@@ -11489,8 +11609,9 @@ function createSessionsRegistry(opts) {
11489
11609
  const recordConversationLink = (rt, adapterSessionIdOverride) => {
11490
11610
  if (!persist || !partitioned) return;
11491
11611
  const desc = rt.desc;
11492
- if (desc.kind !== "agent-cli") return;
11493
- 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);
11494
11615
  const adapterSessionId = adapterSessionIdOverride ?? desc.adapterSessionId;
11495
11616
  const cwd = desc.cwd;
11496
11617
  if (!adapterSlug || !adapterSessionId || !cwd) return;
@@ -11534,6 +11655,81 @@ function createSessionsRegistry(opts) {
11534
11655
  void persistSnapshot();
11535
11656
  }, PERSIST_DEBOUNCE_MS2);
11536
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
+ };
11537
11733
  const snapshotRows = () => Array.from(sessions.values()).map((s) => {
11538
11734
  const {
11539
11735
  processAlive: _processAlive,
@@ -11617,8 +11813,9 @@ function createSessionsRegistry(opts) {
11617
11813
  rt.emitter.emit("line", { line, stream });
11618
11814
  };
11619
11815
  const sniffResumeHints = (rt, line) => {
11620
- if (rt.desc.kind !== "agent-cli") return;
11621
- 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;
11622
11819
  if (!strategy?.outputHint) return;
11623
11820
  const plain = stripAnsiCodes(line);
11624
11821
  const m = plain.match(strategy.outputHint);
@@ -11631,6 +11828,14 @@ function createSessionsRegistry(opts) {
11631
11828
  recordConversationLink(rt, m[1]);
11632
11829
  }
11633
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
+ };
11634
11839
  const appendBytes = (rt, chunk) => {
11635
11840
  terminalTranscriptWriter.appendChunk(rt.desc.id, chunk);
11636
11841
  rt.recentBytes.push(chunk);
@@ -11640,6 +11845,7 @@ function createSessionsRegistry(opts) {
11640
11845
  if (dropped) rt.recentBytesSize -= dropped.byteLength;
11641
11846
  }
11642
11847
  rt.desc.lastOutputAt = (/* @__PURE__ */ new Date()).toISOString();
11848
+ if (rt.conversationTerminal === true) sniffConversationTerminalChunk(rt, chunk);
11643
11849
  rt.emitter.emit("data", chunk);
11644
11850
  };
11645
11851
  const reconcilePtySize = (rt) => {
@@ -13010,6 +13216,19 @@ ${message}`;
13010
13216
  rows: input.rows
13011
13217
  });
13012
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;
13013
13232
  const desc = {
13014
13233
  id,
13015
13234
  kind: "terminal",
@@ -13024,6 +13243,16 @@ ${message}`;
13024
13243
  ...worktreeFields(input.cwd),
13025
13244
  ...input.name ? { name: input.name } : {},
13026
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
+ } : {},
13027
13256
  ...priorCommandSessionId ? { priorCommandSessionId } : {},
13028
13257
  // Parent attribution + depth (orchestrator WP4) — same recording
13029
13258
  // rule as spawnAgent above: depth always set so subtree/depth
@@ -13050,7 +13279,8 @@ ${message}`;
13050
13279
  emitter: new EventEmitter(),
13051
13280
  busy: false,
13052
13281
  textBuf: "",
13053
- thoughtBuf: ""
13282
+ thoughtBuf: "",
13283
+ ...isConversationTerminal(desc) ? { conversationTerminal: true } : {}
13054
13284
  };
13055
13285
  rt.emitter.setMaxListeners(50);
13056
13286
  sessions.set(id, rt);
@@ -13062,6 +13292,10 @@ ${message}`;
13062
13292
  depth: desc.depth ?? 0,
13063
13293
  ts: (/* @__PURE__ */ new Date()).toISOString()
13064
13294
  });
13295
+ if (rt.conversationTerminal) {
13296
+ if (desc.adapterSessionId) recordConversationLink(rt);
13297
+ else startConversationTerminalLinkProbe(rt);
13298
+ }
13065
13299
  pty.onData((chunk) => {
13066
13300
  const buf = Buffer.from(chunk, "utf8");
13067
13301
  rt.bracketedPaste = scanBracketedPaste(
@@ -13272,14 +13506,14 @@ ${message}`;
13272
13506
  };
13273
13507
  rtPre.desc.promptQueue = opts2.force ? [item, ...rtPre.desc.promptQueue ?? []] : [...rtPre.desc.promptQueue ?? [], item];
13274
13508
  schedulePersist();
13275
- return;
13509
+ return { queued: true };
13276
13510
  }
13277
13511
  await maybeResumeAgent(rtPre);
13278
13512
  const rt = validateAgentTurn(id, "enqueuePrompt");
13279
13513
  const structuredAnswer = matchStructuredQuestionAnswer(rt, message);
13280
13514
  if (structuredAnswer) {
13281
13515
  await answerStructuredQuestion(rt, structuredAnswer);
13282
- return;
13516
+ return { queued: false };
13283
13517
  }
13284
13518
  void runAgentTurn(rt, message, opts2?.source ? { promptSource: opts2.source } : void 0).catch((err) => {
13285
13519
  appendLine(
@@ -13288,6 +13522,7 @@ ${message}`;
13288
13522
  "stderr"
13289
13523
  );
13290
13524
  });
13525
+ return { queued: false };
13291
13526
  },
13292
13527
  removeQueuedPrompt(id, queueId) {
13293
13528
  const rt = sessions.get(id);
@@ -13575,7 +13810,7 @@ ${message}`;
13575
13810
  };
13576
13811
  const all = Array.from(sessions.values()).filter((rt) => includeArchived || !rt.desc.archived).filter((rt) => {
13577
13812
  if (!lane) return true;
13578
- if (rt.desc.kind === "terminal" || rt.desc.kind === "command") return false;
13813
+ if (isShellOnlyRow(rt.desc)) return false;
13579
13814
  const machine = hasMachineLineage(rt);
13580
13815
  return lane === "auto" ? machine : !machine;
13581
13816
  }).sort((a, b) => b.desc.startedAt.localeCompare(a.desc.startedAt));
@@ -13588,7 +13823,11 @@ ${message}`;
13588
13823
  stampWatchers(desc);
13589
13824
  desc.childrenBusy = childrenBusy.get(desc.id) ?? 0;
13590
13825
  desc.queuedPrompts = desc.promptQueue?.length ?? 0;
13591
- return toSessionSummary(desc);
13826
+ const summary = toSessionSummary(desc);
13827
+ if (!isShellOnlyRow(desc)) {
13828
+ summary.lane = hasMachineLineage(rt) ? "auto" : "agents";
13829
+ }
13830
+ return summary;
13592
13831
  });
13593
13832
  return { summaries, total: all.length };
13594
13833
  },
@@ -17899,9 +18138,27 @@ function registerSessionTools(rawServer, opts) {
17899
18138
  try {
17900
18139
  if (strategy.kind === "pty-native" || strategy.kind === "pty-plain") {
17901
18140
  if (!ptyEnabled) return ptyNotConfigured("session_restart");
17902
- 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
+ }
17903
18154
  const envVarName = strategy.kind === "pty-native" && prev.adapterSlug ? RESUME_STRATEGIES[prev.adapterSlug]?.configDirEnvVar : void 0;
17904
- 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;
17905
18162
  if (strategy.kind === "pty-native") {
17906
18163
  console.log(
17907
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`)
@@ -17983,6 +18240,15 @@ function registerSessionTools(rawServer, opts) {
17983
18240
  isError: true
17984
18241
  };
17985
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
+ }
17986
18252
  const restarted = await restartAgentSession(registry, resolveAgentAdapter, prev, {
17987
18253
  ...opts.resolveSandboxProvider ? { resolveSandboxProvider: opts.resolveSandboxProvider } : {}
17988
18254
  });
@@ -17995,7 +18261,8 @@ function registerSessionTools(rawServer, opts) {
17995
18261
  ...restarted.desc,
17996
18262
  resumedFrom: restarted.resumedFrom,
17997
18263
  resumeVia: restarted.resumeVia,
17998
- ...restarted.resumeFallback ? { resumeFallback: true } : {}
18264
+ ...restarted.resumeFallback ? { resumeFallback: true } : {},
18265
+ ...nativeResumeDecline ? { nativeResumeDecline } : {}
17999
18266
  },
18000
18267
  null,
18001
18268
  2
@@ -20289,13 +20556,34 @@ function registerWebSearchTools(server, opts = {}) {
20289
20556
  transformers: [catchErrors(), enrichResolverRejections()]
20290
20557
  });
20291
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
+ }
20292
20576
 
20293
20577
  // src/mcp-apps-adapter.ts
20294
20578
  var MIME_TYPE = "text/html;profile=mcp-app";
20579
+ var EMBED_TOKEN_PLACEHOLDER = '"__AGENPROTO_EMBED_TOKEN__"';
20295
20580
  function registerMcpApps(server, apps) {
20296
20581
  for (const app of apps) {
20297
20582
  const resourceUri = `ui://${app.id}/view`;
20298
- 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
+ }
20299
20587
  const resourceMeta = {
20300
20588
  ui: {
20301
20589
  prefersBorder: true,
@@ -20366,7 +20654,11 @@ function makeBuiltinPanelApps(ops) {
20366
20654
  // installed, install notice otherwise; see apps/src/session-chat).
20367
20655
  makeSessionChatApp({
20368
20656
  httpBaseUrl: ops.httpBaseUrl,
20369
- 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")
20370
20662
  }),
20371
20663
  // Work-board widget — kanban over the Task ledger (see apps/src/
20372
20664
  // work-board). Read path only; writes go through task_claim/
@@ -23241,6 +23533,22 @@ async function sendOutbound(provider, input, deps2) {
23241
23533
  return { ok: false, error: "unsupported_provider" };
23242
23534
  }
23243
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
+ }
23244
23552
  async function sendAgentpush(input, deps2) {
23245
23553
  const alias = input.alias;
23246
23554
  if (!alias) {
@@ -23270,7 +23578,7 @@ async function sendAgentpush(input, deps2) {
23270
23578
  if (!upload.ok) {
23271
23579
  return { ok: false, error: `upload_media_failed: ${upload.error}` };
23272
23580
  }
23273
- const result = typeof upload.result === "object" && upload.result !== null ? upload.result : {};
23581
+ const result = unwrapMcpToolResult(upload.result);
23274
23582
  const providerMediaId = typeof result.media_id === "string" ? result.media_id : typeof result.id === "string" ? result.id : void 0;
23275
23583
  if (!providerMediaId) {
23276
23584
  return { ok: false, error: "upload_media_missing_media_id" };
@@ -23295,7 +23603,18 @@ async function sendAgentpush(input, deps2) {
23295
23603
  if (!out.ok) {
23296
23604
  return { ok: false, error: out.error };
23297
23605
  }
23298
- 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);
23299
23618
  return { ok: true, providerMessageId };
23300
23619
  }
23301
23620
  async function sendTelegram(input, deps2) {
@@ -23320,12 +23639,7 @@ async function sendTelegram(input, deps2) {
23320
23639
  parse_mode: "MarkdownV2"
23321
23640
  })
23322
23641
  });
23323
- if (!res2.ok) {
23324
- const body2 = await res2.text().catch(() => "");
23325
- return { ok: false, error: `telegram_http_${res2.status}: ${body2}` };
23326
- }
23327
- const json2 = await res2.json().catch(() => ({}));
23328
- return { ok: true, providerMessageId: extractTelegramMessageId(json2) };
23642
+ return parseTelegramSendResponse(res2);
23329
23643
  }
23330
23644
  const files = [];
23331
23645
  for (const att of input.attachments) {
@@ -23359,12 +23673,7 @@ async function sendTelegram(input, deps2) {
23359
23673
  headers: { "Content-Type": contentType2 },
23360
23674
  body: toFetchBody(body2)
23361
23675
  });
23362
- if (!res2.ok) {
23363
- const bodyText = await res2.text().catch(() => "");
23364
- return { ok: false, error: `telegram_http_${res2.status}: ${bodyText}` };
23365
- }
23366
- const json2 = await res2.json().catch(() => ({}));
23367
- return { ok: true, providerMessageId: extractTelegramMessageId(json2) };
23676
+ return parseTelegramSendResponse(res2);
23368
23677
  }
23369
23678
  const mediaJson = input.attachments.map((att, index) => {
23370
23679
  const item = {
@@ -23397,11 +23706,18 @@ async function sendTelegram(input, deps2) {
23397
23706
  headers: { "Content-Type": contentType },
23398
23707
  body: toFetchBody(body)
23399
23708
  });
23709
+ return parseTelegramSendResponse(res);
23710
+ }
23711
+ async function parseTelegramSendResponse(res) {
23400
23712
  if (!res.ok) {
23401
- const bodyText = await res.text().catch(() => "");
23402
- 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}` };
23403
23715
  }
23404
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
+ }
23405
23721
  return { ok: true, providerMessageId: extractTelegramMessageId(json) };
23406
23722
  }
23407
23723
  function extractProviderMessageId(result) {
@@ -26348,7 +26664,16 @@ function registerOrchestrationTools(rawServer, opts) {
26348
26664
  if (!out.ok) {
26349
26665
  return {
26350
26666
  content: [
26351
- { 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
+ }
26352
26677
  ],
26353
26678
  isError: true
26354
26679
  };
@@ -26364,7 +26689,18 @@ function registerOrchestrationTools(rawServer, opts) {
26364
26689
  provider
26365
26690
  });
26366
26691
  }
26367
- 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
+ };
26368
26704
  }
26369
26705
  );
26370
26706
  }
@@ -26798,7 +27134,7 @@ async function startHttpServer(opts) {
26798
27134
  }
26799
27135
  function authorizeMcp(req, res) {
26800
27136
  const origin = req.headers.origin;
26801
- if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin)) {
27137
+ if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin) && !embedTokenTrusted(req)) {
26802
27138
  const auth = readAuth();
26803
27139
  const header = req.headers.authorization;
26804
27140
  if (auth.mode === "bearer" && header === `Bearer ${auth.token}`) {
@@ -26828,8 +27164,12 @@ async function startHttpServer(opts) {
26828
27164
  }
26829
27165
  const origin = req.headers.origin;
26830
27166
  if (typeof origin === "string" && originAllowed(origin)) return "ok";
27167
+ if (embedTokenTrusted(req)) return "ok";
26831
27168
  return header ? "bad" : "missing";
26832
27169
  }
27170
+ function embedTokenTrusted(req) {
27171
+ return isValidAppEmbedToken(requestEmbedToken(req));
27172
+ }
26833
27173
  function originAllowed(origin) {
26834
27174
  const list = opts.strictOrigins ? opts.allowedOrigins ?? [] : [...DEFAULT_ALLOWED_ORIGINS, ...opts.allowedOrigins ?? []];
26835
27175
  for (const pattern of list) {
@@ -27115,7 +27455,7 @@ async function startHttpServer(opts) {
27115
27455
  }
27116
27456
  function guardBrowserOrigin(req, res) {
27117
27457
  const origin = req.headers.origin;
27118
- if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin)) {
27458
+ if (typeof origin === "string" && origin.length > 0 && !originAllowed(origin) && !embedTokenTrusted(req)) {
27119
27459
  const auth = readAuth();
27120
27460
  const header = req.headers.authorization;
27121
27461
  if (auth.mode === "bearer" && header === `Bearer ${auth.token}`) {
@@ -27134,10 +27474,14 @@ async function startHttpServer(opts) {
27134
27474
  }
27135
27475
  function applyCors(req, res) {
27136
27476
  const origin = req.headers.origin;
27137
- const trusted = typeof origin === "string" && origin.length > 0 && originAllowed(origin);
27138
- 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) {
27139
27481
  res.setHeader("Access-Control-Allow-Origin", origin);
27140
27482
  res.setHeader("Access-Control-Allow-Credentials", "true");
27483
+ } else if (tokenTrusted && origin && origin !== "null") {
27484
+ res.setHeader("Access-Control-Allow-Origin", origin);
27141
27485
  } else {
27142
27486
  res.setHeader("Access-Control-Allow-Origin", "*");
27143
27487
  }
@@ -28105,7 +28449,9 @@ async function startHttpServer(opts) {
28105
28449
  const uiMatch = path.match(/^\/apps\/(.+)\/ui$/);
28106
28450
  if (uiMatch && req.method === "GET") {
28107
28451
  const uiApp = opts.appRegistry.getApp(decodeURIComponent(uiMatch[1]));
28108
- if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && guardBrowserOrigin(req, res)) return;
28452
+ if (!iframeEmbedOriginAllowed(req, uiApp ?? {}) && !isValidAppEmbedToken(requestEmbedToken(req)) && guardBrowserOrigin(req, res)) {
28453
+ return;
28454
+ }
28109
28455
  if (!authorize(req, res)) return;
28110
28456
  await handleAppUiPage(
28111
28457
  req,
@@ -30853,6 +31199,16 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
30853
31199
  if (frameAncestors.length === 0) {
30854
31200
  headers["x-frame-options"] = "SAMEORIGIN";
30855
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
+ }
30856
31212
  }
30857
31213
  res.writeHead(200, headers);
30858
31214
  res.end(injectStandaloneAppBridge(raw));
@@ -30860,6 +31216,7 @@ async function handleAppUiPage(req, res, appId, appRegistry, frameAncestors) {
30860
31216
  function iframeEmbedAllowed(req, app) {
30861
31217
  const secFetchDest = req.headers["sec-fetch-dest"];
30862
31218
  if (secFetchDest !== void 0 && secFetchDest !== "iframe") return false;
31219
+ if (isValidAppEmbedToken(requestEmbedToken(req))) return true;
30863
31220
  const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
30864
31221
  const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
30865
31222
  if (!origin && !referer) return false;
@@ -30880,6 +31237,13 @@ function iframeEmbedAllowed(req, app) {
30880
31237
  }
30881
31238
  return false;
30882
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
+ }
30883
31247
  function iframeEmbedOriginAllowed(req, app) {
30884
31248
  const origin = typeof req.headers.origin === "string" && req.headers.origin.length > 0 ? req.headers.origin : null;
30885
31249
  const referer = typeof req.headers.referer === "string" && req.headers.referer.length > 0 ? req.headers.referer : null;
@@ -34838,6 +35202,7 @@ function createOrchestratorMcpServerFactory(deps2) {
34838
35202
  ...deps2.listHarnessCapabilities ? { listHarnessCapabilities: deps2.listHarnessCapabilities } : {},
34839
35203
  ...deps2.webhookNotifier ? { webhookNotifier: deps2.webhookNotifier } : {},
34840
35204
  ...deps2.resolveSandboxProvider ? { resolveSandboxProvider: deps2.resolveSandboxProvider } : {},
35205
+ ...deps2.defaultAgentPromptInterrupt != null ? { defaultAgentPromptInterrupt: deps2.defaultAgentPromptInterrupt } : {},
34841
35206
  daemonMcpUrl: deps2.daemonMcpUrl
34842
35207
  });
34843
35208
  registerOrchestrationTools(server, {
@@ -38278,6 +38643,10 @@ async function createGateway(opts) {
38278
38643
  // can declare `sandbox` and resolve a provider, instead of throwing
38279
38644
  // `sandbox_provider_not_found`.
38280
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 } : {},
38281
38650
  ...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
38282
38651
  ...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},
38283
38652
  ...opts.listHarnessCapabilities ? { listHarnessCapabilities: opts.listHarnessCapabilities } : {}
@@ -38338,6 +38707,9 @@ async function createGateway(opts) {
38338
38707
  webhookNotifier,
38339
38708
  daemonMcpUrl,
38340
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 } : {},
38341
38713
  ...opts.provisionWorktree ? { provisionWorktree: opts.provisionWorktree } : {},
38342
38714
  ...opts.resolveAgentAdapter ? { resolveAgentAdapter: opts.resolveAgentAdapter } : {},
38343
38715
  ...opts.listAgentAdapters ? { listAgentAdapters: opts.listAgentAdapters } : {},