scream-code 0.18.0 → 0.18.1

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.
@@ -5,8 +5,8 @@ const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
7
  import { A as sharedMemoStore } from "./src-C7C9e5Ds.mjs";
8
- import { $n as encodeWorkDirKey, $t as lerpGradient, A as highlightLines, An as embeddingFailureDetail, At as isTurnElapsedEnabled, B as ActivityGroupComponent, Bn as detectShellEnvironment, Bt as ENABLE_TERMINAL_THEME_REPORTING, Cn as getLlmNotSetMessage, Ct as clearInfoPanelState, D as ToolCallComponent, Dn as sortSlashCommands, Dt as createEditorTheme, E as UserMessageComponent, En as BUILTIN_SLASH_COMMANDS, Et as resolveThemeSync, F as StatusMessageComponent, Fn as PULSE_WAVE_FRAMES, Ft as detectTerminalTheme, G as AgentGroupComponent, Gn as CLI_COMMAND_NAME, Gt as QUERY_TERMINAL_THEME, H as estimateTokens, Hn as getInputHistoryFile, Ht as OSC11_RESPONSE, I as replaceTabs, In as TuiConfigParseError, It as parseOsc11BackgroundTheme, J as getBreathingFrame, Jn as PRODUCT_NAME, Jt as TERMINAL_THEME_DARK, K as WelcomeComponent, Kn as CLI_UI_MODE, Kt as TERMINAL_FOCUS_IN, L as sanitizeShellOutput, Ln as TuiLikePreferencesSchema, Lt as DISABLE_TERMINAL_FOCUS_REPORTING, M as CachedContainer, Mn as hasEmbeddingModelCache, Mt as createThemeStyles, N as ThinkingComponent, Nn as startManualEmbeddingDownload, Nt as contrastTextHex, O as renderDiffLines, On as isExperimentalFlagEnabled, Ot as createMarkdownTheme, P as NoticeMessageComponent, Pn as PIXEL_PULSE_FRAMES, Pt as getColorPalette, Q as refineGoal, Qn as ScreamHarness, Qt as FooterComponent, R as SkillActivationComponent, Rn as loadTuiConfig, Rt as DISABLE_TERMINAL_THEME_REPORTING, S as readJsonlFile, Sn as getCtrlDHint, St as supportsBalance, Tn as buildSkillSlashCommands, U as getSharedSpeedTracker, Un as getLogDir, Ut as OSC11_RESPONSE_PREFIX, V as charsForTokenBudget, Vn as getDataDir, Vt as OSC11_QUERY, W as BackgroundAgentStatusComponent, Wn as detectInstallSource, Wt as OSC11_RESPONSE_PREFIX_NO_ESC, X as CompactionComponent, Xn as fetchCatalog, Xt as isBusy, Y as resetBreathingClock, Yn as DEFAULT_CATALOG_URL, Yt as TERMINAL_THEME_LIGHT, Z as clearGoalState, Zn as saveCatalogCache, Zt as isStreaming, _n as EXIT_CONFIRM_WINDOW_MS, a as syncDiagramCapabilityPrompt, an as printableChar, ar as isRealUserPrompt, at as toTerminalHyperlink, b as readUpdateCache, bn as TIP_ROTATION_INTERVAL_MS, c as disposeChildren, cn as appendStreamingArgsPreview, cr as flushDiagnosticLogs, d as isPlanExpandable, dn as isTodoItemShape, dr as isScreamError, en as SubagentSlots, er as normalizeWorkDir, f as MoonLoader, fn as parseStreamingArgs, fr as isOrphanedToolCallError, ft as handleFusionPlanCommand, gn as EMPTY_SESSION_HINT_URL, hn as truncateErrorMessage, i as buildRoleAdditionalText, it as isInternalMessage, j as langFromPath, jn as getKnowledgeStore, jt as toggleEmptySessionHint, k as renderDiffLinesClustered, kn as setExperimentalFlags, kt as isEmptySessionHintDismissed, l as hasDispose, ln as argsRecord, lr as log, mn as stringValue, mr as SCREAM_ERROR_INFO, mt as handlePlanCommand, n as clearEvalPanelState, nr as readSessionIndex, on as STATUS_BULLET, or as parseRlmMaxDepthArg, ot as changeThinkingLevel, p as formatMemoryMemoForInjection, pn as serializeToolResultOutput, pr as ErrorCodes, q as BREATHE_CYCLE_MS, qn as CLI_USER_AGENT_PRODUCT, qt as TERMINAL_FOCUS_OUT, r as openUrl, rr as removeSessionIndexEntry, sn as appendSessionHeaderHint, sr as resolveScreamHome, st as getModelCycleLevel, t as dispatchInput, tr as appendSessionIndexEntry, u as isExpandable, un as formatErrorMessage, ur as resolveGlobalLogPath, v as selectUpdateTarget, vn as MAIN_AGENT_ID, w as getDaemonInstructions, wn as getNoActiveSessionMessage, x as appendJsonlLine, xn as getCtrlCHint, xt as refreshProviderBalance, y as refreshUpdateCache, yn as SESSION_TIPS, z as AssistantMessageComponent, zn as saveTuiConfig, zt as ENABLE_TERMINAL_FOCUS_REPORTING } from "./dispatch-DfHVLpfA.mjs";
9
- import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-CK6jTftM.mjs";
8
+ import { $n as encodeWorkDirKey, $t as lerpGradient, A as highlightLines, An as embeddingFailureDetail, At as isTurnElapsedEnabled, B as ActivityGroupComponent, Bn as detectShellEnvironment, Bt as ENABLE_TERMINAL_THEME_REPORTING, Cn as getLlmNotSetMessage, Ct as clearInfoPanelState, D as ToolCallComponent, Dn as sortSlashCommands, Dt as createEditorTheme, E as UserMessageComponent, En as BUILTIN_SLASH_COMMANDS, Et as resolveThemeSync, F as StatusMessageComponent, Fn as PULSE_WAVE_FRAMES, Ft as detectTerminalTheme, G as AgentGroupComponent, Gn as CLI_COMMAND_NAME, Gt as QUERY_TERMINAL_THEME, H as estimateTokens, Hn as getInputHistoryFile, Ht as OSC11_RESPONSE, I as replaceTabs, In as TuiConfigParseError, It as parseOsc11BackgroundTheme, J as getBreathingFrame, Jn as PRODUCT_NAME, Jt as TERMINAL_THEME_DARK, K as WelcomeComponent, Kn as CLI_UI_MODE, Kt as TERMINAL_FOCUS_IN, L as sanitizeShellOutput, Ln as TuiLikePreferencesSchema, Lt as DISABLE_TERMINAL_FOCUS_REPORTING, M as CachedContainer, Mn as hasEmbeddingModelCache, Mt as createThemeStyles, N as ThinkingComponent, Nn as startManualEmbeddingDownload, Nt as contrastTextHex, O as renderDiffLines, On as isExperimentalFlagEnabled, Ot as createMarkdownTheme, P as NoticeMessageComponent, Pn as PIXEL_PULSE_FRAMES, Pt as getColorPalette, Q as refineGoal, Qn as ScreamHarness, Qt as FooterComponent, R as SkillActivationComponent, Rn as loadTuiConfig, Rt as DISABLE_TERMINAL_THEME_REPORTING, S as readJsonlFile, Sn as getCtrlDHint, St as supportsBalance, Tn as buildSkillSlashCommands, U as getSharedSpeedTracker, Un as getLogDir, Ut as OSC11_RESPONSE_PREFIX, V as charsForTokenBudget, Vn as getDataDir, Vt as OSC11_QUERY, W as BackgroundAgentStatusComponent, Wn as detectInstallSource, Wt as OSC11_RESPONSE_PREFIX_NO_ESC, X as CompactionComponent, Xn as fetchCatalog, Xt as isBusy, Y as resetBreathingClock, Yn as DEFAULT_CATALOG_URL, Yt as TERMINAL_THEME_LIGHT, Z as clearGoalState, Zn as saveCatalogCache, Zt as isStreaming, _n as EXIT_CONFIRM_WINDOW_MS, a as syncDiagramCapabilityPrompt, an as printableChar, ar as isRealUserPrompt, at as toTerminalHyperlink, b as readUpdateCache, bn as TIP_ROTATION_INTERVAL_MS, c as disposeChildren, cn as appendStreamingArgsPreview, cr as flushDiagnosticLogs, d as isPlanExpandable, dn as isTodoItemShape, dr as isScreamError, en as SubagentSlots, er as normalizeWorkDir, f as MoonLoader, fn as parseStreamingArgs, fr as isOrphanedToolCallError, ft as handleFusionPlanCommand, gn as EMPTY_SESSION_HINT_URL, hn as truncateErrorMessage, i as buildRoleAdditionalText, it as isInternalMessage, j as langFromPath, jn as getKnowledgeStore, jt as toggleEmptySessionHint, k as renderDiffLinesClustered, kn as setExperimentalFlags, kt as isEmptySessionHintDismissed, l as hasDispose, ln as argsRecord, lr as log, mn as stringValue, mr as SCREAM_ERROR_INFO, mt as handlePlanCommand, n as clearEvalPanelState, nr as readSessionIndex, on as STATUS_BULLET, or as parseRlmMaxDepthArg, ot as changeThinkingLevel, p as formatMemoryMemoForInjection, pn as serializeToolResultOutput, pr as ErrorCodes, q as BREATHE_CYCLE_MS, qn as CLI_USER_AGENT_PRODUCT, qt as TERMINAL_FOCUS_OUT, r as openUrl, rr as removeSessionIndexEntry, sn as appendSessionHeaderHint, sr as resolveScreamHome, st as getModelCycleLevel, t as dispatchInput, tr as appendSessionIndexEntry, u as isExpandable, un as formatErrorMessage, ur as resolveGlobalLogPath, v as selectUpdateTarget, vn as MAIN_AGENT_ID, w as getDaemonInstructions, wn as getNoActiveSessionMessage, x as appendJsonlLine, xn as getCtrlCHint, xt as refreshProviderBalance, y as refreshUpdateCache, yn as SESSION_TIPS, z as AssistantMessageComponent, zn as saveTuiConfig, zt as ENABLE_TERMINAL_FOCUS_REPORTING } from "./dispatch-FYZpdI9u.mjs";
9
+ import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-Bu8GPguU.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
12
12
  import { access, appendFile, chmod, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
@@ -33,7 +33,7 @@ function optionalBuildString(value) {
33
33
  return typeof value === "string" && value.length > 0 ? value : void 0;
34
34
  }
35
35
  const SCREAM_BUILD_INFO = {
36
- version: optionalBuildString("0.18.0"),
36
+ version: optionalBuildString("0.18.1"),
37
37
  channel: optionalBuildString(""),
38
38
  commit: optionalBuildString(""),
39
39
  buildTarget: optionalBuildString("darwin-arm64")
@@ -2014,20 +2014,33 @@ function formatBackgroundAgentTranscript(phase, meta, extras = void 0) {
2014
2014
  };
2015
2015
  }
2016
2016
  //#endregion
2017
- //#region src/tui/utils/child-request-notice.ts
2017
+ //#region src/tui/utils/subagent-handoff.ts
2018
2018
  /**
2019
- * Child→parent collaboration requests (`ContactParent`) shown in the main
2020
- * transcript as a two-line notice: a bold title naming the requester, then a
2021
- * dim detail line carrying the request itself.
2019
+ * Foreground→background handoff results, read by both UI faces.
2022
2020
  *
2023
- * Two producers share this formatter so live and replay never disagree:
2024
- * - the live path reads the subagent's `tool.call.started` args;
2025
- * - session replay recovers the same fields from the `<notification>` XML the
2026
- * kernel injects into the parent's context.
2021
+ * When the Agent tool hands a still-running child to the background manager it
2022
+ * returns an ordinary (non-error) result, in two shapes that both carry the
2023
+ * protocol line `status: backgrounded`: one with a `task_id` after a
2024
+ * successful registration, and one with a `could not register` warning when
2025
+ * registration failed. The second shape emits no `background.task.started`
2026
+ * frame, so the result body is the only signal that names the state.
2027
2027
  *
2028
- * Replay cannot recover the subagent's display name (spawn events are not part
2029
- * of the persisted context), so it renders the name-less title variant.
2028
+ * Live and replay therefore read the same fact: the protocol LINE, whether it
2029
+ * arrives in the `tool.result` frame or in the persisted tool-result message a
2030
+ * replay reconstructs.
2031
+ */
2032
+ const BACKGROUNDED_STATUS_LINE = "status: backgrounded";
2033
+ /**
2034
+ * True when a tool-result output carries the handoff protocol line as its own
2035
+ * line. Matching is line-exact after trimming (tolerating `\r`) so an Agent
2036
+ * result that merely quotes the phrase inside prose — e.g. a completed
2037
+ * subagent's summary — is not mistaken for a handoff.
2030
2038
  */
2039
+ function isBackgroundedHandoffResult(output) {
2040
+ return output.split("\n").some((line) => line.trim() === BACKGROUNDED_STATUS_LINE);
2041
+ }
2042
+ //#endregion
2043
+ //#region src/tui/utils/child-request-notice.ts
2031
2044
  const CHILD_REQUEST_KINDS = [
2032
2045
  "info",
2033
2046
  "handoff",
@@ -2075,22 +2088,26 @@ function previewedList(field, items) {
2075
2088
  if (items === void 0 || items.length === 0) return void 0;
2076
2089
  return t(LIST_KEY[field], { value: stringifyList(items) });
2077
2090
  }
2078
- /** Live producer: the args the model handed to `ContactParent`. */
2079
- function childRequestFieldsFromArgs(args) {
2080
- const message = textOf(args["message"]);
2091
+ /**
2092
+ * Live producer: the delivery event the kernel emits once a request landed in
2093
+ * the parent's turn. Events cross the wire as untyped JSON, so every field is
2094
+ * re-checked here rather than trusted from the declared type.
2095
+ */
2096
+ function childRequestFieldsFromEvent(event) {
2097
+ const record = event;
2098
+ const message = textOf(record["message"]);
2081
2099
  if (message === void 0) return null;
2082
- const payload = typeof args["payload"] === "object" && args["payload"] !== null && !Array.isArray(args["payload"]) ? args["payload"] : {};
2083
2100
  const listOf = (key) => {
2084
- const raw = payload[key];
2101
+ const raw = record[key];
2085
2102
  if (!Array.isArray(raw)) return void 0;
2086
2103
  const items = raw.map((item) => textOf(item)).filter((item) => item !== void 0);
2087
2104
  return items.length > 0 ? items : void 0;
2088
2105
  };
2089
2106
  return {
2090
- requestType: kindOf(args["request_type"]),
2107
+ requestType: kindOf(record["requestType"]),
2091
2108
  message,
2092
- needs: textOf(args["needs"]),
2093
- expecting: textOf(payload["expecting"]),
2109
+ needs: textOf(record["needs"]),
2110
+ expecting: textOf(record["expecting"]),
2094
2111
  artifacts: listOf("artifacts"),
2095
2112
  evidence: listOf("evidence"),
2096
2113
  missing: listOf("missing")
@@ -2387,8 +2404,7 @@ function truncate(value) {
2387
2404
  }
2388
2405
  function phaseFromStatus(status) {
2389
2406
  switch (status) {
2390
- case "running":
2391
- case "awaiting_approval": return "started";
2407
+ case "running": return "started";
2392
2408
  case "completed": return "completed";
2393
2409
  case "failed":
2394
2410
  case "killed":
@@ -2402,7 +2418,6 @@ function headlineFor(info) {
2402
2418
  const subject = subjectFor(info.taskId);
2403
2419
  switch (info.status) {
2404
2420
  case "running": return t("bgtask.started_bg", { subject });
2405
- case "awaiting_approval": return t("bgtask.awaiting_approval", { subject });
2406
2421
  case "completed": return t("bgtask.completed_bg", { subject });
2407
2422
  case "failed": return t("bgtask.failed_bg", { subject });
2408
2423
  case "killed": return t("bgtask.killed", { subject });
@@ -2420,10 +2435,6 @@ function detailFor(info) {
2420
2435
  const reason = truncate(info.stopReason);
2421
2436
  parts.push(reason !== void 0 ? t("bgtask.stopped_reason", { reason }) : t("bgtask.stopped"));
2422
2437
  }
2423
- if (info.status === "awaiting_approval") {
2424
- const reason = truncate(info.approvalReason);
2425
- if (reason !== void 0) parts.push(t("bgtask.waiting", { reason }));
2426
- }
2427
2438
  if (info.status === "lost") parts.push(t("bgtask.session_restarted"));
2428
2439
  if (info.timedOut === true) parts.push(t("bgtask.timed_out"));
2429
2440
  return parts.length > 0 ? parts.join(" · ") : void 0;
@@ -2443,6 +2454,223 @@ function formatBackgroundTaskTranscript(info) {
2443
2454
  };
2444
2455
  }
2445
2456
  //#endregion
2457
+ //#region src/tui/utils/media-url.ts
2458
+ function getMediaKindLabels() {
2459
+ return {
2460
+ audio: t("mediaurl.audio"),
2461
+ image: t("mediaurl.image"),
2462
+ video: t("mediaurl.video")
2463
+ };
2464
+ }
2465
+ function mediaUrlPartToText(kind, url) {
2466
+ const labels = getMediaKindLabels();
2467
+ const summary = summarizeDataUrl(url);
2468
+ if (summary !== void 0) {
2469
+ const size = summary.bytes !== void 0 ? `, ${formatByteSize(summary.bytes)}` : "";
2470
+ return `[${labels[kind]} ${summary.mime}${size}]`;
2471
+ }
2472
+ return `<${labels[kind]} url="${escapeAttribute$1(url)}">`;
2473
+ }
2474
+ function summarizeDataUrl(url) {
2475
+ if (!url.startsWith("data:")) return void 0;
2476
+ const commaIndex = url.indexOf(",");
2477
+ const header = commaIndex >= 0 ? url.slice(5, commaIndex) : url.slice(5);
2478
+ const data = commaIndex >= 0 ? url.slice(commaIndex + 1) : "";
2479
+ const [rawMime, ...params] = header.split(";");
2480
+ return {
2481
+ mime: rawMime !== void 0 && rawMime.length > 0 ? rawMime : "application/octet-stream",
2482
+ bytes: params.some((param) => param.toLowerCase() === "base64") ? estimateBase64Bytes(data) : void 0
2483
+ };
2484
+ }
2485
+ function estimateBase64Bytes(data) {
2486
+ const compact = data.replaceAll(/\s/g, "");
2487
+ if (compact.length === 0) return 0;
2488
+ const padding = compact.endsWith("==") ? 2 : compact.endsWith("=") ? 1 : 0;
2489
+ return Math.max(0, Math.floor(compact.length * 3 / 4) - padding);
2490
+ }
2491
+ function formatByteSize(bytes) {
2492
+ if (bytes < 1024) return `${String(bytes)} B`;
2493
+ const kib = bytes / 1024;
2494
+ if (kib < 1024) return `${formatOneDecimal(kib)} KB`;
2495
+ return `${formatOneDecimal(kib / 1024)} MB`;
2496
+ }
2497
+ function formatOneDecimal(value) {
2498
+ return value >= 10 ? value.toFixed(0) : value.toFixed(1);
2499
+ }
2500
+ function escapeAttribute$1(value) {
2501
+ return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2502
+ }
2503
+ //#endregion
2504
+ //#region src/tui/utils/transcript-id.ts
2505
+ let transcriptIdCounter = 0;
2506
+ function nextTranscriptId() {
2507
+ transcriptIdCounter += 1;
2508
+ return `entry-${String(transcriptIdCounter)}`;
2509
+ }
2510
+ //#endregion
2511
+ //#region src/tui/utils/message-replay.ts
2512
+ function appStateFromResumeAgent(agent) {
2513
+ const maxContextTokens = agent.config.modelCapabilities?.max_context_tokens ?? 0;
2514
+ const contextTokens = agent.context.tokenCount;
2515
+ const contextUsage = maxContextTokens > 0 ? contextTokens / maxContextTokens : 0;
2516
+ return {
2517
+ model: agent.config.modelAlias ?? agent.config.provider?.model ?? "",
2518
+ contextTokens,
2519
+ maxContextTokens,
2520
+ contextUsage,
2521
+ planMode: agent.plan !== null ? "plan" : "off",
2522
+ permissionMode: agent.permission.mode
2523
+ };
2524
+ }
2525
+ function isTerminalBackgroundTask(info) {
2526
+ return info.status === "completed" || info.status === "failed" || info.status === "killed" || info.status === "lost";
2527
+ }
2528
+ function countActiveBackgroundTasks(tasks) {
2529
+ let bashTasks = 0;
2530
+ let agentTasks = 0;
2531
+ for (const info of tasks.values()) {
2532
+ if (isTerminalBackgroundTask(info)) continue;
2533
+ if (info.taskId.startsWith("agent-")) agentTasks += 1;
2534
+ else bashTasks += 1;
2535
+ }
2536
+ return {
2537
+ bashTasks,
2538
+ agentTasks
2539
+ };
2540
+ }
2541
+ function replayBackgroundProjection(background) {
2542
+ const backgroundAgentMetadata = /* @__PURE__ */ new Map();
2543
+ for (const info of background) {
2544
+ if (!info.taskId.startsWith("agent-")) continue;
2545
+ if (isTerminalBackgroundTask(info)) continue;
2546
+ const subagentId = info.agentId;
2547
+ if (subagentId === void 0 || subagentId === info.taskId) continue;
2548
+ backgroundAgentMetadata.set(subagentId, {
2549
+ agentId: subagentId,
2550
+ parentToolCallId: "",
2551
+ description: info.description
2552
+ });
2553
+ }
2554
+ return { backgroundAgentMetadata };
2555
+ }
2556
+ function createReplayRenderContext() {
2557
+ return {
2558
+ turnIndex: 0,
2559
+ stepIndex: 0,
2560
+ currentTurnId: void 0,
2561
+ assistant: {
2562
+ thinking: [],
2563
+ text: []
2564
+ },
2565
+ toolCalls: /* @__PURE__ */ new Map(),
2566
+ completedToolCallIds: /* @__PURE__ */ new Set(),
2567
+ skillActivationIds: /* @__PURE__ */ new Set(),
2568
+ suppressNextPlanModeOffNotice: false
2569
+ };
2570
+ }
2571
+ function limitReplayRecordsByTurn(records, maxTurns) {
2572
+ if (maxTurns <= 0) return [];
2573
+ const turnStarts = records.flatMap((record, index) => record.type === "message" && isRealUserPrompt(record.message) ? [index] : []);
2574
+ if (turnStarts.length <= maxTurns) return records;
2575
+ return records.slice(turnStarts[turnStarts.length - maxTurns]);
2576
+ }
2577
+ function replayEntry(context, kind, content, renderMode, extras = {}) {
2578
+ return {
2579
+ id: nextTranscriptId(),
2580
+ kind,
2581
+ turnId: context.currentTurnId,
2582
+ renderMode,
2583
+ content,
2584
+ detail: extras.detail
2585
+ };
2586
+ }
2587
+ function collectReplayMessageContent(target, content) {
2588
+ for (const part of content) switch (part.type) {
2589
+ case "think":
2590
+ target.thinking.push(part.think);
2591
+ break;
2592
+ case "text":
2593
+ target.text.push(part.text);
2594
+ break;
2595
+ case "audio_url":
2596
+ case "image_url":
2597
+ case "video_url": break;
2598
+ }
2599
+ }
2600
+ function toolCallFromReplayMessage(rawToolCall, context) {
2601
+ const id = rawToolCall.id;
2602
+ const name = rawToolCall.name;
2603
+ if (id.length === 0 || name.length === 0) return void 0;
2604
+ return {
2605
+ id,
2606
+ name,
2607
+ args: parseReplayToolArguments(rawToolCall.arguments),
2608
+ step: context.stepIndex,
2609
+ turnId: context.currentTurnId
2610
+ };
2611
+ }
2612
+ function toolResultOutput(content) {
2613
+ if (content.some((part) => part.type !== "text")) return JSON.stringify(content);
2614
+ return contentPartsToText(content);
2615
+ }
2616
+ function contentPartsToText(content) {
2617
+ return content.map(contentPartToText).join("");
2618
+ }
2619
+ function backgroundOrigin(message) {
2620
+ return message.origin?.kind === "background_task" ? message.origin : void 0;
2621
+ }
2622
+ function skillActivationFromOrigin(origin) {
2623
+ if (origin?.kind !== "skill_activation") return void 0;
2624
+ return {
2625
+ activationId: origin.activationId,
2626
+ skillName: origin.skillName,
2627
+ skillArgs: origin.skillArgs,
2628
+ trigger: origin.trigger
2629
+ };
2630
+ }
2631
+ function formatHookResultMessageForTranscript(text, fallbackEvent, blocked) {
2632
+ const results = [];
2633
+ let lastIndex = 0;
2634
+ for (const match of text.matchAll(HOOK_RESULT_RE)) {
2635
+ if (text.slice(lastIndex, match.index).trim().length > 0) return formatHookResultBlock(fallbackEvent, text, blocked);
2636
+ const event = match[1];
2637
+ const body = match[2];
2638
+ if (event === void 0 || body === void 0) return formatHookResultBlock(fallbackEvent, text, blocked);
2639
+ results.push({
2640
+ event,
2641
+ body
2642
+ });
2643
+ lastIndex = match.index + match[0].length;
2644
+ }
2645
+ if (results.length === 0 || text.slice(lastIndex).trim().length > 0) return formatHookResultBlock(fallbackEvent, text, blocked);
2646
+ return results.map(({ event, body }) => formatHookResultBlock(event, body, blocked)).join("\n\n");
2647
+ }
2648
+ function parseReplayToolArguments(value) {
2649
+ if (value === null || value.length === 0) return {};
2650
+ try {
2651
+ const parsed = JSON.parse(value);
2652
+ return isRecord$2(parsed) ? parsed : {};
2653
+ } catch {
2654
+ return {};
2655
+ }
2656
+ }
2657
+ function contentPartToText(part) {
2658
+ switch (part.type) {
2659
+ case "text": return part.text;
2660
+ case "think": return part.think;
2661
+ case "image_url": return mediaUrlPartToText("image", part.imageUrl.url);
2662
+ case "video_url": return mediaUrlPartToText("video", part.videoUrl.url);
2663
+ case "audio_url": return mediaUrlPartToText("audio", part.audioUrl.url);
2664
+ }
2665
+ }
2666
+ const HOOK_RESULT_RE = /<hook_result\s+hook_event="([^"]+)">\n?([\s\S]*?)\n?<\/hook_result>/g;
2667
+ function formatHookResultBlock(event, body, blocked) {
2668
+ return `*${event} hook${blocked ? t("replayhook.blocked") : ""}*\n\n${body.trim() || t("replayhook.empty")}`;
2669
+ }
2670
+ function isRecord$2(value) {
2671
+ return typeof value === "object" && value !== null;
2672
+ }
2673
+ //#endregion
2446
2674
  //#region src/tui/utils/hook-result-format.ts
2447
2675
  function formatHookResultMarkdown(event) {
2448
2676
  return `*${formatHookResultTitle(event)}*\n\n${formatHookResultBody(event)}`;
@@ -2541,7 +2769,7 @@ function parseMcpOAuthAuthorizationUrlUpdate(update) {
2541
2769
  if (update.kind !== "custom") return void 0;
2542
2770
  if (update.customKind !== "mcp.oauth.authorization_url") return void 0;
2543
2771
  const data = update.customData;
2544
- if (!isRecord$2(data)) return void 0;
2772
+ if (!isRecord$1(data)) return void 0;
2545
2773
  const serverName = data["serverName"];
2546
2774
  const authorizationUrl = data["authorizationUrl"];
2547
2775
  if (typeof serverName !== "string" || serverName.length === 0) return void 0;
@@ -2552,7 +2780,7 @@ function parseMcpOAuthAuthorizationUrlUpdate(update) {
2552
2780
  authorizationUrl
2553
2781
  };
2554
2782
  }
2555
- function isRecord$2(value) {
2783
+ function isRecord$1(value) {
2556
2784
  return typeof value === "object" && value !== null && !Array.isArray(value);
2557
2785
  }
2558
2786
  function isHttpUrl(value) {
@@ -2604,13 +2832,6 @@ function formatDuration(ms) {
2604
2832
  return `${(ms / 1e3).toFixed(1)}s`;
2605
2833
  }
2606
2834
  //#endregion
2607
- //#region src/tui/utils/transcript-id.ts
2608
- let transcriptIdCounter = 0;
2609
- function nextTranscriptId() {
2610
- transcriptIdCounter += 1;
2611
- return `entry-${String(transcriptIdCounter)}`;
2612
- }
2613
- //#endregion
2614
2835
  //#region src/tui/streaming-phase.ts
2615
2836
  /**
2616
2837
  * Returns true when transitioning `from → to` is a valid directed edge.
@@ -2721,6 +2942,20 @@ function isZeroUsage(u) {
2721
2942
  function isMainAgentStatusEvent(event) {
2722
2943
  return event.agentId === void 0 || event.agentId === "main";
2723
2944
  }
2945
+ /**
2946
+ * Cap for the visible background-task registry (`backgroundTasks`).
2947
+ *
2948
+ * Eviction policy: terminal entries leave oldest-terminal-first; a
2949
+ * non-terminal task is never evicted. Terminal-time FIFO is chosen over a
2950
+ * per-owner cap because the registry is one flat map — a per-owner cap would
2951
+ * multiply the ceiling by the owner count (the number it is supposed to bound)
2952
+ * and still let one owner's history crowd out another's, while a single global
2953
+ * cap keeps the memory bound exact. Evicting also drops the task's owner tag
2954
+ * and its transcript-dedupe marker, so those two side maps stay bounded with
2955
+ * it. 200 is far above any plausible live set (the manager itself retires at
2956
+ * 20 per owner) and only ever trims scrolling history.
2957
+ */
2958
+ const MAX_BACKGROUND_TASKS = 200;
2724
2959
  var SessionEventHandler = class {
2725
2960
  host;
2726
2961
  constructor(host) {
@@ -2729,6 +2964,16 @@ var SessionEventHandler = class {
2729
2964
  backgroundAgentMetadata = /* @__PURE__ */ new Map();
2730
2965
  backgroundTasks = /* @__PURE__ */ new Map();
2731
2966
  /**
2967
+ * Owner of every subagent-owned entry in {@link backgroundTasks}: the agent
2968
+ * id whose registry emitted the task's lifecycle events. Only subagent-owned
2969
+ * tasks are recorded — the interactive (main) agent is the implicit default,
2970
+ * so `has(taskId)` reads as "not main's". The /tasks browser annotates those
2971
+ * rows with their source subagent and routes output/stop to the owner,
2972
+ * because a subagent's task lives in that subagent's own registry: asked
2973
+ * through main, its output reads empty and its stop is a no-op.
2974
+ */
2975
+ backgroundTaskOwners = /* @__PURE__ */ new Map();
2976
+ /**
2732
2977
  * Compaction trigger of the in-flight compaction, captured in
2733
2978
  * `handleCompactionBegin` (CompactionStartedEvent carries `trigger`) and
2734
2979
  * read in `handleCompactionEnd` (CompactionCompletedEvent does not). Used so
@@ -2740,10 +2985,13 @@ var SessionEventHandler = class {
2740
2985
  */
2741
2986
  lastCompactionTrigger;
2742
2987
  backgroundTaskTranscriptedTerminal = /* @__PURE__ */ new Set();
2743
- /** Active foreground (non-background) subagents. Footer badge counts them
2744
- * separately from background agents: foreground subagents are transient
2745
- * (spawned by the current turn's Agent tool), background agents persist. */
2746
- foregroundSubagentCount = 0;
2988
+ /** Active foreground (non-background) subagents, by subagent id. Footer
2989
+ * badge counts them separately from background agents: foreground
2990
+ * subagents are transient (spawned by the current turn's Agent tool),
2991
+ * background agents persist. Tracking ids instead of a bare count keeps
2992
+ * the releases idempotent and lets a foreground→background handoff move
2993
+ * exactly one run between the two badges. */
2994
+ foregroundSubagentIds = /* @__PURE__ */ new Set();
2747
2995
  subagentInfo = /* @__PURE__ */ new Map();
2748
2996
  /** Sidebar subagent slot state machine (see utils/subagent-slots.ts). */
2749
2997
  subagentSlots = new SubagentSlots();
@@ -2780,8 +3028,10 @@ var SessionEventHandler = class {
2780
3028
  resetRuntimeState() {
2781
3029
  this.backgroundAgentMetadata.clear();
2782
3030
  this.backgroundTasks.clear();
3031
+ this.backgroundTaskOwners.clear();
2783
3032
  this.backgroundTaskTranscriptedTerminal.clear();
2784
3033
  this.subagentInfo.clear();
3034
+ this.foregroundSubagentIds.clear();
2785
3035
  this.subagentSlots.reset();
2786
3036
  this.subagentInstances.clear();
2787
3037
  this.recentSubagentInstances.length = 0;
@@ -2930,6 +3180,9 @@ var SessionEventHandler = class {
2930
3180
  case "subagent.failed":
2931
3181
  this.handleSubagentFailed(event);
2932
3182
  break;
3183
+ case "subagent.child_request":
3184
+ this.handleSubagentChildRequest(event);
3185
+ break;
2933
3186
  case "background.task.started":
2934
3187
  case "background.task.updated":
2935
3188
  case "background.task.terminated":
@@ -3061,11 +3314,11 @@ var SessionEventHandler = class {
3061
3314
  this.subagentSlots.onTerminated(event.subagentId);
3062
3315
  this.closeSubagentInstance(event.subagentId, event.type === "subagent.failed" ? "failed" : "completed");
3063
3316
  break;
3317
+ case "subagent.child_request":
3318
+ this.handleSubagentChildRequest(event);
3319
+ break;
3064
3320
  case "tool.call.started":
3065
- if (event.name === "ContactParent") {
3066
- this.subagentSlots.onRequesting(agentId);
3067
- this.appendChildRequestNotice(agentId, event.args);
3068
- } else this.subagentSlots.onActivity(agentId, "tool", `tool: ${event.name}`);
3321
+ if (event.name !== "ContactParent") this.subagentSlots.onActivity(agentId, "tool", `tool: ${event.name}`);
3069
3322
  break;
3070
3323
  case "tool.call.delta": break;
3071
3324
  case "tool.result":
@@ -3079,15 +3332,22 @@ var SessionEventHandler = class {
3079
3332
  }
3080
3333
  }
3081
3334
  /**
3082
- * Surface a child→parent collaboration request in the transcript the moment
3083
- * the subagent asks, so the human sees it without expanding the agent card.
3084
- * The request always targets the main agent (one subagent host per session),
3085
- * which is why a top-level transcript entry is the right home for it.
3335
+ * A child→parent collaboration request that the kernel actually delivered:
3336
+ * raise the requester's transient requesting marker and cut into the main
3337
+ * transcript, so the human sees it without expanding the agent card. The
3338
+ * kernel emits the frame at the steering success point only, so a request it
3339
+ * deduped or refused never reaches this handler — the live transcript paints
3340
+ * exactly what replay reconstructs from the parent's context.
3086
3341
  */
3087
- appendChildRequestNotice(agentId, args) {
3088
- const fields = childRequestFieldsFromArgs(argsRecord(args));
3342
+ handleSubagentChildRequest(event) {
3343
+ this.subagentSlots.onRequesting(event.subagentId);
3344
+ this.appendChildRequestNotice(event);
3345
+ }
3346
+ appendChildRequestNotice(event) {
3347
+ const fields = childRequestFieldsFromEvent(event);
3089
3348
  if (fields === null) return;
3090
- const notice = renderChildRequestNotice(fields, this.subagentInfo.get(agentId)?.name);
3349
+ const notice = renderChildRequestNotice(fields, event.subagentName ?? this.subagentInfo.get(event.subagentId)?.name);
3350
+ this.host.streamingUI.endActivityGroup();
3091
3351
  this.host.appendTranscriptEntry({
3092
3352
  id: nextTranscriptId(),
3093
3353
  kind: "status",
@@ -3102,6 +3362,10 @@ var SessionEventHandler = class {
3102
3362
  const subagentId = event.agentId;
3103
3363
  if (subagentId === "main") return false;
3104
3364
  this.feedSubagentSlots(event, subagentId);
3365
+ if (event.type === "background.task.started" || event.type === "background.task.updated" || event.type === "background.task.terminated") {
3366
+ this.recordSubagentBackgroundTask(subagentId, event);
3367
+ return true;
3368
+ }
3105
3369
  const { streamingUI } = this.host;
3106
3370
  const info = this.subagentInfo.get(subagentId);
3107
3371
  if (info === void 0 || info.parentToolCallId.length === 0) return true;
@@ -3151,9 +3415,6 @@ var SessionEventHandler = class {
3151
3415
  });
3152
3416
  return true;
3153
3417
  }
3154
- case "background.task.started":
3155
- case "background.task.updated":
3156
- case "background.task.terminated":
3157
3418
  case "compaction.blocked":
3158
3419
  case "compaction.cancelled":
3159
3420
  case "compaction.completed":
@@ -3440,6 +3701,7 @@ var SessionEventHandler = class {
3440
3701
  display: event.display,
3441
3702
  message: event.message
3442
3703
  };
3704
+ if (isBackgroundedHandoffResult(resultData.output)) streamingUI.markSubagentBackgrounded({ toolCallId: event.toolCallId });
3443
3705
  streamingUI.completeToolResult(event.toolCallId, resultData);
3444
3706
  if (!streamingUI.hasPendingToolCalls() && canTransitionTo(this.host.state.appState.streamingPhase, "waiting")) this.host.setAppState({ streamingPhase: "waiting" });
3445
3707
  }
@@ -3748,7 +4010,7 @@ var SessionEventHandler = class {
3748
4010
  this.syncBackgroundAgentBadge();
3749
4011
  return;
3750
4012
  }
3751
- this.foregroundSubagentCount += 1;
4013
+ this.foregroundSubagentIds.add(event.subagentId);
3752
4014
  this.syncBackgroundAgentBadge();
3753
4015
  let tc = streamingUI.getToolComponent(event.parentToolCallId);
3754
4016
  if (tc === void 0) {
@@ -3794,7 +4056,7 @@ var SessionEventHandler = class {
3794
4056
  this.appendBackgroundAgentEntry("completed", backgroundMeta, extras);
3795
4057
  return;
3796
4058
  }
3797
- this.foregroundSubagentCount = Math.max(0, this.foregroundSubagentCount - 1);
4059
+ this.foregroundSubagentIds.delete(event.subagentId);
3798
4060
  this.syncBackgroundAgentBadge();
3799
4061
  const routingId = this.subagentInfo.get(event.subagentId)?.parentToolCallId ?? event.parentToolCallId;
3800
4062
  const tc = streamingUI.getToolComponent(routingId);
@@ -3830,7 +4092,7 @@ var SessionEventHandler = class {
3830
4092
  this.appendBackgroundAgentEntry("failed", backgroundMeta, { error: event.error });
3831
4093
  return;
3832
4094
  }
3833
- this.foregroundSubagentCount = Math.max(0, this.foregroundSubagentCount - 1);
4095
+ this.foregroundSubagentIds.delete(event.subagentId);
3834
4096
  this.syncBackgroundAgentBadge();
3835
4097
  const tc = streamingUI.getToolComponent(this.subagentInfo.get(event.subagentId)?.parentToolCallId ?? event.parentToolCallId);
3836
4098
  if (tc === void 0) return;
@@ -3880,6 +4142,7 @@ var SessionEventHandler = class {
3880
4142
  let match;
3881
4143
  for (const info of this.backgroundTasks.values()) {
3882
4144
  if (!info.taskId.startsWith("agent-")) continue;
4145
+ if (this.backgroundTaskOwners.has(info.taskId)) continue;
3883
4146
  if (info.description !== description) continue;
3884
4147
  if (match !== void 0) return void 0;
3885
4148
  match = info.taskId;
@@ -3915,12 +4178,13 @@ var SessionEventHandler = class {
3915
4178
  const { state } = this.host;
3916
4179
  const { info } = event;
3917
4180
  const previous = this.backgroundTasks.get(info.taskId);
3918
- this.backgroundTasks.set(info.taskId, info);
4181
+ this.rememberBackgroundTask(info);
3919
4182
  const viewer = state.tasksBrowser?.viewer;
3920
4183
  if (viewer !== void 0 && viewer.taskId === info.taskId) this.host.tasksBrowserController.refreshOutputViewer({ silent: true });
3921
4184
  const isTerminal = info.status === "completed" || info.status === "failed" || info.status === "killed" || info.status === "lost";
3922
4185
  if (event.type === "background.task.started") {
3923
4186
  if (info.taskId.startsWith("agent-")) {
4187
+ if (info.agentId !== void 0 && this.foregroundSubagentIds.delete(info.agentId)) this.host.streamingUI.markSubagentBackgrounded({ agentId: info.agentId });
3924
4188
  this.syncBackgroundTaskBadge();
3925
4189
  this.host.tasksBrowserController.repaint();
3926
4190
  return;
@@ -3947,6 +4211,43 @@ var SessionEventHandler = class {
3947
4211
  if (previous?.status !== info.status) this.syncBackgroundTaskBadge();
3948
4212
  this.host.tasksBrowserController.repaint();
3949
4213
  }
4214
+ /**
4215
+ * Record a subagent-owned task in the visible registry. It stays out of the
4216
+ * transcript (a subagent's stream never joins the main body) and out of the
4217
+ * footer badge (see syncBackgroundTaskBadge), but /tasks lists it with its
4218
+ * source agent, and output/stop reach it through that owner.
4219
+ */
4220
+ recordSubagentBackgroundTask(ownerId, event) {
4221
+ const { info } = event;
4222
+ this.backgroundTaskOwners.set(info.taskId, ownerId);
4223
+ this.rememberBackgroundTask(info);
4224
+ const viewer = this.host.state.tasksBrowser?.viewer;
4225
+ if (viewer !== void 0 && viewer.taskId === info.taskId) this.host.tasksBrowserController.refreshOutputViewer({ silent: true });
4226
+ this.host.tasksBrowserController.repaint();
4227
+ }
4228
+ /** Insert/refresh one visible task, then keep the registry within
4229
+ * {@link MAX_BACKGROUND_TASKS}. */
4230
+ rememberBackgroundTask(info) {
4231
+ this.backgroundTasks.set(info.taskId, info);
4232
+ this.evictExcessBackgroundTasks();
4233
+ }
4234
+ /**
4235
+ * Drop the oldest terminal entries once the registry is over the cap. A
4236
+ * non-terminal task is never evicted — it is still running, and a /tasks row
4237
+ * vanishing mid-run would misreport reality. The owner tag and the
4238
+ * transcript-dedupe marker leave with their entry, so those side maps stay
4239
+ * bounded by the same cap instead of growing per task forever.
4240
+ */
4241
+ evictExcessBackgroundTasks() {
4242
+ if (this.backgroundTasks.size <= MAX_BACKGROUND_TASKS) return;
4243
+ const evictable = [...this.backgroundTasks.values()].filter(isTerminalBackgroundTask).toSorted((a, b) => (a.endedAt ?? a.startedAt) - (b.endedAt ?? b.startedAt));
4244
+ for (const info of evictable) {
4245
+ if (this.backgroundTasks.size <= MAX_BACKGROUND_TASKS) break;
4246
+ this.backgroundTasks.delete(info.taskId);
4247
+ this.backgroundTaskOwners.delete(info.taskId);
4248
+ this.backgroundTaskTranscriptedTerminal.delete(info.taskId);
4249
+ }
4250
+ }
3950
4251
  appendBackgroundTaskEntry(info) {
3951
4252
  const status = formatBackgroundTaskTranscript(info);
3952
4253
  const entry = {
@@ -3965,6 +4266,7 @@ var SessionEventHandler = class {
3965
4266
  let bashTasks = 0;
3966
4267
  let agentTasks = 0;
3967
4268
  for (const info of this.backgroundTasks.values()) {
4269
+ if (this.backgroundTaskOwners.has(info.taskId)) continue;
3968
4270
  if (info.status === "completed" || info.status === "failed" || info.status === "killed" || info.status === "lost") continue;
3969
4271
  if (info.taskId.startsWith("agent-")) agentTasks += 1;
3970
4272
  else bashTasks += 1;
@@ -3972,7 +4274,7 @@ var SessionEventHandler = class {
3972
4274
  state.footer?.setBackgroundCounts({
3973
4275
  bashTasks,
3974
4276
  agentTasks,
3975
- foregroundSubagents: this.foregroundSubagentCount
4277
+ foregroundSubagents: this.foregroundSubagentIds.size
3976
4278
  });
3977
4279
  state.ui?.requestRender();
3978
4280
  }
@@ -3991,214 +4293,6 @@ function buildApprovalNotice(args) {
3991
4293
  };
3992
4294
  }
3993
4295
  //#endregion
3994
- //#region src/tui/utils/media-url.ts
3995
- function getMediaKindLabels() {
3996
- return {
3997
- audio: t("mediaurl.audio"),
3998
- image: t("mediaurl.image"),
3999
- video: t("mediaurl.video")
4000
- };
4001
- }
4002
- function mediaUrlPartToText(kind, url) {
4003
- const labels = getMediaKindLabels();
4004
- const summary = summarizeDataUrl(url);
4005
- if (summary !== void 0) {
4006
- const size = summary.bytes !== void 0 ? `, ${formatByteSize(summary.bytes)}` : "";
4007
- return `[${labels[kind]} ${summary.mime}${size}]`;
4008
- }
4009
- return `<${labels[kind]} url="${escapeAttribute$1(url)}">`;
4010
- }
4011
- function summarizeDataUrl(url) {
4012
- if (!url.startsWith("data:")) return void 0;
4013
- const commaIndex = url.indexOf(",");
4014
- const header = commaIndex >= 0 ? url.slice(5, commaIndex) : url.slice(5);
4015
- const data = commaIndex >= 0 ? url.slice(commaIndex + 1) : "";
4016
- const [rawMime, ...params] = header.split(";");
4017
- return {
4018
- mime: rawMime !== void 0 && rawMime.length > 0 ? rawMime : "application/octet-stream",
4019
- bytes: params.some((param) => param.toLowerCase() === "base64") ? estimateBase64Bytes(data) : void 0
4020
- };
4021
- }
4022
- function estimateBase64Bytes(data) {
4023
- const compact = data.replaceAll(/\s/g, "");
4024
- if (compact.length === 0) return 0;
4025
- const padding = compact.endsWith("==") ? 2 : compact.endsWith("=") ? 1 : 0;
4026
- return Math.max(0, Math.floor(compact.length * 3 / 4) - padding);
4027
- }
4028
- function formatByteSize(bytes) {
4029
- if (bytes < 1024) return `${String(bytes)} B`;
4030
- const kib = bytes / 1024;
4031
- if (kib < 1024) return `${formatOneDecimal(kib)} KB`;
4032
- return `${formatOneDecimal(kib / 1024)} MB`;
4033
- }
4034
- function formatOneDecimal(value) {
4035
- return value >= 10 ? value.toFixed(0) : value.toFixed(1);
4036
- }
4037
- function escapeAttribute$1(value) {
4038
- return value.replaceAll("&", "&amp;").replaceAll("\"", "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
4039
- }
4040
- //#endregion
4041
- //#region src/tui/utils/message-replay.ts
4042
- function appStateFromResumeAgent(agent) {
4043
- const maxContextTokens = agent.config.modelCapabilities?.max_context_tokens ?? 0;
4044
- const contextTokens = agent.context.tokenCount;
4045
- const contextUsage = maxContextTokens > 0 ? contextTokens / maxContextTokens : 0;
4046
- return {
4047
- model: agent.config.modelAlias ?? agent.config.provider?.model ?? "",
4048
- contextTokens,
4049
- maxContextTokens,
4050
- contextUsage,
4051
- planMode: agent.plan !== null ? "plan" : "off",
4052
- permissionMode: agent.permission.mode
4053
- };
4054
- }
4055
- function isTerminalBackgroundTask(info) {
4056
- return info.status === "completed" || info.status === "failed" || info.status === "killed" || info.status === "lost";
4057
- }
4058
- function countActiveBackgroundTasks(tasks) {
4059
- let bashTasks = 0;
4060
- let agentTasks = 0;
4061
- for (const info of tasks.values()) {
4062
- if (isTerminalBackgroundTask(info)) continue;
4063
- if (info.taskId.startsWith("agent-")) agentTasks += 1;
4064
- else bashTasks += 1;
4065
- }
4066
- return {
4067
- bashTasks,
4068
- agentTasks
4069
- };
4070
- }
4071
- function replayBackgroundProjection(background) {
4072
- const backgroundAgentMetadata = /* @__PURE__ */ new Map();
4073
- for (const info of background) {
4074
- if (!info.taskId.startsWith("agent-")) continue;
4075
- if (isTerminalBackgroundTask(info)) continue;
4076
- backgroundAgentMetadata.set(info.taskId, {
4077
- agentId: info.taskId,
4078
- parentToolCallId: info.taskId,
4079
- description: info.description
4080
- });
4081
- }
4082
- return { backgroundAgentMetadata };
4083
- }
4084
- function createReplayRenderContext() {
4085
- return {
4086
- turnIndex: 0,
4087
- stepIndex: 0,
4088
- currentTurnId: void 0,
4089
- assistant: {
4090
- thinking: [],
4091
- text: []
4092
- },
4093
- toolCalls: /* @__PURE__ */ new Map(),
4094
- completedToolCallIds: /* @__PURE__ */ new Set(),
4095
- skillActivationIds: /* @__PURE__ */ new Set(),
4096
- suppressNextPlanModeOffNotice: false
4097
- };
4098
- }
4099
- function limitReplayRecordsByTurn(records, maxTurns) {
4100
- if (maxTurns <= 0) return [];
4101
- const turnStarts = records.flatMap((record, index) => record.type === "message" && isRealUserPrompt(record.message) ? [index] : []);
4102
- if (turnStarts.length <= maxTurns) return records;
4103
- return records.slice(turnStarts[turnStarts.length - maxTurns]);
4104
- }
4105
- function replayEntry(context, kind, content, renderMode, extras = {}) {
4106
- return {
4107
- id: nextTranscriptId(),
4108
- kind,
4109
- turnId: context.currentTurnId,
4110
- renderMode,
4111
- content,
4112
- detail: extras.detail
4113
- };
4114
- }
4115
- function collectReplayMessageContent(target, content) {
4116
- for (const part of content) switch (part.type) {
4117
- case "think":
4118
- target.thinking.push(part.think);
4119
- break;
4120
- case "text":
4121
- target.text.push(part.text);
4122
- break;
4123
- case "audio_url":
4124
- case "image_url":
4125
- case "video_url": break;
4126
- }
4127
- }
4128
- function toolCallFromReplayMessage(rawToolCall, context) {
4129
- const id = rawToolCall.id;
4130
- const name = rawToolCall.name;
4131
- if (id.length === 0 || name.length === 0) return void 0;
4132
- return {
4133
- id,
4134
- name,
4135
- args: parseReplayToolArguments(rawToolCall.arguments),
4136
- step: context.stepIndex,
4137
- turnId: context.currentTurnId
4138
- };
4139
- }
4140
- function toolResultOutput(content) {
4141
- if (content.some((part) => part.type !== "text")) return JSON.stringify(content);
4142
- return contentPartsToText(content);
4143
- }
4144
- function contentPartsToText(content) {
4145
- return content.map(contentPartToText).join("");
4146
- }
4147
- function backgroundOrigin(message) {
4148
- return message.origin?.kind === "background_task" ? message.origin : void 0;
4149
- }
4150
- function skillActivationFromOrigin(origin) {
4151
- if (origin?.kind !== "skill_activation") return void 0;
4152
- return {
4153
- activationId: origin.activationId,
4154
- skillName: origin.skillName,
4155
- skillArgs: origin.skillArgs,
4156
- trigger: origin.trigger
4157
- };
4158
- }
4159
- function formatHookResultMessageForTranscript(text, fallbackEvent, blocked) {
4160
- const results = [];
4161
- let lastIndex = 0;
4162
- for (const match of text.matchAll(HOOK_RESULT_RE)) {
4163
- if (text.slice(lastIndex, match.index).trim().length > 0) return formatHookResultBlock(fallbackEvent, text, blocked);
4164
- const event = match[1];
4165
- const body = match[2];
4166
- if (event === void 0 || body === void 0) return formatHookResultBlock(fallbackEvent, text, blocked);
4167
- results.push({
4168
- event,
4169
- body
4170
- });
4171
- lastIndex = match.index + match[0].length;
4172
- }
4173
- if (results.length === 0 || text.slice(lastIndex).trim().length > 0) return formatHookResultBlock(fallbackEvent, text, blocked);
4174
- return results.map(({ event, body }) => formatHookResultBlock(event, body, blocked)).join("\n\n");
4175
- }
4176
- function parseReplayToolArguments(value) {
4177
- if (value === null || value.length === 0) return {};
4178
- try {
4179
- const parsed = JSON.parse(value);
4180
- return isRecord$1(parsed) ? parsed : {};
4181
- } catch {
4182
- return {};
4183
- }
4184
- }
4185
- function contentPartToText(part) {
4186
- switch (part.type) {
4187
- case "text": return part.text;
4188
- case "think": return part.think;
4189
- case "image_url": return mediaUrlPartToText("image", part.imageUrl.url);
4190
- case "video_url": return mediaUrlPartToText("video", part.videoUrl.url);
4191
- case "audio_url": return mediaUrlPartToText("audio", part.audioUrl.url);
4192
- }
4193
- }
4194
- const HOOK_RESULT_RE = /<hook_result\s+hook_event="([^"]+)">\n?([\s\S]*?)\n?<\/hook_result>/g;
4195
- function formatHookResultBlock(event, body, blocked) {
4196
- return `*${event} hook${blocked ? t("replayhook.blocked") : ""}*\n\n${body.trim() || t("replayhook.empty")}`;
4197
- }
4198
- function isRecord$1(value) {
4199
- return typeof value === "object" && value !== null;
4200
- }
4201
- //#endregion
4202
4296
  //#region src/tui/controllers/session-replay.ts
4203
4297
  var SessionReplayRenderer = class {
4204
4298
  host;
@@ -4277,9 +4371,10 @@ var SessionReplayRenderer = class {
4277
4371
  sessionEventHandler.backgroundTasks = new Map(agent.background.map((info) => [info.taskId, info]));
4278
4372
  sessionEventHandler.backgroundTaskTranscriptedTerminal.clear();
4279
4373
  for (const info of agent.background) if (isTerminalBackgroundTask(info)) sessionEventHandler.backgroundTaskTranscriptedTerminal.add(info.taskId);
4374
+ sessionEventHandler.foregroundSubagentIds.clear();
4280
4375
  state.footer.setBackgroundCounts({
4281
4376
  ...countActiveBackgroundTasks(sessionEventHandler.backgroundTasks),
4282
- foregroundSubagents: 0
4377
+ foregroundSubagents: sessionEventHandler.foregroundSubagentIds.size
4283
4378
  });
4284
4379
  state.ui.requestRender();
4285
4380
  }
@@ -4397,6 +4492,7 @@ var SessionReplayRenderer = class {
4397
4492
  if (fields === null) return;
4398
4493
  const notice = renderChildRequestNotice(fields);
4399
4494
  this.flushAssistant(context);
4495
+ this.host.streamingUI.endActivityGroup();
4400
4496
  this.host.appendTranscriptEntry({
4401
4497
  ...replayEntry(context, "status", notice.title, "notice", { detail: notice.detail }),
4402
4498
  noticeMarkerColor: this.host.state.theme.colors.warning
@@ -4427,6 +4523,7 @@ var SessionReplayRenderer = class {
4427
4523
  };
4428
4524
  call.result = result;
4429
4525
  this.applyStepContext(context);
4526
+ if (call.name === "Agent" && isBackgroundedHandoffResult(result.output)) this.host.streamingUI.markSubagentBackgrounded({ toolCallId });
4430
4527
  this.host.streamingUI.onToolCallEnd(toolCallId, result);
4431
4528
  this.host.streamingUI.removeActiveToolCall(toolCallId);
4432
4529
  context.completedToolCallIds.add(toolCallId);
@@ -4574,9 +4671,10 @@ var SessionReplayRenderer = class {
4574
4671
  sessionEventHandler.backgroundTaskTranscriptedTerminal.add(origin.taskId);
4575
4672
  return;
4576
4673
  }
4674
+ const subagentId = agentTaskSubagentId(task);
4577
4675
  const meta = {
4578
- agentId: origin.taskId,
4579
- parentToolCallId: origin.taskId,
4676
+ agentId: subagentId ?? origin.taskId,
4677
+ parentToolCallId: "",
4580
4678
  description: task?.description
4581
4679
  };
4582
4680
  let status = formatBackgroundAgentTranscript(origin.status === "completed" ? "completed" : "failed", meta);
@@ -4593,9 +4691,20 @@ var SessionReplayRenderer = class {
4593
4691
  detail: status.detail,
4594
4692
  backgroundAgentStatus: status
4595
4693
  });
4596
- sessionEventHandler.backgroundAgentMetadata.delete(meta.agentId);
4694
+ if (subagentId !== void 0) sessionEventHandler.backgroundAgentMetadata.delete(subagentId);
4597
4695
  }
4598
4696
  };
4697
+ /**
4698
+ * Subagent id behind an agent-class background task: `info.agentId` when a real
4699
+ * subagent was recorded, else `undefined`. The task-id fallback that
4700
+ * `registerAgentTask` writes when the caller passed no id is not a subagent
4701
+ * identity and must not be treated as one.
4702
+ */
4703
+ function agentTaskSubagentId(info) {
4704
+ if (info === void 0 || !info.taskId.startsWith("agent-")) return void 0;
4705
+ const { agentId } = info;
4706
+ return agentId === void 0 || agentId === info.taskId ? void 0 : agentId;
4707
+ }
4599
4708
  //#endregion
4600
4709
  //#region src/tui/utils/terminal-notification.ts
4601
4710
  function notifyTerminalOnce(state, key, notification) {
@@ -4965,6 +5074,37 @@ var StreamingUIController = class StreamingUIController {
4965
5074
  target.setBackgroundTaskTerminalStatus(args.status, { errorText: args.errorText });
4966
5075
  return true;
4967
5076
  }
5077
+ /**
5078
+ * Marks the Agent card of a subagent that was handed from the foreground to
5079
+ * the background mid-run, so the card keeps reading as `backgrounded`
5080
+ * instead of turning into `done` when the handoff result lands.
5081
+ *
5082
+ * Live callers route by subagent id (the `background.task.started` frame
5083
+ * names it); replay routes by tool call id, the only key a replayed card
5084
+ * has. Returns true when a card was found and marked.
5085
+ */
5086
+ markSubagentBackgrounded(ref) {
5087
+ let target = ref.agentId === void 0 ? void 0 : this.findAgentCardByAgentId(ref.agentId);
5088
+ if (target === void 0 && ref.toolCallId !== void 0) {
5089
+ const byCall = this._pendingToolComponents.get(ref.toolCallId);
5090
+ if (byCall !== void 0 && byCall.toolCallView.name === "Agent") target = byCall;
5091
+ }
5092
+ if (target === void 0) return false;
5093
+ target.setSubagentBackgrounded();
5094
+ return true;
5095
+ }
5096
+ /**
5097
+ * The mounted Agent card backed by the given subagent id — the in-flight
5098
+ * card, a standalone card, or one borrowed by an agent group.
5099
+ */
5100
+ findAgentCardByAgentId(agentId) {
5101
+ for (const tc of this._pendingToolComponents.values()) if (tc.getSubagentAgentId() === agentId) return tc;
5102
+ for (const child of this.host.state.transcriptContainer.children) if (child instanceof ToolCallComponent) {
5103
+ if (child.getSubagentAgentId() === agentId) return child;
5104
+ } else if (child instanceof AgentGroupComponent) {
5105
+ for (const tc of child.getToolComponents()) if (tc.getSubagentAgentId() === agentId) return tc;
5106
+ }
5107
+ }
4968
5108
  /** Registers a tool call that arrived via tool.call.started.
4969
5109
  * Clears any pending streaming state for this id, updates or creates the
4970
5110
  * component, and returns whether the call was new (no previous entry). */
@@ -5631,6 +5771,7 @@ var StreamingUIController = class StreamingUIController {
5631
5771
  if (pending !== null && (pending.step !== step || pending.turnId !== turnId)) this._pendingAgentGroup = null;
5632
5772
  const cur = this._pendingAgentGroup;
5633
5773
  if (cur === null) {
5774
+ this.endActivityGroup();
5634
5775
  this._pendingAgentGroup = {
5635
5776
  step,
5636
5777
  turnId,
@@ -5699,7 +5840,6 @@ var StreamingUIController = class StreamingUIController {
5699
5840
  const ELLIPSIS$4 = "…";
5700
5841
  const STATUS_LABEL$1 = {
5701
5842
  running: "running",
5702
- awaiting_approval: "awaiting",
5703
5843
  completed: "completed",
5704
5844
  failed: "failed",
5705
5845
  killed: "killed",
@@ -5708,7 +5848,6 @@ const STATUS_LABEL$1 = {
5708
5848
  function statusColor$1(colors, status) {
5709
5849
  switch (status) {
5710
5850
  case "running": return colors.success;
5711
- case "awaiting_approval": return colors.warning;
5712
5851
  case "completed": return colors.textMuted;
5713
5852
  case "failed":
5714
5853
  case "killed":
@@ -5890,7 +6029,6 @@ var TaskOutputViewer = class extends Container {
5890
6029
  const ELLIPSIS$3 = "…";
5891
6030
  const STATUS_LABEL = {
5892
6031
  running: "running",
5893
- awaiting_approval: "awaiting",
5894
6032
  completed: "completed",
5895
6033
  failed: "failed",
5896
6034
  killed: "killed",
@@ -5908,7 +6046,6 @@ const LIST_COL_RATIO = .32;
5908
6046
  function statusColor(colors, status) {
5909
6047
  switch (status) {
5910
6048
  case "running": return colors.success;
5911
- case "awaiting_approval": return colors.warning;
5912
6049
  case "completed": return colors.textMuted;
5913
6050
  case "failed":
5914
6051
  case "killed":
@@ -6029,7 +6166,6 @@ function compareTasks(a, b) {
6029
6166
  function countByStatus(tasks) {
6030
6167
  const counts = {
6031
6168
  running: 0,
6032
- awaiting: 0,
6033
6169
  completed: 0,
6034
6170
  terminalFailed: 0
6035
6171
  };
@@ -6037,9 +6173,6 @@ function countByStatus(tasks) {
6037
6173
  case "running":
6038
6174
  counts.running += 1;
6039
6175
  break;
6040
- case "awaiting_approval":
6041
- counts.awaiting += 1;
6042
- break;
6043
6176
  case "completed":
6044
6177
  counts.completed += 1;
6045
6178
  break;
@@ -6251,7 +6384,6 @@ var TasksBrowserApp = class extends Container {
6251
6384
  } else {
6252
6385
  const counts = countByStatus(this.props.tasks);
6253
6386
  if (counts.running > 0) segments.push(chalk.hex(colors.success)(` ${String(counts.running)} ${t("taskbrowser.running")} `));
6254
- if (counts.awaiting > 0) segments.push(chalk.hex(colors.warning)(` ${String(counts.awaiting)} ${t("taskbrowser.awaiting")} `));
6255
6387
  if (counts.completed > 0) segments.push(chalk.hex(colors.textDim)(` ${String(counts.completed)} ${t("taskbrowser.completed")} `));
6256
6388
  if (counts.terminalFailed > 0) segments.push(chalk.hex(colors.error)(` ${String(counts.terminalFailed)} ${t("taskbrowser.interrupted")} `));
6257
6389
  segments.push(chalk.hex(colors.textMuted)(` ${String(this.props.tasks.length)} ${t("taskbrowser.total")} `));
@@ -6387,10 +6519,20 @@ var TasksBrowserApp = class extends Container {
6387
6519
  const status = STATUS_LABEL[task.status];
6388
6520
  const prefix = `${pointerStyled}${idText}${idPad} ${chalk.hex(statusColor(colors, task.status))(status)}`;
6389
6521
  const prefixWidth = visibleWidth(prefix);
6390
- const descBudget = Math.max(0, innerWidth - prefixWidth - 1);
6391
- if (descBudget < 4) return fitExactly$1(prefix, innerWidth);
6392
- const desc = truncateToWidth(singleLine$2(task.description) || singleLine$2(task.command) || "(no description)", descBudget, ELLIPSIS$3);
6393
- return fitExactly$1(`${prefix} ${chalk.hex(colors.text)(desc)}`, innerWidth);
6522
+ const source = this.props.taskSourceNames.get(task.taskId);
6523
+ const tag = source === void 0 ? "" : ` ${t("taskbrowser.task_source_via", { name: source })}`;
6524
+ const remaining = Math.max(0, innerWidth - prefixWidth - 1);
6525
+ const descBudget = Math.max(0, remaining - visibleWidth(tag));
6526
+ const description = singleLine$2(task.description) || singleLine$2(task.command) || "(no description)";
6527
+ if (tag.length === 0) {
6528
+ if (descBudget < 4) return fitExactly$1(prefix, innerWidth);
6529
+ const desc = truncateToWidth(description, descBudget, ELLIPSIS$3);
6530
+ return fitExactly$1(`${prefix} ${chalk.hex(colors.text)(desc)}`, innerWidth);
6531
+ }
6532
+ if (descBudget >= 4) return fitExactly$1(`${prefix} ${chalk.hex(colors.text)(truncateToWidth(description, descBudget, ELLIPSIS$3))}${chalk.hex(colors.textDim)(tag)}`, innerWidth);
6533
+ if (remaining < 4) return fitExactly$1(prefix, innerWidth);
6534
+ const tagOnly = truncateToWidth(tag.slice(1), remaining, ELLIPSIS$3);
6535
+ return fitExactly$1(`${prefix} ${chalk.hex(colors.textDim)(tagOnly)}`, innerWidth);
6394
6536
  }
6395
6537
  adjustScroll(visibleRows) {
6396
6538
  this.listScroll = clampListScroll(this.selectedIndex, this.listScroll, this.sortedVisible.length, visibleRows);
@@ -6417,14 +6559,15 @@ var TasksBrowserApp = class extends Container {
6417
6559
  `${label(t("taskbrowser.status"))}${chalk.hex(statusColor(colors, task.status))(STATUS_LABEL[task.status])}`,
6418
6560
  `${label(t("taskbrowser.description"))}${value(singleLine$2(task.description) || "—")}`
6419
6561
  ];
6562
+ const source = this.props.taskSourceNames.get(task.taskId);
6563
+ if (source !== void 0) lines.push(`${label(t("taskbrowser.agent_source"))}${value(singleLine$2(source))}`);
6420
6564
  if (task.command && task.command !== task.description) lines.push(`${label(t("taskbrowser.command"))}${value(singleLine$2(task.command))}`);
6421
- const timing = task.status === "running" || task.status === "awaiting_approval" ? `${t("taskbrowser.running_time")} ${formatRelativeTime$2(task.startedAt)}` : task.endedAt !== null && task.endedAt !== void 0 ? `${t("taskbrowser.completed_time")} ${formatRelativeTime$2(task.endedAt)}` : "";
6565
+ const timing = task.status === "running" ? `${t("taskbrowser.running_time")} ${formatRelativeTime$2(task.startedAt)}` : task.endedAt !== null && task.endedAt !== void 0 ? `${t("taskbrowser.completed_time")} ${formatRelativeTime$2(task.endedAt)}` : "";
6422
6566
  if (timing.length > 0) lines.push(`${label(t("taskbrowser.time"))}${chalk.hex(colors.textMuted)(timing)}`);
6423
6567
  if (task.pid > 0) lines.push(`${label(t("taskbrowser.process_id"))}${chalk.hex(colors.textMuted)(String(task.pid))}`);
6424
6568
  if (task.exitCode !== null && task.exitCode !== void 0) lines.push(`${label(t("taskbrowser.exit_code"))}${chalk.hex(colors.textMuted)(String(task.exitCode))}`);
6425
6569
  if (task.stopReason !== void 0 && task.stopReason.length > 0) lines.push(`${label(t("taskbrowser.stop_reason"))}${chalk.hex(colors.textMuted)(task.stopReason)}`);
6426
6570
  if (task.timedOut === true) lines.push(`${label(t("taskbrowser.timed_out"))}${chalk.hex(colors.warning)(t("taskbrowser.yes"))}`);
6427
- if (task.approvalReason !== void 0 && task.approvalReason.length > 0) lines.push(`${label(t("taskbrowser.awaiting_label"))}${chalk.hex(colors.warning)(singleLine$2(task.approvalReason))}`);
6428
6571
  while (lines.length < innerHeight) lines.push("");
6429
6572
  return this.renderFrame(t("taskbrowser.detail"), lines, width, height);
6430
6573
  }
@@ -6503,7 +6646,7 @@ var TasksBrowserController = class {
6503
6646
  }
6504
6647
  let tasks = [];
6505
6648
  try {
6506
- tasks = await session.listBackgroundTasks({ activeOnly: false });
6649
+ tasks = this.withSubagentTasks(await session.listBackgroundTasks({ activeOnly: false }));
6507
6650
  } catch (error) {
6508
6651
  this.host.showError(t("tasks.load_failed", { msg: error instanceof Error ? error.message : String(error) }));
6509
6652
  return;
@@ -6513,6 +6656,7 @@ var TasksBrowserController = class {
6513
6656
  const selectedTaskId = this.pickInitialSelection(tasks, filter);
6514
6657
  const component = new TasksBrowserApp({
6515
6658
  tasks,
6659
+ taskSourceNames: this.taskSourceNames(),
6516
6660
  agents: this.snapshotAgentRows(),
6517
6661
  filter,
6518
6662
  selectedTaskId,
@@ -6572,7 +6716,7 @@ var TasksBrowserController = class {
6572
6716
  const myRefreshId = ++viewer.refreshId;
6573
6717
  let output;
6574
6718
  try {
6575
- output = await session.getBackgroundTaskOutput(viewer.taskId);
6719
+ output = await session.getBackgroundTaskOutput(viewer.taskId, { agentId: this.ownerOf(viewer.taskId) });
6576
6720
  } catch (error) {
6577
6721
  if (!opts.silent) {
6578
6722
  const message = error instanceof Error ? error.message : String(error);
@@ -6599,7 +6743,44 @@ var TasksBrowserController = class {
6599
6743
  pickInitialSelection(tasks, filter) {
6600
6744
  const candidates = filter === "all" ? tasks : tasks.filter((t) => t.status !== "completed" && t.status !== "failed" && t.status !== "killed" && t.status !== "lost");
6601
6745
  if (candidates.length === 0) return void 0;
6602
- return candidates.find((t) => t.status === "running" || t.status === "awaiting_approval")?.taskId ?? candidates[0].taskId;
6746
+ return candidates.find((t) => t.status === "running")?.taskId ?? candidates[0].taskId;
6747
+ }
6748
+ /**
6749
+ * Merge the subagent-owned rows into a main-agent list. The polled list is
6750
+ * authoritative for main's own registry — it cannot list a subagent's
6751
+ * registry at all — so those rows come from the event-fed
6752
+ * {@link TasksBrowserHost.backgroundTasks}, tagged in
6753
+ * {@link TasksBrowserHost.backgroundTaskOwners}. Without this, opening /tasks
6754
+ * would show an empty list for work the transcript never mentions and the
6755
+ * subagent's own rows would be invisible.
6756
+ */
6757
+ withSubagentTasks(fetched) {
6758
+ const owners = this.host.backgroundTaskOwners;
6759
+ if (owners.size === 0) return fetched;
6760
+ const merged = new Map(fetched.map((info) => [info.taskId, info]));
6761
+ for (const taskId of owners.keys()) {
6762
+ if (merged.has(taskId)) continue;
6763
+ const local = this.host.backgroundTasks.get(taskId);
6764
+ if (local !== void 0) merged.set(taskId, local);
6765
+ }
6766
+ return [...merged.values()];
6767
+ }
6768
+ /** Row/detail tag: the owning subagent's display name per subagent-owned task. */
6769
+ taskSourceNames() {
6770
+ const names = /* @__PURE__ */ new Map();
6771
+ for (const [taskId, ownerId] of this.host.backgroundTaskOwners) names.set(taskId, this.ownerDisplayName(ownerId));
6772
+ return names;
6773
+ }
6774
+ /** Owning subagent's profile name, falling back to the archived instance row
6775
+ * and finally to the raw agent id — an id is better than a wrong name. */
6776
+ ownerDisplayName(ownerId) {
6777
+ const live = this.host.subagentInstances.get(ownerId);
6778
+ if (live !== void 0) return live.type;
6779
+ return this.host.recentSubagentInstances.find((row) => row.instanceId === ownerId)?.type ?? ownerId;
6780
+ }
6781
+ /** Agent whose registry answers for this task; undefined ⇒ the main agent's. */
6782
+ ownerOf(taskId) {
6783
+ return this.host.backgroundTaskOwners.get(taskId);
6603
6784
  }
6604
6785
  async refresh(opts = {}) {
6605
6786
  const { state } = this.host;
@@ -6615,13 +6796,14 @@ var TasksBrowserController = class {
6615
6796
  return;
6616
6797
  }
6617
6798
  if (state.tasksBrowser !== browser) return;
6618
- this.pushProps(tasks);
6799
+ this.pushProps(this.withSubagentTasks(tasks));
6619
6800
  }
6620
6801
  pushProps(tasks) {
6621
6802
  const browser = this.host.state.tasksBrowser;
6622
6803
  if (browser === void 0) return;
6623
6804
  browser.component.setProps({
6624
6805
  tasks,
6806
+ taskSourceNames: this.taskSourceNames(),
6625
6807
  agents: this.snapshotAgentRows(),
6626
6808
  filter: browser.filter,
6627
6809
  selectedTaskId: browser.selectedTaskId,
@@ -6694,7 +6876,10 @@ var TasksBrowserController = class {
6694
6876
  }
6695
6877
  this.flash(t("tasks.stopping", { name: taskId }), 1500);
6696
6878
  try {
6697
- await session.stopBackgroundTask(taskId, { reason: t("tasks.user_stopped") });
6879
+ await session.stopBackgroundTask(taskId, {
6880
+ reason: t("tasks.user_stopped"),
6881
+ agentId: this.ownerOf(taskId)
6882
+ });
6698
6883
  await this.refresh({ silent: true });
6699
6884
  } catch (error) {
6700
6885
  const message = error instanceof Error ? error.message : String(error);
@@ -6713,7 +6898,7 @@ var TasksBrowserController = class {
6713
6898
  }
6714
6899
  let output;
6715
6900
  try {
6716
- output = await session.getBackgroundTaskOutput(taskId);
6901
+ output = await session.getBackgroundTaskOutput(taskId, { agentId: this.ownerOf(taskId) });
6717
6902
  } catch (error) {
6718
6903
  const message = error instanceof Error ? error.message : String(error);
6719
6904
  this.flash(t("tasks.open_output_failed", { msg: message }));
@@ -6758,7 +6943,10 @@ var TasksBrowserController = class {
6758
6943
  return;
6759
6944
  }
6760
6945
  const requestId = ++browser.tailRequestId;
6761
- session.getBackgroundTaskOutput(taskId, { tail: 4e3 }).then((output) => {
6946
+ session.getBackgroundTaskOutput(taskId, {
6947
+ tail: 4e3,
6948
+ agentId: this.ownerOf(taskId)
6949
+ }).then((output) => {
6762
6950
  const current = state.tasksBrowser;
6763
6951
  if (current === void 0) return;
6764
6952
  if (current !== browser || current.tailRequestId !== requestId) return;
@@ -11558,6 +11746,7 @@ function getPlanRejectChoices() {
11558
11746
  }
11559
11747
  function adaptApprovalRequest(event) {
11560
11748
  const resolved = resolveDisplay(event.toolName, event.display, event.action);
11749
+ const sourceLabel = formatApprovalSource(event);
11561
11750
  return {
11562
11751
  id: event.toolCallId,
11563
11752
  tool_call_id: event.toolCallId,
@@ -11565,9 +11754,29 @@ function adaptApprovalRequest(event) {
11565
11754
  action: event.action,
11566
11755
  description: resolved.description,
11567
11756
  display: resolved.blocks,
11568
- choices: adaptChoices(event.toolName, event.display)
11757
+ choices: adaptChoices(event.toolName, event.display),
11758
+ ...sourceLabel !== void 0 ? { source_label: sourceLabel } : {}
11569
11759
  };
11570
11760
  }
11761
+ /**
11762
+ * "来源:<代理名>(<agentId>)· <工具>" — who asked and what triggered it.
11763
+ * Returns undefined on payloads without attribution (older emitters), so
11764
+ * the panel can skip the row entirely.
11765
+ *
11766
+ * The label itself follows the sidebar's locale-conditional pattern
11767
+ * (`getLocale()`), not the i18n dictionary: this row renders inside the
11768
+ * approval panel's own text and needs no placeholder plumbing.
11769
+ */
11770
+ function formatApprovalSource(event) {
11771
+ const agentId = event.sourceAgentId;
11772
+ const name = event.sourceAgentName;
11773
+ if (agentId === void 0 && name === void 0) return void 0;
11774
+ const tool = event.sourceToolName ?? event.toolName;
11775
+ const label = name ?? agentId ?? "";
11776
+ const agent = agentId !== void 0 && agentId !== "" && agentId !== label ? `${label}(${agentId})` : label;
11777
+ const toolSuffix = tool !== void 0 && tool !== "" ? ` · ${tool}` : "";
11778
+ return `${getLocale() === "zh" ? "来源:" : "Source: "}${agent}${toolSuffix}`;
11779
+ }
11571
11780
  function resolveDisplay(toolName, display, action) {
11572
11781
  if (display.kind === "generic" && isRecord(display.detail)) {
11573
11782
  const extracted = extractFromArgs(toolName, display.detail);
@@ -12460,6 +12669,7 @@ var ApprovalPanelComponent = class extends Container {
12460
12669
  const indent = (s) => ` ${s}`;
12461
12670
  const title = headerFor(data.tool_name);
12462
12671
  const lines = [horizontalBar, indent(`${borderColorBold("→")} ${borderColorBold(title)}`)];
12672
+ if (data.source_label !== void 0 && data.source_label.length > 0) lines.push(indent(dim(truncateToWidth(data.source_label, Math.max(width - 4, 1)))));
12463
12673
  const visibleBlocks = data.display.filter((block) => !isDuplicateBriefBlock(block, data.description)).slice(0, 5);
12464
12674
  const hasPreviewable = visibleBlocks.some((block) => block.type === "diff" || block.type === "file_content");
12465
12675
  if (visibleBlocks.length > 0) {
@@ -14786,6 +14996,11 @@ var ScreamTUI = class {
14786
14996
  get backgroundTasks() {
14787
14997
  return this.sessionEventHandler.backgroundTasks;
14788
14998
  }
14999
+ /** Owner agent of each subagent-owned background task (task id → agent id) —
15000
+ * /tasks tags those rows and routes output/stop through the owner. */
15001
+ get backgroundTaskOwners() {
15002
+ return this.sessionEventHandler.backgroundTaskOwners;
15003
+ }
14789
15004
  /** Live per-type subagent slots (sidebar state machine) — source of the
14790
15005
  * /tasks browser's Agents view. */
14791
15006
  get subagentSlots() {
@@ -14970,7 +15185,7 @@ var ScreamTUI = class {
14970
15185
  const card = new OnboardingCardComponent({
14971
15186
  colors: this.state.theme.colors,
14972
15187
  onConfigure: () => {
14973
- import("./dispatch-CUfxgLwq.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
15188
+ import("./dispatch-Byc9hdXF.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
14974
15189
  this.restoreEditor();
14975
15190
  onDone?.();
14976
15191
  });
@@ -18564,13 +18779,21 @@ var WebSession = class {
18564
18779
  resolve,
18565
18780
  toolName: request.toolName,
18566
18781
  action: request.action,
18567
- display: request.display
18782
+ display: request.display,
18783
+ sourceAgentId: request.sourceAgentId,
18784
+ sourceAgentName: request.sourceAgentName,
18785
+ sourceToolName: request.sourceToolName,
18786
+ sourceCapabilityMode: request.sourceCapabilityMode
18568
18787
  });
18569
18788
  const payload = {
18570
18789
  id,
18571
18790
  toolName: request.toolName,
18572
18791
  action: request.action,
18573
- display: request.display
18792
+ display: request.display,
18793
+ sourceAgentId: request.sourceAgentId,
18794
+ sourceAgentName: request.sourceAgentName,
18795
+ sourceToolName: request.sourceToolName,
18796
+ sourceCapabilityMode: request.sourceCapabilityMode
18574
18797
  };
18575
18798
  this.broadcast({
18576
18799
  type: "approval_request",
@@ -18618,7 +18841,11 @@ var WebSession = class {
18618
18841
  id,
18619
18842
  toolName: approval.toolName,
18620
18843
  action: approval.action,
18621
- display: approval.display
18844
+ display: approval.display,
18845
+ sourceAgentId: approval.sourceAgentId,
18846
+ sourceAgentName: approval.sourceAgentName,
18847
+ sourceToolName: approval.sourceToolName,
18848
+ sourceCapabilityMode: approval.sourceCapabilityMode
18622
18849
  })),
18623
18850
  status: this.cachedStatus ?? {
18624
18851
  model: "unknown",