scream-code 0.16.4 → 0.16.6

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.
@@ -4,8 +4,8 @@ import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
- import { $n as readSessionIndex, $t as SubagentSlots, A as langFromPath, An as startManualEmbeddingDownload, At as toggleEmptySessionHint, B as charsForTokenBudget, Bn as getLogDir, Bt as OSC11_QUERY, C as getDaemonInstructions, Cn as buildSkillSlashCommands, D as renderDiffLines, Dn as setExperimentalFlags, Dt as createMarkdownTheme, E as ToolCallComponent, En as isExperimentalFlagEnabled, Et as createEditorTheme, F as replaceTabs, Fn as loadTuiConfig, Ft as parseOsc11BackgroundTheme, G as WelcomeComponent, Gn as PRODUCT_NAME, Gt as TERMINAL_FOCUS_IN, H as getSharedSpeedTracker, Hn as CLI_COMMAND_NAME, Ht as OSC11_RESPONSE_PREFIX, I as sanitizeShellOutput, In as saveTuiConfig, It as DISABLE_TERMINAL_FOCUS_REPORTING, J as resetBreathingClock, Jn as saveCatalogCache, Jt as TERMINAL_THEME_LIGHT, K as BREATHE_CYCLE_MS, Kn as DEFAULT_CATALOG_URL, Kt as TERMINAL_FOCUS_OUT, L as SkillActivationComponent, Ln as detectShellEnvironment, Lt as DISABLE_TERMINAL_THEME_REPORTING, M as ThinkingComponent, Mn as PULSE_WAVE_FRAMES, Mt as contrastTextHex, N as NoticeMessageComponent, Nn as TuiConfigParseError, Nt as getColorPalette, O as renderDiffLinesClustered, On as getKnowledgeStore, Ot as isEmptySessionHintDismissed, P as StatusMessageComponent, Pn as TuiLikePreferencesSchema, Pt as detectTerminalTheme, Qn as appendSessionIndexEntry, Qt as lerpGradient, R as AssistantMessageComponent, Rn as getDataDir, Rt as ENABLE_TERMINAL_FOCUS_REPORTING, Sn as getNoActiveSessionMessage, St as clearInfoPanelState, T as UserMessageComponent, Tn as sortSlashCommands, Tt as resolveThemeSync, U as BackgroundAgentStatusComponent, Un as CLI_UI_MODE, Ut as OSC11_RESPONSE_PREFIX_NO_ESC, V as estimateTokens, Vn as detectInstallSource, Vt as OSC11_RESPONSE, W as AgentGroupComponent, Wn as CLI_USER_AGENT_PRODUCT, Wt as QUERY_TERMINAL_THEME, X as clearGoalState, Xn as encodeWorkDirKey, Xt as isStreaming, Y as CompactionComponent, Yn as ScreamHarness, Yt as isBusy, Z as refineGoal, Zn as normalizeWorkDir, Zt as FooterComponent, _ as selectUpdateTarget, _n as SESSION_TIPS, an as STATUS_BULLET, ar as log, at as changeThinkingLevel, b as appendJsonlLine, bn as getCtrlDHint, bt as refreshProviderBalance, c as hasDispose, cn as formatErrorMessage, cr as isOrphanedToolCallError, d as MoonLoader, dn as serializeToolResultOutput, dt as handleFusionPlanCommand, er as removeSessionIndexEntry, f as formatMemoryMemoForInjection, fn as stringValue, gn as MAIN_AGENT_ID, hn as EXIT_CONFIRM_WINDOW_MS, i as buildRoleAdditionalText, in as printableChar, ir as flushDiagnosticLogs, it as toTerminalHyperlink, j as CachedContainer, jn as PIXEL_PULSE_FRAMES, jt as createThemeStyles, k as highlightLines, kn as isEmbeddingModelCached, kt as isTurnElapsedEnabled, l as isExpandable, ln as isTodoItemShape, lr as ErrorCodes, mn as EMPTY_SESSION_HINT_URL, n as clearEvalPanelState, nr as resolveScreamHome, on as appendStreamingArgsPreview, or as resolveGlobalLogPath, ot as getModelCycleLevel, pn as truncateErrorMessage, pt as handlePlanCommand, q as getBreathingFrame, qn as fetchCatalog, qt as TERMINAL_THEME_DARK, r as openUrl, rr as MemoryMemoStore, rt as isInternalMessage, s as disposeChildren, sn as argsRecord, sr as isScreamError, t as dispatchInput, u as isPlanExpandable, un as parseStreamingArgs, ur as SCREAM_ERROR_INFO, v as refreshUpdateCache, vn as TIP_ROTATION_INTERVAL_MS, wn as BUILTIN_SLASH_COMMANDS, x as readJsonlFile, xn as getLlmNotSetMessage, xt as supportsBalance, y as readUpdateCache, yn as getCtrlCHint, z as ActivityGroupComponent, zn as getInputHistoryFile, zt as ENABLE_TERMINAL_THEME_REPORTING } from "./dispatch-La1sAZPF.mjs";
8
- import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog--J3ngWFv.mjs";
7
+ import { $n as appendSessionIndexEntry, $t as SubagentSlots, A as langFromPath, An as hasEmbeddingModelCache, At as toggleEmptySessionHint, B as charsForTokenBudget, Bn as getInputHistoryFile, Bt as OSC11_QUERY, C as getDaemonInstructions, Cn as buildSkillSlashCommands, D as renderDiffLines, Dn as setExperimentalFlags, Dt as createMarkdownTheme, E as ToolCallComponent, En as isExperimentalFlagEnabled, Et as createEditorTheme, F as replaceTabs, Fn as TuiLikePreferencesSchema, Ft as parseOsc11BackgroundTheme, G as WelcomeComponent, Gn as CLI_USER_AGENT_PRODUCT, Gt as TERMINAL_FOCUS_IN, H as getSharedSpeedTracker, Hn as detectInstallSource, Ht as OSC11_RESPONSE_PREFIX, I as sanitizeShellOutput, In as loadTuiConfig, It as DISABLE_TERMINAL_FOCUS_REPORTING, J as resetBreathingClock, Jn as fetchCatalog, Jt as TERMINAL_THEME_LIGHT, K as BREATHE_CYCLE_MS, Kn as PRODUCT_NAME, Kt as TERMINAL_FOCUS_OUT, L as SkillActivationComponent, Ln as saveTuiConfig, Lt as DISABLE_TERMINAL_THEME_REPORTING, M as ThinkingComponent, Mn as PIXEL_PULSE_FRAMES, Mt as contrastTextHex, N as NoticeMessageComponent, Nn as PULSE_WAVE_FRAMES, Nt as getColorPalette, O as renderDiffLinesClustered, On as embeddingFailureDetail, Ot as isEmptySessionHintDismissed, P as StatusMessageComponent, Pn as TuiConfigParseError, Pt as detectTerminalTheme, Qn as normalizeWorkDir, Qt as lerpGradient, R as AssistantMessageComponent, Rn as detectShellEnvironment, Rt as ENABLE_TERMINAL_FOCUS_REPORTING, Sn as getNoActiveSessionMessage, St as clearInfoPanelState, T as UserMessageComponent, Tn as sortSlashCommands, Tt as resolveThemeSync, U as BackgroundAgentStatusComponent, Un as CLI_COMMAND_NAME, Ut as OSC11_RESPONSE_PREFIX_NO_ESC, V as estimateTokens, Vn as getLogDir, Vt as OSC11_RESPONSE, W as AgentGroupComponent, Wn as CLI_UI_MODE, Wt as QUERY_TERMINAL_THEME, X as clearGoalState, Xn as ScreamHarness, Xt as isStreaming, Y as CompactionComponent, Yn as saveCatalogCache, Yt as isBusy, Z as refineGoal, Zn as encodeWorkDirKey, Zt as FooterComponent, _ as selectUpdateTarget, _n as SESSION_TIPS, an as STATUS_BULLET, ar as flushDiagnosticLogs, at as changeThinkingLevel, b as appendJsonlLine, bn as getCtrlDHint, bt as refreshProviderBalance, c as hasDispose, cn as formatErrorMessage, cr as isScreamError, d as MoonLoader, dn as serializeToolResultOutput, dr as SCREAM_ERROR_INFO, dt as handleFusionPlanCommand, er as readSessionIndex, f as formatMemoryMemoForInjection, fn as stringValue, gn as MAIN_AGENT_ID, hn as EXIT_CONFIRM_WINDOW_MS, i as buildRoleAdditionalText, in as printableChar, ir as MemoryMemoStore, it as toTerminalHyperlink, j as CachedContainer, jn as startManualEmbeddingDownload, jt as createThemeStyles, k as highlightLines, kn as getKnowledgeStore, kt as isTurnElapsedEnabled, l as isExpandable, ln as isTodoItemShape, lr as isOrphanedToolCallError, mn as EMPTY_SESSION_HINT_URL, n as clearEvalPanelState, on as appendStreamingArgsPreview, or as log, ot as getModelCycleLevel, pn as truncateErrorMessage, pt as handlePlanCommand, q as getBreathingFrame, qn as DEFAULT_CATALOG_URL, qt as TERMINAL_THEME_DARK, r as openUrl, rr as resolveScreamHome, rt as isInternalMessage, s as disposeChildren, sn as argsRecord, sr as resolveGlobalLogPath, t as dispatchInput, tr as removeSessionIndexEntry, u as isPlanExpandable, un as parseStreamingArgs, ur as ErrorCodes, v as refreshUpdateCache, vn as TIP_ROTATION_INTERVAL_MS, wn as BUILTIN_SLASH_COMMANDS, x as readJsonlFile, xn as getLlmNotSetMessage, xt as supportsBalance, y as readUpdateCache, yn as getCtrlCHint, z as ActivityGroupComponent, zn as getDataDir, zt as ENABLE_TERMINAL_THEME_REPORTING } from "./dispatch-fjwbqaYI.mjs";
8
+ import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-DBlx8pMR.mjs";
9
9
  import { createRequire } from "node:module";
10
10
  import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
11
11
  import { access, appendFile, chmod, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
@@ -32,7 +32,7 @@ function optionalBuildString(value) {
32
32
  return typeof value === "string" && value.length > 0 ? value : void 0;
33
33
  }
34
34
  const SCREAM_BUILD_INFO = {
35
- version: optionalBuildString("0.16.4"),
35
+ version: optionalBuildString("0.16.6"),
36
36
  channel: optionalBuildString(""),
37
37
  commit: optionalBuildString(""),
38
38
  buildTarget: optionalBuildString("darwin-arm64")
@@ -1986,7 +1986,8 @@ function formatBackgroundAgentTranscript(phase, meta, extras = void 0) {
1986
1986
  return {
1987
1987
  phase,
1988
1988
  headline,
1989
- detail: detailParts.length > 0 ? detailParts.join(" · ") : void 0
1989
+ detail: detailParts.length > 0 ? detailParts.join(" · ") : void 0,
1990
+ trackingId: meta.agentId
1990
1991
  };
1991
1992
  }
1992
1993
  //#endregion
@@ -2209,7 +2210,8 @@ function formatBackgroundTaskTranscript(info) {
2209
2210
  return {
2210
2211
  phase: phaseFromStatus(info.status),
2211
2212
  headline: headlineFor(info),
2212
- detail: detailFor(info)
2213
+ detail: detailFor(info),
2214
+ trackingId: info.taskId
2213
2215
  };
2214
2216
  }
2215
2217
  //#endregion
@@ -2910,6 +2912,7 @@ var SessionEventHandler = class {
2910
2912
  }
2911
2913
  handleStepCompleted(event) {
2912
2914
  this.host.streamingUI.flushNow();
2915
+ this.host.streamingUI.flushPendingApprovals();
2913
2916
  this.maybeShowDebugTiming(event);
2914
2917
  this.recordProviderLatency(event);
2915
2918
  this.drainQueuedMessagesIntoSteer();
@@ -2936,6 +2939,24 @@ var SessionEventHandler = class {
2936
2939
  sessionApiCalls: this.host.state.appState.sessionApiCalls + 1
2937
2940
  });
2938
2941
  }
2942
+ /**
2943
+ * A retried attempt that produces output again is no longer reconnecting:
2944
+ * drop the retry label as soon as it streams a delta or emits a tool call, so
2945
+ * neither the footer status block nor the status bar keeps claiming
2946
+ * "重连中 N/M" for the rest of the step. The fields are only ever set by
2947
+ * handleStepRetrying, so this is the single clearing point besides
2948
+ * step/turn boundaries.
2949
+ */
2950
+ clearReconnectState() {
2951
+ if (!(this.host.state.appState.reconnectAttempt > 0)) return;
2952
+ this.host.setAppState({
2953
+ reconnectAttempt: 0,
2954
+ reconnectMaxAttempts: void 0,
2955
+ reconnectDelayMs: void 0,
2956
+ reconnectStatusCode: void 0,
2957
+ reconnectErrorName: void 0
2958
+ });
2959
+ }
2939
2960
  maybeShowDebugTiming(event) {
2940
2961
  if (process.env["SCREAM_CODE_DEBUG"] !== "1") return;
2941
2962
  const text = formatStepDebugTiming(event);
@@ -2995,12 +3016,14 @@ var SessionEventHandler = class {
2995
3016
  }
2996
3017
  handleThinkingDelta(event) {
2997
3018
  const { state, streamingUI } = this.host;
3019
+ this.clearReconnectState();
2998
3020
  streamingUI.appendThinkingDelta(event.delta);
2999
3021
  if (canTransitionTo(state.appState.streamingPhase, "thinking")) this.host.setAppState({ streamingPhase: "thinking" });
3000
3022
  streamingUI.scheduleFlush();
3001
3023
  }
3002
3024
  handleAssistantDelta(event) {
3003
3025
  const { state, streamingUI } = this.host;
3026
+ this.clearReconnectState();
3004
3027
  if (streamingUI.hasThinkingDraft()) streamingUI.flushThinkingToTranscript();
3005
3028
  streamingUI.appendAssistantDelta(event.delta);
3006
3029
  this.host.patchLivePane({
@@ -3028,6 +3051,7 @@ var SessionEventHandler = class {
3028
3051
  }
3029
3052
  handleToolCall(event) {
3030
3053
  const { streamingUI } = this.host;
3054
+ this.clearReconnectState();
3031
3055
  streamingUI.flushNow();
3032
3056
  if (event.name === "SendSubagentMessage") {
3033
3057
  const args = argsRecord(event.args);
@@ -3059,6 +3083,7 @@ var SessionEventHandler = class {
3059
3083
  handleToolCallDelta(event) {
3060
3084
  if (event.toolCallId.length === 0) return;
3061
3085
  const { state, streamingUI } = this.host;
3086
+ this.clearReconnectState();
3062
3087
  streamingUI.accumulateToolCallDelta(event.toolCallId, event.name, event.argumentsPart);
3063
3088
  this.host.patchLivePane({
3064
3089
  pendingApproval: null,
@@ -3113,7 +3138,7 @@ var SessionEventHandler = class {
3113
3138
  message: event.message
3114
3139
  };
3115
3140
  streamingUI.completeToolResult(event.toolCallId, resultData);
3116
- if (canTransitionTo(this.host.state.appState.streamingPhase, "waiting")) this.host.setAppState({ streamingPhase: "waiting" });
3141
+ if (!streamingUI.hasPendingToolCalls() && canTransitionTo(this.host.state.appState.streamingPhase, "waiting")) this.host.setAppState({ streamingPhase: "waiting" });
3117
3142
  }
3118
3143
  handleStatusUpdate(event) {
3119
3144
  const patch = {};
@@ -3134,11 +3159,16 @@ var SessionEventHandler = class {
3134
3159
  }
3135
3160
  }
3136
3161
  handleSessionError(event) {
3162
+ const hadActiveTurn = this.host.streamingUI.hasActiveTurn();
3137
3163
  this.host.streamingUI.flushNow();
3138
3164
  this.host.streamingUI.resetToolUi();
3139
3165
  this.host.streamingUI.finalizeLiveTextBuffers();
3140
3166
  this.host.streamingUI.endActivityGroup();
3141
3167
  this.host.showError(`[${event.code}] ${event.message}`);
3168
+ if (event.code === "turn.agent_busy" && !hadActiveTurn && this.host.state.appState.streamingPhase !== "idle") {
3169
+ this.host.setAppState({ streamingPhase: "idle" });
3170
+ this.host.resetLivePane();
3171
+ }
3142
3172
  }
3143
3173
  handleSessionWarning(event) {
3144
3174
  this.host.showStatus(t("handler.warning_prefix", { message: event.message }), this.host.state.theme.colors.warning);
@@ -3637,6 +3667,19 @@ var SessionEventHandler = class {
3637
3667
  }
3638
3668
  };
3639
3669
  //#endregion
3670
+ //#region src/tui/utils/approval-notice.ts
3671
+ function buildApprovalNotice(args) {
3672
+ const sessionScoped = args.decision === "approved" && args.scope === "session";
3673
+ const tone = sessionScoped ? "approved_session" : args.decision;
3674
+ const label = args.decision === "approved" ? sessionScoped ? args.labels.approvedSession : args.labels.approved : args.decision === "rejected" ? args.labels.rejected : args.labels.cancelled;
3675
+ const feedback = args.feedback !== void 0 && args.feedback.length > 0 ? ` — "${args.feedback}"` : "";
3676
+ return {
3677
+ label,
3678
+ detail: `${args.action}${feedback}`,
3679
+ tone
3680
+ };
3681
+ }
3682
+ //#endregion
3640
3683
  //#region src/tui/utils/media-url.ts
3641
3684
  function getMediaKindLabels() {
3642
3685
  return {
@@ -3974,6 +4017,17 @@ var SessionReplayRenderer = class {
3974
4017
  this.renderApprovalResult(context, record.record);
3975
4018
  return;
3976
4019
  case "config_updated": return;
4020
+ case "stream_draft_partial": {
4021
+ this.flushAssistant(context);
4022
+ const draftText = record.text.trim();
4023
+ if (draftText.length > 0) collectReplayMessageContent(context.assistant, [{
4024
+ type: "text",
4025
+ text: draftText
4026
+ }]);
4027
+ this.flushAssistant(context);
4028
+ this.host.appendTranscriptEntry(replayEntry(context, "status", t("replay.streamDraftPartial"), "notice"));
4029
+ return;
4030
+ }
3977
4031
  }
3978
4032
  }
3979
4033
  renderMessage(context, message) {
@@ -4084,6 +4138,7 @@ var SessionReplayRenderer = class {
4084
4138
  }
4085
4139
  advanceTurn(context) {
4086
4140
  this.host.streamingUI.endActivityGroup();
4141
+ this.host.streamingUI.flushPendingApprovals();
4087
4142
  context.turnIndex += 1;
4088
4143
  context.stepIndex = 0;
4089
4144
  context.currentTurnId = `replay:${String(context.turnIndex)}`;
@@ -4162,21 +4217,19 @@ var SessionReplayRenderer = class {
4162
4217
  return;
4163
4218
  }
4164
4219
  const { result } = record;
4165
- const parts = [];
4166
- switch (result.decision) {
4167
- case "approved":
4168
- parts.push(result.scope === "session" ? t("replay.approved_session") : t("replay.approved"));
4169
- break;
4170
- case "rejected":
4171
- parts.push(t("replay.rejected"));
4172
- break;
4173
- case "cancelled":
4174
- parts.push(t("replay.cancelled"));
4175
- break;
4176
- }
4177
- parts.push(`: ${record.action}`);
4178
- if (result.feedback !== void 0 && result.feedback.length > 0) parts.push(` — "${result.feedback}"`);
4179
- this.host.appendTranscriptEntry(replayEntry(context, "status", parts.join(""), "notice"));
4220
+ const notice = buildApprovalNotice({
4221
+ decision: result.decision,
4222
+ scope: result.scope,
4223
+ action: record.action,
4224
+ feedback: result.feedback,
4225
+ labels: {
4226
+ approved: t("replay.approved"),
4227
+ approvedSession: t("replay.approved_session"),
4228
+ rejected: t("replay.rejected"),
4229
+ cancelled: t("replay.cancelled")
4230
+ }
4231
+ });
4232
+ this.host.streamingUI.recordApproval(record.toolCallId, notice.label, notice.detail, notice.tone);
4180
4233
  }
4181
4234
  renderPlanReviewResult(context, record) {
4182
4235
  const { result } = record;
@@ -4355,6 +4408,11 @@ var StreamingUIController = class StreamingUIController {
4355
4408
  _pendingToolComponents = /* @__PURE__ */ new Map();
4356
4409
  _pendingAgentGroup = null;
4357
4410
  _activityGroup;
4411
+ /**
4412
+ * Approval outcomes answered before their tool call was dispatched, waiting
4413
+ * for the call's row to appear (see `recordApproval`). Keyed by call id.
4414
+ */
4415
+ _pendingApprovals = /* @__PURE__ */ new Map();
4358
4416
  /** Guards the single `registerLiveComponent` call for the turn's block. */
4359
4417
  _activityGroupRegistered = false;
4360
4418
  /** Block that borrowed a card, so late results refresh their own block. */
@@ -4567,6 +4625,19 @@ var StreamingUIController = class StreamingUIController {
4567
4625
  this._streamingToolCallArguments.delete(toolCallId);
4568
4626
  return matchedCall;
4569
4627
  }
4628
+ /** True while a tool call of the step being handled has no result yet. A
4629
+ * parallel batch dispatches its results in provider order, so this is what
4630
+ * keeps the footer in "执行中" until the whole batch settles instead of
4631
+ * flipping to "等待中" on the first result. Entries from an earlier step or
4632
+ * turn are ignored: nothing can settle them any more, and counting them
4633
+ * would pin the footer in "执行中" for the rest of the turn. */
4634
+ hasPendingToolCalls() {
4635
+ for (const call of this._activeToolCalls.values()) {
4636
+ if (call.turnId !== this._currentTurnId || call.step !== this._currentStep) continue;
4637
+ if (call.result === void 0) return true;
4638
+ }
4639
+ return false;
4640
+ }
4570
4641
  /** Marks in-flight tool calls as truncated when a step hits max_tokens.
4571
4642
  * Returns the count of tool calls that were truncated. */
4572
4643
  markStepTruncated(turnId, step) {
@@ -4587,6 +4658,7 @@ var StreamingUIController = class StreamingUIController {
4587
4658
  /** Tears down replay-specific state after session history has been rendered. */
4588
4659
  cleanupAfterReplay(completedToolCallIds) {
4589
4660
  this.endActivityGroup();
4661
+ this.flushPendingApprovals();
4590
4662
  this._activeToolCalls.clear();
4591
4663
  for (const toolCallId of completedToolCallIds) this._pendingToolComponents.delete(toolCallId);
4592
4664
  this._pendingAgentGroup = null;
@@ -4654,6 +4726,65 @@ var StreamingUIController = class StreamingUIController {
4654
4726
  group.attachNotice(data);
4655
4727
  return true;
4656
4728
  }
4729
+ /**
4730
+ * Files an approval outcome with the call it allowed.
4731
+ *
4732
+ * The prompt is answered before its tool call is dispatched, so the row it
4733
+ * belongs under usually does not exist yet: while a turn is running the
4734
+ * outcome waits here for its own call to appear, and is filed under it the
4735
+ * moment it does. Two cases keep the row they always had instead — an outcome
4736
+ * answered between turns (a background agent's call, where no block will ever
4737
+ * open), and one whose call's row is not in the block that is open now (a
4738
+ * subagent's call, a card that stays standalone, a block that already
4739
+ * settled), which is mounted when the step settles.
4740
+ */
4741
+ recordApproval(callId, label, detail, tone) {
4742
+ const card = this._pendingToolComponents.get(callId);
4743
+ const owner = card === void 0 ? void 0 : this._groupByToolCard.get(card);
4744
+ if (owner !== void 0 && this.host.state.transcriptContainer.children.includes(owner) && owner.attachApproval(callId, label, detail, tone)) return;
4745
+ const group = this._activityGroup;
4746
+ if (group !== void 0 && group.attachApproval(callId, label, detail, tone)) return;
4747
+ if (!this.hasActiveTurn()) {
4748
+ this.mountApprovalNotices([{
4749
+ label,
4750
+ detail,
4751
+ tone
4752
+ }]);
4753
+ return;
4754
+ }
4755
+ this._pendingApprovals.set(callId, {
4756
+ label,
4757
+ detail,
4758
+ tone
4759
+ });
4760
+ }
4761
+ /** Files the outcome waiting for this call, now that its row exists. */
4762
+ adoptPendingApproval(group, callId) {
4763
+ const pending = this._pendingApprovals.get(callId);
4764
+ if (pending === void 0) return;
4765
+ this._pendingApprovals.delete(callId);
4766
+ if (!group.attachApproval(callId, pending.label, pending.detail, pending.tone)) this.mountApprovalNotices([pending]);
4767
+ }
4768
+ /**
4769
+ * Mounts outcomes as their own notice row — the row an approval used to get
4770
+ * straight away, and the fallback for anything no block ever claims.
4771
+ */
4772
+ mountApprovalNotices(approvals) {
4773
+ for (const approval of approvals) this.host.transcriptController.appendEntry({
4774
+ id: nextTranscriptId(),
4775
+ kind: "status",
4776
+ renderMode: "notice",
4777
+ content: `${approval.label}: ${approval.detail}`
4778
+ });
4779
+ this.host.state.ui.requestRender();
4780
+ }
4781
+ /** Settles anything still waiting: mounted as notices, in the order answered. */
4782
+ flushPendingApprovals() {
4783
+ if (this._pendingApprovals.size === 0) return;
4784
+ const parked = [...this._pendingApprovals.values()];
4785
+ this._pendingApprovals.clear();
4786
+ this.mountApprovalNotices(parked);
4787
+ }
4657
4788
  endActivityGroup() {
4658
4789
  const group = this._activityGroup;
4659
4790
  this._activityGroup = void 0;
@@ -4788,6 +4919,7 @@ var StreamingUIController = class StreamingUIController {
4788
4919
  this._streamingToolCallArguments.clear();
4789
4920
  this.disposeAndClearPendingToolComponents();
4790
4921
  this._pendingAgentGroup = null;
4922
+ this.flushPendingApprovals();
4791
4923
  }
4792
4924
  resetToolCallState() {
4793
4925
  this._activeToolCalls.clear();
@@ -4951,6 +5083,7 @@ var StreamingUIController = class StreamingUIController {
4951
5083
  if (state.planExpanded) tc.setPlanExpanded(true);
4952
5084
  if (toolCall.name !== "Agent") this._pendingAgentGroup = null;
4953
5085
  if (!this.tryAttachAgentToolCall(toolCall, tc)) if (StreamingUIController.STANDALONE_TOOL_NAMES.has(toolCall.name)) {
5086
+ if (toolCall.name === "ExitPlanMode") this.endActivityGroup();
4954
5087
  tc.setExpanded(state.toolOutputExpanded);
4955
5088
  state.transcriptContainer.addChild(tc);
4956
5089
  state.ui.requestRender();
@@ -4958,7 +5091,8 @@ var StreamingUIController = class StreamingUIController {
4958
5091
  const group = this.ensureActivityGroup();
4959
5092
  this.registerActivityGroupEntry(group, entry);
4960
5093
  this._groupByToolCard.set(tc, group);
4961
- group.attachTool(tc, toolCall.step ?? this._currentStep);
5094
+ group.attachTool(tc, toolCall.step ?? this._currentStep, toolCall.id);
5095
+ this.adoptPendingApproval(group, toolCall.id);
4962
5096
  state.ui.requestRender();
4963
5097
  }
4964
5098
  if (toolCall.name === "ExitPlanMode" && typeof toolCall.args["plan"] !== "string") {
@@ -6270,6 +6404,9 @@ var TranscriptController = class TranscriptController {
6270
6404
  welcomeComponent;
6271
6405
  committedComponent;
6272
6406
  liveComponentToEntry = /* @__PURE__ */ new Map();
6407
+ /** Live `started` notice cards by task id, so the terminal notice can stop the
6408
+ * ticker of the card that is still claiming the task is running. */
6409
+ liveNoticesByTrackingId = /* @__PURE__ */ new Map();
6273
6410
  pendingComponents = /* @__PURE__ */ new Set();
6274
6411
  /** Max live transcript children before the oldest are folded into the
6275
6412
  * committed single-line summary. Overridable via SCREAM_TRANSCRIPT_LIVE_LIMIT
@@ -6295,6 +6432,26 @@ var TranscriptController = class TranscriptController {
6295
6432
  releaseLiveComponent(component) {
6296
6433
  this.liveComponentToEntry.delete(component);
6297
6434
  }
6435
+ /**
6436
+ * Stop everything a component owns once it leaves the live area. Committed
6437
+ * views re-render from the entry data, so disposing cannot lose content, but
6438
+ * skipping it would let an animation timer outlive the row it belonged to.
6439
+ */
6440
+ dropLiveComponent(component) {
6441
+ for (const [trackingId, tracked] of this.liveNoticesByTrackingId) if (tracked === component) this.liveNoticesByTrackingId.delete(trackingId);
6442
+ if (hasDispose(component)) component.dispose();
6443
+ }
6444
+ /**
6445
+ * Stops the ticker of the card that announced `trackingId`. Called when the
6446
+ * task reaches a terminal state: that card is history now, and a glyph that
6447
+ * keeps rotating would claim the task is still running.
6448
+ */
6449
+ settleLiveNotice(trackingId) {
6450
+ const tracked = this.liveNoticesByTrackingId.get(trackingId);
6451
+ if (tracked === void 0) return;
6452
+ this.liveNoticesByTrackingId.delete(trackingId);
6453
+ tracked.settle();
6454
+ }
6298
6455
  markPending(component) {
6299
6456
  this.pendingComponents.add(component);
6300
6457
  }
@@ -6336,6 +6493,7 @@ var TranscriptController = class TranscriptController {
6336
6493
  this.committedComponent.appendEntry(entry, state.theme.colors);
6337
6494
  container.removeChild(component);
6338
6495
  this.liveComponentToEntry.delete(component);
6496
+ this.dropLiveComponent(component);
6339
6497
  }
6340
6498
  this.committedComponent.setCount(this.committedComponent.getCount() + toCommit.length);
6341
6499
  if (process.env["SCREAM_CODE_DEBUG"] === "1") this.host.showStatus(`[debug] committed=${this.committedComponent.getCount()} live=${this.getLiveCount()}`);
@@ -6370,10 +6528,10 @@ var TranscriptController = class TranscriptController {
6370
6528
  if (state.planExpanded) tc.setPlanExpanded(true);
6371
6529
  return tc;
6372
6530
  }
6373
- if (entry.backgroundAgentStatus !== void 0) return new BackgroundAgentStatusComponent(entry.backgroundAgentStatus, state.theme.colors);
6531
+ if (entry.backgroundAgentStatus !== void 0) return new BackgroundAgentStatusComponent(entry.backgroundAgentStatus, state.theme.colors, state.ui);
6374
6532
  return entry.renderMode === "notice" ? new NoticeMessageComponent(entry.content, entry.detail, state.theme.colors, entry.noticeMarkerColor) : new StatusMessageComponent(entry.content, state.theme.colors, entry.color);
6375
6533
  case "status":
6376
- if (entry.backgroundAgentStatus !== void 0) return new BackgroundAgentStatusComponent(entry.backgroundAgentStatus, state.theme.colors);
6534
+ if (entry.backgroundAgentStatus !== void 0) return new BackgroundAgentStatusComponent(entry.backgroundAgentStatus, state.theme.colors, state.ui);
6377
6535
  return entry.renderMode === "notice" ? new NoticeMessageComponent(entry.content, entry.detail, state.theme.colors, entry.noticeMarkerColor) : new StatusMessageComponent(entry.content, state.theme.colors, entry.color);
6378
6536
  case "cron":
6379
6537
  if (entry.cronData === void 0) return null;
@@ -6388,8 +6546,15 @@ var TranscriptController = class TranscriptController {
6388
6546
  if (component) {
6389
6547
  this.liveComponentToEntry.set(component, entry);
6390
6548
  const status = entry.backgroundAgentStatus;
6391
- if (status !== void 0 && this.host.streamingUI.attachNotice(status)) this.releaseLiveComponent(component);
6392
- else this.host.state.transcriptContainer.addChild(component);
6549
+ if (status !== void 0 && component instanceof BackgroundAgentStatusComponent) {
6550
+ const trackingId = status.trackingId;
6551
+ if (trackingId !== void 0) if (status.phase === "started") this.liveNoticesByTrackingId.set(trackingId, component);
6552
+ else this.settleLiveNotice(trackingId);
6553
+ }
6554
+ if (status !== void 0 && this.host.streamingUI.attachNotice(status)) {
6555
+ this.releaseLiveComponent(component);
6556
+ this.dropLiveComponent(component);
6557
+ } else this.host.state.transcriptContainer.addChild(component);
6393
6558
  this.host.state.ui.requestRender();
6394
6559
  }
6395
6560
  return component ?? null;
@@ -6415,26 +6580,19 @@ var TranscriptController = class TranscriptController {
6415
6580
  }
6416
6581
  appendApprovalEntry(request, response) {
6417
6582
  if (request.toolName === "ExitPlanMode" || request.display.kind === "plan_review") return;
6418
- const parts = [];
6419
- switch (response.decision) {
6420
- case "approved":
6421
- parts.push(response.scope === "session" ? t("tc.approved_session") : t("tc.approved"));
6422
- break;
6423
- case "rejected":
6424
- parts.push(t("tc.rejected"));
6425
- break;
6426
- case "cancelled":
6427
- parts.push(t("tc.cancelled"));
6428
- break;
6429
- }
6430
- parts.push(`: ${request.action}`);
6431
- if (response.feedback !== void 0 && response.feedback.length > 0) parts.push(` — "${response.feedback}"`);
6432
- this.appendEntry({
6433
- id: nextTranscriptId(),
6434
- kind: "status",
6435
- renderMode: "notice",
6436
- content: parts.join("")
6583
+ const notice = buildApprovalNotice({
6584
+ decision: response.decision,
6585
+ scope: response.scope,
6586
+ action: request.action,
6587
+ feedback: response.feedback,
6588
+ labels: {
6589
+ approved: t("tc.approved"),
6590
+ approvedSession: t("tc.approved_session"),
6591
+ rejected: t("tc.rejected"),
6592
+ cancelled: t("tc.cancelled")
6593
+ }
6437
6594
  });
6595
+ this.host.streamingUI.recordApproval(request.toolCallId, notice.label, notice.detail, notice.tone);
6438
6596
  }
6439
6597
  renderWelcome() {
6440
6598
  const { state } = this.host;
@@ -6453,6 +6611,7 @@ var TranscriptController = class TranscriptController {
6453
6611
  const { state, streamingUI, imageStore } = this.host;
6454
6612
  streamingUI.discardPending();
6455
6613
  streamingUI.endActivityGroup();
6614
+ streamingUI.flushPendingApprovals();
6456
6615
  state.transcriptEntries = [];
6457
6616
  streamingUI.disposeActiveCompactionBlock();
6458
6617
  streamingUI.resetLiveText();
@@ -13362,6 +13521,7 @@ function createInitialAppState(input) {
13362
13521
  isSwitchingSession: false,
13363
13522
  streamingPhase: "idle",
13364
13523
  streamingStartTime: 0,
13524
+ pendingUserAction: null,
13365
13525
  theme: input.tuiConfig.theme,
13366
13526
  version: input.version,
13367
13527
  hasNewVersion: false,
@@ -13798,14 +13958,28 @@ var ScreamTUI = class {
13798
13958
  patchLivePane(patch) {
13799
13959
  if (!hasPatchChanges(this.state.livePane, patch)) return;
13800
13960
  Object.assign(this.state.livePane, patch);
13961
+ this.syncPendingUserAction();
13801
13962
  this.lifecycleController.updateActivityPane();
13802
13963
  this.state.ui.requestRender();
13803
13964
  }
13804
13965
  resetLivePane() {
13805
13966
  this.state.livePane = { ...INITIAL_LIVE_PANE };
13967
+ this.syncPendingUserAction();
13806
13968
  this.lifecycleController.updateActivityPane();
13807
13969
  this.state.ui.requestRender();
13808
13970
  }
13971
+ /**
13972
+ * Mirror the live pane's pending panels onto `AppState.pendingUserAction` so
13973
+ * the footer status block can report "等待批准/等待回答" instead of claiming a
13974
+ * running phase while a panel is blocked on the user. Every mutation of
13975
+ * `livePane` goes through patchLivePane/resetLivePane, so this stays the
13976
+ * single writer of that field.
13977
+ */
13978
+ syncPendingUserAction() {
13979
+ const { pendingApproval, pendingQuestion } = this.state.livePane;
13980
+ const next = pendingApproval !== null ? "approval" : pendingQuestion !== null ? "question" : null;
13981
+ if (next !== this.state.appState.pendingUserAction) this.setAppState({ pendingUserAction: next });
13982
+ }
13809
13983
  requireSession() {
13810
13984
  if (this.session === void 0) throw new Error(getNoActiveSessionMessage());
13811
13985
  return this.session;
@@ -13882,7 +14056,7 @@ var ScreamTUI = class {
13882
14056
  const card = new OnboardingCardComponent({
13883
14057
  colors: this.state.theme.colors,
13884
14058
  onConfigure: () => {
13885
- import("./dispatch-C7ujqj4p.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
14059
+ import("./dispatch-dDbUBD7J.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
13886
14060
  this.restoreEditor();
13887
14061
  onDone?.();
13888
14062
  });
@@ -13903,7 +14077,7 @@ var ScreamTUI = class {
13903
14077
  if (this.state.startupState === "picker") return;
13904
14078
  try {
13905
14079
  await getKnowledgeStore();
13906
- if (isEmbeddingModelCached()) {
14080
+ if (hasEmbeddingModelCache()) {
13907
14081
  startManualEmbeddingDownload().catch(() => {});
13908
14082
  return;
13909
14083
  }
@@ -13918,8 +14092,9 @@ var ScreamTUI = class {
13918
14092
  const spinner = this.showProgressSpinner(t("kw.embedding_downloading"));
13919
14093
  let ok = false;
13920
14094
  let error;
14095
+ let failureKind;
13921
14096
  try {
13922
- ({ok, error} = await startManualEmbeddingDownload());
14097
+ ({ok, error, failureKind} = await startManualEmbeddingDownload());
13923
14098
  } catch (error) {
13924
14099
  error = error instanceof Error ? error.message : String(error);
13925
14100
  }
@@ -13929,7 +14104,7 @@ var ScreamTUI = class {
13929
14104
  });
13930
14105
  if (ok) this.showStatus(t("kw.embedding_ready"));
13931
14106
  else {
13932
- const detail = [t("knowledge.download_model_retry_hint"), error].filter(Boolean).join("\n");
14107
+ const detail = embeddingFailureDetail(failureKind, error);
13933
14108
  this.showNotice(t("kw.embedding_failed"), detail);
13934
14109
  }
13935
14110
  })();
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
- import { nn as handleConnectCommand } from "./dispatch-La1sAZPF.mjs";
6
+ import { nn as handleConnectCommand } from "./dispatch-fjwbqaYI.mjs";
7
7
  export { handleConnectCommand };