scream-code 0.16.6 → 0.16.7

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 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";
7
+ import { $n as normalizeWorkDir, $t as lerpGradient, A as highlightLines, An as getKnowledgeStore, At as isTurnElapsedEnabled, B as ActivityGroupComponent, Bn as getDataDir, Bt as ENABLE_TERMINAL_THEME_REPORTING, Cn as getNoActiveSessionMessage, Ct as clearInfoPanelState, D as ToolCallComponent, Dn as isExperimentalFlagEnabled, Dt as createEditorTheme, E as UserMessageComponent, En as sortSlashCommands, Et as resolveThemeSync, F as StatusMessageComponent, Fn as TuiConfigParseError, Ft as detectTerminalTheme, G as AgentGroupComponent, Gn as CLI_UI_MODE, Gt as QUERY_TERMINAL_THEME, H as estimateTokens, Hn as getLogDir, Ht as OSC11_RESPONSE, I as replaceTabs, In as TuiLikePreferencesSchema, It as parseOsc11BackgroundTheme, J as getBreathingFrame, Jn as DEFAULT_CATALOG_URL, Jt as TERMINAL_THEME_DARK, K as WelcomeComponent, Kn as CLI_USER_AGENT_PRODUCT, Kt as TERMINAL_FOCUS_IN, L as sanitizeShellOutput, Ln as loadTuiConfig, Lt as DISABLE_TERMINAL_FOCUS_REPORTING, M as CachedContainer, Mn as startManualEmbeddingDownload, Mt as createThemeStyles, N as ThinkingComponent, Nn as PIXEL_PULSE_FRAMES, Nt as contrastTextHex, O as renderDiffLines, On as setExperimentalFlags, Ot as createMarkdownTheme, P as NoticeMessageComponent, Pn as PULSE_WAVE_FRAMES, Pt as getColorPalette, Q as refineGoal, Qn as encodeWorkDirKey, Qt as FooterComponent, R as SkillActivationComponent, Rn as saveTuiConfig, Rt as DISABLE_TERMINAL_THEME_REPORTING, S as readJsonlFile, Sn as getLlmNotSetMessage, St as supportsBalance, Tn as BUILTIN_SLASH_COMMANDS, U as getSharedSpeedTracker, Un as detectInstallSource, Ut as OSC11_RESPONSE_PREFIX, V as charsForTokenBudget, Vn as getInputHistoryFile, Vt as OSC11_QUERY, W as BackgroundAgentStatusComponent, Wn as CLI_COMMAND_NAME, Wt as OSC11_RESPONSE_PREFIX_NO_ESC, X as CompactionComponent, Xn as saveCatalogCache, Xt as isBusy, Y as resetBreathingClock, Yn as fetchCatalog, Yt as TERMINAL_THEME_LIGHT, Z as clearGoalState, Zn as ScreamHarness, Zt as isStreaming, _n as MAIN_AGENT_ID, a as syncDiagramCapabilityPrompt, an as printableChar, ar as MemoryMemoStore, at as toTerminalHyperlink, b as readUpdateCache, bn as getCtrlCHint, c as disposeChildren, cn as argsRecord, cr as resolveGlobalLogPath, d as isPlanExpandable, dn as parseStreamingArgs, dr as ErrorCodes, en as SubagentSlots, er as appendSessionIndexEntry, f as MoonLoader, fn as serializeToolResultOutput, fr as SCREAM_ERROR_INFO, ft as handleFusionPlanCommand, gn as EXIT_CONFIRM_WINDOW_MS, hn as EMPTY_SESSION_HINT_URL, i as buildRoleAdditionalText, ir as resolveScreamHome, it as isInternalMessage, j as langFromPath, jn as hasEmbeddingModelCache, jt as toggleEmptySessionHint, k as renderDiffLinesClustered, kn as embeddingFailureDetail, kt as isEmptySessionHintDismissed, l as hasDispose, ln as formatErrorMessage, lr as isScreamError, mn as truncateErrorMessage, mt as handlePlanCommand, n as clearEvalPanelState, nr as removeSessionIndexEntry, on as STATUS_BULLET, or as flushDiagnosticLogs, ot as changeThinkingLevel, p as formatMemoryMemoForInjection, pn as stringValue, q as BREATHE_CYCLE_MS, qn as PRODUCT_NAME, qt as TERMINAL_FOCUS_OUT, r as openUrl, sn as appendStreamingArgsPreview, sr as log, st as getModelCycleLevel, t as dispatchInput, tr as readSessionIndex, u as isExpandable, un as isTodoItemShape, ur as isOrphanedToolCallError, v as selectUpdateTarget, vn as SESSION_TIPS, w as getDaemonInstructions, wn as buildSkillSlashCommands, x as appendJsonlLine, xn as getCtrlDHint, xt as refreshProviderBalance, y as refreshUpdateCache, yn as TIP_ROTATION_INTERVAL_MS, z as AssistantMessageComponent, zn as detectShellEnvironment, zt as ENABLE_TERMINAL_FOCUS_REPORTING } from "./dispatch-Vw9ccFm7.mjs";
8
+ import { a as setLocale, i as getLocale, o as t } from "./text-input-dialog-D9ByqSsK.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.6"),
35
+ version: optionalBuildString("0.16.7"),
36
36
  channel: optionalBuildString(""),
37
37
  commit: optionalBuildString(""),
38
38
  buildTarget: optionalBuildString("darwin-arm64")
@@ -2363,15 +2363,15 @@ function formatStepDebugTiming(input) {
2363
2363
  const latency = input.llmFirstTokenLatencyMs;
2364
2364
  const streamMs = input.llmStreamDurationMs;
2365
2365
  if (latency === void 0 || streamMs === void 0) return void 0;
2366
- const parts = [`TTFT: ${formatDuration$1(latency)}`];
2366
+ const parts = [`TTFT: ${formatDuration(latency)}`];
2367
2367
  const outputTokens = input.usage?.output;
2368
2368
  if (outputTokens !== void 0 && outputTokens > 0 && streamMs > 0) {
2369
2369
  const tps = (outputTokens / (streamMs / 1e3)).toFixed(1);
2370
- parts.push(`TPS: ${tps} tok/s (${outputTokens} tokens in ${formatDuration$1(streamMs)})`);
2370
+ parts.push(`TPS: ${tps} tok/s (${outputTokens} tokens in ${formatDuration(streamMs)})`);
2371
2371
  }
2372
2372
  return `[Debug] ${parts.join(" | ")}`;
2373
2373
  }
2374
- function formatDuration$1(ms) {
2374
+ function formatDuration(ms) {
2375
2375
  if (ms < 1e3) return `${Math.round(ms)}ms`;
2376
2376
  return `${(ms / 1e3).toFixed(1)}s`;
2377
2377
  }
@@ -4907,6 +4907,7 @@ var StreamingUIController = class StreamingUIController {
4907
4907
  this._assistantDraft = "";
4908
4908
  this._shownAssistantLength = 0;
4909
4909
  this.clearFlushTimerIfIdle();
4910
+ this._streamingBlock?.component.setStreaming(false);
4910
4911
  this._streamingBlock = null;
4911
4912
  this._thinkingDraft = "";
4912
4913
  this.lastDeltaAt = void 0;
@@ -5010,6 +5011,7 @@ var StreamingUIController = class StreamingUIController {
5010
5011
  const block = this._streamingBlock;
5011
5012
  if (block !== null) {
5012
5013
  block.entry.content = fullText;
5014
+ block.component.setStreaming(true);
5013
5015
  block.component.updateContent(fullText);
5014
5016
  this.host.state.ui.requestRender();
5015
5017
  }
@@ -5019,6 +5021,7 @@ var StreamingUIController = class StreamingUIController {
5019
5021
  const block = this._streamingBlock;
5020
5022
  this._streamingBlock = null;
5021
5023
  if (block !== null) {
5024
+ block.component.setStreaming(false);
5022
5025
  this.host.transcriptController.unmarkPending(block.component);
5023
5026
  this.host.transcriptController.commit();
5024
5027
  }
@@ -6879,20 +6882,6 @@ const gitPanel = {
6879
6882
  };
6880
6883
  //#endregion
6881
6884
  //#region src/tui/components/sidebar/panels/session-panel.ts
6882
- function formatDuration(ms) {
6883
- const totalSeconds = Math.max(0, Math.floor(ms / 1e3));
6884
- if (getLocale() === "zh") {
6885
- if (totalSeconds < 60) return `${totalSeconds}秒`;
6886
- const hours = Math.floor(totalSeconds / 3600);
6887
- const minutes = Math.floor(totalSeconds % 3600 / 60);
6888
- return hours > 0 ? `${hours}小时${minutes}分` : `${minutes}分钟`;
6889
- }
6890
- if (totalSeconds < 60) return `${totalSeconds}s`;
6891
- const hours = Math.floor(totalSeconds / 3600);
6892
- const minutes = Math.floor(totalSeconds % 3600 / 60);
6893
- if (hours > 0) return `${hours}h ${minutes}m`;
6894
- return `${minutes}m`;
6895
- }
6896
6885
  /** 1,234 → "1,234" via the platform locale grouping. */
6897
6886
  function formatCount$1(n) {
6898
6887
  return n.toLocaleString("en-US");
@@ -6918,8 +6907,8 @@ function gridFits(rows, width) {
6918
6907
  /**
6919
6908
  * Session summary, live-rendered each frame as a strict two-column table:
6920
6909
  * one field per row, labels sharing a left column, values sharing a right
6921
- * edge. Uptime derives from `startedAt` so it ticks between data updates;
6922
- * every other value comes from the snapshot in getData().
6910
+ * edge. Every value comes from the snapshot in getData(); nothing here is
6911
+ * derived from wall-clock time.
6923
6912
  */
6924
6913
  var SessionPanelContent = class {
6925
6914
  ctx;
@@ -6934,7 +6923,6 @@ var SessionPanelContent = class {
6934
6923
  if (stats === void 0) return [chalk.hex(this.colors.textDim)(t("sidebar.session_empty"))];
6935
6924
  const label = (s) => chalk.hex(this.colors.textDim)(s);
6936
6925
  const value = (s) => chalk.hex(this.colors.text)(s);
6937
- const now = Date.now();
6938
6926
  const tokenRows = [
6939
6927
  [t("sidebar.tokens"), stats.tokensTotal],
6940
6928
  [t("sidebar.tokens_cache_hit"), stats.tokensInputCacheHit],
@@ -6942,7 +6930,6 @@ var SessionPanelContent = class {
6942
6930
  [t("sidebar.tokens_output"), stats.tokensOutput]
6943
6931
  ];
6944
6932
  const countRows = [
6945
- [t("sidebar.uptime"), formatDuration(now - stats.startedAt)],
6946
6933
  [t("sidebar.turns"), formatCount$1(stats.turns)],
6947
6934
  [t("sidebar.tools"), formatCount$1(stats.toolCalls)],
6948
6935
  [t("sidebar.calls"), formatCount$1(stats.apiCalls)],
@@ -6989,8 +6976,8 @@ function statusText(status) {
6989
6976
  }
6990
6977
  }
6991
6978
  /**
6992
- * Fixed 8-slot subagent panel: one row per default subagent type (plus any
6993
- * custom types appended in spawn order). Live-rendered each frame from the
6979
+ * Fixed default-slot subagent panel: one row per default subagent type (plus
6980
+ * any custom types appended in spawn order). Live-rendered each frame from the
6994
6981
  * current snapshot; idle slots stay greyed out.
6995
6982
  */
6996
6983
  var AgentsPanelContent = class {
@@ -7013,7 +7000,7 @@ var AgentsPanelContent = class {
7013
7000
  renderSlot(cell, slot, index, width) {
7014
7001
  const dim = (s) => chalk.hex(this.colors.textDim)(s);
7015
7002
  const idle = slot.status === "idle";
7016
- const phase = (Date.now() % AGENT_GRADIENT_CYCLE_MS / AGENT_GRADIENT_CYCLE_MS + index * .125) % 1;
7003
+ const phase = (Date.now() % AGENT_GRADIENT_CYCLE_MS / AGENT_GRADIENT_CYCLE_MS + index * .6180339887498949) % 1;
7017
7004
  const active = slot.status === "working" || slot.status === "outputting" ? chalk.hex(lerpGradient(phase)).bold : void 0;
7018
7005
  const marker = padLabel((active ?? chalk.hex(this.statusColor(slot.status)))(idle ? "○" : "●"), 2);
7019
7006
  const label = (idle ? dim : chalk.hex(this.colors.textStrong))(cell.label);
@@ -7273,23 +7260,38 @@ function toneFor(site, stale) {
7273
7260
  /**
7274
7261
  * Merge the measured provider reading with the probe samples into Hub rows.
7275
7262
  * The provider row always leads (it answers "is the model I'm using reachable"),
7276
- * and it staleness-checks against its own budget: a long idle stretch means
7277
- * nothing was asked, not that the network died.
7263
+ * it staleness-checks against its own budget: a long idle stretch means nothing
7264
+ * was asked, not that the network died — and it is coloured on its own ladder
7265
+ * (see {@link hubModelTone}), not on the probe bar.
7278
7266
  */
7279
7267
  function buildHubSamples(snapshot, provider, now = Date.now()) {
7280
7268
  const ageMs = provider.sampledAt === void 0 ? 0 : now - provider.sampledAt;
7281
7269
  return [{
7282
7270
  id: HUB_MODEL_ROW_ID,
7283
7271
  ms: provider.ms,
7284
- tone: hubLatencyTone(provider.ms, ageMs >= HUB_MODEL_STALE_MS)
7272
+ tone: hubModelTone(provider.ms, ageMs >= HUB_MODEL_STALE_MS)
7285
7273
  }, ...snapshot.samples];
7286
7274
  }
7287
- /** Shared tone rule for every Hub row (probe sites and the measured provider). */
7275
+ /** Tone rule for the probed endpoint rows: green ≤{@link HUB_GOOD_MS}, amber above. */
7288
7276
  function hubLatencyTone(ms, stale = false) {
7289
7277
  if (ms === void 0 || stale) return "dim";
7290
7278
  return ms <= 100 ? "ok" : "warn";
7291
7279
  }
7292
7280
  /**
7281
+ * Tone rule for the measured provider row (model first token), deliberately
7282
+ * separate from the probe ladder: a first token and a HEAD request are not
7283
+ * comparable, and one shared bar would call every healthy answer slow. Green
7284
+ * ≤{@link HUB_MODEL_GOOD_MS}, amber up to {@link HUB_MODEL_SLOW_MS}, red past
7285
+ * it — that long is worth flagging, not merely dimming. `undefined` (nothing
7286
+ * measured yet, or a provider just switched) and a stale reading stay dim:
7287
+ * neither is an outage.
7288
+ */
7289
+ function hubModelTone(ms, stale = false) {
7290
+ if (ms === void 0 || stale) return "dim";
7291
+ if (ms <= 5e3) return "ok";
7292
+ return ms <= 15e3 ? "warn" : "down";
7293
+ }
7294
+ /**
7293
7295
  * Compact latency reading: whole milliseconds below a second, one-decimal
7294
7296
  * seconds above — a probe that never answers must never read as `65400ms`.
7295
7297
  */
@@ -7310,9 +7312,11 @@ const EMPTY_MARK = "--";
7310
7312
  *
7311
7313
  * Values share one right edge through the same two-column engine the Session
7312
7314
  * panel uses, so rows never jitter as numbers change width, and only the value
7313
- * carries colour (green ≤100ms, amber above it, red when a site stopped
7314
- * answering). The panel is a pure view: probing happens in the data provider,
7315
- * which is what keeps a slow endpoint from ever touching a frame.
7315
+ * carries colour. Two ladders, because two different things are timed: probe
7316
+ * rows are green ≤100ms, amber above it, red once a site stopped answering,
7317
+ * while the measured provider row (model first token) is green ≤5s, amber up to
7318
+ * 15s and red past that. The panel is a pure view: probing happens in the data
7319
+ * provider, which is what keeps a slow endpoint from ever touching a frame.
7316
7320
  */
7317
7321
  var HubPanelContent = class {
7318
7322
  ctx;
@@ -7924,10 +7928,6 @@ var LifecycleController = class LifecycleController {
7924
7928
  * frame asked, so a closed sidebar produces no traffic. */
7925
7929
  hubProbe = createHubProbe();
7926
7930
  sidebarGitCacheWorkDir = "";
7927
- /** Session start time for the sidebar uptime counter. Reset on session
7928
- * switch so a resumed old session doesn't show the TUI process age. */
7929
- sidebarSessionStartedAt = Date.now();
7930
- sidebarSessionId;
7931
7931
  /** Memo for the session-stats aggregation: recomputed only when the
7932
7932
  * transcript length, compaction count or usage object changes. */
7933
7933
  sidebarStatsMemo;
@@ -8195,10 +8195,6 @@ var LifecycleController = class LifecycleController {
8195
8195
  };
8196
8196
  }
8197
8197
  readSidebarSessionStats(appState) {
8198
- if (this.sidebarSessionId !== appState.sessionId) {
8199
- this.sidebarSessionId = appState.sessionId;
8200
- this.sidebarSessionStartedAt = Date.now();
8201
- }
8202
8198
  const entries = this.host.state.transcriptEntries;
8203
8199
  const usage = appState.sessionUsage;
8204
8200
  const subagentUsage = appState.subagentUsage;
@@ -8220,8 +8216,7 @@ var LifecycleController = class LifecycleController {
8220
8216
  tokensTotal: LifecycleController.sumTokens(usage) + subagentOutput,
8221
8217
  tokensInputCacheHit: usage.inputCacheRead,
8222
8218
  tokensInputCacheMiss: usage.inputOther + usage.inputCacheCreation,
8223
- tokensOutput: usage.output + subagentOutput,
8224
- startedAt: this.sidebarSessionStartedAt
8219
+ tokensOutput: usage.output + subagentOutput
8225
8220
  };
8226
8221
  this.sidebarStatsMemo = {
8227
8222
  length: entries.length,
@@ -8515,7 +8510,7 @@ var FileMentionProvider = class {
8515
8510
  const atPrefix = extractAtPrefix(textBeforeCursor);
8516
8511
  if (!options.force && atPrefix === null && textBeforeCursor.startsWith("/") && !textBeforeCursor.includes(" ")) {
8517
8512
  const query = textBeforeCursor.slice(1);
8518
- const filtered = fuzzyFilter(this.slashCommandItems, query, (item) => item.value).slice(0, MAX_SUGGESTIONS_WHEN_QUERY);
8513
+ const filtered = fuzzyFilter(this.slashCommandItems, query, (item) => !query.startsWith("skill:") && item.value.startsWith("skill:") ? item.value.slice(6) : item.value);
8519
8514
  if (filtered.length === 0) return null;
8520
8515
  return {
8521
8516
  items: filtered,
@@ -9467,7 +9462,7 @@ const SIDE_PADDING = 1;
9467
9462
  * Renders the stacked sidebar: every visible registered panel gets its own
9468
9463
  * full box frame (┌─┐│└┘) with the panel title embedded in the top frame, one
9469
9464
  * below the other. Panels render live each frame (their body reads the
9470
- * current data snapshot), so time-based values (uptime, goal wall-clock)
9465
+ * current data snapshot), so time-based values (goal wall-clock)
9471
9466
  * tick without rebuilding. The focused panel's title is bolded; focus is
9472
9467
  * moved by the SidebarManager (`/sidebar next/prev`, Ctrl+X toggles).
9473
9468
  * The whole frame is painted in the theme accent colour and the rendered
@@ -9481,9 +9476,10 @@ var SidebarContainer = class extends Container {
9481
9476
  * only rebuilt when the visible panel set changes. */
9482
9477
  bodies = /* @__PURE__ */ new Map();
9483
9478
  builtStackKey = "";
9484
- /** 1s heartbeat while the sidebar is open: keeps live values (uptime,
9485
- * goal wall-clock, gradient phase of busy agent slots) ticking without
9486
- * needing an event. Stopped when the sidebar closes. */
9479
+ /** 1s heartbeat for the whole container lifetime: keeps live values (goal
9480
+ * wall-clock, gradient phase of busy agent slots) ticking while the sidebar
9481
+ * is open — the tick is a no-op when it is closed, and only `dispose()`
9482
+ * (TUI teardown) clears the interval. */
9487
9483
  heartbeat;
9488
9484
  constructor(manager, requestRender, colors) {
9489
9485
  super();
@@ -11150,6 +11146,7 @@ var SessionManager$1 = class {
11150
11146
  await this.unloadCurrentSession("switching session")?.close({ extractMemories: false });
11151
11147
  this.host.session = session;
11152
11148
  this.registerSessionHandlers(session);
11149
+ await syncDiagramCapabilityPrompt(session);
11153
11150
  }
11154
11151
  async syncRuntimeState(session = this.requireSession()) {
11155
11152
  const status = await session.getStatus();
@@ -14056,7 +14053,7 @@ var ScreamTUI = class {
14056
14053
  const card = new OnboardingCardComponent({
14057
14054
  colors: this.state.theme.colors,
14058
14055
  onConfigure: () => {
14059
- import("./dispatch-dDbUBD7J.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
14056
+ import("./dispatch-CIGyjBDa.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
14060
14057
  this.restoreEditor();
14061
14058
  onDone?.();
14062
14059
  });
@@ -16339,13 +16336,16 @@ async function ensureSessionsDir(homeDir) {
16339
16336
  const dir = getSessionsDir(homeDir);
16340
16337
  if (!existsSync(dir)) await mkdir(dir, { recursive: true });
16341
16338
  }
16342
- async function appendJournalLine(homeDir, sessionId, line) {
16339
+ async function appendJournalLine(homeDir, sessionId, line, throwOnError = false) {
16340
+ let writeError;
16343
16341
  await appendJournalLineSerialized(getJournalPath(homeDir, sessionId), line, (error) => {
16342
+ writeError = error;
16344
16343
  log.warn("web: failed to persist journal line", {
16345
16344
  sessionId,
16346
16345
  error: String(error)
16347
16346
  });
16348
16347
  });
16348
+ if (throwOnError && writeError !== void 0) throw writeError;
16349
16349
  }
16350
16350
  let metaWriteSeq = 0;
16351
16351
  async function saveMetadata(homeDir, meta) {
@@ -16430,16 +16430,22 @@ function isMessageBearingEntry(entry) {
16430
16430
  */
16431
16431
  function contextHistoryToChatMessages(history) {
16432
16432
  const out = [];
16433
+ let pendingAnchor = "other";
16433
16434
  for (const message of history) {
16434
16435
  if (message.role !== "user" && message.role !== "assistant") continue;
16435
16436
  if (isInternalMessage(message)) continue;
16436
- if (message.origin !== void 0 && message.origin.kind !== "user") continue;
16437
+ const anchor = classifyUndoAnchor(message.origin);
16438
+ if (message.role === "user") {
16439
+ pendingAnchor = anchor;
16440
+ if (anchor === "skill" || anchor === "other") continue;
16441
+ }
16437
16442
  const text = message.content.map((part) => part.type === "text" ? part.text : "").join("").trim();
16438
16443
  if (text.length === 0) continue;
16439
16444
  out.push({
16440
16445
  role: message.role,
16441
16446
  content: text,
16442
- tools: []
16447
+ tools: [],
16448
+ undoAnchor: message.role === "assistant" ? pendingAnchor : anchor
16443
16449
  });
16444
16450
  }
16445
16451
  return out;
@@ -16469,6 +16475,65 @@ function deriveTitle(messages) {
16469
16475
  const text = firstUser.content.trim();
16470
16476
  return text.length > 40 ? text.slice(0, 40) + "..." : text;
16471
16477
  }
16478
+ /**
16479
+ * Remove complete user-turn blocks from a rendered transcript. The core
16480
+ * context owns the authoritative undo operation; the web journal is append
16481
+ * only, so a durable undo marker asks this projection to hide the same turns
16482
+ * when snapshots are rebuilt (including after a restart).
16483
+ *
16484
+ * `beforeSeq` is an inclusive cursor captured before the core mutation. It
16485
+ * also covers a user message that reserved the next sequence but never
16486
+ * emitted a turn event.
16487
+ */
16488
+ function removeLastTurnsBefore(messages, count, beforeSeq) {
16489
+ if (count <= 0 || messages.length === 0) return;
16490
+ const firstFutureIndex = messages.findIndex((message) => typeof message.seq === "number" && message.seq > beforeSeq);
16491
+ const prefixLength = firstFutureIndex === -1 ? messages.length : firstFutureIndex;
16492
+ const prefix = messages.slice(0, prefixLength);
16493
+ const tail = messages.slice(prefixLength);
16494
+ let remaining = count;
16495
+ while (remaining > 0) {
16496
+ let anchor = -1;
16497
+ for (let i = prefix.length - 1; i >= 0; i -= 1) {
16498
+ const message = prefix[i];
16499
+ if (message?.role === "assistant" && message.undoAnchor === "skill") {
16500
+ anchor = i;
16501
+ while (anchor > 0) {
16502
+ const previous = prefix[anchor - 1];
16503
+ if (previous?.role !== "assistant" || previous.undoAnchor !== "skill") break;
16504
+ anchor -= 1;
16505
+ }
16506
+ break;
16507
+ }
16508
+ if (message?.role === "user") {
16509
+ anchor = i;
16510
+ break;
16511
+ }
16512
+ }
16513
+ if (anchor < 0) break;
16514
+ prefix.splice(anchor);
16515
+ remaining -= 1;
16516
+ }
16517
+ messages.splice(0, messages.length, ...prefix, ...tail);
16518
+ }
16519
+ function classifyUndoAnchor(origin) {
16520
+ if (origin === void 0 || origin === null) return "user";
16521
+ if (typeof origin !== "object") return "other";
16522
+ const kind = origin.kind;
16523
+ if (kind === "user") return "user";
16524
+ if (kind === "skill_activation" && origin.trigger === "user-slash") return "skill";
16525
+ return "other";
16526
+ }
16527
+ /** Match the core `/undo` anchor rule without importing agent-core internals. */
16528
+ function countUndoAnchors(history) {
16529
+ let count = 0;
16530
+ for (const message of history) {
16531
+ if (message.role !== "user") continue;
16532
+ const origin = message.origin;
16533
+ if (origin === void 0 || origin.kind === "user" || origin.kind === "skill_activation" && origin.trigger === "user-slash") count += 1;
16534
+ }
16535
+ return count;
16536
+ }
16472
16537
  const execFileAsync = promisify(execFile);
16473
16538
  /** Raw porcelain rows: repo-root-relative path + XY status. `getGitStatus`
16474
16539
  * enriches these into full `GitFileChange` records. */
@@ -16731,6 +16796,8 @@ var WebSession = class {
16731
16796
  heartbeatTimer = null;
16732
16797
  unsubscribe = null;
16733
16798
  busy = false;
16799
+ /** Prevent two concurrent REST `/undo` calls from mutating core history together. */
16800
+ undoInFlight = false;
16734
16801
  /**
16735
16802
  * In-flight turn count across all agents (main + subagents). Used by the
16736
16803
  * idle-exit watchdog: a single boolean is not enough because a subagent's
@@ -16819,13 +16886,13 @@ var WebSession = class {
16819
16886
  });
16820
16887
  return entries;
16821
16888
  }
16822
- async persistEntry(entry) {
16889
+ async persistEntry(entry, throwOnError = false) {
16823
16890
  if (!this.homeDir) return;
16824
16891
  const line = JSON.stringify({
16825
16892
  type: "journal",
16826
16893
  ...entry
16827
16894
  });
16828
- await appendJournalLine(this.homeDir, this.sessionId, line);
16895
+ await appendJournalLine(this.homeDir, this.sessionId, line, throwOnError);
16829
16896
  }
16830
16897
  async persistUserMessage(text, beforeSeq, clientMessageId) {
16831
16898
  this.userMessages.push({
@@ -16882,6 +16949,9 @@ var WebSession = class {
16882
16949
  * journal holds no message records.
16883
16950
  */
16884
16951
  seededMessages = [];
16952
+ /** Undo markers already reflected by a core-history seed must not be applied twice. */
16953
+ seedCoveredUndoMarkers = /* @__PURE__ */ new Set();
16954
+ historySeeded = false;
16885
16955
  /**
16886
16956
  * Mark the journal untrustworthy (byte-interleaving damage, see
16887
16957
  * journal-writer.ts).
@@ -16923,7 +16993,10 @@ var WebSession = class {
16923
16993
  * monotonic with live events and consistent with the getMessagesOlder cursor.
16924
16994
  */
16925
16995
  seedHistory(messages) {
16926
- if (messages.length === 0 || this.seededMessages.length > 0) return;
16996
+ if (this.historySeeded) return;
16997
+ this.historySeeded = true;
16998
+ for (const entry of this.journal) if (entry.payload.type === "web.history.undone") this.seedCoveredUndoMarkers.add(entry.seq);
16999
+ if (messages.length === 0) return;
16927
17000
  let seq = 1;
16928
17001
  for (const message of messages) {
16929
17002
  this.seededMessages.push({
@@ -16970,7 +17043,7 @@ var WebSession = class {
16970
17043
  * clients. Used for server-owned reconstruction material (finalized
16971
17044
  * assistant snapshots) that clients never consume as live events.
16972
17045
  */
16973
- appendDurableSilent(payload) {
17046
+ appendDurableSilent(payload, throwOnPersistenceError = false) {
16974
17047
  const entry = {
16975
17048
  seq: this.nextSeq++,
16976
17049
  epoch: this.epoch,
@@ -16978,8 +17051,12 @@ var WebSession = class {
16978
17051
  payload
16979
17052
  };
16980
17053
  this.journal.push(entry);
16981
- this.trackPending(this.persistEntry(entry));
16982
- return entry;
17054
+ const persisted = this.persistEntry(entry, throwOnPersistenceError);
17055
+ this.trackPending(persisted);
17056
+ return {
17057
+ entry,
17058
+ persisted
17059
+ };
16983
17060
  }
16984
17061
  subscribeEvents() {
16985
17062
  if (!this.session) return;
@@ -17308,13 +17385,14 @@ var WebSession = class {
17308
17385
  });
17309
17386
  }
17310
17387
  async handleCommand(ws, command, args, pendingMsgId) {
17311
- const ok = (message) => {
17388
+ const ok = (message, extra) => {
17312
17389
  this.broadcast({
17313
17390
  type: "command_result",
17314
17391
  command,
17315
17392
  ok: true,
17316
17393
  message,
17317
- pendingMsgId
17394
+ pendingMsgId,
17395
+ ...extra
17318
17396
  }, false);
17319
17397
  };
17320
17398
  const fail = (message) => {
@@ -17350,9 +17428,10 @@ var WebSession = class {
17350
17428
  }
17351
17429
  return;
17352
17430
  case "auto":
17431
+ case "ask":
17353
17432
  case "yes":
17354
17433
  case "bot": {
17355
- const mode = command === "auto" ? "auto" : command === "bot" ? "bot" : "yolo";
17434
+ const mode = command === "auto" ? "auto" : command === "ask" ? "ask" : command === "bot" ? "bot" : "yolo";
17356
17435
  try {
17357
17436
  await this.session.setPermission(mode);
17358
17437
  this.permission = mode;
@@ -17395,7 +17474,7 @@ var WebSession = class {
17395
17474
  fail("Fork 失败:无法复制当前会话。");
17396
17475
  return;
17397
17476
  }
17398
- ok(`会话已 fork,新会话 ID:${result.sessionId}`);
17477
+ ok(`会话已 fork,新会话 ID:${result.sessionId}`, { sessionId: result.sessionId });
17399
17478
  } catch (error) {
17400
17479
  fail(`Fork 失败:${errMsg(error)}`);
17401
17480
  }
@@ -17506,6 +17585,7 @@ var WebSession = class {
17506
17585
  planMode: false,
17507
17586
  wolfpackMode: false,
17508
17587
  rlmEnabled: false,
17588
+ rlmMaxDepth: null,
17509
17589
  contextTokens: 0,
17510
17590
  maxContextTokens: 0,
17511
17591
  contextUsage: 0
@@ -17682,6 +17762,64 @@ var WebSession = class {
17682
17762
  if (!this.session) throw new HttpError(409, "Session is archived (read-only)", ErrorCodes.SESSION_CLOSED);
17683
17763
  return this.session;
17684
17764
  }
17765
+ /**
17766
+ * Undo user turns in the core session and append a durable web projection
17767
+ * marker. The core RPC deliberately has no UI knowledge, so without the
17768
+ * marker the old append-only web journal would resurrect the removed turns
17769
+ * on the next snapshot or after a restart.
17770
+ */
17771
+ async undoHistory(count) {
17772
+ if (!Number.isSafeInteger(count) || count < 1) throw new HttpError(400, "Undo count must be a positive integer.");
17773
+ if (this.busy) throw new HttpError(409, "Session is busy", ErrorCodes.TURN_AGENT_BUSY);
17774
+ const session = this.requireLiveSession();
17775
+ if (this.undoInFlight) throw new HttpError(409, "Undo already in progress");
17776
+ this.undoInFlight = true;
17777
+ try {
17778
+ const beforeSeq = this.nextSeq;
17779
+ let beforeAnchors = null;
17780
+ try {
17781
+ beforeAnchors = countUndoAnchors((await session.getContext()).history);
17782
+ } catch (error) {
17783
+ log.warn("web: failed to read context before undo", {
17784
+ sessionId: this.sessionId,
17785
+ error: String(error)
17786
+ });
17787
+ }
17788
+ await session.undoHistory(count);
17789
+ let removed = count;
17790
+ if (beforeAnchors !== null) try {
17791
+ const afterAnchors = countUndoAnchors((await session.getContext()).history);
17792
+ removed = Math.max(0, beforeAnchors - afterAnchors);
17793
+ } catch (error) {
17794
+ log.warn("web: failed to read context after undo", {
17795
+ sessionId: this.sessionId,
17796
+ error: String(error)
17797
+ });
17798
+ }
17799
+ if (removed <= 0) {
17800
+ await this.refreshStatus();
17801
+ this.broadcastStatus();
17802
+ return;
17803
+ }
17804
+ const { entry, persisted } = this.appendDurableSilent({
17805
+ type: "web.history.undone",
17806
+ count: removed,
17807
+ beforeSeq
17808
+ }, true);
17809
+ await persisted;
17810
+ this.broadcast({
17811
+ type: "event",
17812
+ seq: entry.seq,
17813
+ epoch: entry.epoch,
17814
+ payload: entry.payload
17815
+ }, false);
17816
+ this.trackPending(this.updateTitle());
17817
+ await this.refreshStatus();
17818
+ this.broadcastStatus();
17819
+ } finally {
17820
+ this.undoInFlight = false;
17821
+ }
17822
+ }
17685
17823
  requireGoalSession(allowBusy = false) {
17686
17824
  if (!this.session) throw new HttpError(409, "Session is archived (read-only)", ErrorCodes.SESSION_CLOSED);
17687
17825
  if (!allowBusy && this.busy) throw new HttpError(409, "Session is busy", ErrorCodes.TURN_AGENT_BUSY);
@@ -17759,6 +17897,7 @@ var WebSession = class {
17759
17897
  role: "assistant",
17760
17898
  content: "",
17761
17899
  tools: [],
17900
+ undoAnchor: classifyUndoAnchor(event.origin),
17762
17901
  seq: entry.seq,
17763
17902
  model: this.cachedStatus?.model,
17764
17903
  turnStats: {
@@ -17812,13 +17951,20 @@ var WebSession = class {
17812
17951
  break;
17813
17952
  case "web.message.finalized": {
17814
17953
  const finalized = event.message;
17815
- finalized.seq = messages.length > 0 && messages.at(-1)?.role === "assistant" ? messages.at(-1).seq : entry.seq;
17954
+ const lastSkeletonSeq = messages.length > 0 && messages.at(-1)?.role === "assistant" ? messages.at(-1).seq : entry.seq;
17955
+ if (finalized.undoAnchor === void 0 && messages.at(-1)?.role === "assistant") finalized.undoAnchor = messages.at(-1).undoAnchor;
17956
+ finalized.seq = lastSkeletonSeq;
17816
17957
  if (messages.length > 0 && messages.at(-1)?.role === "assistant") messages[messages.length - 1] = finalized;
17817
17958
  else messages.push(finalized);
17818
17959
  currentAssistant = null;
17819
17960
  turnFinalized = true;
17820
17961
  break;
17821
17962
  }
17963
+ case "web.history.undone":
17964
+ if (!this.seedCoveredUndoMarkers.has(entry.seq)) removeLastTurnsBefore(messages, event.count, event.beforeSeq);
17965
+ currentAssistant = null;
17966
+ turnFinalized = false;
17967
+ break;
17822
17968
  case "turn.ended":
17823
17969
  if (event.reason === "failed" && currentAssistant) currentAssistant.isError = true;
17824
17970
  if (currentAssistant && !turnFinalized && currentAssistant.content === "") currentAssistant.degraded = true;
@@ -17830,7 +17976,10 @@ var WebSession = class {
17830
17976
  }
17831
17977
  for (let i = userIndex; i < this.userMessages.length; i++) {
17832
17978
  const item = this.userMessages[i];
17833
- if (item !== void 0) messages.push(item.msg);
17979
+ if (item !== void 0) messages.push({
17980
+ ...item.msg,
17981
+ seq: item.beforeSeq
17982
+ });
17834
17983
  }
17835
17984
  return messages;
17836
17985
  }
@@ -18427,7 +18576,9 @@ var SessionManager = class {
18427
18576
  await ws.switchRlm(enabled, maxDepth);
18428
18577
  }
18429
18578
  async undoHistory(sessionId, count) {
18430
- await this.getLiveSession(sessionId).undoHistory(count);
18579
+ const ws = this.sessions.get(sessionId);
18580
+ if (!ws) throw new HttpError(404, "Session not found", ErrorCodes.SESSION_NOT_FOUND);
18581
+ await ws.undoHistory(count);
18431
18582
  }
18432
18583
  async compact(sessionId, instruction) {
18433
18584
  await this.getLiveSession(sessionId).compact(instruction ? { instruction } : {});
@@ -18597,16 +18748,32 @@ async function handleSessionDataRoutes(req, res, url, method, resolveSession, wo
18597
18748
  }
18598
18749
  return true;
18599
18750
  }
18600
- if (url === `${API_PREFIX}/git/status` && method === "GET") {
18601
- const gs = await getGitStatus(workDir);
18602
- res.writeHead(200, { "Content-Type": "application/json" });
18603
- res.end(JSON.stringify(gs));
18604
- return true;
18605
- }
18606
- if (url.startsWith(`${API_PREFIX}/git/diff?`) && method === "GET") {
18607
- const relPath = new URLSearchParams(url.split("?")[1] ?? "").get("path") ?? "";
18751
+ const gitPath = url.split("?")[0];
18752
+ const isGitStatusRoute = gitPath === `${API_PREFIX}/git/status` && method === "GET";
18753
+ if (isGitStatusRoute || gitPath === `${API_PREFIX}/git/diff` && method === "GET") {
18754
+ const gitQuery = new URLSearchParams(url.split("?")[1] ?? "");
18755
+ const requestedGitSessionId = gitQuery.get("sessionId");
18756
+ let gitWorkDir = workDir;
18757
+ if (requestedGitSessionId !== null) {
18758
+ const gitSession = resolveSession(requestedGitSessionId);
18759
+ if (!gitSession) {
18760
+ sendJson(res, 404, {
18761
+ code: ErrorCodes.SESSION_NOT_FOUND,
18762
+ message: "Session not found"
18763
+ });
18764
+ return true;
18765
+ }
18766
+ gitWorkDir = gitSession.workDir;
18767
+ }
18768
+ if (isGitStatusRoute) {
18769
+ const gs = await getGitStatus(gitWorkDir);
18770
+ res.writeHead(200, { "Content-Type": "application/json" });
18771
+ res.end(JSON.stringify(gs));
18772
+ return true;
18773
+ }
18774
+ const relPath = gitQuery.get("path") ?? "";
18608
18775
  try {
18609
- const result = await getGitFileDiff(workDir, relPath);
18776
+ const result = await getGitFileDiff(gitWorkDir, relPath);
18610
18777
  res.writeHead(200, { "Content-Type": "application/json" });
18611
18778
  res.end(JSON.stringify({
18612
18779
  path: relPath,
@@ -18817,6 +18984,7 @@ async function handleSessionControlRoutes(req, res, url, method, manager) {
18817
18984
  return false;
18818
18985
  }
18819
18986
  async function handleResourceRoutes(req, res, url, method, manager) {
18987
+ const routePath = url.split("?")[0] ?? url;
18820
18988
  const skillsMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/skills$`).exec(url);
18821
18989
  if (skillsMatch && method === "GET") {
18822
18990
  try {
@@ -19004,7 +19172,7 @@ async function handleResourceRoutes(req, res, url, method, manager) {
19004
19172
  }
19005
19173
  return true;
19006
19174
  }
19007
- const tasksMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/tasks$`).exec(url);
19175
+ const tasksMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/tasks$`).exec(routePath);
19008
19176
  if (tasksMatch && method === "GET") {
19009
19177
  try {
19010
19178
  const query = new URLSearchParams(url.split("?")[1] ?? "");
@@ -19016,7 +19184,7 @@ async function handleResourceRoutes(req, res, url, method, manager) {
19016
19184
  }
19017
19185
  return true;
19018
19186
  }
19019
- const taskOutputMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/tasks/([^/]+)/output$`).exec(url);
19187
+ const taskOutputMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/tasks/([^/]+)/output$`).exec(routePath);
19020
19188
  if (taskOutputMatch && method === "GET") {
19021
19189
  try {
19022
19190
  const query = new URLSearchParams(url.split("?")[1] ?? "");
@@ -19027,7 +19195,7 @@ async function handleResourceRoutes(req, res, url, method, manager) {
19027
19195
  }
19028
19196
  return true;
19029
19197
  }
19030
- const taskStopMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/tasks/([^/]+)/stop$`).exec(url);
19198
+ const taskStopMatch = new RegExp(`^${API_PREFIX}/sessions/([^/]+)/tasks/([^/]+)/stop$`).exec(routePath);
19031
19199
  if (taskStopMatch && method === "POST") {
19032
19200
  try {
19033
19201
  const body = await readJsonBody(req);