scream-code 0.7.5 → 0.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -103136,7 +103136,7 @@ var SessionSubagentHost = class {
103136
103136
  signal: controller.signal
103137
103137
  }, () => {
103138
103138
  const binding = this.resolveModelBinding(profileName);
103139
- const modelAlias = binding ?? parent.config.modelAlias;
103139
+ const modelAlias = this.resolveValidModelAlias(parent, binding);
103140
103140
  const thinkingLevel = this.resolveThinkingLevel(parent, binding, parent.config.thinkingLevel);
103141
103141
  child.config.update({
103142
103142
  modelAlias,
@@ -103248,7 +103248,7 @@ var SessionSubagentHost = class {
103248
103248
  }
103249
103249
  async configureChild(parent, child, profile) {
103250
103250
  const binding = this.resolveModelBinding(profile.name);
103251
- const modelAlias = binding ?? parent.config.modelAlias;
103251
+ const modelAlias = this.resolveValidModelAlias(parent, binding);
103252
103252
  const thinkingLevel = this.resolveThinkingLevel(parent, binding, parent.config.thinkingLevel);
103253
103253
  child.config.update({
103254
103254
  cwd: parent.config.cwd,
@@ -103266,6 +103266,25 @@ var SessionSubagentHost = class {
103266
103266
  return alias;
103267
103267
  }
103268
103268
  /**
103269
+ * Validate that a bound alias still resolves in the provider registry.
103270
+ * Returns the binding when valid; falls back to the parent's current model
103271
+ * when the alias is unconfigured (or no provider is available) so a stale
103272
+ * `/model diy` binding cannot send the subagent into a hung LLM call.
103273
+ * `binding === undefined` (follow-main) is the common path and skips the
103274
+ * probe entirely.
103275
+ */
103276
+ resolveValidModelAlias(parent, binding) {
103277
+ if (binding === void 0) return parent.config.modelAlias;
103278
+ if (parent.modelProvider === void 0) return parent.config.modelAlias;
103279
+ try {
103280
+ parent.modelProvider.resolveProviderConfig(binding);
103281
+ return binding;
103282
+ } catch (error) {
103283
+ this.session.log?.warn(`subagent binding "${binding}" no longer resolves in the provider registry; falling back to parent model "${parent.config.modelAlias}"`, error);
103284
+ return parent.config.modelAlias;
103285
+ }
103286
+ }
103287
+ /**
103269
103288
  * When a profile binds to a different model, the parent's thinkingLevel may
103270
103289
  * not be supported (e.g. Claude parent with `thinking=high` spawning a GPT
103271
103290
  * subagent). Probe the bound model's capability and force `off` when it
@@ -122202,6 +122221,179 @@ function validateOptions(opts) {
122202
122221
  };
122203
122222
  }
122204
122223
  //#endregion
122224
+ //#region src/tui/config.ts
122225
+ /**
122226
+ * TUI-owned configuration.
122227
+ *
122228
+ * Agent/runtime settings live in core's `config.toml`; this file owns only
122229
+ * terminal UI preferences for the scream-code client.
122230
+ */
122231
+ const INVALID_TUI_CONFIG_MESSAGE = "~/.scream-code/tui.toml 中的 TUI 配置无效;使用默认配置。";
122232
+ const TuiThemeSchema = z.enum([
122233
+ "dark",
122234
+ "light",
122235
+ "auto"
122236
+ ]);
122237
+ const NotificationConditionSchema = z.enum(["unfocused", "always"]);
122238
+ const NotificationsConfigSchema = z.object({
122239
+ enabled: z.boolean(),
122240
+ condition: NotificationConditionSchema
122241
+ });
122242
+ const TuiLikePreferencesSchema = z.object({
122243
+ nickname: z.string().optional(),
122244
+ tone: z.string().optional(),
122245
+ other: z.string().optional()
122246
+ });
122247
+ const TuiConfigFileSchema = z.object({
122248
+ theme: TuiThemeSchema.optional(),
122249
+ editor: z.object({ command: z.string().optional() }).optional(),
122250
+ notifications: z.object({
122251
+ enabled: z.boolean().optional(),
122252
+ notification_condition: NotificationConditionSchema.optional()
122253
+ }).optional(),
122254
+ like: TuiLikePreferencesSchema.optional(),
122255
+ fusionPlan: z.object({
122256
+ timeoutSeconds: z.number().int().min(30).max(3600).optional(),
122257
+ workerCount: z.number().int().min(1).max(8).optional()
122258
+ }).optional(),
122259
+ subagentModels: z.record(z.string(), z.string()).optional()
122260
+ });
122261
+ const TuiConfigSchema = z.object({
122262
+ theme: TuiThemeSchema,
122263
+ editorCommand: z.string().nullable(),
122264
+ notifications: NotificationsConfigSchema,
122265
+ like: TuiLikePreferencesSchema,
122266
+ fusionPlan: z.object({
122267
+ timeoutSeconds: z.number().int().min(30).max(3600),
122268
+ workerCount: z.number().int().min(1).max(8)
122269
+ }),
122270
+ subagentModels: z.record(z.string(), z.string())
122271
+ });
122272
+ const DEFAULT_NOTIFICATIONS_CONFIG = {
122273
+ enabled: true,
122274
+ condition: "unfocused"
122275
+ };
122276
+ const DEFAULT_TUI_CONFIG = TuiConfigSchema.parse({
122277
+ theme: "auto",
122278
+ editorCommand: null,
122279
+ notifications: DEFAULT_NOTIFICATIONS_CONFIG,
122280
+ like: {},
122281
+ fusionPlan: {
122282
+ timeoutSeconds: 600,
122283
+ workerCount: 3
122284
+ },
122285
+ subagentModels: {}
122286
+ });
122287
+ /**
122288
+ * Thrown by `loadTuiConfig` when the on-disk TOML cannot be parsed.
122289
+ * Carries `fallback` so the caller can recover without re-running the
122290
+ * discovery code.
122291
+ */
122292
+ var TuiConfigParseError = class extends Error {
122293
+ name = "TuiConfigParseError";
122294
+ fallback;
122295
+ constructor(fallback) {
122296
+ super(INVALID_TUI_CONFIG_MESSAGE);
122297
+ this.fallback = fallback;
122298
+ }
122299
+ };
122300
+ function getTuiConfigPath() {
122301
+ return join(getDataDir(), "tui.toml");
122302
+ }
122303
+ async function loadTuiConfig(filePath = getTuiConfigPath()) {
122304
+ if (!existsSync(filePath)) {
122305
+ await saveTuiConfig(DEFAULT_TUI_CONFIG, filePath);
122306
+ return DEFAULT_TUI_CONFIG;
122307
+ }
122308
+ try {
122309
+ return parseTuiConfig(await readFile(filePath, "utf-8"));
122310
+ } catch {
122311
+ throw new TuiConfigParseError(DEFAULT_TUI_CONFIG);
122312
+ }
122313
+ }
122314
+ function parseTuiConfig(tomlText) {
122315
+ if (tomlText.trim().length === 0) return DEFAULT_TUI_CONFIG;
122316
+ const raw = parse$1(tomlText);
122317
+ return normalizeTuiConfig(TuiConfigFileSchema.parse(raw));
122318
+ }
122319
+ async function saveTuiConfig(config, filePath = getTuiConfigPath()) {
122320
+ await mkdir(dirname$1(filePath), { recursive: true });
122321
+ await writeFile(filePath, renderTuiConfig(config), "utf-8");
122322
+ }
122323
+ function normalizeTuiConfig(config) {
122324
+ const command = config.editor?.command?.trim();
122325
+ const like = config.like ?? {};
122326
+ const fusionPlan = config.fusionPlan ?? {};
122327
+ return TuiConfigSchema.parse({
122328
+ theme: config.theme ?? DEFAULT_TUI_CONFIG.theme,
122329
+ editorCommand: command === void 0 || command.length === 0 ? null : command,
122330
+ notifications: {
122331
+ enabled: config.notifications?.enabled ?? DEFAULT_NOTIFICATIONS_CONFIG.enabled,
122332
+ condition: config.notifications?.notification_condition ?? DEFAULT_NOTIFICATIONS_CONFIG.condition
122333
+ },
122334
+ like: {
122335
+ nickname: normalizeOptionalString(like.nickname),
122336
+ tone: normalizeOptionalString(like.tone),
122337
+ other: normalizeOptionalString(like.other)
122338
+ },
122339
+ fusionPlan: {
122340
+ timeoutSeconds: fusionPlan.timeoutSeconds ?? DEFAULT_TUI_CONFIG.fusionPlan.timeoutSeconds,
122341
+ workerCount: fusionPlan.workerCount ?? DEFAULT_TUI_CONFIG.fusionPlan.workerCount
122342
+ },
122343
+ subagentModels: normalizeSubagentModels(config.subagentModels)
122344
+ });
122345
+ }
122346
+ function normalizeOptionalString(value) {
122347
+ if (value === void 0) return void 0;
122348
+ const trimmed = value.trim();
122349
+ return trimmed.length > 0 ? trimmed : void 0;
122350
+ }
122351
+ function normalizeSubagentModels(raw) {
122352
+ if (raw === void 0) return {};
122353
+ const out = {};
122354
+ for (const [profileName, alias] of Object.entries(raw)) {
122355
+ const key = profileName.trim();
122356
+ const val = typeof alias === "string" ? alias.trim() : "";
122357
+ if (key.length > 0 && val.length > 0) out[key] = val;
122358
+ }
122359
+ return out;
122360
+ }
122361
+ function renderTuiConfig(config) {
122362
+ const nickname = escapeTomlBasicString(config.like.nickname ?? "");
122363
+ const tone = escapeTomlBasicString(config.like.tone ?? "");
122364
+ const other = escapeTomlBasicString(config.like.other ?? "");
122365
+ const subagentModelsBlock = renderSubagentModelsBlock(config.subagentModels);
122366
+ return `# ~/.scream-code/tui.toml
122367
+ # Terminal UI preferences for scream-code.
122368
+ # Agent/runtime settings stay in ~/.scream-code/config.toml.
122369
+
122370
+ theme = "${config.theme}" # "auto" | "dark" | "light"
122371
+
122372
+ [editor]
122373
+ command = "${escapeTomlBasicString(config.editorCommand ?? "")}" # Empty uses $VISUAL / $EDITOR
122374
+
122375
+ [notifications]
122376
+ enabled = ${String(config.notifications.enabled)} # true | false
122377
+ notification_condition = "${config.notifications.condition}" # "unfocused" | "always"
122378
+
122379
+ [like]
122380
+ nickname = "${nickname}"
122381
+ tone = "${tone}"
122382
+ other = "${other}"
122383
+
122384
+ [fusionPlan]
122385
+ timeoutSeconds = ${config.fusionPlan.timeoutSeconds} # 30..3600, default 600
122386
+ workerCount = ${config.fusionPlan.workerCount} # 1..8, default 3${subagentModelsBlock}`;
122387
+ }
122388
+ function renderSubagentModelsBlock(models) {
122389
+ const entries = Object.entries(models);
122390
+ if (entries.length === 0) return "\n";
122391
+ return `\n\n[subagentModels]\n${entries.map(([name, alias]) => `${name} = "${escapeTomlBasicString(alias)}"`).join("\n")}\n`;
122392
+ }
122393
+ function escapeTomlBasicString(value) {
122394
+ return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\b", "\\b").replaceAll(" ", "\\t").replaceAll("\n", "\\n").replaceAll("\f", "\\f").replaceAll("\r", "\\r");
122395
+ }
122396
+ //#endregion
122205
122397
  //#region src/cli/run-prompt.ts
122206
122398
  const PROMPT_UI_MODE = "print";
122207
122399
  const PROMPT_MAIN_AGENT_ID = "main";
@@ -122218,6 +122410,15 @@ async function runPrompt(opts, version, io = {}) {
122218
122410
  uiMode: PROMPT_UI_MODE,
122219
122411
  skillDirs: opts.skillsDirs
122220
122412
  });
122413
+ let promptTuiConfig;
122414
+ try {
122415
+ promptTuiConfig = await loadTuiConfig();
122416
+ } catch (error) {
122417
+ if (!(error instanceof TuiConfigParseError)) throw error;
122418
+ promptTuiConfig = error.fallback;
122419
+ }
122420
+ const promptSubagentModels = promptTuiConfig.subagentModels;
122421
+ harness.setSubagentModelBindings(() => promptSubagentModels);
122221
122422
  log.info("scream-code starting", {
122222
122423
  version,
122223
122424
  uiMode: PROMPT_UI_MODE,
@@ -122661,179 +122862,6 @@ function formatTurnEndedFailure(event) {
122661
122862
  return `提示回合结束,原因:${event.reason}`;
122662
122863
  }
122663
122864
  //#endregion
122664
- //#region src/tui/config.ts
122665
- /**
122666
- * TUI-owned configuration.
122667
- *
122668
- * Agent/runtime settings live in core's `config.toml`; this file owns only
122669
- * terminal UI preferences for the scream-code client.
122670
- */
122671
- const INVALID_TUI_CONFIG_MESSAGE = "~/.scream-code/tui.toml 中的 TUI 配置无效;使用默认配置。";
122672
- const TuiThemeSchema = z.enum([
122673
- "dark",
122674
- "light",
122675
- "auto"
122676
- ]);
122677
- const NotificationConditionSchema = z.enum(["unfocused", "always"]);
122678
- const NotificationsConfigSchema = z.object({
122679
- enabled: z.boolean(),
122680
- condition: NotificationConditionSchema
122681
- });
122682
- const TuiLikePreferencesSchema = z.object({
122683
- nickname: z.string().optional(),
122684
- tone: z.string().optional(),
122685
- other: z.string().optional()
122686
- });
122687
- const TuiConfigFileSchema = z.object({
122688
- theme: TuiThemeSchema.optional(),
122689
- editor: z.object({ command: z.string().optional() }).optional(),
122690
- notifications: z.object({
122691
- enabled: z.boolean().optional(),
122692
- notification_condition: NotificationConditionSchema.optional()
122693
- }).optional(),
122694
- like: TuiLikePreferencesSchema.optional(),
122695
- fusionPlan: z.object({
122696
- timeoutSeconds: z.number().int().min(30).max(3600).optional(),
122697
- workerCount: z.number().int().min(1).max(8).optional()
122698
- }).optional(),
122699
- subagentModels: z.record(z.string(), z.string()).optional()
122700
- });
122701
- const TuiConfigSchema = z.object({
122702
- theme: TuiThemeSchema,
122703
- editorCommand: z.string().nullable(),
122704
- notifications: NotificationsConfigSchema,
122705
- like: TuiLikePreferencesSchema,
122706
- fusionPlan: z.object({
122707
- timeoutSeconds: z.number().int().min(30).max(3600),
122708
- workerCount: z.number().int().min(1).max(8)
122709
- }),
122710
- subagentModels: z.record(z.string(), z.string())
122711
- });
122712
- const DEFAULT_NOTIFICATIONS_CONFIG = {
122713
- enabled: true,
122714
- condition: "unfocused"
122715
- };
122716
- const DEFAULT_TUI_CONFIG = TuiConfigSchema.parse({
122717
- theme: "auto",
122718
- editorCommand: null,
122719
- notifications: DEFAULT_NOTIFICATIONS_CONFIG,
122720
- like: {},
122721
- fusionPlan: {
122722
- timeoutSeconds: 600,
122723
- workerCount: 3
122724
- },
122725
- subagentModels: {}
122726
- });
122727
- /**
122728
- * Thrown by `loadTuiConfig` when the on-disk TOML cannot be parsed.
122729
- * Carries `fallback` so the caller can recover without re-running the
122730
- * discovery code.
122731
- */
122732
- var TuiConfigParseError = class extends Error {
122733
- name = "TuiConfigParseError";
122734
- fallback;
122735
- constructor(fallback) {
122736
- super(INVALID_TUI_CONFIG_MESSAGE);
122737
- this.fallback = fallback;
122738
- }
122739
- };
122740
- function getTuiConfigPath() {
122741
- return join(getDataDir(), "tui.toml");
122742
- }
122743
- async function loadTuiConfig(filePath = getTuiConfigPath()) {
122744
- if (!existsSync(filePath)) {
122745
- await saveTuiConfig(DEFAULT_TUI_CONFIG, filePath);
122746
- return DEFAULT_TUI_CONFIG;
122747
- }
122748
- try {
122749
- return parseTuiConfig(await readFile(filePath, "utf-8"));
122750
- } catch {
122751
- throw new TuiConfigParseError(DEFAULT_TUI_CONFIG);
122752
- }
122753
- }
122754
- function parseTuiConfig(tomlText) {
122755
- if (tomlText.trim().length === 0) return DEFAULT_TUI_CONFIG;
122756
- const raw = parse$1(tomlText);
122757
- return normalizeTuiConfig(TuiConfigFileSchema.parse(raw));
122758
- }
122759
- async function saveTuiConfig(config, filePath = getTuiConfigPath()) {
122760
- await mkdir(dirname$1(filePath), { recursive: true });
122761
- await writeFile(filePath, renderTuiConfig(config), "utf-8");
122762
- }
122763
- function normalizeTuiConfig(config) {
122764
- const command = config.editor?.command?.trim();
122765
- const like = config.like ?? {};
122766
- const fusionPlan = config.fusionPlan ?? {};
122767
- return TuiConfigSchema.parse({
122768
- theme: config.theme ?? DEFAULT_TUI_CONFIG.theme,
122769
- editorCommand: command === void 0 || command.length === 0 ? null : command,
122770
- notifications: {
122771
- enabled: config.notifications?.enabled ?? DEFAULT_NOTIFICATIONS_CONFIG.enabled,
122772
- condition: config.notifications?.notification_condition ?? DEFAULT_NOTIFICATIONS_CONFIG.condition
122773
- },
122774
- like: {
122775
- nickname: normalizeOptionalString(like.nickname),
122776
- tone: normalizeOptionalString(like.tone),
122777
- other: normalizeOptionalString(like.other)
122778
- },
122779
- fusionPlan: {
122780
- timeoutSeconds: fusionPlan.timeoutSeconds ?? DEFAULT_TUI_CONFIG.fusionPlan.timeoutSeconds,
122781
- workerCount: fusionPlan.workerCount ?? DEFAULT_TUI_CONFIG.fusionPlan.workerCount
122782
- },
122783
- subagentModels: normalizeSubagentModels(config.subagentModels)
122784
- });
122785
- }
122786
- function normalizeOptionalString(value) {
122787
- if (value === void 0) return void 0;
122788
- const trimmed = value.trim();
122789
- return trimmed.length > 0 ? trimmed : void 0;
122790
- }
122791
- function normalizeSubagentModels(raw) {
122792
- if (raw === void 0) return {};
122793
- const out = {};
122794
- for (const [profileName, alias] of Object.entries(raw)) {
122795
- const key = profileName.trim();
122796
- const val = typeof alias === "string" ? alias.trim() : "";
122797
- if (key.length > 0 && val.length > 0) out[key] = val;
122798
- }
122799
- return out;
122800
- }
122801
- function renderTuiConfig(config) {
122802
- const nickname = escapeTomlBasicString(config.like.nickname ?? "");
122803
- const tone = escapeTomlBasicString(config.like.tone ?? "");
122804
- const other = escapeTomlBasicString(config.like.other ?? "");
122805
- const subagentModelsBlock = renderSubagentModelsBlock(config.subagentModels);
122806
- return `# ~/.scream-code/tui.toml
122807
- # Terminal UI preferences for scream-code.
122808
- # Agent/runtime settings stay in ~/.scream-code/config.toml.
122809
-
122810
- theme = "${config.theme}" # "auto" | "dark" | "light"
122811
-
122812
- [editor]
122813
- command = "${escapeTomlBasicString(config.editorCommand ?? "")}" # Empty uses $VISUAL / $EDITOR
122814
-
122815
- [notifications]
122816
- enabled = ${String(config.notifications.enabled)} # true | false
122817
- notification_condition = "${config.notifications.condition}" # "unfocused" | "always"
122818
-
122819
- [like]
122820
- nickname = "${nickname}"
122821
- tone = "${tone}"
122822
- other = "${other}"
122823
-
122824
- [fusionPlan]
122825
- timeoutSeconds = ${config.fusionPlan.timeoutSeconds} # 30..3600, default 600
122826
- workerCount = ${config.fusionPlan.workerCount} # 1..8, default 3${subagentModelsBlock}`;
122827
- }
122828
- function renderSubagentModelsBlock(models) {
122829
- const entries = Object.entries(models);
122830
- if (entries.length === 0) return "\n";
122831
- return `\n\n[subagentModels]\n${entries.map(([name, alias]) => `${name} = "${escapeTomlBasicString(alias)}"`).join("\n")}\n`;
122832
- }
122833
- function escapeTomlBasicString(value) {
122834
- return value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\b", "\\b").replaceAll(" ", "\\t").replaceAll("\n", "\\n").replaceAll("\f", "\\f").replaceAll("\r", "\\r");
122835
- }
122836
- //#endregion
122837
122865
  //#region src/tui/constant/rendering.ts
122838
122866
  const MAX_SHELL_OUTPUT_BYTES = 128 * 1024;
122839
122867
  const BRAILLE_SPINNER_FRAMES = [
@@ -143920,6 +143948,7 @@ var ScreamTUI = class {
143920
143948
  this.startupNotice = startupInput.startupNotice;
143921
143949
  this.updatePrefetched = startupInput.updatePrefetched === true;
143922
143950
  this.state = createTUIState(tuiOptions);
143951
+ this.harness.setSubagentModelBindings(() => this.state.appState.subagentModels);
143923
143952
  this.reverseRpcDisposers.push(...registerReverseRPCHandlers(this.approvalController, this.questionController, {
143924
143953
  showApprovalPanel: (payload) => {
143925
143954
  this.showApprovalPanel(payload);
@@ -145272,6 +145301,15 @@ async function runStreamJson(opts) {
145272
145301
  uiMode: "print",
145273
145302
  skillDirs: opts.skillsDirs
145274
145303
  });
145304
+ let streamTuiConfig;
145305
+ try {
145306
+ streamTuiConfig = await loadTuiConfig();
145307
+ } catch (error) {
145308
+ if (!(error instanceof TuiConfigParseError)) throw error;
145309
+ streamTuiConfig = error.fallback;
145310
+ }
145311
+ const streamSubagentModels = streamTuiConfig.subagentModels;
145312
+ harness.setSubagentModelBindings(() => streamSubagentModels);
145275
145313
  const writer = new ClaudeStreamJsonWriter((line) => {
145276
145314
  process.stdout.write(`${line}\n`);
145277
145315
  });
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-9HhcF-mA.mjs")).main();
9
+ (await import("./app-9AyEiYzR.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.7.5",
3
+ "version": "0.7.6",
4
4
  "description": "A terminal-native AI agent for builders",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",