scream-code 0.14.5 → 0.14.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 { $t as isTodoItemShape, A as renderDiffLinesClustered, An as CLI_COMMAND_NAME, At as ENABLE_TERMINAL_FOCUS_REPORTING, B as BackgroundAgentStatusComponent, Bn as MemoryMemoStore, Bt as TERMINAL_THEME_LIGHT, Cn as loadTuiConfig, Ct as createThemeStyles, D as toggleEmptySessionHint, Dn as getInputHistoryFile, Dt as parseOsc11BackgroundTheme, E as isTurnElapsedEnabled, En as getDataDir, Et as detectTerminalTheme, F as estimateTokens, Fn as fetchCatalog, Ft as OSC11_RESPONSE_PREFIX_NO_ESC, G as getBreathingFrame, Gn as isOrphanedToolCallError, H as AgentGroupComponent, Hn as log, Ht as isStreaming, I as getSharedSpeedTracker, In as saveCatalogCache, It as QUERY_TERMINAL_THEME, J as refineGoal, Jt as printableChar, K as resetBreathingClock, Kn as ErrorCodes, L as SkillActivationComponent, Ln as ScreamHarness, Lt as TERMINAL_FOCUS_IN, M as langFromPath, Mn as CLI_USER_AGENT_PRODUCT, Mt as OSC11_QUERY, N as CachedContainer, Nn as PRODUCT_NAME, Nt as OSC11_RESPONSE, O as ToolCallComponent, On as getLogDir, Ot as DISABLE_TERMINAL_FOCUS_REPORTING, P as ThinkingComponent, Pn as DEFAULT_CATALOG_URL, Pt as OSC11_RESPONSE_PREFIX, Qt as formatErrorMessage, R as ReadGroupComponent, Rt as TERMINAL_FOCUS_OUT, S as getDaemonInstructions, Sn as TuiLikePreferencesSchema, St as createMarkdownTheme, T as isEmptySessionHintDismissed, Tn as detectShellEnvironment, Tt as getColorPalette, U as WelcomeComponent, Un as resolveGlobalLogPath, Ut as FooterComponent, V as AssistantMessageComponent, Vn as flushDiagnosticLogs, Vt as isBusy, W as BREATHE_CYCLE_MS, Wn as isScreamError, Xt as appendStreamingArgsPreview, Yt as STATUS_BULLET, Zt as argsRecord, _ as refreshUpdateCache, _n as isExperimentalFlagEnabled, _t as clearInfoPanelState, an as EMPTY_SESSION_HINT_URL, b as readJsonlFile, bn as PULSE_WAVE_FRAMES, bt as resolveThemeSync, c as hasDispose, cn as SESSION_TIPS, ct as handlePlanCommand, d as formatMemoryMemoForInjection, dn as getCtrlDHint, en as parseStreamingArgs, et as toTerminalHyperlink, fn as getLlmNotSetMessage, g as selectUpdateTarget, gn as sortSlashCommands, gt as supportsBalance, hn as BUILTIN_SLASH_COMMANDS, ht as refreshProviderBalance, i as buildRoleAdditionalText, in as CHARS_PER_TOKEN, j as highlightLines, jn as CLI_UI_MODE, jt as ENABLE_TERMINAL_THEME_REPORTING, k as renderDiffLines, kn as detectInstallSource, kt as DISABLE_TERMINAL_THEME_REPORTING, l as isPlanExpandable, ln as TIP_ROTATION_INTERVAL_MS, mn as buildSkillSlashCommands, n as clearEvalPanelState, nn as stringValue, nt as getModelCycleLevel, on as EXIT_CONFIRM_WINDOW_MS, ot as handleFusionPlanCommand, pn as getNoActiveSessionMessage, q as clearGoalState, qn as SCREAM_ERROR_INFO, r as openUrl, rn as truncateErrorMessage, s as disposeChildren, sn as MAIN_AGENT_ID, t as dispatchInput, tn as serializeToolResultOutput, tt as changeThinkingLevel, u as MoonLoader, un as getCtrlCHint, v as readUpdateCache, vn as setExperimentalFlags, w as UserMessageComponent, wn as saveTuiConfig, wt as contrastTextHex, xn as TuiConfigParseError, xt as createEditorTheme, y as appendJsonlLine, yn as PIXEL_PULSE_FRAMES, z as parseReadGroupOutput, zn as resolveScreamHome, zt as TERMINAL_THEME_DARK } from "./dispatch-BSLRpoBA.mjs";
8
- import { a as setLocale, o as t } from "./text-input-dialog-Ciit7nAM.mjs";
7
+ import { $t as isTodoItemShape, A as renderDiffLinesClustered, An as CLI_COMMAND_NAME, At as ENABLE_TERMINAL_FOCUS_REPORTING, B as BackgroundAgentStatusComponent, Bn as MemoryMemoStore, Bt as TERMINAL_THEME_LIGHT, Cn as loadTuiConfig, Ct as createThemeStyles, D as toggleEmptySessionHint, Dn as getInputHistoryFile, Dt as parseOsc11BackgroundTheme, E as isTurnElapsedEnabled, En as getDataDir, Et as detectTerminalTheme, F as estimateTokens, Fn as fetchCatalog, Ft as OSC11_RESPONSE_PREFIX_NO_ESC, G as getBreathingFrame, Gn as isOrphanedToolCallError, H as AgentGroupComponent, Hn as log, Ht as isStreaming, I as getSharedSpeedTracker, In as saveCatalogCache, It as QUERY_TERMINAL_THEME, J as refineGoal, Jt as printableChar, K as resetBreathingClock, Kn as ErrorCodes, L as SkillActivationComponent, Ln as ScreamHarness, Lt as TERMINAL_FOCUS_IN, M as langFromPath, Mn as CLI_USER_AGENT_PRODUCT, Mt as OSC11_QUERY, N as CachedContainer, Nn as PRODUCT_NAME, Nt as OSC11_RESPONSE, O as ToolCallComponent, On as getLogDir, Ot as DISABLE_TERMINAL_FOCUS_REPORTING, P as ThinkingComponent, Pn as DEFAULT_CATALOG_URL, Pt as OSC11_RESPONSE_PREFIX, Qt as formatErrorMessage, R as ReadGroupComponent, Rt as TERMINAL_FOCUS_OUT, S as getDaemonInstructions, Sn as TuiLikePreferencesSchema, St as createMarkdownTheme, T as isEmptySessionHintDismissed, Tn as detectShellEnvironment, Tt as getColorPalette, U as WelcomeComponent, Un as resolveGlobalLogPath, Ut as FooterComponent, V as AssistantMessageComponent, Vn as flushDiagnosticLogs, Vt as isBusy, W as BREATHE_CYCLE_MS, Wn as isScreamError, Xt as appendStreamingArgsPreview, Yt as STATUS_BULLET, Zt as argsRecord, _ as refreshUpdateCache, _n as isExperimentalFlagEnabled, _t as clearInfoPanelState, an as EMPTY_SESSION_HINT_URL, b as readJsonlFile, bn as PULSE_WAVE_FRAMES, bt as resolveThemeSync, c as hasDispose, cn as SESSION_TIPS, ct as handlePlanCommand, d as formatMemoryMemoForInjection, dn as getCtrlDHint, en as parseStreamingArgs, et as toTerminalHyperlink, fn as getLlmNotSetMessage, g as selectUpdateTarget, gn as sortSlashCommands, gt as supportsBalance, hn as BUILTIN_SLASH_COMMANDS, ht as refreshProviderBalance, i as buildRoleAdditionalText, in as CHARS_PER_TOKEN, j as highlightLines, jn as CLI_UI_MODE, jt as ENABLE_TERMINAL_THEME_REPORTING, k as renderDiffLines, kn as detectInstallSource, kt as DISABLE_TERMINAL_THEME_REPORTING, l as isPlanExpandable, ln as TIP_ROTATION_INTERVAL_MS, mn as buildSkillSlashCommands, n as clearEvalPanelState, nn as stringValue, nt as getModelCycleLevel, on as EXIT_CONFIRM_WINDOW_MS, ot as handleFusionPlanCommand, pn as getNoActiveSessionMessage, q as clearGoalState, qn as SCREAM_ERROR_INFO, r as openUrl, rn as truncateErrorMessage, s as disposeChildren, sn as MAIN_AGENT_ID, t as dispatchInput, tn as serializeToolResultOutput, tt as changeThinkingLevel, u as MoonLoader, un as getCtrlCHint, v as readUpdateCache, vn as setExperimentalFlags, w as UserMessageComponent, wn as saveTuiConfig, wt as contrastTextHex, xn as TuiConfigParseError, xt as createEditorTheme, y as appendJsonlLine, yn as PIXEL_PULSE_FRAMES, z as parseReadGroupOutput, zn as resolveScreamHome, zt as TERMINAL_THEME_DARK } from "./dispatch-MWaAkj2l.mjs";
8
+ import { a as setLocale, o as t } from "./text-input-dialog-B0yHChBj.mjs";
9
9
  import { createRequire } from "node:module";
10
10
  import { randomUUID } from "node:crypto";
11
11
  import { access, mkdir, mkdtemp, readFile, readdir, realpath, rm, unlink, writeFile } from "node:fs/promises";
@@ -23,14 +23,14 @@ import process$1 from "node:process";
23
23
  import { Command, Option } from "commander";
24
24
  import { createInterface } from "node:readline/promises";
25
25
  import chalk, { chalkStderr } from "chalk";
26
- import { CombinedAutocompleteProvider, Container, Editor, HStack, Input, Key, ProcessTerminal, ScrollView, Spacer, Text, TuiAltScreen, VStack, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, isKeyRelease, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@liutod-scream/pi-tui";
26
+ import { CombinedAutocompleteProvider, Container, Editor, Input, Key, ProcessTerminal, ScrollView, Spacer, Text, TuiAltScreen, VStack, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, isKeyRelease, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@liutod-scream/pi-tui";
27
27
  import { createInterface as createInterface$1 } from "node:readline";
28
28
  //#region src/cli/build-info.ts
29
29
  function optionalBuildString(value) {
30
30
  return typeof value === "string" && value.length > 0 ? value : void 0;
31
31
  }
32
32
  const SCREAM_BUILD_INFO = {
33
- version: optionalBuildString("0.14.5"),
33
+ version: optionalBuildString("0.14.7"),
34
34
  channel: optionalBuildString(""),
35
35
  commit: optionalBuildString(""),
36
36
  buildTarget: optionalBuildString("darwin-arm64")
@@ -2542,7 +2542,13 @@ var SessionEventHandler = class {
2542
2542
  handleStepRetrying(event) {
2543
2543
  this.host.streamingUI.resetLiveText();
2544
2544
  this.host.streamingUI.resetToolUi();
2545
- this.host.setAppState({ reconnectAttempt: event.nextAttempt });
2545
+ this.host.setAppState({
2546
+ reconnectAttempt: event.nextAttempt,
2547
+ reconnectMaxAttempts: event.maxAttempts,
2548
+ reconnectDelayMs: event.delayMs,
2549
+ reconnectStatusCode: event.statusCode,
2550
+ reconnectErrorName: event.errorName
2551
+ });
2546
2552
  }
2547
2553
  maybeShowDebugTiming(event) {
2548
2554
  if (process.env["SCREAM_CODE_DEBUG"] !== "1") return;
@@ -2701,7 +2707,8 @@ var SessionEventHandler = class {
2701
2707
  output: serializeToolResultOutput(event.output),
2702
2708
  is_error: event.isError,
2703
2709
  synthetic: event.synthetic,
2704
- display: event.display
2710
+ display: event.display,
2711
+ message: event.message
2705
2712
  };
2706
2713
  streamingUI.completeToolResult(event.toolCallId, resultData);
2707
2714
  if (canTransitionTo(this.host.state.appState.streamingPhase, "waiting")) this.host.setAppState({ streamingPhase: "waiting" });
@@ -6153,6 +6160,30 @@ var GutterContainer = class extends Container {
6153
6160
  //#endregion
6154
6161
  //#region src/tui/components/panes/status-bar-pane.ts
6155
6162
  /**
6163
+ * Single-line row: pulse wave immediately followed by its label.
6164
+ *
6165
+ * HStack cannot produce this layout: pi-tui's Text.render pads every line to
6166
+ * the full row width, so each child's intrinsic width equals the full width
6167
+ * and HStack's shrink distribution pushes the label far to the right.
6168
+ * Composing the line manually from the loader's unpadded frame text keeps
6169
+ * the label snug against the wave, and it re-reads the frame on every render
6170
+ * so the wave keeps animating.
6171
+ */
6172
+ var PulseWaveLabelRow = class {
6173
+ wave;
6174
+ label;
6175
+ constructor(wave, label) {
6176
+ this.wave = wave;
6177
+ this.label = label;
6178
+ }
6179
+ render(width) {
6180
+ return [truncateToWidth(` ${this.wave.getFrameText()} ${this.label}`, Math.max(1, width))];
6181
+ }
6182
+ invalidate() {
6183
+ this.wave.invalidate();
6184
+ }
6185
+ };
6186
+ /**
6156
6187
  * A fixed one-line status bar shown directly above the input editor: it
6157
6188
  * displays the current work phase (idle / waiting / thinking / composing /
6158
6189
  * tool) with its spinner, pinned to the bottom of the terminal via the dock
@@ -6178,12 +6209,8 @@ var StatusBarPaneComponent = class extends Container {
6178
6209
  }
6179
6210
  if (options.mode === "waiting" || options.mode === "tool" || options.mode === "composing" || options.mode === "thinking") {
6180
6211
  if (options.pulseWave !== void 0) {
6181
- if (options.label.length > 0) {
6182
- const row = new HStack();
6183
- row.addChild(options.pulseWave);
6184
- row.addChild(new Text(` ${options.label}`, 1, 0));
6185
- this.addChild(row);
6186
- } else this.addChild(options.pulseWave);
6212
+ if (options.label.length > 0) this.addChild(new PulseWaveLabelRow(options.pulseWave, options.label));
6213
+ else this.addChild(options.pulseWave);
6187
6214
  return;
6188
6215
  }
6189
6216
  if (options.spinner !== void 0) {
@@ -6444,6 +6471,14 @@ var PulseWaveLoader = class extends Text {
6444
6471
  intervalId = null;
6445
6472
  ui;
6446
6473
  colorHex;
6474
+ /** Raw frame text without Text's full-width render padding, exposed so
6475
+ * callers can compose the wave inline with trailing content (e.g. the
6476
+ * reconnect label in the status bar). */
6477
+ frameText = "";
6478
+ /** The current frame's cells ("■ ⬝ ⬝"), unpadded. */
6479
+ getFrameText() {
6480
+ return this.frameText;
6481
+ }
6447
6482
  constructor(ui, colorHex) {
6448
6483
  super("", 1, 0);
6449
6484
  this.ui = ui;
@@ -6474,7 +6509,8 @@ var PulseWaveLoader = class extends Text {
6474
6509
  1,
6475
6510
  2
6476
6511
  ].map((idx) => this.renderCell(idx, step.active, step.forward));
6477
- this.setText(cells.join(" "));
6512
+ this.frameText = cells.join(" ");
6513
+ this.setText(this.frameText);
6478
6514
  this.ui.requestRender();
6479
6515
  }
6480
6516
  renderCell(index, active, forward) {
@@ -6498,6 +6534,7 @@ var LifecycleController = class LifecycleController {
6498
6534
  terminalFocusTrackingDispose;
6499
6535
  terminalThemeTrackingDispose;
6500
6536
  lastActivityMode;
6537
+ lastReconnectAttempt = 0;
6501
6538
  /** The active status-bar loader (PulseWaveLoader or MoonLoader). Owned by
6502
6539
  * this controller; stopped before replacement to avoid leaking timers. */
6503
6540
  statusBarLoader;
@@ -6740,8 +6777,11 @@ var LifecycleController = class LifecycleController {
6740
6777
  updateActivityPane() {
6741
6778
  const effectiveMode = this.resolveActivityPaneMode();
6742
6779
  this.syncTerminalProgress(this.shouldShowTerminalProgress(effectiveMode));
6743
- if (effectiveMode === this.lastActivityMode) return;
6780
+ const reconnectAttempt = this.host.state.appState.reconnectAttempt;
6781
+ const reconnectChanged = reconnectAttempt !== this.lastReconnectAttempt;
6782
+ if (effectiveMode === this.lastActivityMode && !reconnectChanged) return;
6744
6783
  this.lastActivityMode = effectiveMode;
6784
+ this.lastReconnectAttempt = reconnectAttempt;
6745
6785
  this.updateStatusBar(effectiveMode);
6746
6786
  const { state } = this.host;
6747
6787
  state.activityContainer.clear();
@@ -6786,7 +6826,7 @@ var LifecycleController = class LifecycleController {
6786
6826
  this.statusBarLoader = loader;
6787
6827
  state.statusBarContainer.addChild(new StatusBarPaneComponent({
6788
6828
  mode,
6789
- label: "",
6829
+ label: this.buildReconnectLabel(state),
6790
6830
  pulseWave: loader
6791
6831
  }));
6792
6832
  }
@@ -6807,6 +6847,25 @@ var LifecycleController = class LifecycleController {
6807
6847
  }
6808
6848
  state.ui.requestRender();
6809
6849
  }
6850
+ /** Builds the reconnect label shown next to the status-bar pulse wave
6851
+ * while a step retry is in flight, e.g. "重连中 3/10 · 限流 · 20s 后重试".
6852
+ * Returns an empty string when no retry is active. Static by design: the
6853
+ * delay shown is the value from the latest retry event, not a ticking
6854
+ * countdown. */
6855
+ buildReconnectLabel(state) {
6856
+ const a = state.appState;
6857
+ if (a.reconnectAttempt <= 0) return "";
6858
+ const reasonKey = a.reconnectStatusCode === 429 ? "status.reason_rate_limit" : a.reconnectStatusCode !== void 0 && a.reconnectStatusCode >= 500 ? "status.reason_server" : (a.reconnectErrorName ?? "").includes("Timeout") ? "status.reason_timeout" : "status.reason_connection";
6859
+ const reason = a.reconnectStatusCode !== void 0 ? `${t(reasonKey)} (${String(a.reconnectStatusCode)})` : a.reconnectErrorName !== void 0 && a.reconnectErrorName.length > 0 ? `${t(reasonKey)} (${a.reconnectErrorName})` : t(reasonKey);
6860
+ const delaySec = Math.max(1, Math.round((a.reconnectDelayMs ?? 0) / 1e3));
6861
+ const label = t("status.reconnecting_detail", {
6862
+ attempt: String(a.reconnectAttempt),
6863
+ max: String(a.reconnectMaxAttempts ?? 0),
6864
+ reason,
6865
+ delay: String(delaySec)
6866
+ });
6867
+ return chalk.hex(state.theme.colors.textDim)(label);
6868
+ }
6810
6869
  /** Force a status-bar refresh even when the mode is unchanged. Used after
6811
6870
  * session switches/clears: the transcript is emptied, so the empty-session
6812
6871
  * hint must (re)appear even though idle -> idle normally short-circuits. */
@@ -12189,7 +12248,7 @@ var ScreamTUI = class {
12189
12248
  const card = new OnboardingCardComponent({
12190
12249
  colors: this.state.theme.colors,
12191
12250
  onConfigure: () => {
12192
- import("./dispatch-DHFfsRy8.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
12251
+ import("./dispatch-CmY89ob7.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
12193
12252
  this.restoreEditor();
12194
12253
  });
12195
12254
  },
@@ -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 { Kt as handleConnectCommand } from "./dispatch-BSLRpoBA.mjs";
6
+ import { Kt as handleConnectCommand } from "./dispatch-MWaAkj2l.mjs";
7
7
  export { handleConnectCommand };
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } from "./chunk-D90kvbyJ.mjs";
7
7
  import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-BH9W5k24.mjs";
8
8
  import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
9
- import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-Ciit7nAM.mjs";
9
+ import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-B0yHChBj.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
12
12
  import * as fs$1 from "node:fs/promises";
@@ -64654,7 +64654,7 @@ function renderPrompt(template, vars) {
64654
64654
  }
64655
64655
  //#endregion
64656
64656
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/skill-tool.md
64657
- var skill_tool_default = "Invoke a registered skill from the current skill listing. BLOCKING REQUIREMENT: when a skill from the listing matches the user's request, you MUST call this tool (not free-form text). Do NOT call the same skill repeatedly inside one turn — recursive depth is capped at {{ MAX_SKILL_QUERY_DEPTH }}.\n\n## Currently model-invocable skills\n{{ AVAILABLE_SKILLS }}\nThis list is a construction-time snapshot; for the live catalog inspect the skill registry.";
64657
+ var skill_tool_default = "Invoke a registered skill from the current skill listing. BLOCKING REQUIREMENT: when a skill from the listing matches the user's request, you MUST call this tool (not free-form text). Do NOT call the same skill repeatedly inside one turn — recursive depth is capped at {{ MAX_SKILL_QUERY_DEPTH }}.\n\n## Currently model-invocable skills\n{{ AVAILABLE_SKILLS }}\nThis list is a construction-time snapshot; for the live catalog inspect the skill registry.\n\n## Matching guide\n\n- A skill matches when the user's request involves the scenarios/trigger conditions listed in the skill's \"When to use\" line.\n- Look for: (a) keywords in the user's request that match the skill's trigger phrases, (b) the task type or scenario described in the skill's \"When to use\", (c) similar requests the skill was designed for.\n- If a skill seems relevant, invoke it first — if it doesn't fit, you can always fall back to doing it yourself.";
64658
64658
  /** Max skills to list inline in the tool description; rest are summarized. */
64659
64659
  const MAX_INLINE_SKILLS = 15;
64660
64660
  /** Max description length per skill line. */
@@ -67623,7 +67623,7 @@ Analyze the conversation transcript and the user's explicit guidance, then produ
67623
67623
  The package must contain:
67624
67624
  - name: kebab-case skill name. Prefer the user's nameHint if it is valid kebab-case; otherwise derive a concise name from the purpose.
67625
67625
  - description: one sentence describing when to use this skill.
67626
- - when-to-use: **required** — 1-2 sentences describing the specific situations/trigger conditions that should make the agent use this skill (e.g. "when the user asks to convert a document to PDF", "when debugging a vitest snapshot failure"). This drives the skill listing's "When to use" line, which is how the model decides to invoke the skill. Never leave it empty.
67626
+ - when-to-use: **required** — 1-2 sentences describing the specific situations/trigger conditions that should make the agent use this skill. Must include: (a) concrete trigger phrases/keywords the user might say (e.g. "convert", "PDF", "debug", "snapshot"), (b) the specific scenario or task type (e.g. "document conversion", "test debugging"), (c) avoid vague terms like "when needed" or "when appropriate". Use "When the user [action/request]..." or "When [task type]..." format. Examples: "when the user asks to convert a document to PDF", "when debugging a vitest snapshot failure", "when the user requests to deploy a model gateway". This drives the skill listing's "When to use" line, which is how the model decides to invoke the skill. Never leave it empty.
67627
67627
  - content: the complete Markdown body of SKILL.md, following the MANDATORY structure below.
67628
67628
  - files: optional supporting files (e.g. scripts, data files) relative to the skill directory. Empty array if none.
67629
67629
 
@@ -67786,7 +67786,7 @@ function validateSkillPlan(plan, nameHint) {
67786
67786
  const normalizedHint = sanitizeSkillName(nameHint);
67787
67787
  if (plan.name !== normalizedHint) throw new ScreamError(ErrorCodes.REQUEST_INVALID, `Generated skill name "${plan.name}" does not match the user's requested name "${normalizedHint}". Please use the requested name.`);
67788
67788
  }
67789
- if (plan.whenToUse.trim().length === 0) throw new ScreamError(ErrorCodes.REQUEST_INVALID, "Generated skill plan is missing the required \"whenToUse\" field. Describe the specific situations that should trigger this skill (it powers the skill listing's \"When to use\" line).");
67789
+ if (plan.whenToUse.trim().length === 0) throw new ScreamError(ErrorCodes.REQUEST_INVALID, "Generated skill plan is missing the required \"whenToUse\" field. Describe the specific situations that should trigger this skill with concrete trigger conditions (e.g. \"when the user asks to convert a document to PDF\") — it powers the skill listing's \"When to use\" line.");
67790
67790
  let parsedSkill;
67791
67791
  try {
67792
67792
  parsedSkill = parseSkillText({
@@ -68459,11 +68459,16 @@ var EditTool = class {
68459
68459
  await this.jian.writeText(safePath, materializeModelText(newContent, modelView.lineEndingStyle));
68460
68460
  scanCache.clear();
68461
68461
  const { notice, hasErrors } = await this.appendDiagnostics(safePath);
68462
- const output = `Replaced 1 occurrence in ${args.path}${notice}`;
68462
+ const output = `Replaced 1 occurrence in ${args.path}`;
68463
+ const message = notice.length > 0 ? notice : void 0;
68463
68464
  return hasErrors ? {
68464
68465
  isError: true,
68465
- output
68466
- } : { output };
68466
+ output,
68467
+ message
68468
+ } : {
68469
+ output,
68470
+ message
68471
+ };
68467
68472
  }
68468
68473
  const parts = content.split(args.old_string);
68469
68474
  const replacementCount = parts.length - 1;
@@ -68478,11 +68483,16 @@ var EditTool = class {
68478
68483
  await this.jian.writeText(safePath, materializeModelText(newContent, modelView.lineEndingStyle));
68479
68484
  scanCache.clear();
68480
68485
  const { notice, hasErrors } = await this.appendDiagnostics(safePath);
68481
- const output = `Replaced ${String(replacementCount)} occurrences in ${args.path}${notice}`;
68486
+ const output = `Replaced ${String(replacementCount)} occurrences in ${args.path}`;
68487
+ const message = notice.length > 0 ? notice : void 0;
68482
68488
  return hasErrors ? {
68483
68489
  isError: true,
68484
- output
68485
- } : { output };
68490
+ output,
68491
+ message
68492
+ } : {
68493
+ output,
68494
+ message
68495
+ };
68486
68496
  } catch (error) {
68487
68497
  if (error?.code === "EISDIR") return {
68488
68498
  isError: true,
@@ -74214,11 +74224,16 @@ var WriteTool = class {
74214
74224
  scanCache.clear();
74215
74225
  const bytesWritten = Buffer.byteLength(args.content, "utf8");
74216
74226
  const { notice, hasErrors } = await this.appendDiagnostics(safePath);
74217
- const output = `${mode === "append" ? "Appended" : "Wrote"} ${String(bytesWritten)} bytes to ${args.path}${notice}`;
74227
+ const output = `${mode === "append" ? "Appended" : "Wrote"} ${String(bytesWritten)} bytes to ${args.path}`;
74228
+ const message = notice.length > 0 ? notice : void 0;
74218
74229
  return hasErrors ? {
74219
74230
  isError: true,
74220
- output
74221
- } : { output };
74231
+ output,
74232
+ message
74233
+ } : {
74234
+ output,
74235
+ message
74236
+ };
74222
74237
  } catch (error) {
74223
74238
  if (error?.code === "ENOENT") return {
74224
74239
  isError: true,
@@ -97417,7 +97432,8 @@ function mapLoopEvent(event, turnId) {
97417
97432
  toolCallId: event.toolCallId,
97418
97433
  output: event.result.output,
97419
97434
  isError: event.result.isError,
97420
- display: event.result.isError === true ? void 0 : event.result.display
97435
+ display: event.result.isError === true ? void 0 : event.result.display,
97436
+ message: event.result.message
97421
97437
  };
97422
97438
  case "turn.interrupted":
97423
97439
  if (event.activeStep === void 0) return void 0;
@@ -127011,9 +127027,8 @@ function lerpGradient(t) {
127011
127027
  const b = Math.round(b0 + (b1 - b0) * localT);
127012
127028
  return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
127013
127029
  }
127014
- function buildStatusLine(streamingPhase, streamingStartTime, reconnectAttempt) {
127030
+ function buildStatusLine(streamingPhase, streamingStartTime) {
127015
127031
  if (streamingPhase === "idle") return t("status.idle");
127016
- if (reconnectAttempt > 0 && streamingPhase === "waiting") return chalk.hex("#E85454").bold("◎") + " " + chalk.hex("#E85454")(`${t("status.reconnecting")} ${String(reconnectAttempt)}`);
127017
127032
  let label;
127018
127033
  if (streamingPhase === "tool") label = t("status.tool");
127019
127034
  else if (streamingPhase === "waiting") label = t("status.waiting");
@@ -127191,7 +127206,7 @@ var FooterComponent = class {
127191
127206
  let rightText;
127192
127207
  if (this.transientHint) rightText = chalk.hex(colors.warning).bold(this.transientHint);
127193
127208
  else {
127194
- const statusLine = buildStatusLine(state.streamingPhase, state.streamingStartTime, state.reconnectAttempt);
127209
+ const statusLine = buildStatusLine(state.streamingPhase, state.streamingStartTime);
127195
127210
  const ccDot = state.ccConnectActive ? chalk.hex(colors.success)("●") : chalk.hex(colors.textDim)("●");
127196
127211
  const sessionUsage = state.sessionUsage ?? {
127197
127212
  inputOther: 0,
@@ -129510,7 +129525,7 @@ async function guidedGoalSetup(host) {
129510
129525
  host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
129511
129526
  return;
129512
129527
  }
129513
- const { TextInputDialogComponent } = await import("./text-input-dialog-BLM32jyJ.mjs");
129528
+ const { TextInputDialogComponent } = await import("./text-input-dialog-B9uwX1Uy.mjs");
129514
129529
  const initialDesc = await promptText(host, TextInputDialogComponent, {
129515
129530
  title: t("goal.setup_title_initial"),
129516
129531
  subtitle: t("goal.setup_desc_hint"),
@@ -129531,7 +129546,7 @@ async function guidedGoalSetup(host) {
129531
129546
  await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
129532
129547
  }
129533
129548
  async function showGoalConfigWizard(host, session, objective, replace) {
129534
- const { TextInputDialogComponent } = await import("./text-input-dialog-BLM32jyJ.mjs");
129549
+ const { TextInputDialogComponent } = await import("./text-input-dialog-B9uwX1Uy.mjs");
129535
129550
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
129536
129551
  title: t("goal.wizard_title", { objective }),
129537
129552
  subtitle: t("goal.budget_turns_hint"),
@@ -130900,7 +130915,7 @@ var ThinkingComponent = class {
130900
130915
  }
130901
130916
  const truncated = rendered.slice(0, 3);
130902
130917
  const remaining = contentLines.length - 2;
130903
- const hint = `... (${String(remaining)} more lines, ctrl+o to expand)`;
130918
+ const hint = t("thinking.more_lines", { count: String(remaining) });
130904
130919
  const hintWidth = Math.max(0, width - 2);
130905
130920
  truncated.push(" " + chalk.dim(truncateToWidth(hint, hintWidth, "…")));
130906
130921
  if (isRenderCacheEnabled()) {
@@ -131594,12 +131609,10 @@ var TruncatedOutputComponent = class {
131594
131609
  expanded;
131595
131610
  maxLines;
131596
131611
  hintFormatter;
131597
- collapseHintFormatter;
131598
131612
  constructor(output, options) {
131599
131613
  this.expanded = options.expanded;
131600
131614
  this.maxLines = options.maxLines ?? PREVIEW_LINES;
131601
131615
  this.hintFormatter = options.hintFormatter;
131602
- this.collapseHintFormatter = options.collapseHintFormatter;
131603
131616
  const tint = options.isError ? chalk.hex(options.colors.error) : chalk.dim;
131604
131617
  const cleaned = trimTrailingEmptyLines(output.split("\n")).join("\n");
131605
131618
  const stripped = options.isError ? cleaned.replaceAll(/\u001B\[[0-9;]*m/g, "") : cleaned;
@@ -131611,15 +131624,11 @@ var TruncatedOutputComponent = class {
131611
131624
  }
131612
131625
  render(width) {
131613
131626
  const contentLines = this.textComponent.render(width);
131614
- if (contentLines.length <= this.maxLines) return contentLines;
131615
- if (this.expanded) {
131616
- const collapseHint = this.collapseHintFormatter ? this.collapseHintFormatter() : t("shell.collapse_hint");
131617
- return [chalk.dim(collapseHint), ...contentLines];
131618
- }
131627
+ if (this.expanded || contentLines.length <= this.maxLines) return contentLines;
131619
131628
  const remaining = contentLines.length - this.maxLines;
131620
131629
  const tail = this.maxLines <= 0 ? [] : contentLines.slice(-this.maxLines);
131621
131630
  const expandHint = this.hintFormatter ? this.hintFormatter(remaining) : t("shell.more_lines", { count: String(remaining) });
131622
- return [chalk.dim(expandHint), ...tail];
131631
+ return [...new Text(chalk.dim(expandHint), 2, 0).render(width), ...tail];
131623
131632
  }
131624
131633
  };
131625
131634
  const renderTruncated = (_toolCall, result, ctx) => {
@@ -131655,8 +131664,7 @@ var ShellExecutionComponent = class extends Container {
131655
131664
  colors,
131656
131665
  maxLines: previewLines,
131657
131666
  maxBytes: MAX_SHELL_OUTPUT_BYTES,
131658
- hintFormatter: (remaining) => t("shell.more_lines", { count: String(remaining) }),
131659
- collapseHintFormatter: () => t("shell.collapse_hint")
131667
+ hintFormatter: (remaining) => t("shell.more_lines", { count: String(remaining) })
131660
131668
  }));
131661
131669
  }
131662
131670
  };
@@ -133495,6 +133503,7 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
133495
133503
  colors: this.colors
133496
133504
  });
133497
133505
  for (const component of components) this.addChild(component);
133506
+ if (result.message !== void 0 && result.message.length > 0) for (const line of result.message.split("\n")) this.addChild(new Text(chalk.dim(line), 2, 0));
133498
133507
  }
133499
133508
  /**
133500
133509
  * Render AskUserQuestion's JSON payload as a friendly Q/A list.
package/dist/main.mjs CHANGED
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
7
  //#region src/main.ts
8
8
  try {
9
- (await import("./app-4wRRcsTF.mjs")).main();
9
+ (await import("./app-D_n0flAk.mjs")).main();
10
10
  } catch (error) {
11
11
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
12
12
  process.exit(1);
@@ -126,7 +126,11 @@ const dictionaries = {
126
126
  "status.composing": "输出中",
127
127
  "status.tool": "执行中",
128
128
  "status.waiting": "等待中",
129
- "status.reconnecting": "重连中",
129
+ "status.reconnecting_detail": "重连中 {attempt}/{max} · {reason} · {delay}s 后重试",
130
+ "status.reason_rate_limit": "限流",
131
+ "status.reason_server": "服务器错误",
132
+ "status.reason_timeout": "网络超时",
133
+ "status.reason_connection": "连接问题",
130
134
  "approval.allow_once": "批准一次",
131
135
  "approval.allow_session": "批准(当前会话)",
132
136
  "approval.deny": "拒绝",
@@ -959,8 +963,8 @@ const dictionaries = {
959
963
  "cronmsg.missed": "{count} 次错过",
960
964
  "cronmsg.final_delivery": "最终投递",
961
965
  "thinking.in_progress": "思考中...",
962
- "shell.more_lines": " 还有 {count} 行,按 ctrl+o 展开",
963
- "shell.collapse_hint": " ctrl+o 折叠",
966
+ "thinking.more_lines": "...(还有 {count} 行,按 ctrl+o 展开)",
967
+ "shell.more_lines": "...(还有 {count} 行,按 ctrl+o 展开)",
964
968
  "ccconnect.note_botfather": "需先在 @BotFather 创建 bot",
965
969
  "ccconnect.note_napcat": "需要 NapCat/OneBot",
966
970
  "ccconnect.note_wecom": "需要公网 IP",
@@ -1224,7 +1228,11 @@ const dictionaries = {
1224
1228
  "status.composing": "Writing",
1225
1229
  "status.tool": "Running",
1226
1230
  "status.waiting": "Waiting",
1227
- "status.reconnecting": "Reconnecting",
1231
+ "status.reconnecting_detail": "Reconnecting {attempt}/{max} · {reason} · retry in {delay}s",
1232
+ "status.reason_rate_limit": "rate limited",
1233
+ "status.reason_server": "server error",
1234
+ "status.reason_timeout": "network timeout",
1235
+ "status.reason_connection": "connection issue",
1228
1236
  "approval.allow_once": "Allow Once",
1229
1237
  "approval.allow_session": "Allow Session",
1230
1238
  "approval.deny": "Deny",
@@ -2057,8 +2065,8 @@ const dictionaries = {
2057
2065
  "cronmsg.missed": "{count} missed",
2058
2066
  "cronmsg.final_delivery": "Final delivery",
2059
2067
  "thinking.in_progress": "Thinking...",
2060
- "shell.more_lines": " {count} lines hidden, press ctrl+o to expand",
2061
- "shell.collapse_hint": " Press ctrl+o to collapse",
2068
+ "thinking.more_lines": "... ({count} more lines, ctrl+o to expand)",
2069
+ "shell.more_lines": "... ({count} more lines, ctrl+o to expand)",
2062
2070
  "ccconnect.note_botfather": "Create a bot at @BotFather first",
2063
2071
  "ccconnect.note_napcat": "Requires NapCat/OneBot",
2064
2072
  "ccconnect.note_wecom": "Requires public IP",
@@ -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 { t as TextInputDialogComponent } from "./text-input-dialog-Ciit7nAM.mjs";
6
+ import { t as TextInputDialogComponent } from "./text-input-dialog-B0yHChBj.mjs";
7
7
  export { TextInputDialogComponent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.14.5",
3
+ "version": "0.14.7",
4
4
  "description": "A terminal-native AI agent for builders",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",