scream-code 0.18.8 → 0.18.9

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,9 +4,9 @@ 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 { A as sharedMemoStore } from "./src-ClB_Hzp9.mjs";
8
- import { $ as clearGoalState, $n as fetchCatalog, $t as isStreaming, A as renderDiffLines, An as isExperimentalFlagEnabled, At as createMarkdownTheme, B as SkillActivationComponent, Bn as TuiLikePreferencesSchema, Bt as DISABLE_TERMINAL_THEME_REPORTING, C as appendJsonlLine, Cn as getCtrlCHint, Ct as refreshProviderBalance, Dn as buildSkillSlashCommands, E as getDaemonInstructions, En as getNoActiveSessionMessage, F as ThinkingComponent, Fn as hasEmbeddingModelCache, Ft as contrastTextHex, G as getSharedSpeedTracker, Gn as getInputHistoryFile, Gt as OSC11_RESPONSE_PREFIX, H as ActivityGroupComponent, Hn as saveTuiConfig, Ht as ENABLE_TERMINAL_THEME_REPORTING, I as NoticeMessageComponent, In as startManualEmbeddingDownload, It as getColorPalette, J as WelcomeComponent, Jn as CLI_COMMAND_NAME, Jt as TERMINAL_FOCUS_IN, K as BackgroundAgentStatusComponent, Kn as getLogDir, Kt as OSC11_RESPONSE_PREFIX_NO_ESC, L as StatusMessageComponent, Ln as PIXEL_PULSE_FRAMES, Lt as detectTerminalTheme, M as highlightLines, Mn as embeddingProgressLabel, Mt as isTurnElapsedEnabled, N as langFromPath, Nn as embeddingFailureDetail, Nt as toggleEmptySessionHint, O as UserMessageComponent, On as BUILTIN_SLASH_COMMANDS, Ot as resolveThemeSync, P as CachedContainer, Pn as getKnowledgeStore, Pt as createThemeStyles, Q as CompactionComponent, Qn as DEFAULT_CATALOG_URL, Qt as isBusy, R as replaceTabs, Rn as PULSE_WAVE_FRAMES, Rt as parseOsc11BackgroundTheme, S as readUpdateCache, Sn as TIP_ROTATION_INTERVAL_MS, Tn as getLlmNotSetMessage, Tt as clearInfoPanelState, U as charsForTokenBudget, Un as detectShellEnvironment, Ut as OSC11_QUERY, V as AssistantMessageComponent, Vn as loadTuiConfig, Vt as ENABLE_TERMINAL_FOCUS_REPORTING, W as estimateTokens, Wn as getDataDir, Wt as OSC11_RESPONSE, X as getBreathingFrame, Xn as CLI_USER_AGENT_PRODUCT, Xt as TERMINAL_THEME_DARK, Y as BREATHE_CYCLE_MS, Yn as CLI_UI_MODE, Yt as TERMINAL_FOCUS_OUT, Z as resetBreathingClock, Zn as PRODUCT_NAME, Zt as TERMINAL_THEME_LIGHT, _n as truncateErrorMessage, _r as SCREAM_ERROR_INFO, a as buildRoleAdditionalText, ar as readSessionIndex, b as selectUpdateTarget, bn as MAIN_AGENT_ID, cn as STATUS_BULLET, cr as isRealUserPrompt, ct as changeThinkingLevel, d as hasDispose, dn as argsRecord, dr as flushDiagnosticLogs, en as FooterComponent, er as saveCatalogCache, et as refineGoal, f as isExpandable, fn as formatErrorMessage, fr as log, gn as stringValue, gr as ErrorCodes, gt as handlePlanCommand, h as formatMemoryMemoForInjection, hn as serializeToolResultOutput, hr as isOrphanedToolCallError, i as openUrl, ir as appendSessionIndexEntry, j as renderDiffLinesClustered, jn as setExperimentalFlags, jt as isEmptySessionHintDismissed, k as ToolCallComponent, kn as sortSlashCommands, kt as createEditorTheme, ln as appendSessionHeaderHint, lr as parseRlmMaxDepthArg, lt as getModelCycleLevel, m as MoonLoader, mn as parseStreamingArgs, mr as isScreamError, mt as handleFusionPlanCommand, n as clearEvalPanelState, nn as SubagentSlots, nr as encodeWorkDirKey, o as writeTextClipboard, or as removeSessionIndexEntry, ot as isInternalMessage, p as isPlanExpandable, pn as isTodoItemShape, pr as resolveGlobalLogPath, q as AgentGroupComponent, qn as detectInstallSource, qt as QUERY_TERMINAL_THEME, rr as normalizeWorkDir, s as syncDiagramCapabilityPrompt, sn as printableChar, st as toTerminalHyperlink, t as dispatchInput, tn as lerpGradient, tr as ScreamHarness, u as disposeChildren, un as appendStreamingArgsPreview, ur as resolveScreamHome, w as readJsonlFile, wn as getCtrlDHint, wt as supportsBalance, x as refreshUpdateCache, xn as SESSION_TIPS, yn as EXIT_CONFIRM_WINDOW_MS, z as sanitizeShellOutput, zn as TuiConfigParseError, zt as DISABLE_TERMINAL_FOCUS_REPORTING } from "./dispatch-BPfUTccZ.mjs";
9
- import { i as t, n as getLocale, r as setLocale } from "./text-input-dialog-DHDsHCqR.mjs";
7
+ import { A as sharedMemoStore } from "./src-DIVvoAYO.mjs";
8
+ import { $ as clearGoalState, $n as DEFAULT_CATALOG_URL, $t as isStreaming, A as renderDiffLines, An as sortSlashCommands, At as createMarkdownTheme, B as SkillActivationComponent, Bn as TuiConfigParseError, Bt as DISABLE_TERMINAL_THEME_REPORTING, C as appendJsonlLine, Cn as TIP_ROTATION_INTERVAL_MS, Ct as refreshProviderBalance, Dn as getNoActiveSessionMessage, E as getDaemonInstructions, En as getLlmNotSetMessage, F as ThinkingComponent, Fn as getKnowledgeStore, Ft as contrastTextHex, G as getSharedSpeedTracker, Gn as getDataDir, Gt as OSC11_RESPONSE_PREFIX, H as ActivityGroupComponent, Hn as loadTuiConfig, Ht as ENABLE_TERMINAL_THEME_REPORTING, I as NoticeMessageComponent, In as hasEmbeddingModelCache, It as getColorPalette, J as WelcomeComponent, Jn as detectInstallSource, Jt as TERMINAL_FOCUS_IN, K as BackgroundAgentStatusComponent, Kn as getInputHistoryFile, Kt as OSC11_RESPONSE_PREFIX_NO_ESC, L as StatusMessageComponent, Ln as startManualEmbeddingDownload, Lt as detectTerminalTheme, M as highlightLines, Mn as setExperimentalFlags, Mt as isTurnElapsedEnabled, N as langFromPath, Nn as embeddingProgressLabel, Nt as toggleEmptySessionHint, O as UserMessageComponent, On as buildSkillSlashCommands, Ot as resolveThemeSync, P as CachedContainer, Pn as embeddingFailureDetail, Pt as createThemeStyles, Q as CompactionComponent, Qn as PRODUCT_NAME, Qt as isBusy, R as replaceTabs, Rn as PIXEL_PULSE_FRAMES, Rt as parseOsc11BackgroundTheme, S as readUpdateCache, Sn as SESSION_TIPS, Tn as getCtrlDHint, Tt as clearInfoPanelState, U as charsForTokenBudget, Un as saveTuiConfig, Ut as OSC11_QUERY, V as AssistantMessageComponent, Vn as TuiLikePreferencesSchema, Vt as ENABLE_TERMINAL_FOCUS_REPORTING, W as estimateTokens, Wn as detectShellEnvironment, Wt as OSC11_RESPONSE, X as getBreathingFrame, Xn as CLI_UI_MODE, Xt as TERMINAL_THEME_DARK, Y as BREATHE_CYCLE_MS, Yn as CLI_COMMAND_NAME, Yt as TERMINAL_FOCUS_OUT, Z as resetBreathingClock, Zn as CLI_USER_AGENT_PRODUCT, Zt as TERMINAL_THEME_LIGHT, _n as stringValue, _r as ErrorCodes, a as buildRoleAdditionalText, ar as appendSessionIndexEntry, b as selectUpdateTarget, bn as EXIT_CONFIRM_WINDOW_MS, cn as printableChar, ct as changeThinkingLevel, d as hasDispose, dn as appendStreamingArgsPreview, dr as resolveScreamHome, en as FooterComponent, er as fetchCatalog, et as refineGoal, f as isExpandable, fn as argsRecord, fr as flushDiagnosticLogs, gn as serializeToolResultOutput, gr as isOrphanedToolCallError, gt as handlePlanCommand, h as formatMemoryMemoForInjection, hn as parseStreamingArgs, hr as isScreamError, i as openUrl, ir as normalizeWorkDir, j as renderDiffLinesClustered, jn as isExperimentalFlagEnabled, jt as isEmptySessionHintDismissed, k as ToolCallComponent, kn as BUILTIN_SLASH_COMMANDS, kt as createEditorTheme, ln as STATUS_BULLET, lr as isRealUserPrompt, lt as getModelCycleLevel, m as MoonLoader, mn as isTodoItemShape, mr as resolveGlobalLogPath, mt as handleFusionPlanCommand, n as clearEvalPanelState, nn as lerpHex, nr as ScreamHarness, o as writeTextClipboard, or as readSessionIndex, ot as isInternalMessage, p as isPlanExpandable, pn as formatErrorMessage, pr as log, q as AgentGroupComponent, qn as getLogDir, qt as QUERY_TERMINAL_THEME, rn as SubagentSlots, rr as encodeWorkDirKey, s as syncDiagramCapabilityPrompt, sr as removeSessionIndexEntry, st as toTerminalHyperlink, t as dispatchInput, tn as lerpGradient, tr as saveCatalogCache, u as disposeChildren, un as appendSessionHeaderHint, ur as parseRlmMaxDepthArg, vn as truncateErrorMessage, vr as SCREAM_ERROR_INFO, w as readJsonlFile, wn as getCtrlCHint, wt as supportsBalance, x as refreshUpdateCache, xn as MAIN_AGENT_ID, z as sanitizeShellOutput, zn as PULSE_WAVE_FRAMES, zt as DISABLE_TERMINAL_FOCUS_REPORTING } from "./dispatch-BcRHd4ya.mjs";
9
+ import { i as t, n as getLocale, r as setLocale } from "./text-input-dialog-CqagQQtW.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
12
12
  import { access, appendFile, chmod, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
@@ -33,7 +33,7 @@ function optionalBuildString(value) {
33
33
  return typeof value === "string" && value.length > 0 ? value : void 0;
34
34
  }
35
35
  const SCREAM_BUILD_INFO = {
36
- version: optionalBuildString("0.18.8"),
36
+ version: optionalBuildString("0.18.9"),
37
37
  channel: optionalBuildString(""),
38
38
  commit: optionalBuildString(""),
39
39
  buildTarget: optionalBuildString("darwin-arm64")
@@ -2997,6 +2997,19 @@ const INITIAL_LIVE_PANE = {
2997
2997
  function sliceLeading(text, max = 48) {
2998
2998
  return text.length > max ? `${text.slice(0, max)}…` : text;
2999
2999
  }
3000
+ /**
3001
+ * Inbound events that prove the stream is still delivering. `TUIState.lastActivityAt`
3002
+ * is stamped from this set so the status-bar wave can colour a stream that went
3003
+ * silent mid-reasoning — before the 60s transport-level stall timeout fires.
3004
+ */
3005
+ const ACTIVITY_EVENT_TYPES = new Set([
3006
+ "assistant.delta",
3007
+ "thinking.delta",
3008
+ "tool.call.started",
3009
+ "tool.call.delta",
3010
+ "tool.progress",
3011
+ "tool.result"
3012
+ ]);
3000
3013
  function addTokenUsage(a, b) {
3001
3014
  return {
3002
3015
  inputOther: a.inputOther + b.inputOther,
@@ -3186,6 +3199,7 @@ var SessionEventHandler = class {
3186
3199
  if (hidden.length > 0) host.showStatus(formatMcpStartupStatusSummary(hidden, visible.length), host.state.theme.colors.textMuted);
3187
3200
  }
3188
3201
  handleEvent(event, sendQueued) {
3202
+ if (ACTIVITY_EVENT_TYPES.has(event.type)) this.host.state.lastActivityAt = Date.now();
3189
3203
  if (this.routeSubagentEvent(event)) return;
3190
3204
  if ("turnId" in event && event.turnId !== void 0) this.host.streamingUI.setTurnId(String(event.turnId));
3191
3205
  switch (event.type) {
@@ -4057,7 +4071,10 @@ var SessionEventHandler = class {
4057
4071
  if (next !== void 0) setTimeout(() => {
4058
4072
  sendQueued(next);
4059
4073
  }, 0);
4060
- } else this.host.setAppState({ isCompacting: false });
4074
+ } else this.host.setAppState({
4075
+ isCompacting: false,
4076
+ streamingStartTime: Date.now()
4077
+ });
4061
4078
  }
4062
4079
  handleSubagentSpawned(event) {
4063
4080
  this.subagentSlots.onSpawned(event.subagentId, event.subagentName, event.description);
@@ -8899,6 +8916,49 @@ function installTerminalThemeTracking(state, onTheme) {
8899
8916
  };
8900
8917
  }
8901
8918
  //#endregion
8919
+ //#region src/tui/utils/wave-color.ts
8920
+ /**
8921
+ * Silence ramp for the status-bar pulse wave.
8922
+ *
8923
+ * The wave reports one thing: how long the agent has been working without
8924
+ * receiving anything. That covers a request awaiting its first token, a
8925
+ * reasoning or text stream that stopped delivering, and a tool that has been
8926
+ * running without progress. Any inbound event resets the clock, so the wave
8927
+ * returns to the brand colour the moment work resumes.
8928
+ *
8929
+ * Timeline (elapsed since the last inbound event, or since the request was
8930
+ * sent while no token has arrived yet):
8931
+ * 0 – 10s brand green, nothing to report
8932
+ * 10 – 15s green → amber
8933
+ * 15 – 25s amber → red (15s is pure amber, 25s pure red)
8934
+ * ≥ 25s red
8935
+ *
8936
+ * Red is an early warning, not an error: retries are driven by real transport
8937
+ * failures (the stall timeout, disconnects, rate limits), never by this colour.
8938
+ *
8939
+ * Colours come from the active palette (primary / warning / error), so light
8940
+ * and dark themes each ramp between their own contrast-checked values and a
8941
+ * theme switch takes effect on the next frame.
8942
+ */
8943
+ /** Green is held this long before the ramp starts. */
8944
+ const WAVE_GREEN_HOLD_MS = 1e4;
8945
+ /** Pure amber is reached here. */
8946
+ const WAVE_AMBER_AT_MS = 15e3;
8947
+ /** Pure red is reached here. */
8948
+ const WAVE_RED_AT_MS = 25e3;
8949
+ /**
8950
+ * Colour for a request that has been waiting `elapsedMs`.
8951
+ *
8952
+ * Non-finite or negative input falls back to green: a broken clock must never
8953
+ * make the indicator claim a stall.
8954
+ */
8955
+ function resolveWaveColor(elapsedMs, stops) {
8956
+ if (!Number.isFinite(elapsedMs) || elapsedMs < 1e4) return stops.green;
8957
+ if (elapsedMs < 15e3) return lerpHex(stops.green, stops.amber, (elapsedMs - WAVE_GREEN_HOLD_MS) / (WAVE_AMBER_AT_MS - WAVE_GREEN_HOLD_MS));
8958
+ if (elapsedMs < 25e3) return lerpHex(stops.amber, stops.red, (elapsedMs - WAVE_AMBER_AT_MS) / (WAVE_RED_AT_MS - WAVE_AMBER_AT_MS));
8959
+ return stops.red;
8960
+ }
8961
+ //#endregion
8902
8962
  //#region src/tui/components/chrome/pulse-wave-loader.ts
8903
8963
  /** Leading/trailing glyph: U+2588 (Block Elements — guaranteed single-column
8904
8964
  * width). The remaining stage cells render muted middle dots (U+00B7). */
@@ -8925,7 +8985,9 @@ var PulseWaveLoader = class extends Text {
8925
8985
  currentFrame = 0;
8926
8986
  intervalId = null;
8927
8987
  ui;
8928
- colorHex;
8988
+ /** Resolved on every frame so a theme switch lands immediately and the
8989
+ * first-token ramp can advance without rebuilding the status bar. */
8990
+ resolveColor;
8929
8991
  /** Wall-clock time of the last frame advance, for drift-free scheduling. */
8930
8992
  lastTickAt = 0;
8931
8993
  /** Raw frame text without Text's full-width render padding, exposed so
@@ -8936,10 +8998,10 @@ var PulseWaveLoader = class extends Text {
8936
8998
  getFrameText() {
8937
8999
  return this.frameText;
8938
9000
  }
8939
- constructor(ui, colorHex) {
9001
+ constructor(ui, resolveColor) {
8940
9002
  super("", 1, 0);
8941
9003
  this.ui = ui;
8942
- this.colorHex = colorHex;
9004
+ this.resolveColor = resolveColor;
8943
9005
  this.start();
8944
9006
  }
8945
9007
  start() {
@@ -8953,6 +9015,12 @@ var PulseWaveLoader = class extends Text {
8953
9015
  this.intervalId = null;
8954
9016
  }
8955
9017
  }
9018
+ /** Re-resolve the colour and repaint now. The cached frame text still carries
9019
+ * the previous colour, and the next scheduled tick can be up to 80ms away —
9020
+ * a palette swapped in place should land on the frame that follows it. */
9021
+ repaint() {
9022
+ this.updateDisplay();
9023
+ }
8956
9024
  dispose() {
8957
9025
  this.stop();
8958
9026
  }
@@ -8981,21 +9049,18 @@ var PulseWaveLoader = class extends Text {
8981
9049
  }, delayMs);
8982
9050
  this.intervalId = timer;
8983
9051
  }
8984
- setColorHex(colorHex) {
8985
- this.colorHex = colorHex;
8986
- this.updateDisplay();
8987
- }
8988
9052
  updateDisplay() {
8989
9053
  const step = PULSE_WAVE_FRAMES[this.currentFrame] ?? PULSE_WAVE_FRAMES[0];
8990
- const cells = Array.from({ length: 8 }, (_, idx) => this.renderCell(idx, step.active, step.forward));
9054
+ const color = this.resolveColor();
9055
+ const cells = Array.from({ length: 8 }, (_, idx) => this.renderCell(idx, step.active, step.forward, color));
8991
9056
  this.frameText = cells.join("");
8992
9057
  this.setText(this.frameText);
8993
9058
  this.ui.requestRender();
8994
9059
  }
8995
- renderCell(index, active, forward) {
9060
+ renderCell(index, active, forward, color) {
8996
9061
  const distance = forward ? active - index : index - active;
8997
- if (distance === 0) return chalk.hex(this.colorHex)(LEADING_BOX);
8998
- if (distance === 1) return chalk.hex(this.colorHex).dim(LEADING_BOX);
9062
+ if (distance === 0) return chalk.hex(color)(LEADING_BOX);
9063
+ if (distance === 1) return chalk.hex(color).dim(LEADING_BOX);
8999
9064
  return chalk.dim("·");
9000
9065
  }
9001
9066
  };
@@ -9397,6 +9462,7 @@ var LifecycleController = class LifecycleController {
9397
9462
  state.theme.resolvedTheme = nextTheme.resolvedTheme;
9398
9463
  state.theme.styles = nextTheme.styles;
9399
9464
  state.theme.markdownTheme = nextTheme.markdownTheme;
9465
+ this.statusBarLoader?.repaint();
9400
9466
  this.host.setAppState({ theme });
9401
9467
  this.host.updateEditorBorderHighlight();
9402
9468
  for (const child of state.transcriptContainer.children) child.invalidate?.();
@@ -9451,7 +9517,7 @@ var LifecycleController = class LifecycleController {
9451
9517
  case "tool":
9452
9518
  case "thinking":
9453
9519
  {
9454
- const loader = new PulseWaveLoader(state.ui, state.theme.colors.primary);
9520
+ const loader = new PulseWaveLoader(state.ui, () => this.currentWaveColor());
9455
9521
  this.statusBarLoader = loader;
9456
9522
  state.statusBarContainer.addChild(new StatusBarPaneComponent({
9457
9523
  mode,
@@ -9544,6 +9610,32 @@ var LifecycleController = class LifecycleController {
9544
9610
  this.host.state.terminal.setProgress(active);
9545
9611
  this.host.state.terminalState.progressActive = active;
9546
9612
  }
9613
+ /**
9614
+ * Colour for the status-bar pulse wave.
9615
+ *
9616
+ * Measures silence in whatever the agent is doing: a request awaiting its
9617
+ * first token, a reasoning/text stream that stopped delivering, or a tool
9618
+ * that has been running without emitting progress. Red is an early warning
9619
+ * only — it never triggers a retry, because retries are driven by real
9620
+ * transport errors (the 60s stall timeout, disconnects, rate limits).
9621
+ *
9622
+ * Resolved per frame, so the ramp advances and a live theme switch applies
9623
+ * without rebuilding the status bar.
9624
+ */
9625
+ currentWaveColor() {
9626
+ const { appState, theme, lastActivityAt } = this.host.state;
9627
+ const colors = theme.colors;
9628
+ const { streamingPhase, streamingStartTime, reconnectAttempt } = appState;
9629
+ if (streamingPhase === "idle") return colors.primary;
9630
+ const anchor = Math.max(lastActivityAt, streamingStartTime);
9631
+ if (anchor === 0) return colors.primary;
9632
+ const elapsed = Date.now() - anchor;
9633
+ return resolveWaveColor(reconnectAttempt > 0 ? Math.max(elapsed, WAVE_AMBER_AT_MS) : elapsed, {
9634
+ green: colors.primary,
9635
+ amber: colors.warning,
9636
+ red: colors.error
9637
+ });
9638
+ }
9547
9639
  stopActivitySpinner() {
9548
9640
  if (this.host.state.activitySpinner !== null) {
9549
9641
  this.host.state.activitySpinner.instance.stop();
@@ -11669,6 +11761,7 @@ function createTUIState(options) {
11669
11761
  activeDialog: null,
11670
11762
  tasksBrowser: void 0,
11671
11763
  externalEditorRunning: false,
11764
+ lastActivityAt: 0,
11672
11765
  queuedMessages: [],
11673
11766
  fdPath: detectFdPath(),
11674
11767
  gitLsFilesCache: createGitLsFilesCache(initialAppState.workDir),
@@ -12474,6 +12567,11 @@ var SessionManager$1 = class {
12474
12567
  resetSessionRuntime() {
12475
12568
  this.host.state.queuedMessages = [];
12476
12569
  this.host.harness.interactiveAgentId = MAIN_AGENT_ID;
12570
+ this.host.setAppState({
12571
+ streamingPhase: "idle",
12572
+ streamingStartTime: 0
12573
+ });
12574
+ this.host.state.lastActivityAt = 0;
12477
12575
  this.host.streamingUI.discardPending();
12478
12576
  this.host.streamingUI.resetToolCallState();
12479
12577
  this.host.streamingUI.resetToolUi();
@@ -13160,6 +13258,8 @@ var HelpPanelComponent = class extends Container {
13160
13258
  * Esc Cancel / back
13161
13259
  */
13162
13260
  const ELLIPSIS$1 = "…";
13261
+ /** One-sentence conclusion: generous for a rule, bounded for prompt injection. */
13262
+ const MAX_DISTILLED_CHARS = 200;
13163
13263
  function formatRelativeTime$1(ts) {
13164
13264
  if (!Number.isFinite(ts) || ts <= 0) return "";
13165
13265
  const diffSec = Math.floor(Math.max(0, Date.now() - ts) / 1e3);
@@ -13170,6 +13270,13 @@ function formatRelativeTime$1(ts) {
13170
13270
  if (hours < 24) return t("memory.hours_ago", { hours });
13171
13271
  return t("memory.days_ago", { days: Math.floor(hours / 24) });
13172
13272
  }
13273
+ /** Local calendar date as YYYY-MM-DD; avoids the UTC off-by-one of toISOString. */
13274
+ function formatDate(ts) {
13275
+ const date = new Date(ts);
13276
+ const month = String(date.getMonth() + 1).padStart(2, "0");
13277
+ const day = String(date.getDate()).padStart(2, "0");
13278
+ return `${date.getFullYear()}-${month}-${day}`;
13279
+ }
13173
13280
  function singleLine$1(text) {
13174
13281
  return text.replaceAll(/\s+/g, " ").trim();
13175
13282
  }
@@ -13227,6 +13334,12 @@ var MemoryPickerComponent = class extends Container {
13227
13334
  searchQuery = "";
13228
13335
  isSearching = false;
13229
13336
  searchInput = "";
13337
+ /** Draft text for the in-place edit of the latest distillation. */
13338
+ editBuffer = "";
13339
+ /** Set when the last save failed; keeps the draft on screen for a retry. */
13340
+ editError = false;
13341
+ /** Guards against a second Enter while the update is in flight. */
13342
+ saving = false;
13230
13343
  /** Memo ids ticked for batch delete (space toggles, a selects all). */
13231
13344
  selectedIds = /* @__PURE__ */ new Set();
13232
13345
  memos;
@@ -13287,6 +13400,29 @@ var MemoryPickerComponent = class extends Container {
13287
13400
  if (ch.length > 0) this.searchInput += ch;
13288
13401
  return;
13289
13402
  }
13403
+ if (this.mode === "editDistilled") {
13404
+ if (matchesKey(data, Key.escape)) {
13405
+ this.editBuffer = "";
13406
+ this.mode = "detail";
13407
+ this.ui?.requestRender();
13408
+ return;
13409
+ }
13410
+ if (matchesKey(data, Key.enter)) {
13411
+ if (!this.saving) this.saveDistilled();
13412
+ return;
13413
+ }
13414
+ if (matchesKey(data, Key.backspace) || matchesKey(data, Key.delete)) {
13415
+ this.editBuffer = Array.from(this.editBuffer).slice(0, -1).join("");
13416
+ this.ui?.requestRender();
13417
+ return;
13418
+ }
13419
+ const typed = printableChar(data);
13420
+ if (typed.length > 0) {
13421
+ if (Array.from(this.editBuffer).length < MAX_DISTILLED_CHARS) this.editBuffer += typed;
13422
+ this.ui?.requestRender();
13423
+ }
13424
+ return;
13425
+ }
13290
13426
  if (this.mode === "confirmDelete") {
13291
13427
  if (matchesKey(data, Key.enter)) {
13292
13428
  if (this.selectedIds.size > 0) this.deleteSelectedAndReload();
@@ -13308,6 +13444,14 @@ var MemoryPickerComponent = class extends Container {
13308
13444
  this.detailMemo = null;
13309
13445
  return;
13310
13446
  }
13447
+ const key = printableChar(data);
13448
+ if ((key === "e" || key === "E") && this.detailMemo !== null) {
13449
+ this.editBuffer = this.detailMemo.distilled ?? "";
13450
+ this.editError = false;
13451
+ this.mode = "editDistilled";
13452
+ this.ui?.requestRender();
13453
+ return;
13454
+ }
13311
13455
  }
13312
13456
  if (matchesKey(data, Key.escape)) {
13313
13457
  if (this.searchQuery.length > 0) {
@@ -13376,6 +13520,52 @@ var MemoryPickerComponent = class extends Container {
13376
13520
  if (this.detailMemo) this.mode = "detail";
13377
13521
  this.ui?.requestRender();
13378
13522
  }
13523
+ /**
13524
+ * Persist the edited distillation. A non-empty text becomes a user-authored
13525
+ * rule; clearing it drops back to a plain extracted memo.
13526
+ */
13527
+ async saveDistilled() {
13528
+ const memo = this.detailMemo;
13529
+ if (memo === null) {
13530
+ this.mode = "list";
13531
+ return;
13532
+ }
13533
+ this.saving = true;
13534
+ const text = this.editBuffer.trim();
13535
+ if (text === (memo.distilled ?? "").trim()) {
13536
+ this.saving = false;
13537
+ this.editBuffer = "";
13538
+ this.mode = "detail";
13539
+ this.ui?.requestRender();
13540
+ return;
13541
+ }
13542
+ try {
13543
+ await this.store.update(memo.id, {
13544
+ distilled: text.length > 0 ? text : void 0,
13545
+ distilledFrom: text.length > 0 ? memo.distilledFrom : void 0,
13546
+ origin: text.length > 0 ? "user" : "session",
13547
+ distilledAt: text.length > 0 ? Date.now() : void 0
13548
+ });
13549
+ } catch {
13550
+ this.editError = true;
13551
+ this.saving = false;
13552
+ this.ui?.requestRender();
13553
+ return;
13554
+ }
13555
+ this.saving = false;
13556
+ this.editError = false;
13557
+ this.editBuffer = "";
13558
+ await this.loadMemos();
13559
+ const index = this.memos.findIndex((item) => item.id === memo.id);
13560
+ if (index < 0) {
13561
+ this.detailMemo = null;
13562
+ this.mode = "list";
13563
+ this.ui?.requestRender();
13564
+ return;
13565
+ }
13566
+ this.selectedIndex = index;
13567
+ await this.showDetail();
13568
+ }
13379
13569
  async deleteAndReload(id) {
13380
13570
  try {
13381
13571
  await this.store.delete(id);
@@ -13396,8 +13586,7 @@ var MemoryPickerComponent = class extends Container {
13396
13586
  }
13397
13587
  render(width) {
13398
13588
  const c = this.colors;
13399
- const lines = [];
13400
- lines.push(chalk.hex(c.primary)("─".repeat(width)));
13589
+ const lines = [chalk.hex(c.primary)("─".repeat(width))];
13401
13590
  if (this.isSearching) {
13402
13591
  const prompt = t("memory.search_label");
13403
13592
  const input = this.searchInput.length > 0 ? this.searchInput : "█";
@@ -13426,15 +13615,18 @@ var MemoryPickerComponent = class extends Container {
13426
13615
  return lines;
13427
13616
  }
13428
13617
  if (this.mode === "detail" && this.detailMemo) return this.renderDetail(lines, width, c);
13618
+ if (this.mode === "editDistilled" && this.detailMemo) return this.renderEditDistilled(lines, width, c);
13429
13619
  if (this.mode === "confirmDelete") {
13430
13620
  if (this.selectedIds.size > 0) {
13431
13621
  lines.push(truncateToWidth(chalk.hex(c.warning).bold(t("memory.batch_delete_confirm", { count: String(this.selectedIds.size) })), width, ELLIPSIS$1));
13432
13622
  lines.push(chalk.hex(c.warning)(t("memory.batch_delete_hint")));
13623
+ if (this.memos.some((memo) => this.selectedIds.has(memo.id) && memo.origin !== void 0 && memo.origin !== "session")) lines.push(chalk.hex(c.warning)(truncateToWidth(` ${t("memory.delete_rule_warning")}`, width, ELLIPSIS$1)));
13433
13624
  } else {
13434
13625
  const memo = this.memos[this.selectedIndex];
13435
13626
  if (memo) {
13436
13627
  lines.push(truncateToWidth(chalk.hex(c.warning).bold(` ${t("memory.deleting")}${memo.userNeed}`), width, ELLIPSIS$1));
13437
13628
  lines.push(chalk.hex(c.warning)(t("memory.delete_confirm_hint")));
13629
+ if (memo.origin !== void 0 && memo.origin !== "session") lines.push(chalk.hex(c.warning)(truncateToWidth(` ${t("memory.delete_rule_warning")}`, width, ELLIPSIS$1)));
13438
13630
  }
13439
13631
  }
13440
13632
  lines.push(chalk.hex(c.primary)("─".repeat(width)));
@@ -13468,7 +13660,10 @@ var MemoryPickerComponent = class extends Container {
13468
13660
  const indentWidth = visibleWidth(indent);
13469
13661
  const titleColor = isSelected ? c.primary : c.text;
13470
13662
  const titleStyle = isSelected ? chalk.hex(titleColor).bold : chalk.hex(titleColor);
13471
- const trailingParts = [formatRelativeTime$1(memo.recordedAt), sourceLabel(memo.extractionSource)].filter((p) => p.length > 0);
13663
+ const sources = memo.distilledFrom ?? [];
13664
+ const isRule = memo.origin !== void 0 && memo.origin !== "session";
13665
+ const trailingParts = [formatRelativeTime$1(isRule ? memo.distilledAt ?? memo.recordedAt : memo.recordedAt), isRule ? memo.origin === "user" ? t("memory.origin_user") : t("memory.origin_dream") : sourceLabel(memo.extractionSource)].filter((p) => p.length > 0);
13666
+ if (isRule && sources.length > 0) trailingParts.push(t("memory.source_count", { count: String(sources.length) }));
13472
13667
  const trailingText = trailingParts.length > 0 ? " " + trailingParts.join(" ") : "";
13473
13668
  const trailingWidth = visibleWidth(trailingText);
13474
13669
  const headerPrefixWidth = visibleWidth(pointer) + 1 + visibleWidth(check) + 1;
@@ -13494,17 +13689,22 @@ var MemoryPickerComponent = class extends Container {
13494
13689
  const approachPreview = t("memory.plan_label") + singleLine$1(memo.approach);
13495
13690
  card.push(indent + chalk.hex(c.textDim)(truncateToWidth(approachPreview, Math.max(8, width - indentWidth), ELLIPSIS$1)));
13496
13691
  }
13692
+ if (memo.distilled !== void 0 && memo.distilled.length > 0) {
13693
+ const distilledPreview = t("memory.distilled_label") + singleLine$1(memo.distilled);
13694
+ card.push(indent + chalk.hex(c.success ?? c.primary)(truncateToWidth(distilledPreview, Math.max(8, width - indentWidth), ELLIPSIS$1)));
13695
+ }
13497
13696
  return card;
13498
13697
  }
13499
13698
  renderDetail(lines, width, c) {
13500
13699
  const memo = this.detailMemo;
13501
- const time = new Date(memo.recordedAt).toLocaleString("zh-CN");
13700
+ const time = formatDate(memo.recordedAt);
13502
13701
  const sessionLabel = memo.sourceSessionTitle && memo.sourceSessionTitle.length > 0 ? `${memo.sourceSessionTitle} (${memo.sourceSessionId.slice(0, 12)})` : memo.sourceSessionId.slice(0, 12);
13503
13702
  const indent = " ";
13504
13703
  const contentWidth = Math.max(8, width - visibleWidth(indent));
13505
13704
  lines.push(truncateToWidth(chalk.hex(c.primary).bold(`${indent}${t("memory.requirement_label")}${memo.userNeed}`), width, ELLIPSIS$1));
13506
13705
  lines.push("");
13507
- lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${t("memory.result_label")}${memo.outcome} ${t("memory.source_label")}${sourceLabel(memo.extractionSource)} ${time}`, width, ELLIPSIS$1)));
13706
+ const sourceName = memo.origin !== void 0 && memo.origin !== "session" ? memo.origin === "user" ? t("memory.origin_user") : t("memory.origin_dream") : sourceLabel(memo.extractionSource);
13707
+ lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${t("memory.result_label")}${memo.outcome} ${t("memory.source_label")}${sourceName} ${time}`, width, ELLIPSIS$1)));
13508
13708
  lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${t("memory.session_label")}${sessionLabel}`, width, ELLIPSIS$1)));
13509
13709
  const project = formatProject(memo);
13510
13710
  const tags = formatTags(memo);
@@ -13539,6 +13739,27 @@ var MemoryPickerComponent = class extends Container {
13539
13739
  }
13540
13740
  lines.push("");
13541
13741
  }
13742
+ if (memo.distilled !== void 0 && memo.distilled.length > 0) {
13743
+ const label = t("memory.distilled_label");
13744
+ const wrapped = wrapText(memo.distilled, contentWidth - visibleWidth(label));
13745
+ for (let i = 0; i < wrapped.length; i++) {
13746
+ const prefix = i === 0 ? `${indent}${label}` : indent + " ".repeat(visibleWidth(label));
13747
+ lines.push(truncateToWidth(chalk.hex(c.success ?? c.primary)(prefix + wrapped[i]), width, ELLIPSIS$1));
13748
+ }
13749
+ lines.push("");
13750
+ }
13751
+ if (memo.origin !== void 0 && memo.origin !== "session") {
13752
+ const distilledAtLabel = memo.distilledAt !== void 0 ? `${t("memory.distilled_at")} ${formatDate(memo.distilledAt)}` : t("memory.distilled_at_unknown");
13753
+ lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${distilledAtLabel}`, width, ELLIPSIS$1)));
13754
+ const sources = memo.distilledFrom ?? [];
13755
+ lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${sources.length > 0 ? t("memory.source_count", { count: String(sources.length) }) : t("memory.source_unknown")}`, width, ELLIPSIS$1)));
13756
+ for (const source of sources.slice(0, 5)) {
13757
+ const date = formatDate(source.at);
13758
+ lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}- ${date} · ${singleLine$1(source.title)}`, width, ELLIPSIS$1)));
13759
+ }
13760
+ if (sources.length > 5) lines.push(chalk.hex(c.textDim)(truncateToWidth(`${indent}${t("memory.source_more", { count: String(sources.length - 5) })}`, width, ELLIPSIS$1)));
13761
+ lines.push("");
13762
+ }
13542
13763
  if (memo.note !== void 0 && memo.note.length > 0) {
13543
13764
  const label = t("memory.note_label");
13544
13765
  const wrapped = wrapText(memo.note, contentWidth - visibleWidth(label));
@@ -13552,6 +13773,20 @@ var MemoryPickerComponent = class extends Container {
13552
13773
  lines.push(chalk.hex(c.primary)("─".repeat(width)));
13553
13774
  return lines;
13554
13775
  }
13776
+ renderEditDistilled(lines, width, c) {
13777
+ const memo = this.detailMemo;
13778
+ const indent = " ";
13779
+ lines.push(truncateToWidth(chalk.hex(c.primary).bold(`${indent}${t("memory.requirement_label")}${memo.userNeed}`), width, ELLIPSIS$1));
13780
+ lines.push("");
13781
+ lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${t("memory.distilled_label")}`, width, ELLIPSIS$1)));
13782
+ const contentWidth = Math.max(8, width - 4);
13783
+ for (const line of wrapText(`${this.editBuffer}█`, contentWidth)) lines.push(chalk.hex(c.success ?? c.primary)(`${indent}${line}`));
13784
+ lines.push("");
13785
+ if (this.editError) lines.push(chalk.hex(c.error)(truncateToWidth(`${indent}${t("memory.edit_save_failed")}`, width, ELLIPSIS$1)));
13786
+ lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${t("memory.edit_distilled_hint")}`, width, ELLIPSIS$1)));
13787
+ lines.push(chalk.hex(c.primary)("─".repeat(width)));
13788
+ return lines;
13789
+ }
13555
13790
  };
13556
13791
  //#endregion
13557
13792
  //#region src/tui/utils/session-label.ts
@@ -15321,7 +15556,7 @@ var ScreamTUI = class {
15321
15556
  const card = new OnboardingCardComponent({
15322
15557
  colors: this.state.theme.colors,
15323
15558
  onConfigure: () => {
15324
- import("./dispatch-zsFzFFoY.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
15559
+ import("./dispatch-JYsyD0aN.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
15325
15560
  this.restoreEditor();
15326
15561
  onDone?.();
15327
15562
  });