scream-code 0.17.9 → 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-BGYq0dnV.mjs";
9
- import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-BlSqBYxO.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.17.9"),
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;
@@ -4206,6 +4300,7 @@ var SessionReplayRenderer = class {
4206
4300
  this.host = host;
4207
4301
  }
4208
4302
  async hydrateFromReplay(session) {
4303
+ this.host.state.ui.resetTextSelection();
4209
4304
  this.host.setAppState({ isReplaying: true });
4210
4305
  try {
4211
4306
  const main = session.getResumeState()?.agents["main"];
@@ -4276,9 +4371,10 @@ var SessionReplayRenderer = class {
4276
4371
  sessionEventHandler.backgroundTasks = new Map(agent.background.map((info) => [info.taskId, info]));
4277
4372
  sessionEventHandler.backgroundTaskTranscriptedTerminal.clear();
4278
4373
  for (const info of agent.background) if (isTerminalBackgroundTask(info)) sessionEventHandler.backgroundTaskTranscriptedTerminal.add(info.taskId);
4374
+ sessionEventHandler.foregroundSubagentIds.clear();
4279
4375
  state.footer.setBackgroundCounts({
4280
4376
  ...countActiveBackgroundTasks(sessionEventHandler.backgroundTasks),
4281
- foregroundSubagents: 0
4377
+ foregroundSubagents: sessionEventHandler.foregroundSubagentIds.size
4282
4378
  });
4283
4379
  state.ui.requestRender();
4284
4380
  }
@@ -4396,6 +4492,7 @@ var SessionReplayRenderer = class {
4396
4492
  if (fields === null) return;
4397
4493
  const notice = renderChildRequestNotice(fields);
4398
4494
  this.flushAssistant(context);
4495
+ this.host.streamingUI.endActivityGroup();
4399
4496
  this.host.appendTranscriptEntry({
4400
4497
  ...replayEntry(context, "status", notice.title, "notice", { detail: notice.detail }),
4401
4498
  noticeMarkerColor: this.host.state.theme.colors.warning
@@ -4426,6 +4523,7 @@ var SessionReplayRenderer = class {
4426
4523
  };
4427
4524
  call.result = result;
4428
4525
  this.applyStepContext(context);
4526
+ if (call.name === "Agent" && isBackgroundedHandoffResult(result.output)) this.host.streamingUI.markSubagentBackgrounded({ toolCallId });
4429
4527
  this.host.streamingUI.onToolCallEnd(toolCallId, result);
4430
4528
  this.host.streamingUI.removeActiveToolCall(toolCallId);
4431
4529
  context.completedToolCallIds.add(toolCallId);
@@ -4573,9 +4671,10 @@ var SessionReplayRenderer = class {
4573
4671
  sessionEventHandler.backgroundTaskTranscriptedTerminal.add(origin.taskId);
4574
4672
  return;
4575
4673
  }
4674
+ const subagentId = agentTaskSubagentId(task);
4576
4675
  const meta = {
4577
- agentId: origin.taskId,
4578
- parentToolCallId: origin.taskId,
4676
+ agentId: subagentId ?? origin.taskId,
4677
+ parentToolCallId: "",
4579
4678
  description: task?.description
4580
4679
  };
4581
4680
  let status = formatBackgroundAgentTranscript(origin.status === "completed" ? "completed" : "failed", meta);
@@ -4592,9 +4691,20 @@ var SessionReplayRenderer = class {
4592
4691
  detail: status.detail,
4593
4692
  backgroundAgentStatus: status
4594
4693
  });
4595
- sessionEventHandler.backgroundAgentMetadata.delete(meta.agentId);
4694
+ if (subagentId !== void 0) sessionEventHandler.backgroundAgentMetadata.delete(subagentId);
4596
4695
  }
4597
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
+ }
4598
4708
  //#endregion
4599
4709
  //#region src/tui/utils/terminal-notification.ts
4600
4710
  function notifyTerminalOnce(state, key, notification) {
@@ -4964,6 +5074,37 @@ var StreamingUIController = class StreamingUIController {
4964
5074
  target.setBackgroundTaskTerminalStatus(args.status, { errorText: args.errorText });
4965
5075
  return true;
4966
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
+ }
4967
5108
  /** Registers a tool call that arrived via tool.call.started.
4968
5109
  * Clears any pending streaming state for this id, updates or creates the
4969
5110
  * component, and returns whether the call was new (no previous entry). */
@@ -5630,6 +5771,7 @@ var StreamingUIController = class StreamingUIController {
5630
5771
  if (pending !== null && (pending.step !== step || pending.turnId !== turnId)) this._pendingAgentGroup = null;
5631
5772
  const cur = this._pendingAgentGroup;
5632
5773
  if (cur === null) {
5774
+ this.endActivityGroup();
5633
5775
  this._pendingAgentGroup = {
5634
5776
  step,
5635
5777
  turnId,
@@ -5698,7 +5840,6 @@ var StreamingUIController = class StreamingUIController {
5698
5840
  const ELLIPSIS$4 = "…";
5699
5841
  const STATUS_LABEL$1 = {
5700
5842
  running: "running",
5701
- awaiting_approval: "awaiting",
5702
5843
  completed: "completed",
5703
5844
  failed: "failed",
5704
5845
  killed: "killed",
@@ -5707,7 +5848,6 @@ const STATUS_LABEL$1 = {
5707
5848
  function statusColor$1(colors, status) {
5708
5849
  switch (status) {
5709
5850
  case "running": return colors.success;
5710
- case "awaiting_approval": return colors.warning;
5711
5851
  case "completed": return colors.textMuted;
5712
5852
  case "failed":
5713
5853
  case "killed":
@@ -5889,7 +6029,6 @@ var TaskOutputViewer = class extends Container {
5889
6029
  const ELLIPSIS$3 = "…";
5890
6030
  const STATUS_LABEL = {
5891
6031
  running: "running",
5892
- awaiting_approval: "awaiting",
5893
6032
  completed: "completed",
5894
6033
  failed: "failed",
5895
6034
  killed: "killed",
@@ -5907,7 +6046,6 @@ const LIST_COL_RATIO = .32;
5907
6046
  function statusColor(colors, status) {
5908
6047
  switch (status) {
5909
6048
  case "running": return colors.success;
5910
- case "awaiting_approval": return colors.warning;
5911
6049
  case "completed": return colors.textMuted;
5912
6050
  case "failed":
5913
6051
  case "killed":
@@ -6028,7 +6166,6 @@ function compareTasks(a, b) {
6028
6166
  function countByStatus(tasks) {
6029
6167
  const counts = {
6030
6168
  running: 0,
6031
- awaiting: 0,
6032
6169
  completed: 0,
6033
6170
  terminalFailed: 0
6034
6171
  };
@@ -6036,9 +6173,6 @@ function countByStatus(tasks) {
6036
6173
  case "running":
6037
6174
  counts.running += 1;
6038
6175
  break;
6039
- case "awaiting_approval":
6040
- counts.awaiting += 1;
6041
- break;
6042
6176
  case "completed":
6043
6177
  counts.completed += 1;
6044
6178
  break;
@@ -6250,7 +6384,6 @@ var TasksBrowserApp = class extends Container {
6250
6384
  } else {
6251
6385
  const counts = countByStatus(this.props.tasks);
6252
6386
  if (counts.running > 0) segments.push(chalk.hex(colors.success)(` ${String(counts.running)} ${t("taskbrowser.running")} `));
6253
- if (counts.awaiting > 0) segments.push(chalk.hex(colors.warning)(` ${String(counts.awaiting)} ${t("taskbrowser.awaiting")} `));
6254
6387
  if (counts.completed > 0) segments.push(chalk.hex(colors.textDim)(` ${String(counts.completed)} ${t("taskbrowser.completed")} `));
6255
6388
  if (counts.terminalFailed > 0) segments.push(chalk.hex(colors.error)(` ${String(counts.terminalFailed)} ${t("taskbrowser.interrupted")} `));
6256
6389
  segments.push(chalk.hex(colors.textMuted)(` ${String(this.props.tasks.length)} ${t("taskbrowser.total")} `));
@@ -6386,10 +6519,20 @@ var TasksBrowserApp = class extends Container {
6386
6519
  const status = STATUS_LABEL[task.status];
6387
6520
  const prefix = `${pointerStyled}${idText}${idPad} ${chalk.hex(statusColor(colors, task.status))(status)}`;
6388
6521
  const prefixWidth = visibleWidth(prefix);
6389
- const descBudget = Math.max(0, innerWidth - prefixWidth - 1);
6390
- if (descBudget < 4) return fitExactly$1(prefix, innerWidth);
6391
- const desc = truncateToWidth(singleLine$2(task.description) || singleLine$2(task.command) || "(no description)", descBudget, ELLIPSIS$3);
6392
- 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);
6393
6536
  }
6394
6537
  adjustScroll(visibleRows) {
6395
6538
  this.listScroll = clampListScroll(this.selectedIndex, this.listScroll, this.sortedVisible.length, visibleRows);
@@ -6416,14 +6559,15 @@ var TasksBrowserApp = class extends Container {
6416
6559
  `${label(t("taskbrowser.status"))}${chalk.hex(statusColor(colors, task.status))(STATUS_LABEL[task.status])}`,
6417
6560
  `${label(t("taskbrowser.description"))}${value(singleLine$2(task.description) || "—")}`
6418
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))}`);
6419
6564
  if (task.command && task.command !== task.description) lines.push(`${label(t("taskbrowser.command"))}${value(singleLine$2(task.command))}`);
6420
- 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)}` : "";
6421
6566
  if (timing.length > 0) lines.push(`${label(t("taskbrowser.time"))}${chalk.hex(colors.textMuted)(timing)}`);
6422
6567
  if (task.pid > 0) lines.push(`${label(t("taskbrowser.process_id"))}${chalk.hex(colors.textMuted)(String(task.pid))}`);
6423
6568
  if (task.exitCode !== null && task.exitCode !== void 0) lines.push(`${label(t("taskbrowser.exit_code"))}${chalk.hex(colors.textMuted)(String(task.exitCode))}`);
6424
6569
  if (task.stopReason !== void 0 && task.stopReason.length > 0) lines.push(`${label(t("taskbrowser.stop_reason"))}${chalk.hex(colors.textMuted)(task.stopReason)}`);
6425
6570
  if (task.timedOut === true) lines.push(`${label(t("taskbrowser.timed_out"))}${chalk.hex(colors.warning)(t("taskbrowser.yes"))}`);
6426
- if (task.approvalReason !== void 0 && task.approvalReason.length > 0) lines.push(`${label(t("taskbrowser.awaiting_label"))}${chalk.hex(colors.warning)(singleLine$2(task.approvalReason))}`);
6427
6571
  while (lines.length < innerHeight) lines.push("");
6428
6572
  return this.renderFrame(t("taskbrowser.detail"), lines, width, height);
6429
6573
  }
@@ -6502,7 +6646,7 @@ var TasksBrowserController = class {
6502
6646
  }
6503
6647
  let tasks = [];
6504
6648
  try {
6505
- tasks = await session.listBackgroundTasks({ activeOnly: false });
6649
+ tasks = this.withSubagentTasks(await session.listBackgroundTasks({ activeOnly: false }));
6506
6650
  } catch (error) {
6507
6651
  this.host.showError(t("tasks.load_failed", { msg: error instanceof Error ? error.message : String(error) }));
6508
6652
  return;
@@ -6512,6 +6656,7 @@ var TasksBrowserController = class {
6512
6656
  const selectedTaskId = this.pickInitialSelection(tasks, filter);
6513
6657
  const component = new TasksBrowserApp({
6514
6658
  tasks,
6659
+ taskSourceNames: this.taskSourceNames(),
6515
6660
  agents: this.snapshotAgentRows(),
6516
6661
  filter,
6517
6662
  selectedTaskId,
@@ -6571,7 +6716,7 @@ var TasksBrowserController = class {
6571
6716
  const myRefreshId = ++viewer.refreshId;
6572
6717
  let output;
6573
6718
  try {
6574
- output = await session.getBackgroundTaskOutput(viewer.taskId);
6719
+ output = await session.getBackgroundTaskOutput(viewer.taskId, { agentId: this.ownerOf(viewer.taskId) });
6575
6720
  } catch (error) {
6576
6721
  if (!opts.silent) {
6577
6722
  const message = error instanceof Error ? error.message : String(error);
@@ -6598,7 +6743,44 @@ var TasksBrowserController = class {
6598
6743
  pickInitialSelection(tasks, filter) {
6599
6744
  const candidates = filter === "all" ? tasks : tasks.filter((t) => t.status !== "completed" && t.status !== "failed" && t.status !== "killed" && t.status !== "lost");
6600
6745
  if (candidates.length === 0) return void 0;
6601
- 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);
6602
6784
  }
6603
6785
  async refresh(opts = {}) {
6604
6786
  const { state } = this.host;
@@ -6614,13 +6796,14 @@ var TasksBrowserController = class {
6614
6796
  return;
6615
6797
  }
6616
6798
  if (state.tasksBrowser !== browser) return;
6617
- this.pushProps(tasks);
6799
+ this.pushProps(this.withSubagentTasks(tasks));
6618
6800
  }
6619
6801
  pushProps(tasks) {
6620
6802
  const browser = this.host.state.tasksBrowser;
6621
6803
  if (browser === void 0) return;
6622
6804
  browser.component.setProps({
6623
6805
  tasks,
6806
+ taskSourceNames: this.taskSourceNames(),
6624
6807
  agents: this.snapshotAgentRows(),
6625
6808
  filter: browser.filter,
6626
6809
  selectedTaskId: browser.selectedTaskId,
@@ -6693,7 +6876,10 @@ var TasksBrowserController = class {
6693
6876
  }
6694
6877
  this.flash(t("tasks.stopping", { name: taskId }), 1500);
6695
6878
  try {
6696
- 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
+ });
6697
6883
  await this.refresh({ silent: true });
6698
6884
  } catch (error) {
6699
6885
  const message = error instanceof Error ? error.message : String(error);
@@ -6712,7 +6898,7 @@ var TasksBrowserController = class {
6712
6898
  }
6713
6899
  let output;
6714
6900
  try {
6715
- output = await session.getBackgroundTaskOutput(taskId);
6901
+ output = await session.getBackgroundTaskOutput(taskId, { agentId: this.ownerOf(taskId) });
6716
6902
  } catch (error) {
6717
6903
  const message = error instanceof Error ? error.message : String(error);
6718
6904
  this.flash(t("tasks.open_output_failed", { msg: message }));
@@ -6720,6 +6906,7 @@ var TasksBrowserController = class {
6720
6906
  }
6721
6907
  const current = state.tasksBrowser;
6722
6908
  if (current === void 0 || current !== browser) return;
6909
+ if (current.viewer !== void 0) return;
6723
6910
  const viewer = new TaskOutputViewer({
6724
6911
  taskId,
6725
6912
  info: this.host.backgroundTasks.get(taskId),
@@ -6729,7 +6916,7 @@ var TasksBrowserController = class {
6729
6916
  this.closeOutputViewer();
6730
6917
  }
6731
6918
  }, state.terminal);
6732
- const savedBrowserLayout = state.layoutRoot;
6919
+ const savedBrowserLayout = browser.component;
6733
6920
  state.ui.setLayoutRoot(viewer);
6734
6921
  state.ui.setFocus(viewer);
6735
6922
  state.ui.requestRender(true);
@@ -6756,7 +6943,10 @@ var TasksBrowserController = class {
6756
6943
  return;
6757
6944
  }
6758
6945
  const requestId = ++browser.tailRequestId;
6759
- session.getBackgroundTaskOutput(taskId, { tail: 4e3 }).then((output) => {
6946
+ session.getBackgroundTaskOutput(taskId, {
6947
+ tail: 4e3,
6948
+ agentId: this.ownerOf(taskId)
6949
+ }).then((output) => {
6760
6950
  const current = state.tasksBrowser;
6761
6951
  if (current === void 0) return;
6762
6952
  if (current !== browser || current.tailRequestId !== requestId) return;
@@ -7211,6 +7401,7 @@ var TranscriptController = class TranscriptController {
7211
7401
  this.dropLiveComponent(component);
7212
7402
  }
7213
7403
  this.committedComponent.setCount(this.committedComponent.getCount() + toCommit.length);
7404
+ state.ui.resetTextSelection();
7214
7405
  if (process.env["SCREAM_CODE_DEBUG"] === "1") this.host.showStatus(`[debug] committed=${this.committedComponent.getCount()} live=${this.getLiveCount()}`);
7215
7406
  container.invalidate();
7216
7407
  state.ui.requestRender();
@@ -7385,6 +7576,7 @@ var TranscriptController = class TranscriptController {
7385
7576
  }
7386
7577
  clearAndRedraw() {
7387
7578
  const { state, streamingUI, imageStore } = this.host;
7579
+ state.ui.resetTextSelection();
7388
7580
  streamingUI.discardPending();
7389
7581
  streamingUI.endActivityGroup();
7390
7582
  streamingUI.flushPendingApprovals();
@@ -7449,12 +7641,17 @@ var TranscriptController = class TranscriptController {
7449
7641
  const { state } = this.host;
7450
7642
  const next = !state.toolOutputExpanded;
7451
7643
  const children = state.transcriptContainer.children;
7644
+ let resized = false;
7452
7645
  for (let i = children.length - 1; i >= 0; i -= 1) {
7453
7646
  const child = children[i];
7454
7647
  if (child === void 0) continue;
7455
7648
  if (child instanceof UserMessageComponent || child instanceof SkillActivationComponent || child instanceof CronMessageComponent || child instanceof CommittedTranscriptComponent) break;
7456
- if (isExpandable(child)) child.setExpanded(next);
7649
+ if (isExpandable(child)) {
7650
+ child.setExpanded(next);
7651
+ resized = true;
7652
+ }
7457
7653
  }
7654
+ if (resized) state.ui.resetTextSelection();
7458
7655
  state.toolOutputExpanded = next;
7459
7656
  state.ui.requestRender();
7460
7657
  }
@@ -7464,6 +7661,7 @@ var TranscriptController = class TranscriptController {
7464
7661
  let toggled = false;
7465
7662
  for (const child of state.transcriptContainer.children) if (isPlanExpandable(child) && child.setPlanExpanded(next)) toggled = true;
7466
7663
  if (!toggled) return false;
7664
+ state.ui.resetTextSelection();
7467
7665
  state.planExpanded = next;
7468
7666
  state.ui.requestRender();
7469
7667
  return true;
@@ -10935,6 +11133,7 @@ var CustomEditor = class extends Editor {
10935
11133
  thinkingLevel: this.thinkingLevel,
10936
11134
  toolPriority: this.toolPriority,
10937
11135
  paint: this.borderColor ?? ((s) => s),
11136
+ modeHex: getPermissionModeColor(this.permissionMode, this.colors),
10938
11137
  borderHex: this.borderHex
10939
11138
  });
10940
11139
  return lines;
@@ -11085,6 +11284,15 @@ function injectPromptSymbol(line) {
11085
11284
  }
11086
11285
  const THINK_LABEL_MIN_WIDTH = 14;
11087
11286
  const MODE_BADGE_MIN_WIDTH = 10;
11287
+ function getPermissionModeColor(mode, colors) {
11288
+ return {
11289
+ manual: colors.permissionManual,
11290
+ yolo: colors.permissionYolo,
11291
+ auto: colors.permissionAuto,
11292
+ ask: colors.permissionAsk,
11293
+ bot: colors.permissionBot
11294
+ }[mode];
11295
+ }
11088
11296
  function isLightBg(hex) {
11089
11297
  const r = parseInt(hex.slice(1, 3), 16);
11090
11298
  const g = parseInt(hex.slice(3, 5), 16);
@@ -11115,7 +11323,7 @@ function injectBorderBadges(lines, width, opts) {
11115
11323
  let right = paint("─");
11116
11324
  if (width >= MODE_BADGE_MIN_WIDTH) {
11117
11325
  const badgeText = ` ${opts.mode} `;
11118
- left = paint("──") + (opts.borderHex ? chalk.hex(opts.borderHex).bold(badgeText) : paint(badgeText));
11326
+ left = paint("──") + chalk.hex(opts.modeHex).bold(badgeText);
11119
11327
  }
11120
11328
  if (opts.thinking && width >= THINK_LABEL_MIN_WIDTH) right = makeBadge(opts.thinkingLevel !== "off" ? ` Think ${opts.thinkingLevel} ` : " Think ", opts.borderHex) + right;
11121
11329
  if (opts.toolPriority === "skill" || opts.toolPriority === "mcp") right = makeBadge(` First ${opts.toolPriority} `, opts.borderHex) + paint("─") + right;
@@ -11538,6 +11746,7 @@ function getPlanRejectChoices() {
11538
11746
  }
11539
11747
  function adaptApprovalRequest(event) {
11540
11748
  const resolved = resolveDisplay(event.toolName, event.display, event.action);
11749
+ const sourceLabel = formatApprovalSource(event);
11541
11750
  return {
11542
11751
  id: event.toolCallId,
11543
11752
  tool_call_id: event.toolCallId,
@@ -11545,9 +11754,29 @@ function adaptApprovalRequest(event) {
11545
11754
  action: event.action,
11546
11755
  description: resolved.description,
11547
11756
  display: resolved.blocks,
11548
- choices: adaptChoices(event.toolName, event.display)
11757
+ choices: adaptChoices(event.toolName, event.display),
11758
+ ...sourceLabel !== void 0 ? { source_label: sourceLabel } : {}
11549
11759
  };
11550
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
+ }
11551
11780
  function resolveDisplay(toolName, display, action) {
11552
11781
  if (display.kind === "generic" && isRecord(display.detail)) {
11553
11782
  const extracted = extractFromArgs(toolName, display.detail);
@@ -12440,6 +12669,7 @@ var ApprovalPanelComponent = class extends Container {
12440
12669
  const indent = (s) => ` ${s}`;
12441
12670
  const title = headerFor(data.tool_name);
12442
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)))));
12443
12673
  const visibleBlocks = data.display.filter((block) => !isDuplicateBriefBlock(block, data.description)).slice(0, 5);
12444
12674
  const hasPreviewable = visibleBlocks.some((block) => block.type === "diff" || block.type === "file_content");
12445
12675
  if (visibleBlocks.length > 0) {
@@ -12693,6 +12923,10 @@ const DEFAULT_KEYBOARD_SHORTCUTS = [
12693
12923
  keys: "Shift-Tab",
12694
12924
  description: t("help.toggle_plan")
12695
12925
  },
12926
+ {
12927
+ keys: "Tab",
12928
+ description: t("help.cycle_thinking")
12929
+ },
12696
12930
  {
12697
12931
  keys: "Ctrl-G",
12698
12932
  description: t("help.external_editor")
@@ -12701,10 +12935,26 @@ const DEFAULT_KEYBOARD_SHORTCUTS = [
12701
12935
  keys: "Ctrl-O",
12702
12936
  description: t("help.toggle_output")
12703
12937
  },
12938
+ {
12939
+ keys: "Ctrl-E",
12940
+ description: t("help.toggle_plan_view")
12941
+ },
12704
12942
  {
12705
12943
  keys: "Ctrl-S",
12706
12944
  description: t("help.interrupt")
12707
12945
  },
12946
+ {
12947
+ keys: "Ctrl-X",
12948
+ description: t("help.toggle_sidebar")
12949
+ },
12950
+ {
12951
+ keys: "Ctrl-B",
12952
+ description: t("help.toggle_hint")
12953
+ },
12954
+ {
12955
+ keys: "Ctrl-W",
12956
+ description: t("help.cancel_memory")
12957
+ },
12708
12958
  {
12709
12959
  keys: "Shift-Enter / Ctrl-J",
12710
12960
  description: t("help.newline")
@@ -14746,6 +14996,11 @@ var ScreamTUI = class {
14746
14996
  get backgroundTasks() {
14747
14997
  return this.sessionEventHandler.backgroundTasks;
14748
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
+ }
14749
15004
  /** Live per-type subagent slots (sidebar state machine) — source of the
14750
15005
  * /tasks browser's Agents view. */
14751
15006
  get subagentSlots() {
@@ -14930,7 +15185,7 @@ var ScreamTUI = class {
14930
15185
  const card = new OnboardingCardComponent({
14931
15186
  colors: this.state.theme.colors,
14932
15187
  onConfigure: () => {
14933
- import("./dispatch-DoaBG--u.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
15188
+ import("./dispatch-Byc9hdXF.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
14934
15189
  this.restoreEditor();
14935
15190
  onDone?.();
14936
15191
  });
@@ -18524,13 +18779,21 @@ var WebSession = class {
18524
18779
  resolve,
18525
18780
  toolName: request.toolName,
18526
18781
  action: request.action,
18527
- display: request.display
18782
+ display: request.display,
18783
+ sourceAgentId: request.sourceAgentId,
18784
+ sourceAgentName: request.sourceAgentName,
18785
+ sourceToolName: request.sourceToolName,
18786
+ sourceCapabilityMode: request.sourceCapabilityMode
18528
18787
  });
18529
18788
  const payload = {
18530
18789
  id,
18531
18790
  toolName: request.toolName,
18532
18791
  action: request.action,
18533
- display: request.display
18792
+ display: request.display,
18793
+ sourceAgentId: request.sourceAgentId,
18794
+ sourceAgentName: request.sourceAgentName,
18795
+ sourceToolName: request.sourceToolName,
18796
+ sourceCapabilityMode: request.sourceCapabilityMode
18534
18797
  };
18535
18798
  this.broadcast({
18536
18799
  type: "approval_request",
@@ -18578,7 +18841,11 @@ var WebSession = class {
18578
18841
  id,
18579
18842
  toolName: approval.toolName,
18580
18843
  action: approval.action,
18581
- display: approval.display
18844
+ display: approval.display,
18845
+ sourceAgentId: approval.sourceAgentId,
18846
+ sourceAgentName: approval.sourceAgentName,
18847
+ sourceToolName: approval.sourceToolName,
18848
+ sourceCapabilityMode: approval.sourceCapabilityMode
18582
18849
  })),
18583
18850
  status: this.cachedStatus ?? {
18584
18851
  model: "unknown",