scream-code 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ 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 getInputHistoryFile, At as ENABLE_TERMINAL_FOCUS_REPORTING, B as BackgroundAgentStatusComponent, Bn as ScreamHarness, Bt as TERMINAL_THEME_LIGHT, Cn as PULSE_WAVE_FRAMES, Ct as createThemeStyles, D as toggleEmptySessionHint, Dn as saveTuiConfig, Dt as parseOsc11BackgroundTheme, E as isTurnElapsedEnabled, En as loadTuiConfig, Et as detectTerminalTheme, F as estimateTokens, Fn as CLI_USER_AGENT_PRODUCT, Ft as OSC11_RESPONSE_PREFIX_NO_ESC, G as getBreathingFrame, Gn as log, H as AgentGroupComponent, Hn as resolveScreamHome, Ht as isStreaming, I as getSharedSpeedTracker, In as PRODUCT_NAME, It as QUERY_TERMINAL_THEME, J as refineGoal, Jn as isOrphanedToolCallError, Jt as printableChar, K as resetBreathingClock, Kn as resolveGlobalLogPath, L as SkillActivationComponent, Ln as DEFAULT_CATALOG_URL, Lt as TERMINAL_FOCUS_IN, M as langFromPath, Mn as detectInstallSource, Mt as OSC11_QUERY, N as CachedContainer, Nn as CLI_COMMAND_NAME, Nt as OSC11_RESPONSE, O as ToolCallComponent, On as detectShellEnvironment, Ot as DISABLE_TERMINAL_FOCUS_REPORTING, P as ThinkingComponent, Pn as CLI_UI_MODE, Pt as OSC11_RESPONSE_PREFIX, Qt as formatErrorMessage, R as ReadGroupComponent, Rn as fetchCatalog, Rt as TERMINAL_FOCUS_OUT, S as getDaemonInstructions, Sn as PIXEL_PULSE_FRAMES, St as createMarkdownTheme, T as isEmptySessionHintDismissed, Tn as TuiLikePreferencesSchema, Tt as getColorPalette, U as WelcomeComponent, Un as MemoryMemoStore, Ut as FooterComponent, V as AssistantMessageComponent, Vt as isBusy, W as BREATHE_CYCLE_MS, Wn as flushDiagnosticLogs, Xn as SCREAM_ERROR_INFO, Xt as appendStreamingArgsPreview, Yn as ErrorCodes, 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 isEmbeddingModelCached, 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 getLogDir, jt as ENABLE_TERMINAL_THEME_REPORTING, k as renderDiffLines, kn as getDataDir, 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 isScreamError, 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 TuiConfigParseError, wt as contrastTextHex, xn as startManualEmbeddingDownload, xt as createEditorTheme, y as appendJsonlLine, yn as getKnowledgeStore, z as parseReadGroupOutput, zn as saveCatalogCache, zt as TERMINAL_THEME_DARK } from "./dispatch-D8ovLbmr.mjs";
7
+ import { $t as isTodoItemShape, A as renderDiffLinesClustered, An as getInputHistoryFile, At as ENABLE_TERMINAL_FOCUS_REPORTING, B as BackgroundAgentStatusComponent, Bn as ScreamHarness, Bt as TERMINAL_THEME_LIGHT, Cn as PULSE_WAVE_FRAMES, Ct as createThemeStyles, D as toggleEmptySessionHint, Dn as saveTuiConfig, Dt as parseOsc11BackgroundTheme, E as isTurnElapsedEnabled, En as loadTuiConfig, Et as detectTerminalTheme, F as estimateTokens, Fn as CLI_USER_AGENT_PRODUCT, Ft as OSC11_RESPONSE_PREFIX_NO_ESC, G as getBreathingFrame, Gn as log, H as AgentGroupComponent, Hn as resolveScreamHome, Ht as isStreaming, I as getSharedSpeedTracker, In as PRODUCT_NAME, It as QUERY_TERMINAL_THEME, J as refineGoal, Jn as isOrphanedToolCallError, Jt as printableChar, K as resetBreathingClock, Kn as resolveGlobalLogPath, L as SkillActivationComponent, Ln as DEFAULT_CATALOG_URL, Lt as TERMINAL_FOCUS_IN, M as langFromPath, Mn as detectInstallSource, Mt as OSC11_QUERY, N as CachedContainer, Nn as CLI_COMMAND_NAME, Nt as OSC11_RESPONSE, O as ToolCallComponent, On as detectShellEnvironment, Ot as DISABLE_TERMINAL_FOCUS_REPORTING, P as ThinkingComponent, Pn as CLI_UI_MODE, Pt as OSC11_RESPONSE_PREFIX, Qt as formatErrorMessage, R as ReadGroupComponent, Rn as fetchCatalog, Rt as TERMINAL_FOCUS_OUT, S as getDaemonInstructions, Sn as PIXEL_PULSE_FRAMES, St as createMarkdownTheme, T as isEmptySessionHintDismissed, Tn as TuiLikePreferencesSchema, Tt as getColorPalette, U as WelcomeComponent, Un as MemoryMemoStore, Ut as FooterComponent, V as AssistantMessageComponent, Vt as isBusy, W as BREATHE_CYCLE_MS, Wn as flushDiagnosticLogs, Xn as SCREAM_ERROR_INFO, Xt as appendStreamingArgsPreview, Yn as ErrorCodes, 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 isEmbeddingModelCached, 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 getLogDir, jt as ENABLE_TERMINAL_THEME_REPORTING, k as renderDiffLines, kn as getDataDir, 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 isScreamError, 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 TuiConfigParseError, wt as contrastTextHex, xn as startManualEmbeddingDownload, xt as createEditorTheme, y as appendJsonlLine, yn as getKnowledgeStore, z as parseReadGroupOutput, zn as saveCatalogCache, zt as TERMINAL_THEME_DARK } from "./dispatch-ATQMQq1p.mjs";
8
8
  import { a as setLocale, o as t } from "./text-input-dialog-BUXAKycx.mjs";
9
9
  import { createRequire } from "node:module";
10
10
  import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
@@ -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.15.0"),
35
+ version: optionalBuildString("0.15.1"),
36
36
  channel: optionalBuildString(""),
37
37
  commit: optionalBuildString(""),
38
38
  buildTarget: optionalBuildString("darwin-arm64")
@@ -12397,7 +12397,7 @@ var ScreamTUI = class {
12397
12397
  const card = new OnboardingCardComponent({
12398
12398
  colors: this.state.theme.colors,
12399
12399
  onConfigure: () => {
12400
- import("./dispatch-BPcn43Vw.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
12400
+ import("./dispatch-Doe-DlpL.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
12401
12401
  this.restoreEditor();
12402
12402
  onDone?.();
12403
12403
  });
@@ -14629,9 +14629,13 @@ async function appendJournalLine(homeDir, sessionId, line) {
14629
14629
  });
14630
14630
  }
14631
14631
  }
14632
+ let metaWriteSeq = 0;
14632
14633
  async function saveMetadata(homeDir, meta) {
14633
14634
  try {
14634
- await writeFile(getMetaPath(homeDir, meta.sessionId), JSON.stringify(meta, null, 2));
14635
+ const path = getMetaPath(homeDir, meta.sessionId);
14636
+ const tmp = `${path}.${process.pid}.${metaWriteSeq++}.tmp`;
14637
+ await writeFile(tmp, JSON.stringify(meta, null, 2));
14638
+ await rename(tmp, path);
14635
14639
  } catch (error) {
14636
14640
  log.warn("web: failed to save metadata", {
14637
14641
  sessionId: meta.sessionId,
@@ -15032,6 +15036,15 @@ var WebSession = class {
15032
15036
  });
15033
15037
  await appendJournalLine(this.homeDir, this.sessionId, line);
15034
15038
  }
15039
+ /** In-flight fire-and-forget persistence writes, drained by close(). */
15040
+ pendingWrites = /* @__PURE__ */ new Set();
15041
+ /** Register a background persistence promise so close() can await it. */
15042
+ trackPending(promise) {
15043
+ const tracked = promise.catch(() => {}).then(() => {
15044
+ this.pendingWrites.delete(tracked);
15045
+ });
15046
+ this.pendingWrites.add(tracked);
15047
+ }
15035
15048
  loadFromPersisted(entries) {
15036
15049
  for (const entry of entries) if ("type" in entry && entry.type === "user_message") {
15037
15050
  const um = entry;
@@ -15082,7 +15095,7 @@ var WebSession = class {
15082
15095
  payload
15083
15096
  };
15084
15097
  this.journal.push(entry);
15085
- if (!entry.volatile) this.persistEntry(entry);
15098
+ if (!entry.volatile) this.trackPending(this.persistEntry(entry));
15086
15099
  return entry;
15087
15100
  }
15088
15101
  /**
@@ -15098,7 +15111,7 @@ var WebSession = class {
15098
15111
  payload
15099
15112
  };
15100
15113
  this.journal.push(entry);
15101
- this.persistEntry(entry);
15114
+ this.trackPending(this.persistEntry(entry));
15102
15115
  return entry;
15103
15116
  }
15104
15117
  subscribeEvents() {
@@ -15148,7 +15161,7 @@ var WebSession = class {
15148
15161
  });
15149
15162
  }
15150
15163
  this.liveAssistant = null;
15151
- this.updateTitle();
15164
+ this.trackPending(this.updateTitle());
15152
15165
  } else if (event.type === "assistant.delta") {
15153
15166
  const live = this.liveAssistant;
15154
15167
  if (live) live.content += event.delta;
@@ -15336,7 +15349,7 @@ var WebSession = class {
15336
15349
  return;
15337
15350
  }
15338
15351
  this.busy = true;
15339
- this.persistUserMessage(text, this.nextSeq, clientMessageId);
15352
+ this.trackPending(this.persistUserMessage(text, this.nextSeq, clientMessageId));
15340
15353
  this.broadcast({
15341
15354
  type: "user_message",
15342
15355
  clientMessageId,
@@ -15910,6 +15923,7 @@ var WebSession = class {
15910
15923
  });
15911
15924
  this.pendingApprovals.clear();
15912
15925
  this.unsubscribe?.();
15926
+ await Promise.allSettled(this.pendingWrites);
15913
15927
  for (const [ws] of this.connections) ws.terminate();
15914
15928
  this.connections.clear();
15915
15929
  if (this.session) {
@@ -123049,24 +123049,28 @@ var ScreamCore = class {
123049
123049
  return this.config;
123050
123050
  }
123051
123051
  async setScreamConfig(input) {
123052
- const config = mergeConfigPatch(readConfigFile(this.configPath), input);
123053
- await writeConfigFile(this.configPath, config);
123054
- return this.config = loadRuntimeConfig(this.configPath);
123052
+ return this.enqueueConfigMutation(async () => {
123053
+ const config = mergeConfigPatch(readConfigFile(this.configPath), input);
123054
+ await writeConfigFile(this.configPath, config);
123055
+ return this.config = loadRuntimeConfig(this.configPath);
123056
+ });
123055
123057
  }
123056
123058
  async removeScreamProvider(input) {
123057
- const config = readConfigFile(this.configPath);
123058
- delete config.providers[input.providerId];
123059
- let removedDefault = false;
123060
- const existingModels = config.models ?? {};
123061
- for (const [key, model] of Object.entries(existingModels)) if (typeof model === "object" && model !== null && !Array.isArray(model) && model["provider"] === input.providerId) {
123062
- delete existingModels[key];
123063
- if (config.defaultModel === key) removedDefault = true;
123064
- }
123065
- config.models = existingModels;
123066
- if (removedDefault) config.defaultModel = void 0;
123067
- if (config.defaultProvider === input.providerId) config.defaultProvider = void 0;
123068
- await writeConfigFile(this.configPath, config);
123069
- return this.config = loadRuntimeConfig(this.configPath);
123059
+ return this.enqueueConfigMutation(async () => {
123060
+ const config = readConfigFile(this.configPath);
123061
+ delete config.providers[input.providerId];
123062
+ let removedDefault = false;
123063
+ const existingModels = config.models ?? {};
123064
+ for (const [key, model] of Object.entries(existingModels)) if (typeof model === "object" && model !== null && !Array.isArray(model) && model["provider"] === input.providerId) {
123065
+ delete existingModels[key];
123066
+ if (config.defaultModel === key) removedDefault = true;
123067
+ }
123068
+ config.models = existingModels;
123069
+ if (removedDefault) config.defaultModel = void 0;
123070
+ if (config.defaultProvider === input.providerId) config.defaultProvider = void 0;
123071
+ await writeConfigFile(this.configPath, config);
123072
+ return this.config = loadRuntimeConfig(this.configPath);
123073
+ });
123070
123074
  }
123071
123075
  setRuntimeSystemPrompt({ sessionId, ...payload }) {
123072
123076
  return this.sessionApi(sessionId).setRuntimeSystemPrompt(payload);
@@ -123608,6 +123612,23 @@ var ScreamCore = class {
123608
123612
  reloadProviderManager() {
123609
123613
  return this.config = loadRuntimeConfig(this.configPath);
123610
123614
  }
123615
+ /**
123616
+ * Serialize config-file mutations. Both setScreamConfig and
123617
+ * removeScreamProvider are read-modify-write cycles that read the file
123618
+ * synchronously and then await the write; two calls issued together (e.g.
123619
+ * cycling thinking levels plus confirming a model in the picker fire
123620
+ * back-to-back setConfig patches) would otherwise read the SAME stale
123621
+ * snapshot, and the later write silently reverts the earlier patch — a
123622
+ * lost update that makes e.g. a just-selected default model flip back to
123623
+ * the old one. The queue guarantees each cycle re-reads after the
123624
+ * previous write completed.
123625
+ */
123626
+ configMutationQueue = Promise.resolve();
123627
+ enqueueConfigMutation(mutate) {
123628
+ const run = this.configMutationQueue.then(mutate, mutate);
123629
+ this.configMutationQueue = run.catch(() => void 0);
123630
+ return run;
123631
+ }
123611
123632
  async refreshSessionRuntimeConfig(session, config) {
123612
123633
  const api = new SessionAPIImpl(session);
123613
123634
  const requested = (await api.getModel({ agentId: "main" })).trim();
@@ -130796,11 +130817,10 @@ async function changeThinkingLevel(host, alias, level) {
130796
130817
  }
130797
130818
  const prevLevel = host.state.appState.thinkingLevel;
130798
130819
  const isActiveModel = alias === host.state.appState.model;
130820
+ const session = host.session;
130799
130821
  if (isActiveModel && level !== prevLevel) {
130800
- const session = host.session;
130801
130822
  try {
130802
- if (session === void 0) await host.authFlow.activateModelAfterLogin(alias, level);
130803
- else await session.setThinking(level);
130823
+ if (session !== void 0) await session.setThinking(level);
130804
130824
  } catch (error) {
130805
130825
  const msg = formatErrorMessage(error);
130806
130826
  host.showError(`Failed to set thinking: ${msg}`);
@@ -130816,7 +130836,7 @@ async function changeThinkingLevel(host, alias, level) {
130816
130836
  host.showError(`Failed to save thinking default: ${msg}`);
130817
130837
  return;
130818
130838
  }
130819
- const status = isActiveModel && level !== prevLevel ? `Thinking set to ${level} for ${alias}.` : persisted ? `Saved thinking ${level} as default for ${alias}.` : `Thinking already ${level} for ${alias}.`;
130839
+ const status = isActiveModel && level !== prevLevel ? session === void 0 ? persisted ? `No active session - thinking ${level} for ${alias} saved as default.` : `No active session - thinking ${level} was NOT saved (${alias} is not the default model, so its level applies only when a new session selects it).` : `Thinking set to ${level} for ${alias}.` : persisted ? `Saved thinking ${level} as default for ${alias}.` : `Thinking already ${level} for ${alias}.`;
130820
130840
  host.showStatus(status, host.state.theme.colors.success);
130821
130841
  }
130822
130842
  async function persistThinkingDefault(host, alias, level) {
@@ -130845,18 +130865,16 @@ async function performModelSwitch(host, alias, thinkingLevel) {
130845
130865
  const prevThinkingLevel = host.state.appState.thinkingLevel;
130846
130866
  const modelChanged = alias !== prevModel;
130847
130867
  const thinkingChanged = thinkingLevel !== prevThinkingLevel;
130848
- const needsSessionActivation = modelChanged || thinkingChanged;
130849
- const overflow = alias !== prevModel ? contextOverflowForModel(host.state.appState, alias) : null;
130868
+ const session = host.session;
130869
+ const overflow = session !== void 0 && alias !== prevModel ? contextOverflowForModel(host.state.appState, alias) : null;
130850
130870
  if (overflow !== null) {
130851
130871
  host.showNotice("Storm Breaker(风暴守护者)", `无法切换到模型「${alias}」:当前会话上下文 ${formatTokenCount$1(overflow.currentTokens)} 已超出该模型上限 ${formatTokenCount$1(overflow.maxContextTokens)}。建议先执行 /compact 压缩上下文,或选择上下文窗口更大的模型。`);
130852
130872
  return;
130853
130873
  }
130854
- const session = host.session;
130855
130874
  let effectiveAlias = alias;
130856
130875
  let effectiveThinking = thinkingLevel;
130857
130876
  try {
130858
- if (session === void 0 && needsSessionActivation) await host.authFlow.activateModelAfterLogin(alias, thinkingLevel);
130859
- else if (session !== void 0) {
130877
+ if (session !== void 0) {
130860
130878
  if (modelChanged) await session.setModel(alias);
130861
130879
  if (thinkingChanged) await session.setThinking(thinkingLevel);
130862
130880
  const confirmed = await session.getStatus().catch(() => null);
@@ -130879,12 +130897,13 @@ async function performModelSwitch(host, alias, thinkingLevel) {
130879
130897
  persisted = await persistModelSelection(host, alias, thinkingLevel);
130880
130898
  } catch (error) {
130881
130899
  const msg = formatErrorMessage(error);
130882
- host.showError(`Switched to ${effectiveAlias}, but failed to save default: ${msg}`);
130900
+ host.showError(`${session === void 0 ? "Selected" : "Switched to"} ${effectiveAlias}, but failed to save default: ${msg}`);
130883
130901
  return;
130884
130902
  }
130885
130903
  const hasHistory = host.state.appState.contextTokens > 0;
130886
130904
  const cacheWarning = modelChanged && hasHistory ? " Note: switching models invalidates the existing prompt cache - use /new to avoid extra token costs." : "";
130887
130905
  const status = (() => {
130906
+ if (session === void 0 && (modelChanged || thinkingChanged)) return `No active session - ${effectiveAlias} (thinking ${effectiveThinking}) saved as default. It applies when you start a new session.`;
130888
130907
  if (modelChanged) return `Switched to ${effectiveAlias} with thinking ${effectiveThinking}.${cacheWarning}`;
130889
130908
  if (thinkingChanged) return `Thinking set to ${effectiveThinking} for ${effectiveAlias}.`;
130890
130909
  if (persisted) return `Saved ${effectiveAlias} with thinking ${effectiveThinking} as default.`;
@@ -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-D8ovLbmr.mjs";
6
+ import { Kt as handleConnectCommand } from "./dispatch-ATQMQq1p.mjs";
7
7
  export { handleConnectCommand };
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-CPu6R7nd.mjs")).main();
9
+ (await import("./app-C3aR5fdI.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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.15.0",
3
+ "version": "0.15.1",
4
4
  "description": "A terminal-native AI agent for builders",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",