scream-code 0.7.1 → 0.7.3

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.
@@ -556,7 +556,7 @@ const SCREAM_ERROR_INFO = {
556
556
  title: "Compaction failed",
557
557
  retryable: false,
558
558
  public: true,
559
- action: "Inspect logs and consider increasing compaction limits."
559
+ action: "The model returned an empty or invalid compaction summary. Common causes: max output tokens too low, an incompatible model, or a provider/proxy dropping the stream. Try /compact again, switch to a different model, or check your provider configuration."
560
560
  },
561
561
  "compaction.unable": {
562
562
  title: "Unable to compact",
@@ -50067,11 +50067,11 @@ async function generate(provider, systemPrompt, tools, history, callbacks, optio
50067
50067
  await throwIfAborted(options?.signal, stream);
50068
50068
  options?.onStreamEnd?.();
50069
50069
  if (pendingPart !== null) flushPart(message, pendingPart, toolCallIndexMap);
50070
- if (message.content.length === 0 && message.toolCalls.length === 0) throw new APIEmptyResponseError(`The API returned an empty response (no content, no tool calls). Provider: ${provider.name}, model: ${provider.modelName}`);
50070
+ if (message.content.length === 0 && message.toolCalls.length === 0) throw new APIEmptyResponseError(`The API returned an empty response (no content, no tool calls). Provider: ${provider.name}, model: ${provider.modelName}. Common causes: the model's max output tokens are set too low, the model does not support this request format, or the provider/proxy returned an empty stream. Try again, switch models, or check provider settings.`);
50071
50071
  const hasThink = message.content.some((p) => p.type === "think");
50072
50072
  const hasText = message.content.some((p) => p.type === "text" && p.text.trim().length > 0);
50073
50073
  const hasToolCalls = message.toolCalls.length > 0;
50074
- if (hasThink && !hasText && !hasToolCalls) throw new APIEmptyResponseError(`The API returned a response containing only thinking content without any text or tool calls. This usually indicates the stream was interrupted or the output token budget was exhausted during reasoning. Provider: ${provider.name}, model: ${provider.modelName}`);
50074
+ if (hasThink && !hasText && !hasToolCalls) throw new APIEmptyResponseError(`The API returned a response containing only thinking content without any text or tool calls. This usually indicates the stream was interrupted or the output token budget was exhausted during reasoning. Provider: ${provider.name}, model: ${provider.modelName}. If this persists, reduce reasoning effort or switch to a model with a larger output budget.`);
50075
50075
  if (callbacks?.onToolCall !== void 0) for (const toolCall of message.toolCalls) {
50076
50076
  await throwIfAborted(options?.signal, stream);
50077
50077
  await callbacks.onToolCall(toolCall);
@@ -69535,6 +69535,14 @@ const ProviderConfigSchema = z.object({
69535
69535
  env: StringRecordSchema.optional(),
69536
69536
  customHeaders: StringRecordSchema.optional()
69537
69537
  });
69538
+ const ThinkingEffortSchema = z.enum([
69539
+ "off",
69540
+ "low",
69541
+ "medium",
69542
+ "high",
69543
+ "xhigh",
69544
+ "max"
69545
+ ]);
69538
69546
  const ModelAliasSchema = z.object({
69539
69547
  provider: z.string(),
69540
69548
  model: z.string(),
@@ -69543,7 +69551,8 @@ const ModelAliasSchema = z.object({
69543
69551
  capabilities: z.array(z.string()).optional(),
69544
69552
  displayName: z.string().optional(),
69545
69553
  reasoningKey: z.string().optional(),
69546
- adaptiveThinking: z.boolean().optional()
69554
+ adaptiveThinking: z.boolean().optional(),
69555
+ thinkingLevels: z.array(ThinkingEffortSchema).optional()
69547
69556
  });
69548
69557
  const ThinkingConfigSchema = z.object({
69549
69558
  mode: z.enum([
@@ -69647,6 +69656,13 @@ const McpServerConfigSchema = z.preprocess((raw) => {
69647
69656
  };
69648
69657
  return obj;
69649
69658
  }, McpServerConfigDiscriminatedSchema);
69659
+ const SecretEntrySchema = z.object({
69660
+ type: z.enum(["plain", "regex"]),
69661
+ content: z.string().min(1),
69662
+ mode: z.enum(["obfuscate", "replace"]).default("obfuscate"),
69663
+ replacement: z.string().optional(),
69664
+ flags: z.string().optional()
69665
+ });
69650
69666
  const ScreamConfigSchema = z.object({
69651
69667
  providers: z.record(z.string(), ProviderConfigSchema).default({}),
69652
69668
  defaultProvider: z.string().optional(),
@@ -69665,6 +69681,7 @@ const ScreamConfigSchema = z.object({
69665
69681
  extraSkillDirs: z.array(z.string()).optional(),
69666
69682
  loopControl: LoopControlSchema.optional(),
69667
69683
  background: BackgroundConfigSchema.optional(),
69684
+ secrets: z.array(SecretEntrySchema).optional(),
69668
69685
  raw: z.record(z.string(), z.unknown()).optional()
69669
69686
  });
69670
69687
  const ProviderConfigPatchSchema = ProviderConfigSchema.partial();
@@ -69697,7 +69714,8 @@ const ScreamConfigPatchSchema = z.object({
69697
69714
  mergeAllAvailableSkills: z.boolean().optional(),
69698
69715
  extraSkillDirs: z.array(z.string()).optional(),
69699
69716
  loopControl: LoopControlPatchSchema.optional(),
69700
- background: BackgroundConfigPatchSchema.optional()
69717
+ background: BackgroundConfigPatchSchema.optional(),
69718
+ secrets: z.array(SecretEntrySchema).optional()
69701
69719
  }).strict();
69702
69720
  function getDefaultConfig() {
69703
69721
  return { providers: {} };
@@ -71423,6 +71441,25 @@ function makeCarriageReturnsVisible(text) {
71423
71441
  function computeAnchor(text) {
71424
71442
  return createHash("sha256").update(text).digest("hex").slice(0, 8);
71425
71443
  }
71444
+ const entries$1 = /* @__PURE__ */ new Map();
71445
+ function recordFailedEdit(canonicalPath, inputHash) {
71446
+ const prev = entries$1.get(canonicalPath);
71447
+ const count = prev && prev.hash === inputHash ? prev.count + 1 : 1;
71448
+ entries$1.set(canonicalPath, {
71449
+ hash: inputHash,
71450
+ count
71451
+ });
71452
+ return {
71453
+ count,
71454
+ escalate: count >= 3
71455
+ };
71456
+ }
71457
+ function resetNoopLoop(canonicalPath) {
71458
+ entries$1.delete(canonicalPath);
71459
+ }
71460
+ function hashEditPayload(input) {
71461
+ return createHash("sha256").update(JSON.stringify(input)).digest("hex");
71462
+ }
71426
71463
  //#endregion
71427
71464
  //#region ../../packages/agent-core/src/tools/builtin/file/edit.md
71428
71465
  var edit_default = "Perform exact string replacements against the text view returned by Read.\n\n- When copying from Read output, omit the line-number prefix and tab; match only the file content.\n- By default, old_string must occur exactly once. If it matches multiple locations, add surrounding context or set replace_all when every occurrence should change.\n- Prefer Edit for targeted changes to existing files; use Write only for new files or complete overwrites.\n- To modify a file, always use Edit; do not run a Shell `sed` command for edits.\n- When making several independent changes, issue multiple Edit calls in parallel within a single response; edits to the same file are serialized automatically by a write lock.\n- When several parallel Edit calls target the same file, a write lock serializes them; they apply in the order the calls appear in your response. An edit fails with `old_string not found` if its old_string was taken from text an earlier edit already replaced — base every old_string on the latest Read view and order dependent edits accordingly.\n- For pure CRLF files, Read shows LF and Edit.old_string/new_string should use LF; Edit writes the file back with CRLF preserved.\n- For mixed line endings or lone carriage returns, Read displays carriage returns as \\r; include actual \\r escapes in old_string/new_string for those positions.\n- When Read returned an `Anchor:` value in its status block, pass it as `anchor` to verify the file has not changed since it was read. If the anchor does not match, re-read the file before editing.\n- Edit refuses if `old_string` lands inside an unresolved merge conflict block, or if `new_string` would introduce `<<<<<<<`/`=======`/`>>>>>>>` markers. To clean up a conflict, include the markers in `old_string` so Edit replaces them.";
@@ -71478,6 +71515,31 @@ var EditTool = class {
71478
71515
  };
71479
71516
  }
71480
71517
  async execution(args, safePath) {
71518
+ const result = await this.executionCore(args, safePath);
71519
+ const inputHash = hashEditPayload({
71520
+ path: args.path,
71521
+ old_string: args.old_string,
71522
+ new_string: args.new_string,
71523
+ replace_all: args.replace_all,
71524
+ anchor: args.anchor
71525
+ });
71526
+ if (result.isError !== true) {
71527
+ resetNoopLoop(safePath);
71528
+ return result;
71529
+ }
71530
+ const { count, escalate } = recordFailedEdit(safePath, inputHash);
71531
+ if (escalate) return {
71532
+ isError: true,
71533
+ stopTurn: true,
71534
+ output: `Edit to ${args.path} has failed ${String(3)} times in a row with the same payload. The model appears stuck in a loop. Turn stopped. Re-read ${args.path} with the Read tool to observe current content, then issue a different edit.`
71535
+ };
71536
+ if (count >= 2) return {
71537
+ ...result,
71538
+ output: (typeof result.output === "string" ? result.output : "") + `\n\nWarning: attempt ${String(count)} of ${String(3)} with the same payload on ${args.path}. The file may have changed — re-read it before retrying. One more identical failure will stop the turn.`
71539
+ };
71540
+ return result;
71541
+ }
71542
+ async executionCore(args, safePath) {
71481
71543
  if (args.old_string === args.new_string) return {
71482
71544
  isError: true,
71483
71545
  output: "No changes to make: old_string and new_string are exactly the same."
@@ -75232,7 +75294,7 @@ z.object({
75232
75294
  numMatches: z.number().int().nonnegative().optional(),
75233
75295
  appliedLimit: z.number().int().nonnegative().optional()
75234
75296
  });
75235
- const DEFAULT_TIMEOUT_MS = 2e4;
75297
+ const DEFAULT_TIMEOUT_MS$1 = 2e4;
75236
75298
  const SIGTERM_GRACE_MS$1 = 5e3;
75237
75299
  const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
75238
75300
  const RG_MAX_COLUMNS = 500;
@@ -75335,7 +75397,7 @@ var GrepTool = class {
75335
75397
  if (bufferTruncated || timedOut) stdoutText = omitIncompleteTrailingRecord(stdoutText, mode);
75336
75398
  if (timedOut && stdoutText.trim() === "") return {
75337
75399
  isError: true,
75338
- output: `Grep timed out after ${String(DEFAULT_TIMEOUT_MS / 1e3)}s. Try a more specific path or pattern.`
75400
+ output: `Grep timed out after ${String(DEFAULT_TIMEOUT_MS$1 / 1e3)}s. Try a more specific path or pattern.`
75339
75401
  };
75340
75402
  if (signal.aborted) return {
75341
75403
  isError: true,
@@ -75375,7 +75437,7 @@ var GrepTool = class {
75375
75437
  else messages.push(paginationNotice);
75376
75438
  }
75377
75439
  if (bufferTruncated) messages.push(`[stdout truncated at ${String(MAX_OUTPUT_BYTES)} bytes; incomplete trailing line omitted]`);
75378
- if (timedOut) messages.push(`Grep timed out after ${String(DEFAULT_TIMEOUT_MS / 1e3)}s; partial results returned`);
75440
+ if (timedOut) messages.push(`Grep timed out after ${String(DEFAULT_TIMEOUT_MS$1 / 1e3)}s; partial results returned`);
75379
75441
  const contentIncludesLineNumbers = mode === "content" && args["-n"] !== false;
75380
75442
  const contentBody = limited.map((line) => formatDisplayLine(line, mode, this.workspace.workspaceDir, pathClass, contentIncludesLineNumbers)).join("\n");
75381
75443
  const visibleBody = orderedLines.length === 0 && filteredSensitive.size > 0 ? "No non-sensitive matches found" : contentBody;
@@ -75492,7 +75554,7 @@ async function runRipgrepOnce(jian, rgArgs, signal) {
75492
75554
  const timeoutHandle = setTimeout(() => {
75493
75555
  timedOut = true;
75494
75556
  killProc();
75495
- }, DEFAULT_TIMEOUT_MS);
75557
+ }, DEFAULT_TIMEOUT_MS$1);
75496
75558
  let exitCode = 0;
75497
75559
  let stdoutText = "";
75498
75560
  let stderrText = "";
@@ -77185,10 +77247,10 @@ var WriteTool = class {
77185
77247
  };
77186
77248
  //#endregion
77187
77249
  //#region ../../packages/agent-core/src/tools/builtin/planning/enter-plan-mode.md
77188
- var enter_plan_mode_default = "Use this tool proactively when you're about to start a non-trivial implementation task.\nGetting user sign-off on your approach via ExitPlanMode before writing code prevents wasted effort.\n\nUse it when ANY of these conditions apply:\n\n1. New Feature Implementation - e.g. \"Add a caching layer to the API\"\n2. Multiple Valid Approaches - e.g. \"Optimize database queries\" (indexing vs rewrite vs caching)\n3. Code Modifications - e.g. \"Refactor auth module to support OAuth\"\n4. Architectural Decisions - e.g. \"Add WebSocket support\"\n5. Multi-File Changes - involves more than 2-3 files\n6. Unclear Requirements - need exploration to understand scope\n7. User Preferences Matter - if user input would materially change the implementation approach, use EnterPlanMode to structure the decision\n\nPermission mode notes:\n- EnterPlanMode enters plan mode automatically without an approval prompt in all permission modes.\n- In yolo and manual modes, ExitPlanMode still presents the plan to the user for approval.\n- In auto permission mode, do not use AskUserQuestion; make the best decision from available context.\n- In auto permission mode, ExitPlanMode exits plan mode without asking the user.\n- Use EnterPlanMode only when planning itself adds value.\n\nWhen NOT to use:\n- Single-line or few-line fixes (typos, obvious bugs, small tweaks)\n- User gave very specific, detailed instructions\n- Pure research/exploration tasks\n\n## What Happens in Plan Mode\nIn plan mode, you will:\n1. Identify 2-3 key questions about the codebase that are critical to your plan. If you are not confident about the codebase structure or relevant code paths, use `Agent(subagent_type=\"explore\")` to investigate these questions first - this is strongly recommended for non-trivial tasks.\n2. Explore the codebase using Glob, Grep, Read, and other read-only tools for any remaining quick lookups. Use Bash only when needed; Bash follows the normal permission mode and rules.\n3. Design an implementation approach based on your findings\n4. Write your plan to the current plan file with Write or Edit\n5. Present your plan to the user via ExitPlanMode for approval\n";
77250
+ var enter_plan_mode_default = "Use this tool proactively when you're about to start a non-trivial implementation task.\nGetting user sign-off on your approach via ExitPlanMode before writing code prevents wasted effort.\n\n## Planning Modes\n\nThe host supports two planning strategies. You can request either one via this tool:\n\n- **Normal plan** (default): You investigate the codebase, design a single implementation approach, write it to the plan file, and present it for approval. Best when the task is straightforward or you are already confident about the right approach.\n- **Fusion plan**: The host spawns multiple independent planning subagents in parallel, each exploring a different angle, then synthesizes their outputs into one consolidated plan. Best when the task is ambiguous, has many valid approaches, crosses many files, or when exploration itself adds significant value. Fusion plan may take longer but tends to surface risks and alternatives you might miss.\n\nTo request a fusion plan, include `mode: 'fusion'` in your tool arguments. If you omit `mode` or set it to `normal`, the host uses the normal plan flow.\n\n### When to choose which mode\n\nPrefer **normal plan** when:\n1. The user gave specific, detailed instructions.\n2. The change is small or localized (1-3 files, single concern).\n3. You are confident about the codebase structure and the right approach.\n4. Speed matters more than exploring alternatives.\n\nPrefer **fusion plan** when:\n1. The task is open-ended or ambiguous (e.g. \"improve performance\", \"refactor auth\").\n2. Multiple valid architectures or approaches exist.\n3. The change spans more than 3-5 files or touches core abstractions.\n4. You are unfamiliar with the relevant code paths and want parallel exploration.\n5. The user explicitly asked for a thorough plan or mentioned comparing options.\n\nIf unsure, choose normal plan for small fixes and fusion plan for larger design tasks.\n\n## When to Use\n\nUse this tool when ANY of these conditions apply:\n\n1. New Feature Implementation - e.g. \"Add a caching layer to the API\"\n2. Multiple Valid Approaches - e.g. \"Optimize database queries\" (indexing vs rewrite vs caching)\n3. Code Modifications - e.g. \"Refactor auth module to support OAuth\"\n4. Architectural Decisions - e.g. \"Add WebSocket support\"\n5. Multi-File Changes - involves more than 2-3 files\n6. Unclear Requirements - need exploration to understand scope\n7. User Preferences Matter - if user input would materially change the implementation approach, use EnterPlanMode to structure the decision\n\nPermission mode notes:\n- EnterPlanMode enters plan mode automatically without an approval prompt in all permission modes.\n- In yolo and manual modes, ExitPlanMode still presents the plan to the user for approval.\n- In auto permission mode, do not use AskUserQuestion; make the best decision from available context.\n- In auto permission mode, ExitPlanMode exits plan mode without asking the user.\n- Use EnterPlanMode only when planning itself adds value.\n\nWhen NOT to use:\n- Single-line or few-line fixes (typos, obvious bugs, small tweaks)\n- User gave very specific, detailed instructions\n- Pure research/exploration tasks\n\n## What Happens in Plan Mode\nIn plan mode, you will:\n1. Identify 2-3 key questions about the codebase that are critical to your plan. If you are not confident about the codebase structure or relevant code paths, use `Agent(subagent_type=\"explore\")` to investigate these questions first - this is strongly recommended for non-trivial tasks.\n2. Explore the codebase using Glob, Grep, Read, and other read-only tools for any remaining quick lookups. Use Bash only when needed; Bash follows the normal permission mode and rules.\n3. Design an implementation approach based on your findings (or, for fusion plan, review the synthesized plan the host provides).\n4. Write the plan to the current plan file with Write or Edit.\n5. Present your plan to the user via ExitPlanMode for approval\n\nFor fusion plan, the host performs the parallel exploration and synthesis for you; you should still review the result, fill in any gaps, and ensure it matches the user's intent before exiting plan mode.\n";
77189
77251
  //#endregion
77190
77252
  //#region ../../packages/agent-core/src/tools/builtin/planning/enter-plan-mode.ts
77191
- const EnterPlanModeInputSchema = z.object({}).strict();
77253
+ const EnterPlanModeInputSchema = z.object({ mode: z.enum(["normal", "fusion"]).default("normal").describe("Planning strategy. 'normal' (default): you design the plan. 'fusion': the host spawns parallel planning subagents and synthesizes a single plan for you to review. Use 'fusion' for ambiguous, large, or multi-approach tasks.") }).strict();
77192
77254
  var EnterPlanModeTool = class {
77193
77255
  agent;
77194
77256
  name = "EnterPlanMode";
@@ -77207,7 +77269,7 @@ var EnterPlanModeTool = class {
77207
77269
  output: "Plan mode is already active. Use ExitPlanMode when the plan is ready."
77208
77270
  };
77209
77271
  try {
77210
- await this.agent.planMode.enter();
77272
+ await this.agent.planMode.enter(void 0, false, true, _args.mode);
77211
77273
  } catch (error) {
77212
77274
  return {
77213
77275
  isError: true,
@@ -78766,10 +78828,10 @@ var FullCompaction = class {
78766
78828
  async compactionWorker(signal, data, compactedCount) {
78767
78829
  const originalHistory = [...this.agent.context.history];
78768
78830
  const tokensBefore = estimateTokensForMessages(originalHistory);
78831
+ const model = this.agent.config.model;
78769
78832
  let retryCount = 0;
78770
78833
  try {
78771
78834
  await this.triggerPreCompactHook(data, tokensBefore, signal);
78772
- const model = this.agent.config.model;
78773
78835
  const delays = retryBackoffDelays(5);
78774
78836
  let usage;
78775
78837
  let summary;
@@ -78788,8 +78850,7 @@ var FullCompaction = class {
78788
78850
  const response = await this.agent.generate(this.agent.config.provider, COMPACTION_SYSTEM_PROMPT, [], messages, void 0, { signal });
78789
78851
  if (response.finishReason === "truncated") throw new TruncatedError();
78790
78852
  usage = response.usage;
78791
- summary = extractCompactionSummary(response);
78792
- summary = this.postProcessSummary(summary);
78853
+ summary = extractCompactionSummary(response, model);
78793
78854
  break;
78794
78855
  } catch (error) {
78795
78856
  if (error instanceof APIContextOverflowError || error instanceof TruncatedError) compactedCount = this.strategy.reduceCompactOnOverflow(messagesToCompact);
@@ -78836,11 +78897,19 @@ var FullCompaction = class {
78836
78897
  });
78837
78898
  this.agent.log.error("compaction failed", {
78838
78899
  code: isScreamError(error) ? error.code : void 0,
78839
- error
78900
+ error,
78901
+ model,
78902
+ retryCount,
78903
+ compactedCount,
78904
+ tokensBefore
78840
78905
  });
78841
78906
  this.markCanceled();
78842
78907
  if (!blockedByTurn) {
78843
- const payload = isScreamError(error) && error.code === ErrorCodes.AUTH_LOGIN_REQUIRED ? toScreamErrorPayload(error) : makeErrorPayload(ErrorCodes.COMPACTION_FAILED, String(error));
78908
+ const details = {
78909
+ model,
78910
+ retryCount
78911
+ };
78912
+ const payload = isScreamError(error) && error.code === ErrorCodes.AUTH_LOGIN_REQUIRED ? toScreamErrorPayload(error) : makeErrorPayload(ErrorCodes.COMPACTION_FAILED, String(error), { details });
78844
78913
  this.agent.emitEvent({
78845
78914
  type: "error",
78846
78915
  ...payload
@@ -78921,9 +78990,9 @@ var FullCompaction = class {
78921
78990
  return `${summary.trim()}\n\n${todoMarkdown}`;
78922
78991
  }
78923
78992
  };
78924
- function extractCompactionSummary(response) {
78993
+ function extractCompactionSummary(response, model) {
78925
78994
  const summary = typeof response.message.content === "string" ? response.message.content : response.message.content.map((part) => part.type === "text" ? part.text : "").join("");
78926
- if (summary.trim().length === 0) throw new APIEmptyResponseError("The compaction response did not contain a non-empty summary.");
78995
+ if (summary.trim().length === 0) throw new APIEmptyResponseError(`The compaction response did not contain a non-empty summary. Model "${model}" returned empty content. Common causes: output token limit set too low, an incompatible model, or a provider/proxy that dropped the stream. Try /compact again, switch models, or check your provider configuration.`);
78927
78996
  return summary;
78928
78997
  }
78929
78998
  const COMPACTION_INSTRUCTION = (customInstruction = "") => renderPrompt(compaction_instruction_default, { customInstruction });
@@ -82415,17 +82484,19 @@ var PlanMode = class {
82415
82484
  _isActive = false;
82416
82485
  _planId = null;
82417
82486
  _planFilePath = null;
82487
+ _strategy = "normal";
82418
82488
  constructor(agent) {
82419
82489
  this.agent = agent;
82420
82490
  }
82421
82491
  createPlanId() {
82422
82492
  return generateHeroSlug(randomUUID(), /* @__PURE__ */ new Set());
82423
82493
  }
82424
- async enter(id = this.createPlanId(), createFile = false, emitStatus = true) {
82494
+ async enter(id = this.createPlanId(), createFile = false, emitStatus = true, strategy = "normal") {
82425
82495
  if (this._isActive) throw new Error("Already in plan mode");
82426
82496
  this._isActive = true;
82427
82497
  this._planId = id;
82428
82498
  this._planFilePath = null;
82499
+ this._strategy = strategy;
82429
82500
  let enterRecorded = false;
82430
82501
  try {
82431
82502
  const planFilePath = this.planFilePathFor(id);
@@ -82433,7 +82504,8 @@ var PlanMode = class {
82433
82504
  await this.ensurePlanDirectory(planFilePath);
82434
82505
  this.agent.records.logRecord({
82435
82506
  type: "plan_mode.enter",
82436
- id
82507
+ id,
82508
+ strategy
82437
82509
  });
82438
82510
  enterRecorded = true;
82439
82511
  if (createFile) await this.writeEmptyPlanFile(planFilePath);
@@ -82443,19 +82515,22 @@ var PlanMode = class {
82443
82515
  this._isActive = false;
82444
82516
  this._planId = null;
82445
82517
  this._planFilePath = null;
82518
+ this._strategy = "normal";
82446
82519
  }
82447
82520
  throw error;
82448
82521
  }
82449
82522
  if (emitStatus) this.agent.emitStatusUpdated();
82450
82523
  }
82451
- restoreEnter({ id }) {
82524
+ restoreEnter({ id, strategy }) {
82452
82525
  this.agent.replayBuilder.push({
82453
82526
  type: "plan_updated",
82454
- enabled: true
82527
+ enabled: true,
82528
+ strategy
82455
82529
  });
82456
82530
  this._isActive = true;
82457
82531
  this._planId = id;
82458
82532
  this._planFilePath = this.planFilePathFor(id);
82533
+ this._strategy = strategy ?? "normal";
82459
82534
  }
82460
82535
  cancel(id) {
82461
82536
  this.agent.records.logRecord({
@@ -82469,6 +82544,7 @@ var PlanMode = class {
82469
82544
  this._isActive = false;
82470
82545
  this._planId = null;
82471
82546
  this._planFilePath = null;
82547
+ this._strategy = "normal";
82472
82548
  this.agent.emitStatusUpdated();
82473
82549
  }
82474
82550
  async clear() {
@@ -82487,6 +82563,7 @@ var PlanMode = class {
82487
82563
  this._isActive = false;
82488
82564
  this._planId = null;
82489
82565
  this._planFilePath = null;
82566
+ this._strategy = "normal";
82490
82567
  this.agent.emitStatusUpdated();
82491
82568
  }
82492
82569
  get isActive() {
@@ -82495,6 +82572,9 @@ var PlanMode = class {
82495
82572
  get planFilePath() {
82496
82573
  return this._planFilePath;
82497
82574
  }
82575
+ get strategy() {
82576
+ return this._strategy;
82577
+ }
82498
82578
  async data() {
82499
82579
  if (!this._planId || !this._planFilePath) return null;
82500
82580
  let content = "";
@@ -82528,6 +82608,286 @@ function isMissingFileError(error) {
82528
82608
  return error.code === "ENOENT";
82529
82609
  }
82530
82610
  //#endregion
82611
+ //#region ../../packages/agent-core/src/agent/secrets.ts
82612
+ const PLACEHOLDER_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
82613
+ const PLACEHOLDER_LENGTH = 6;
82614
+ const PLACEHOLDER_RE = /#[A-Z2-9]{6}#/g;
82615
+ function compileSecretRegex(pattern, flags) {
82616
+ let resolvedPattern = pattern;
82617
+ let resolvedFlags = flags ?? "";
82618
+ const literalMatch = /^\/((?:[^\\/]|\\.)*)\/([ gimsuy]*)$/.exec(pattern);
82619
+ if (literalMatch) {
82620
+ if (literalMatch[1] !== void 0) resolvedPattern = literalMatch[1];
82621
+ if (literalMatch[2] !== void 0) {
82622
+ const combined = new Set(Array.from(resolvedFlags).concat(Array.from(literalMatch[2])));
82623
+ resolvedFlags = Array.from(combined).join("");
82624
+ }
82625
+ }
82626
+ if (!resolvedFlags.includes("g")) resolvedFlags += "g";
82627
+ return new RegExp(resolvedPattern, resolvedFlags);
82628
+ }
82629
+ function replaceAll(text, search, replacement) {
82630
+ if (search.length === 0) return text;
82631
+ return text.split(search).join(replacement);
82632
+ }
82633
+ function buildPlaceholder(secret, salt) {
82634
+ const hash = createHash("sha256").update(`${salt}:${secret}`).digest();
82635
+ let s = "#";
82636
+ for (let i = 0; i < PLACEHOLDER_LENGTH; i++) {
82637
+ const byte = hash[i] ?? 0;
82638
+ s += PLACEHOLDER_ALPHABET[byte % 32];
82639
+ }
82640
+ return s + "#";
82641
+ }
82642
+ function buildReplaceMarker(secret, salt) {
82643
+ const hash = createHash("sha256").update(`replace:${salt}:${secret}`).digest();
82644
+ let s = "[REDACTED-";
82645
+ for (let i = 0; i < 4; i++) {
82646
+ const byte = hash[i] ?? 0;
82647
+ s += PLACEHOLDER_ALPHABET[byte % 32];
82648
+ }
82649
+ return s + "]";
82650
+ }
82651
+ var SecretObfuscator = class {
82652
+ plainObfuscate = /* @__PURE__ */ new Map();
82653
+ plainReplace = /* @__PURE__ */ new Map();
82654
+ regexEntries = [];
82655
+ placeholderToSecret = /* @__PURE__ */ new Map();
82656
+ secretToPlaceholder = /* @__PURE__ */ new Map();
82657
+ hasAny;
82658
+ constructor(entries) {
82659
+ let hasReal = false;
82660
+ for (const entry of entries) {
82661
+ const mode = entry.mode ?? "obfuscate";
82662
+ if (entry.type === "plain") if (mode === "obfuscate") {
82663
+ if (entry.content.length < 8) continue;
82664
+ const placeholder = buildPlaceholder(entry.content, "plain");
82665
+ this.plainObfuscate.set(entry.content, placeholder);
82666
+ this.placeholderToSecret.set(placeholder, entry.content);
82667
+ this.secretToPlaceholder.set(entry.content, placeholder);
82668
+ hasReal = true;
82669
+ } else {
82670
+ const replacement = entry.replacement ?? buildReplaceMarker(entry.content, "plain");
82671
+ this.plainReplace.set(entry.content, replacement);
82672
+ hasReal = true;
82673
+ }
82674
+ else try {
82675
+ const regex = compileSecretRegex(entry.content, entry.flags);
82676
+ this.regexEntries.push({
82677
+ regex,
82678
+ mode,
82679
+ replacement: entry.replacement,
82680
+ salt: entry.content
82681
+ });
82682
+ hasReal = true;
82683
+ } catch {}
82684
+ }
82685
+ this.hasAny = hasReal;
82686
+ }
82687
+ placeholderFor(secret, salt) {
82688
+ const existing = this.secretToPlaceholder.get(secret);
82689
+ if (existing !== void 0) return existing;
82690
+ let placeholder = buildPlaceholder(secret, salt);
82691
+ if (this.placeholderToSecret.has(placeholder)) placeholder = buildPlaceholder(secret, `${salt}:alt`);
82692
+ this.secretToPlaceholder.set(secret, placeholder);
82693
+ this.placeholderToSecret.set(placeholder, secret);
82694
+ return placeholder;
82695
+ }
82696
+ hasSecrets() {
82697
+ return this.hasAny;
82698
+ }
82699
+ obfuscate(text) {
82700
+ if (!this.hasAny) return text;
82701
+ let result = text;
82702
+ const replaceSorted = [...this.plainReplace.entries()].toSorted((a, b) => b[0].length - a[0].length);
82703
+ for (const [secret, replacement] of replaceSorted) result = replaceAll(result, secret, replacement);
82704
+ const obfuscateSorted = [...this.plainObfuscate.entries()].toSorted((a, b) => b[0].length - a[0].length);
82705
+ for (const [secret, placeholder] of obfuscateSorted) result = replaceAll(result, secret, placeholder);
82706
+ for (const entry of this.regexEntries) {
82707
+ entry.regex.lastIndex = 0;
82708
+ const matches = /* @__PURE__ */ new Set();
82709
+ for (;;) {
82710
+ const match = entry.regex.exec(result);
82711
+ if (match === null) break;
82712
+ if (match[0].length === 0) {
82713
+ entry.regex.lastIndex++;
82714
+ continue;
82715
+ }
82716
+ matches.add(match[0]);
82717
+ }
82718
+ for (const matchValue of matches) if (entry.mode === "replace") {
82719
+ const replacement = entry.replacement ?? buildReplaceMarker(matchValue, entry.salt);
82720
+ result = replaceAll(result, matchValue, replacement);
82721
+ } else {
82722
+ if (matchValue.length < 8) continue;
82723
+ const placeholder = this.placeholderFor(matchValue, entry.salt);
82724
+ result = replaceAll(result, matchValue, placeholder);
82725
+ }
82726
+ }
82727
+ return result;
82728
+ }
82729
+ deobfuscate(text) {
82730
+ if (!this.hasAny || !text.includes("#")) return text;
82731
+ return text.replaceAll(PLACEHOLDER_RE, (match) => this.placeholderToSecret.get(match) ?? match);
82732
+ }
82733
+ deobfuscateJsonString(jsonStr) {
82734
+ if (jsonStr === null) return null;
82735
+ if (!this.hasAny || !jsonStr.includes("#")) return jsonStr;
82736
+ let parsed;
82737
+ try {
82738
+ parsed = JSON.parse(jsonStr);
82739
+ } catch {
82740
+ return this.deobfuscate(jsonStr);
82741
+ }
82742
+ const walked = mapJsonStrings(parsed, (s) => this.deobfuscate(s));
82743
+ return JSON.stringify(walked);
82744
+ }
82745
+ obfuscateJsonString(jsonStr) {
82746
+ if (jsonStr === null) return null;
82747
+ if (!this.hasAny) return jsonStr;
82748
+ let parsed;
82749
+ try {
82750
+ parsed = JSON.parse(jsonStr);
82751
+ } catch {
82752
+ return this.obfuscate(jsonStr);
82753
+ }
82754
+ const walked = mapJsonStrings(parsed, (s) => this.obfuscate(s));
82755
+ return JSON.stringify(walked);
82756
+ }
82757
+ };
82758
+ function mapJsonStrings(value, fn) {
82759
+ if (typeof value === "string") return fn(value);
82760
+ if (Array.isArray(value)) {
82761
+ let changed = false;
82762
+ const out = value.map((item) => {
82763
+ const next = mapJsonStrings(item, fn);
82764
+ if (next !== item) changed = true;
82765
+ return next;
82766
+ });
82767
+ return changed ? out : value;
82768
+ }
82769
+ if (value !== null && typeof value === "object") {
82770
+ let changed = false;
82771
+ const out = {};
82772
+ for (const key of Object.keys(value)) {
82773
+ const item = value[key];
82774
+ if (item === void 0) continue;
82775
+ const next = mapJsonStrings(item, fn);
82776
+ if (next !== item) changed = true;
82777
+ out[key] = next;
82778
+ }
82779
+ return changed ? out : value;
82780
+ }
82781
+ return value;
82782
+ }
82783
+ function obfuscateMessages(obf, messages) {
82784
+ if (!obf.hasSecrets()) return [...messages];
82785
+ let changed = false;
82786
+ const result = messages.map((message) => {
82787
+ let localChanged = false;
82788
+ const content = message.content.map((part) => {
82789
+ if (part.type === "text") {
82790
+ const text = obf.obfuscate(part.text);
82791
+ if (text === part.text) return part;
82792
+ localChanged = true;
82793
+ return {
82794
+ ...part,
82795
+ text
82796
+ };
82797
+ }
82798
+ if (part.type === "think") {
82799
+ const think = obf.obfuscate(part.think);
82800
+ if (think === part.think) return part;
82801
+ localChanged = true;
82802
+ return {
82803
+ ...part,
82804
+ think
82805
+ };
82806
+ }
82807
+ return part;
82808
+ });
82809
+ let toolCalls = message.toolCalls;
82810
+ if (message.role === "assistant" && message.toolCalls.length > 0) {
82811
+ let tcChanged = false;
82812
+ toolCalls = message.toolCalls.map((call) => {
82813
+ if (call.arguments === null) return call;
82814
+ const obfuscated = obf.obfuscateJsonString(call.arguments);
82815
+ if (obfuscated === call.arguments) return call;
82816
+ tcChanged = true;
82817
+ return {
82818
+ ...call,
82819
+ arguments: obfuscated
82820
+ };
82821
+ });
82822
+ if (tcChanged) localChanged = true;
82823
+ }
82824
+ if (!localChanged) return message;
82825
+ changed = true;
82826
+ return {
82827
+ ...message,
82828
+ content,
82829
+ toolCalls
82830
+ };
82831
+ });
82832
+ return changed ? result : [...messages];
82833
+ }
82834
+ function deobfuscateToolCalls(obf, toolCalls) {
82835
+ if (!obf.hasSecrets()) return [...toolCalls];
82836
+ let changed = false;
82837
+ const result = toolCalls.map((call) => {
82838
+ const args = obf.deobfuscateJsonString(call.arguments);
82839
+ if (args === call.arguments) return call;
82840
+ changed = true;
82841
+ return {
82842
+ ...call,
82843
+ arguments: args
82844
+ };
82845
+ });
82846
+ return changed ? result : [...toolCalls];
82847
+ }
82848
+ const DEFAULT_SECRET_PATTERNS = [
82849
+ {
82850
+ type: "regex",
82851
+ content: "AKIA[0-9A-Z]{16}",
82852
+ mode: "obfuscate"
82853
+ },
82854
+ {
82855
+ type: "regex",
82856
+ content: "ghp_[A-Za-z0-9]{36}",
82857
+ mode: "obfuscate"
82858
+ },
82859
+ {
82860
+ type: "regex",
82861
+ content: "gho_[A-Za-z0-9]{36}",
82862
+ mode: "obfuscate"
82863
+ },
82864
+ {
82865
+ type: "regex",
82866
+ content: "ghs_[A-Za-z0-9]{36}",
82867
+ mode: "obfuscate"
82868
+ },
82869
+ {
82870
+ type: "regex",
82871
+ content: "sk-proj-[A-Za-z0-9\\-_]{20,}",
82872
+ mode: "obfuscate"
82873
+ },
82874
+ {
82875
+ type: "regex",
82876
+ content: "sk-[A-Za-z0-9]{48}",
82877
+ mode: "obfuscate"
82878
+ },
82879
+ {
82880
+ type: "regex",
82881
+ content: "sk-ant-[A-Za-z0-9\\-_]{20,}",
82882
+ mode: "obfuscate"
82883
+ },
82884
+ {
82885
+ type: "regex",
82886
+ content: "(api[_-]?key|apikey|token|secret)[\"'\\s:=]+[\"']?([A-Za-z0-9\\-_]{20,})",
82887
+ mode: "obfuscate"
82888
+ }
82889
+ ];
82890
+ //#endregion
82531
82891
  //#region ../../packages/agent-core/src/agent/wolfpack/index.ts
82532
82892
  var WolfPackMode = class {
82533
82893
  agent;
@@ -83296,7 +83656,10 @@ function restoreAgentRecord(agent, input) {
83296
83656
  agent.fullCompaction.markCompleted();
83297
83657
  return;
83298
83658
  case "plan_mode.enter":
83299
- agent.planMode.restoreEnter(input);
83659
+ agent.planMode.restoreEnter({
83660
+ id: input.id,
83661
+ strategy: input.strategy
83662
+ });
83300
83663
  return;
83301
83664
  case "plan_mode.cancel":
83302
83665
  agent.planMode.cancel(input.id);
@@ -95668,17 +96031,46 @@ const RawAgentProfileSchema = z.object({
95668
96031
  subagents: z.record(z.string(), RawSubagentProfileSchema).optional()
95669
96032
  });
95670
96033
  //#endregion
96034
+ //#region ../../packages/agent-core/src/config/path.ts
96035
+ function resolveScreamHome(homeDir) {
96036
+ return homeDir ?? process.env["SCREAM_CODE_HOME"] ?? join$1(homedir(), ".scream-code");
96037
+ }
96038
+ function resolveConfigPath$1(input) {
96039
+ return input.configPath ?? join$1(resolveScreamHome(input.homeDir), "config.toml");
96040
+ }
96041
+ function ensureScreamHome(homeDir) {
96042
+ mkdirSync(homeDir, {
96043
+ recursive: true,
96044
+ mode: 448
96045
+ });
96046
+ }
96047
+ //#endregion
95671
96048
  //#region ../../packages/agent-core/src/profile/context.ts
95672
96049
  const AGENTS_MD_MAX_BYTES = 32 * 1024;
95673
96050
  const S_IFMT = 61440;
95674
96051
  const S_IFREG = 32768;
95675
96052
  async function prepareSystemPromptContext(jian) {
95676
- const [cwdListing, agentsMd] = await Promise.all([listDirectory(jian), loadAgentsMd(jian)]);
96053
+ const [cwdListing, agentsMd, roleAdditional] = await Promise.all([
96054
+ listDirectory(jian),
96055
+ loadAgentsMd(jian),
96056
+ loadRoleAdditional(jian)
96057
+ ]);
95677
96058
  return {
95678
96059
  cwdListing,
95679
- agentsMd
96060
+ agentsMd,
96061
+ roleAdditional
95680
96062
  };
95681
96063
  }
96064
+ async function loadRoleAdditional(_jian) {
96065
+ const prefsPath = join$1(resolveScreamHome(), "user-prefs.md");
96066
+ if (!await pathExists(_jian, prefsPath)) return;
96067
+ try {
96068
+ const trimmed = (await _jian.readText(prefsPath)).trim();
96069
+ return trimmed.length > 0 ? trimmed : void 0;
96070
+ } catch {
96071
+ return;
96072
+ }
96073
+ }
95682
96074
  async function loadAgentsMd(jian) {
95683
96075
  const workDir = jian.getcwd();
95684
96076
  const dirs = dirsRootToLeaf(jian, workDir, await findProjectRoot(jian, workDir));
@@ -95768,7 +96160,7 @@ function renderAgentFiles(files) {
95768
96160
  continue;
95769
96161
  }
95770
96162
  let content = file.content;
95771
- if (byteLength(content) > remaining) content = truncateUtf8(content, remaining).trim();
96163
+ if (byteLength(content) > remaining) content = truncateUtf8$1(content, remaining).trim();
95772
96164
  remaining -= byteLength(content);
95773
96165
  budgeted[i] = {
95774
96166
  path: file.path,
@@ -95777,7 +96169,7 @@ function renderAgentFiles(files) {
95777
96169
  }
95778
96170
  return budgeted.filter((file) => file !== void 0 && file.content.length > 0).map((file) => `${annotationFor(file.path)}${file.content}`).join("\n\n");
95779
96171
  }
95780
- function truncateUtf8(text, maxBytes) {
96172
+ function truncateUtf8$1(text, maxBytes) {
95781
96173
  let result = text;
95782
96174
  while (byteLength(result) > maxBytes) result = result.slice(0, -1);
95783
96175
  return result;
@@ -95885,9 +96277,16 @@ function buildTemplateVars(context, promptVars) {
95885
96277
  SCREAM_AGENTS_MD: context.agentsMd ?? "",
95886
96278
  SCREAM_SKILLS: skills,
95887
96279
  SCREAM_ADDITIONAL_DIRS_INFO: context.additionalDirsInfo ?? "",
95888
- ROLE_ADDITIONAL: context.roleAdditional ?? promptVars["ROLE_ADDITIONAL"] ?? promptVars["roleAdditional"] ?? ""
96280
+ ROLE_ADDITIONAL: mergeRoleAdditional(context.roleAdditional, promptVars)
95889
96281
  };
95890
96282
  }
96283
+ function mergeRoleAdditional(contextValue, promptVars) {
96284
+ const userPrefs = contextValue?.trim() ?? "";
96285
+ const profileAdditional = promptVars["ROLE_ADDITIONAL"]?.trim() ?? promptVars["roleAdditional"]?.trim() ?? "";
96286
+ if (userPrefs.length === 0) return profileAdditional;
96287
+ if (profileAdditional.length === 0) return userPrefs;
96288
+ return `${userPrefs}\n\n${profileAdditional}`;
96289
+ }
95891
96290
  function applySubagentDescriptions(mergedProfiles, resolvedProfiles) {
95892
96291
  for (const [ownerName, owner] of mergedProfiles) {
95893
96292
  if (owner.subagents === void 0) continue;
@@ -95976,7 +96375,7 @@ const PROFILE_SOURCES = {
95976
96375
  "profile/default/oracle.yaml": "extends: agent\nname: oracle\npromptVars:\n roleAdditional: |\n You are now running as a sub-agent. All `user` messages are sent by the main agent.\n You are the Oracle sub-agent. Your role is deep debugging, architecture decisions,\n and second opinions.\n\n # Behavior\n\n - Investigate root causes, not symptoms.\n - Ask clarifying questions only when the premise is genuinely ambiguous.\n - Return concise, evidence-based conclusions with concrete file paths and line numbers.\n - Do NOT implement fixes unless explicitly asked to do so.\n - Do NOT run project-wide verification, lint, or format unless explicitly asked.\n - Do NOT ask the end user questions.\n\n # Output format\n\n When the task is complete, return:\n 1. A one-sentence verdict.\n 2. The key evidence (file paths, line numbers, command output, or URLs).\n 3. The recommended next step for the parent agent.\nwhenToUse: |\n Use when the main agent is stuck on a complex bug, needs an architecture trade-off,\n or wants a second opinion before a risky change.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - mcp__*\n",
95977
96376
  "profile/default/plan.yaml": "extends: agent\nname: plan\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n Before designing your implementation plan, consider whether you fully understand the codebase areas relevant to the task. If not, recommend the parent agent to use the explore agent (subagent_type=\"explore\") to investigate key questions first. In your response, clearly state:\n 1. What you already know from the information provided\n 2. What questions remain unanswered that would benefit from explore agent investigation\n 3. Your implementation plan (either preliminary if questions remain, or final if sufficient context exists)\nwhenToUse: |\n Use this agent when the parent agent needs a step-by-step implementation plan, key file identification, and architectural trade-off analysis before code changes are made.\ntools:\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - WebSearch\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - FetchURL\n",
95978
96377
  "profile/default/reviewer.yaml": "extends: agent\nname: reviewer\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n You are a code review specialist. Your job is to identify bugs the author would want fixed before merge.\n\n # Procedure\n\n 1. Run `git diff`, `jj diff --git`, or read modified files to view the patch.\n 2. Read modified files for full context.\n 3. Call `ReportFinding` for each issue you identify.\n 4. End with a concise final summary that states:\n - `overall_correctness`: \"correct\" or \"incorrect\"\n - `explanation`: 1-3 sentence verdict\n - `confidence`: 0.0-1.0\n\n You NEVER make file edits or trigger builds. Bash is read-only: `git diff`, `git log`, `git show`, `jj diff --git`.\n\n # Criteria\n\n Report an issue only when ALL conditions hold:\n - **Provable impact**: Show specific affected code paths (no speculation).\n - **Actionable**: Discrete fix, not vague \"consider improving X\".\n - **Unintentional**: Clearly not a deliberate design choice.\n - **Introduced in patch**: Do not flag pre-existing bugs unless asked.\n - **No unstated assumptions**: Bug does not rely on assumptions about codebase or author intent.\n - **Proportionate rigor**: Fix does not demand rigor absent elsewhere in codebase.\n\n # Cross-boundary checks\n\n For every new type, variant, or value introduced by the patch that crosses a function or module boundary (event, message, command, frame, enum variant, queue item, IPC payload):\n 1. Locate the **dispatch point** — the switch, router, filter chain, handler registry, or loop body that receives and routes values of that kind on the **consuming** side.\n 2. Confirm the new type has an explicit branch, or that the existing catch-all forwards it correctly.\n 3. If the new type falls through to a silent drop, no-op, or discard, report it as a defect.\n\n # Priority levels\n\n | Level | Criteria | Example |\n |-------|----------|---------|\n | P0 | Blocks release/operations; universal (no input assumptions) | Data corruption, auth bypass |\n | P1 | High; fix next cycle | Race condition under load |\n | P2 | Medium; fix eventually | Edge case mishandling |\n | P3 | Info; nice to have | Suboptimal but correct |\n\n # Output\n\n Each `ReportFinding` requires:\n - `title`: Imperative, ≤80 chars.\n - `body`: One paragraph — bug, trigger, impact.\n - `priority`: P0, P1, P2, or P3.\n - `confidence`: 0.0-1.0.\n - `file_path`: Path to affected file.\n - `line_start`, `line_end`: Range ≤10 lines, must overlap the diff.\n\n Final summary format:\n ```\n Review verdict: incorrect\n Confidence: 0.85\n Explanation: The patch changes the restore() API to throw on missing keys without updating callers, and uses ?? '' to hide missing data instead of surfacing the error.\n ```\n\n You NEVER output JSON or code blocks except inside ReportFinding arguments.\n\n Correctness ignores non-blocking issues (style, docs, nits).\nwhenToUse: |\n Code review specialist. Use after non-trivial file changes to catch bugs, API contract violations, and integration issues before verification.\ntools:\n - Bash\n - Read\n - Grep\n - Glob\n - LSP\n - ReportFinding\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - mcp__*\n",
95979
- "profile/default/system.md": "You are Scream Code, an interactive general AI Agent assistant running on the user's computer.\n\nYour primary goal is to help users with software engineering tasks by taking action — use the tools available to you to make real changes on the user's system. You should also answer questions when asked. Always adhere strictly to the following system instructions and the user's requirements.\n\n{{ ROLE_ADDITIONAL }}\n\n# Prompt and Tool Use\n\nThe user's messages may contain questions and/or task descriptions in natural language, code snippets, logs, file paths, or other forms of information. Read them, understand them and do what they requested. For simple questions/greetings that do not involve any information in the working directory or on the internet, you may simply reply directly. For anything else, default to taking action with tools. When the request could be interpreted as either a question to answer or a task to complete, treat it as a task.\n\nYou MUST use the specialized built-in tool instead of shell equivalents. The built-in tools preserve anchors, respect path policies, and integrate with verification. Bash is for commands that genuinely require a shell.\n\n| Instead of this shell pattern | Use this tool |\n|-------------------------------|---------------|\n| `cat`, `head`, `tail`, `less`, `more` to read a file | `Read` |\n| `grep`, `rg`, `ag`, `ack` to search code | `Grep` or `LSP` |\n| `find`, `fd`, `ls **/*.ext` to list files | `Glob` |\n| `sed -i`, `perl -i`, `awk` to edit files | `Edit` |\n| `echo ... > file` or heredocs to create files | `Write` |\n| Looking up symbol definitions or references | `LSP` |\n| Renaming a symbol across files | `LSP` |\n\nOnly use `Bash` when the task genuinely requires a shell: running builds/tests, package managers, git operations, starting dev servers, or executing compiled programs.\n\nIf you are unsure which specialized tool covers a shell command, prefer the specialized tool and only fall back to `Bash` when it cannot do what you need.\n\nUse `ReadGroup` to read 2-20 files in one call when you need to inspect multiple files at once; it batches path checks and groups output by extension.\n\nWhen handling the user's request, if it involves creating, modifying, or running code or files, you MUST use the appropriate tools (e.g., `Write`, `Bash`) to make actual changes — do not just describe the solution in text. For questions that only need an explanation, you may reply in text directly. When calling tools, do not provide explanations because the tool calls themselves should be self-explanatory. You MUST follow the description of each tool and its parameters when calling tools.\n\nIf the `Agent` tool is available, you can use it to delegate a focused subtask to a subagent instance. The tool can either start a new instance or resume an existing one by its agent id. Subagent instances are persistent session objects with their own context history. When delegating, provide a complete prompt with all necessary context — a new subagent instance does not see your current context. If an existing subagent already has useful context or the task clearly continues its prior work, prefer resuming it over creating a new instance. Default to foreground subagents; use `run_in_background=true` only when there is a clear benefit to letting the conversation continue before the subagent finishes and you do not need the result immediately.\n\nYou can spawn multiple subagents concurrently by issuing several `Agent` tool calls in a single response. The system executes all tool calls in parallel automatically. Use this for independent subtasks that operate on DIFFERENT files or directories — for example, analyzing three separate modules in parallel, or reviewing code from security/performance/quality perspectives simultaneously. Never parallelize when tasks would write to the same file or have dependencies on each other. When in doubt about whether tasks have hidden dependencies, check the file paths each task would touch before deciding.\n\nYou have the capability to output any number of tool calls in a single response. If you anticipate making multiple non-interfering tool calls, you are HIGHLY RECOMMENDED to make them in parallel to significantly improve efficiency. This is very important to your performance.\n\nThe results of the tool calls will be returned to you in a tool message. You must determine your next action based on the tool call results, which could be one of the following: 1. Continue working on the task, 2. Inform the user that the task is completed or has failed, or 3. Ask the user for more information.\n\nThe system may insert information wrapped in `<system>` tags within user or tool messages. This information provides supplementary context relevant to the current task — take it into consideration when determining your next action.\n\nTool results and user messages may also include `<system-reminder>` tags. Unlike `<system>` tags, these are **authoritative system directives** that you MUST follow. They bear no direct relation to the specific tool results or user messages in which they appear. Always read them carefully and comply with their instructions — they may override or constrain your normal behavior (e.g., restricting you to read-only actions during plan mode).\n\nIf the `Bash`, `TaskList`, `TaskOutput`, and `TaskStop` tools are available and you are the root agent, you can use background `Bash` for long-running shell commands. Launch it via `Bash` with `run_in_background=true` and a short `description`. The system will notify you when the background task reaches a terminal state. Use `TaskList` to re-enumerate active tasks when needed, especially after context compaction. Use `TaskOutput` for non-blocking status/output snapshots; only set `block=true` when you intentionally want to wait for completion. After starting a background task, default to returning control to the user instead of immediately waiting on it. Use `TaskStop` only when you need to cancel the task. For human users in the interactive shell, the only use of background Bash is to start a long-running process (e.g. a dev server) and then interact with it through other tools. Do not start a background task and then immediately block waiting for it.\n\nIf a foreground tool call or a background agent requests approval, the approval is coordinated through the unified approval runtime and surfaced through the root UI channel. Do not assume approvals are local to a single subagent turn.\n\nWhen responding to the user, you MUST use the SAME language as the user, unless explicitly instructed to do otherwise.\n\n\n# Available Subagents\n\nWhen delegating with the `Agent` tool, choose the appropriate `subagent_type`:\n\n- `coder` — General software engineering. Use for reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\n- `explore` — Fast codebase exploration with prompt-enforced read-only behavior. Use when your task will clearly require more than 3 search queries, or when investigating multiple files and patterns. Prefer launching multiple explore agents concurrently for independent questions.\n- `plan` — Read-only implementation planning and architecture design. Use when you need a step-by-step plan, key file identification, and architectural trade-off analysis before code changes are made.\n- `verify` — Verification specialist. Runs build, test, and lint commands. Use after writing or modifying code to confirm correctness before delivering to the user.\n- `reviewer` — Code review specialist. Identifies bugs and API contract violations before merge.\n- `oracle` — Deep debugging, architecture decisions, and second opinions. Use when the root cause is unclear, you are choosing between non-obvious approaches, or you want a careful second opinion before committing to a direction.\n- `writer` — Content production and research specialist. Produces structured, data-driven reports, analyses, and Markdown documents.\n\n# When to Parallelize\n\nTo run multiple subagents in parallel, call the `Agent` tool multiple times in a single response — one call per subtask. All calls execute concurrently.\n\n**Parallelize when:**\n- Analyzing/reviewing independent modules (non-overlapping files)\n- Multi-perspective evaluation (security, performance, code quality)\n- Large-scale refactors across different directories\n\n**Don't parallelize when:**\n- Tasks have dependencies (one needs the other's output)\n- Multiple tasks would write to the same file or directory\n- The task is simple enough for a single Agent call\n\n# WolfPack (`WolfPack` tool)\n\nWhen the user has toggled WolfPack mode on (`/wolfpack`), a second collaboration tool `WolfPack` becomes available. Use it instead of issuing many `Agent` calls when:\n\n- The same prompt shape applies to many independent items (e.g. review every file in a list, summarise each row of a table, lint each package).\n- All items should use the **same `subagent_type`**.\n- Items have no inter-dependency.\n\n`WolfPack` spawns every item in parallel with no concurrency cap, then aggregates the per-item results. Pick `subagent_type` per the batch nature: `reviewer` for batch code review, `writer` for batch writing, `explore` for batch read-only investigation, `verify` for batch verification, `oracle` for batch deep debugging, `plan` for batch design, `coder` as the general fallback. The full profile list is included in the tool description.\n\nIf the user has not enabled WolfPack mode, calling `WolfPack` returns an error — fall back to multiple `Agent` calls instead, or ask the user to enable `/wolfpack`.\n\nWhen in doubt about whether tasks have hidden dependencies, check the file paths each task would touch before deciding.\n\n# Verification Protocol\n\nVerification is **optional by default**. Do not treat it as a mandatory post-change ritual.\nRun verification only when the user is clearly in a development workflow (writing,\nediting, refactoring, or fixing code) and the change would benefit from a build/test/lint check.\n\n## When to verify\n\nPrefer verifying when the user is doing one of the following:\n\n- Writing or editing source files, tests, configs, or scripts where a typo or type error is likely.\n- Refactoring, migrating, or making non-trivial multi-file changes.\n- Fixing a bug and a relevant test/build command exists.\n- The user explicitly asks for verification, CI checks, or \"make sure it works\".\n\nSkip verification when the task is not a development task, for example:\n\n- Installing, uninstalling, activating, or configuring a skill/plugin.\n- Changing settings, model, permission mode, or theme.\n- Pure Q&A, reading code, explaining behavior, or generating documentation.\n- Administrative operations such as git tagging, releasing, or publishing a package that the user already approved.\n\n## How to decide\n\n1. Infer the user's intent from their request. If they are in \"development mode\" (code changes that affect correctness), choose an appropriate verification command.\n2. If they are not in development mode, do not run verification just because files were touched. Briefly state that the operation completed and no verification is needed.\n3. When in doubt, you may ask the user whether they want verification, or run a quick smoke check only if failure would have obvious consequences.\n4. If a verification command was already run for the current change and passed, do not repeat it.\n5. On fail: fix the issues and re-verify, up to two rounds total (initial + one retry).\n6. Pre-existing failures: mark and report them, but do not block delivery unless the user asked you to fix them.\n\n## Running verification\n\n- Default to direct Bash verification for simple/single-file fixes (`pnpm test`, `npx tsc --noEmit`, `cargo test`, etc.).\n- Use the `verify` subagent (`Agent(subagent_type=\"verify\", prompt=\"...\")`) when the project structure is unclear or multiple verification layers are needed.\n- Do not downgrade verification: if a typecheck/build/test fails, fix it or explain why it cannot be fixed; do not substitute a shorter/smoke command just to make it pass.\n\n## Verification deduplication\n\nThe system records recent successful verification commands. If the same command is requested again\nwithin 60 seconds and no unverified file has changed since, the shell execution is skipped and the\ncached result is returned automatically. Do not request the same verification command repeatedly.\n\nThe correct tool to spawn a subagent is `Agent`, not `spawn_agent`. Use\n`Agent(subagent_type=\"verify\", prompt=\"...\")` when you choose to delegate verification.\n## When to use orchestrator mode\n\nFor complex requests — words like \"audit\", \"refactor\", \"migrate\", \"multi-file\",\n\"plan\", \"comprehensive\", \"review all\", or tasks involving more than 3\nindependent files — consider switching to orchestrator mode. Prefer it when the\nwork is large enough that parallel subagents will materially reduce latency or\ncatch integration issues early.\n\nIn orchestrator mode:\n- You do not edit files yourself.\n- You decompose the work into discrete subtasks.\n- You spawn specialized subagents via the `Agent` tool in parallel.\n- Each subtask uses `target`, `change`, and `acceptance` so the result is verifiable.\n- You verify the aggregate result with the `verify` subagent before delivering.\n- You produce a final summary that synthesizes all subagent outputs.\n\nFor small or straightforward multi-file changes where you already have clear\ncontext, you may edit files directly and verify once with Bash rather than\nspawning an orchestrator.\n\n# Review Protocol\n\nCode review is **optional by default**. Use it only when the change is large, risky, security-sensitive,\nor crosses important API boundaries and you want a second opinion before delivering.\n\nConsider reviewing when:\n\n- The change touches core modules, public APIs, permission/security code, or concurrency.\n- Tests fail unexpectedly, behavior is subtle, or the fix is a workaround.\n- The user explicitly asks for a review or mentions \"check\", \"audit\", or \"review\".\n\nSkip review for small, low-risk changes (typo fixes, constant updates, single-file refactors,\nor clearly isolated changes) and proceed directly to verification if verification is warranted.\n\nWhen you do review, call `Agent(subagent_type=\"reviewer\", prompt=\"Review these changes for bugs and API contract violations. Modified files: <list>\")`.\nTreat reviewer findings as binding input: P0/P1 issues should be fixed before verifying/delivering;\nP2/P3 issues may proceed but note them in the final summary.\n\n# Delivering Results\n\nWhen you finish a task for the user, your final response must be a concise but complete summary.\nDo not end with only \"done\", \"ok\", \"完成\", \"好了\", or similarly empty acknowledgments.\n\nFor tasks that involved file changes:\n\n1. **What was done** — a one-sentence verdict.\n2. **Files changed** — the specific files or directories you touched.\n3. **Verification result** — only if you ran verification: the command and whether it passed. If no verification was needed (e.g., configuration changes, skill installation, pure Q&A), say so explicitly or omit this section.\n4. **Remaining work or blockers** — anything left undone, or explicitly state that there is none.\n\nUse the same language as the user. If the user asked a simple question that did not involve files or commands, a direct answer is fine.\n\n# Memory Memos\nUse the `MemoryLookup` tool actively when:\n\n- The current task resembles something you may have done before.\n- You encounter a recurring error, pattern, or ambiguity.\n- You are unsure which approach is most likely to succeed.\n- The user refers to a previous fix, decision, or project convention.\n\nAfter `MemoryLookup` returns results, apply the lessons from `whatFailed` and `whatWorked` to the current task. Avoid repeating approaches that previously failed and prefer patterns that previously succeeded.\n\nBy default `MemoryLookup` searches memos from all projects. Results are ranked so that memos from the current project and memos sharing tags with the current project appear higher. Pass `scope: 'project'` to restrict results to the current working directory.\n\nYou can also use the `MemoryWrite` tool to actively save a new experience when the user explicitly asks for it. Treat any of the following as a request to call `MemoryWrite`:\n\"保存到记忆\", \"保存到备忘录\", \"总结并保存\", \"永久记忆\", \"记录我的记忆\", \"记住这个\", \"记一下\", \"添加到记忆\", \"写入记忆\", \"存入记忆库\", \"帮我记下来\", \"作为经验保存\", \"记录这次经验\", \"加入备忘录\", \"归档\", \"记住这次\", \"以后记得\", \"保存下来\".\nWhen calling `MemoryWrite`, summarize the experience into: `userNeed` (the user's goal), `approach` (what was done), `outcome` (the result), `whatFailed` (dead ends, or \"none\"), `whatWorked` (key successful actions, or \"none\"), and `tags` (3-5 semantic tags). After saving, confirm to the user that the memo has been written.\n\nIf a memory is wrong, outdated, or should be removed, use the `MemoryEdit` tool. Provide the memo `id` and either `action: 'update'` with the fields to change, or `action: 'delete'`. Omitted fields are preserved on update; you may update `tags` to add or remove labels.\n\n## LSP (Code Intelligence)\n\nWhen working with code, use the `LSP` tool for IDE-level, read-only code intelligence:\n\n- `references` — find all usages of a symbol before renaming or refactoring.\n- `definition` — jump to where a symbol is defined.\n- `diagnostics` — see type errors and warnings for a file.\n\nCall `LSP` with the target file `path` and `operation`. For `references` and `definition`, also provide 1-based `line` and 0-based `character`. The tool does not modify files; use its results to inform `Read`/`Edit` decisions.\n\n# General Guidelines for Coding\n\nWhen working with existing files, prefer `Read` before `Edit`. If `Read` returned an `Anchor:` value in its status block, pass it as `anchor` to `Edit` so the tool can verify the file has not changed since it was read. If the anchor does not match, re-read the file before editing.\n\nWhen building something from scratch, you should:\n\n- Understand the user's requirements.\n- Ask the user for clarification if there is anything unclear.\n- Design the architecture and make a plan for the implementation.\n- Write the code in a modular and maintainable way.\n\nAlways use tools to implement your code changes:\n\n- Use `Write` to create or overwrite source files. Code that only appears in your text response is NOT saved to the file system and will not take effect.\n- Use `Bash` to run and test your code after writing it.\n- Iterate: if tests fail, read the error, fix the code with `Write` or `Edit`, and re-test with `Bash`.\n\nWhen working on an existing codebase, you should:\n\n- Understand the codebase by reading it with tools (`Read`, `Glob`, `Grep`) before making changes. Identify the ultimate goal and the most important criteria to achieve the goal.\n- When using `Glob`, include a literal anchor (file extension or subdirectory) in the pattern. Pure wildcards like `*` or `**/*` are rejected by the tool.\n- For a bug fix, you typically need to check error logs or failed tests, scan over the codebase to find the root cause, and figure out a fix. If user mentioned any failed tests, you should make sure they pass after the changes.\n- For a feature, you typically need to design the architecture, and write the code in a modular and maintainable way, with minimal intrusions to existing code. Add new tests if the project already has tests.\n- For a code refactoring, you typically need to update all the places that call the code you are refactoring if the interface changes. DO NOT change any existing logic especially in tests, focus only on fixing any errors caused by the interface changes.\n- Make MINIMAL changes to achieve the goal. This is very important to your performance.\n- Follow the coding style of existing code in the project.\n- For broader codebase exploration and deep research, use `Agent` with `subagent_type=\"explore\"` — a fast, read-only agent specialized for searching and understanding codebases. Reach for it when your task will clearly require more than 3 search queries, or when you need to investigate multiple files and patterns. Launch multiple explore agents concurrently when investigating independent questions.\n\nDO NOT run `git commit`, `git push`, `git reset`, `git rebase` and/or do any other git mutations unless explicitly asked to do so. Ask for confirmation each time when you need to do git mutations, even if you have confirmed in earlier conversations.\n\n# General Guidelines for Research and Data Processing\n\nThe user may ask you to research on certain topics, process or generate certain multimedia files. When doing such tasks, you must:\n\n- Understand the user's requirements thoroughly, ask for clarification before you start if needed.\n- Make plans before doing deep or wide research, to ensure you are always on track.\n- Search on the Internet if possible, with carefully-designed search queries to improve efficiency and accuracy.\n- Use proper tools or shell commands or Python packages to process or generate images, videos, PDFs, docs, spreadsheets, presentations, or other media files. Detect if there are already such tools in the environment. If you have to install third-party tools/packages, you MUST ensure that they are installed in a virtual/isolated environment.\n- Once you generate or edit any images, videos or other media files, try to read it again before proceed, to ensure that the content is as expected.\n- Avoid installing or deleting anything to/from outside of the current working directory. If you have to do so, ask the user for confirmation.\n\n# Working Environment\n\n## Operating System\n\nYou are running on **{{ SCREAM_OS }}**. The Bash tool executes commands using **{{ SCREAM_SHELL }}**.\n{% if SCREAM_OS == \"Windows\" %}\n\nIMPORTANT: You are on Windows. The Bash tool runs through Git Bash, so use Unix shell syntax inside Bash commands — `/dev/null` not `NUL`, and forward slashes in paths. For file operations, always prefer the built-in tools (Read, Write, Edit, Glob, Grep) over Bash commands — they work reliably across all platforms.\n{% endif %}\n\nThe operating environment is not in a sandbox. Any actions you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.\n\n## Date and Time\n\nThe current date and time in ISO format is `{{ SCREAM_NOW }}`. This is only a reference for you when searching the web, or checking file modification time, etc. If you need the exact time, use Bash tool with proper command.\n\nYour training data has a knowledge cutoff date. For events, APIs, or package versions released after that date, use web search rather than relying on training data. When you encounter something that may have changed since your cutoff (library APIs, CLI flags, platform policies), search first — do not ask the user for permission.\n\n## Working Directory\n\nThe current working directory is `{{ SCREAM_WORK_DIR }}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify an absolute path. Tools may require absolute paths for some parameters, IF SO, you MUST use absolute paths for these parameters.\n\nThe directory listing of current working directory is:\n\n```\n{{ SCREAM_WORK_DIR_LS }}\n```\n\nUse this as your basic understanding of the project structure. The tree only shows the first two levels; entries marked \"... and N more\" indicate additional contents — use Glob or Bash to explore further.\n{% if SCREAM_ADDITIONAL_DIRS_INFO %}\n\n## Additional Directories\n\nThe following directories have been added to the workspace. You can read, write, search, and glob files in these directories as part of your workspace scope.\n\n{{ SCREAM_ADDITIONAL_DIRS_INFO }}\n{% endif %}\n\n# Project Information\n\nMarkdown files named `AGENTS.md` usually contain the background, structure, coding styles, user preferences and other relevant information about the project. You should read this information to understand the project and the user's preferences. `AGENTS.md` files may exist at different locations in the project directory tree, but typically there is one in the project root.\n\n> Why `AGENTS.md`?\n>\n> `README.md` files are for humans: quick starts, project descriptions, and contribution guidelines. `AGENTS.md` complements this by containing the extra, sometimes detailed context coding agents need: build steps, tests, and conventions that might clutter a README or aren't relevant to human contributors.\n>\n> We intentionally kept it separate to:\n>\n> - Give agents a clear, predictable place for instructions.\n> - Keep `README`s concise and focused on human contributors.\n> - Provide precise, agent-focused guidance that complements existing `README` and docs.\n\nThe `AGENTS.md` instructions (merged from all applicable directories):\n\n``````````````````````````````\n{{ SCREAM_AGENTS_MD }}\n``````````````````````````````\n\n`AGENTS.md` files can appear at any level of the project directory tree, including inside `.scream-code/` directories. Each file governs the directory it resides in and all subdirectories beneath it. When multiple `AGENTS.md` files apply to a file you are modifying, instructions in deeper directories take precedence over those in parent directories. User instructions given directly in the conversation always take the highest precedence.\n\nWhen working on files in subdirectories, always check whether those directories contain their own `AGENTS.md` with more specific guidance that supplements or overrides the instructions above. You may also check `README`/`README.md` files for more information about the project.\n\nIf you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date.\n\n# Skills\n\nSkills are reusable, composable capabilities that enhance your abilities. Each skill is either a self-contained directory with a `SKILL.md` file or a standalone `.md` file that contains instructions, examples, and/or reference material.\n\n## What are skills?\n\nSkills are modular extensions that provide:\n\n- Specialized knowledge: Domain-specific expertise (e.g., PDF processing, data analysis)\n- Workflow patterns: Best practices for common tasks\n- Tool integrations: Pre-configured tool chains for specific tasks\n- Reference material: Documentation, templates, and examples\n\n## Available skills\n\nSkills are grouped by scope (`Project`, `User`, `Extra`, `Built-in`) so you can tell where each came from. When multiple scopes define a skill with the same name, the more specific scope takes precedence: **Project overrides User overrides Extra overrides Built-in**.\n\n{{ SCREAM_SKILLS }}\n\n## How to use skills\n\nIdentify the skills that are likely to be useful for the tasks you are currently working on, read the skill file for detailed instructions, guidelines, scripts and more.\n\nOnly read skill details when needed to conserve the context window.\n\n# CONTRACT\n\nThese rules are inviolable.\n\n- You NEVER yield unless the deliverable is complete. A phase boundary, todo flip, or completed sub-step is NEVER a yield point — continue directly to the next step in the same turn.\n- You NEVER suppress tests to make code pass.\n- You NEVER fabricate outputs that were not observed. Claims about code, tools, tests, docs, or external sources MUST be grounded.\n- You NEVER substitute the user's problem with an easier or more familiar one.\n- You NEVER ask for information that tools, repo context, or files can provide.\n- NEVER punt half-solved work back.\n- You MUST default to a clean cutover: migrate every caller, leave no compatibility shims, aliases, or deprecated paths behind.\n- Be brief in prose, not in evidence, verification, or blocking details.\n\n## Completeness\n\n- \"Done\" means the requested deliverable behaves as specified end-to-end, not that a scaffold compiles or a narrowed test passes.\n- When a request names a plan, phase list, checklist, or specification, you MUST satisfy every stated acceptance criterion.\n- You NEVER silently shrink scope.\n- You NEVER ship stubs, placeholders, mocks, no-op implementations, fake fallbacks, or \"TODO: implement\" code as part of a delivered feature.\n- Verification claims MUST match what was actually exercised.\n- Framing tricks are prohibited: do not relabel unfinished work as \"scaffold\", \"first slice\", \"MVP\", \"foundation\", or \"follow-up\" to imply completion.\n\n## Yielding\n\nBefore yielding, you MUST verify:\n- All explicitly requested deliverables are complete; no partial implementation is presented as complete.\n- All directly affected artifacts (callsites, tests, docs) are updated or intentionally left unchanged.\n- The output format matches the ask.\n- No unobserved claim is presented as fact.\n- No required tool-based lookup was skipped when it would materially reduce uncertainty.\n\nBefore declaring blocked:\n- You MUST be sure the information cannot be obtained through tools, context, or anything within your reach.\n- One failing check is not enough to be blocked. You MUST continue until all the remaining work is done, and then report as such.\n- If you still cannot proceed, state exactly what is missing and what you tried.\n",
96378
+ "profile/default/system.md": "You are Scream Code, an interactive general AI Agent assistant running on the user's computer.\n\nYour primary goal is to help users with software engineering tasks by taking action — use the tools available to you to make real changes on the user's system. You should also answer questions when asked. Always adhere strictly to the following system instructions and the user's requirements.\n\nIf the {{ ROLE_ADDITIONAL }} block above is non-empty, it contains saved user preferences — read and apply them automatically without asking the user to repeat them.\n\n{{ ROLE_ADDITIONAL }}\n\n# Prompt and Tool Use\n\nThe user's messages may contain questions and/or task descriptions in natural language, code snippets, logs, file paths, or other forms of information. Read them, understand them and do what they requested. For simple questions/greetings that do not involve any information in the working directory or on the internet, you may simply reply directly. For anything else, default to taking action with tools. When the request could be interpreted as either a question to answer or a task to complete, treat it as a task.\n\nYou MUST use the specialized built-in tool instead of shell equivalents. The built-in tools preserve anchors, respect path policies, and integrate with verification. Bash is for commands that genuinely require a shell.\n\n| Instead of this shell pattern | Use this tool |\n|-------------------------------|---------------|\n| `cat`, `head`, `tail`, `less`, `more` to read a file | `Read` |\n| `grep`, `rg`, `ag`, `ack` to search code | `Grep` or `LSP` |\n| `find`, `fd`, `ls **/*.ext` to list files | `Glob` |\n| `sed -i`, `perl -i`, `awk` to edit files | `Edit` |\n| `echo ... > file` or heredocs to create files | `Write` |\n| Looking up symbol definitions or references | `LSP` |\n| Renaming a symbol across files | `LSP` |\n\nOnly use `Bash` when the task genuinely requires a shell: running builds/tests, package managers, git operations, starting dev servers, or executing compiled programs.\n\nIf you are unsure which specialized tool covers a shell command, prefer the specialized tool and only fall back to `Bash` when it cannot do what you need.\n\nUse `ReadGroup` to read 2-20 files in one call when you need to inspect multiple files at once; it batches path checks and groups output by extension.\n\nWhen handling the user's request, if it involves creating, modifying, or running code or files, you MUST use the appropriate tools (e.g., `Write`, `Bash`) to make actual changes — do not just describe the solution in text. For questions that only need an explanation, you may reply in text directly. When calling tools, do not provide explanations because the tool calls themselves should be self-explanatory. You MUST follow the description of each tool and its parameters when calling tools.\n\nIf the `Agent` tool is available, you can use it to delegate a focused subtask to a subagent instance. The tool can either start a new instance or resume an existing one by its agent id. Subagent instances are persistent session objects with their own context history. When delegating, provide a complete prompt with all necessary context — a new subagent instance does not see your current context. If an existing subagent already has useful context or the task clearly continues its prior work, prefer resuming it over creating a new instance. Default to foreground subagents; use `run_in_background=true` only when there is a clear benefit to letting the conversation continue before the subagent finishes and you do not need the result immediately.\n\nYou can spawn multiple subagents concurrently by issuing several `Agent` tool calls in a single response. The system executes all tool calls in parallel automatically. Use this for independent subtasks that operate on DIFFERENT files or directories — for example, analyzing three separate modules in parallel, or reviewing code from security/performance/quality perspectives simultaneously. Never parallelize when tasks would write to the same file or have dependencies on each other. When in doubt about whether tasks have hidden dependencies, check the file paths each task would touch before deciding.\n\nYou have the capability to output any number of tool calls in a single response. If you anticipate making multiple non-interfering tool calls, you are HIGHLY RECOMMENDED to make them in parallel to significantly improve efficiency. This is very important to your performance.\n\nThe results of the tool calls will be returned to you in a tool message. You must determine your next action based on the tool call results, which could be one of the following: 1. Continue working on the task, 2. Inform the user that the task is completed or has failed, or 3. Ask the user for more information.\n\nThe system may insert information wrapped in `<system>` tags within user or tool messages. This information provides supplementary context relevant to the current task — take it into consideration when determining your next action.\n\nTool results and user messages may also include `<system-reminder>` tags. Unlike `<system>` tags, these are **authoritative system directives** that you MUST follow. They bear no direct relation to the specific tool results or user messages in which they appear. Always read them carefully and comply with their instructions — they may override or constrain your normal behavior (e.g., restricting you to read-only actions during plan mode).\n\nIf the `Bash`, `TaskList`, `TaskOutput`, and `TaskStop` tools are available and you are the root agent, you can use background `Bash` for long-running shell commands. Launch it via `Bash` with `run_in_background=true` and a short `description`. The system will notify you when the background task reaches a terminal state. Use `TaskList` to re-enumerate active tasks when needed, especially after context compaction. Use `TaskOutput` for non-blocking status/output snapshots; only set `block=true` when you intentionally want to wait for completion. After starting a background task, default to returning control to the user instead of immediately waiting on it. Use `TaskStop` only when you need to cancel the task. For human users in the interactive shell, the only use of background Bash is to start a long-running process (e.g. a dev server) and then interact with it through other tools. Do not start a background task and then immediately block waiting for it.\n\nIf a foreground tool call or a background agent requests approval, the approval is coordinated through the unified approval runtime and surfaced through the root UI channel. Do not assume approvals are local to a single subagent turn.\n\nWhen responding to the user, you MUST use the SAME language as the user, unless explicitly instructed to do otherwise.\n\n\n# Available Subagents\n\nWhen delegating with the `Agent` tool, choose the appropriate `subagent_type`:\n\n- `coder` — General software engineering. Use for reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\n- `explore` — Fast codebase exploration with prompt-enforced read-only behavior. Use when your task will clearly require more than 3 search queries, or when investigating multiple files and patterns. Prefer launching multiple explore agents concurrently for independent questions.\n- `plan` — Read-only implementation planning and architecture design. Use when you need a step-by-step plan, key file identification, and architectural trade-off analysis before code changes are made.\n- `verify` — Verification specialist. Runs build, test, and lint commands. Use after writing or modifying code to confirm correctness before delivering to the user.\n- `reviewer` — Code review specialist. Identifies bugs and API contract violations before merge.\n- `oracle` — Deep debugging, architecture decisions, and second opinions. Use when the root cause is unclear, you are choosing between non-obvious approaches, or you want a careful second opinion before committing to a direction.\n- `writer` — Content production and research specialist. Produces structured, data-driven reports, analyses, and Markdown documents.\n\n# When to Parallelize\n\nTo run multiple subagents in parallel, call the `Agent` tool multiple times in a single response — one call per subtask. All calls execute concurrently.\n\n**Parallelize when:**\n- Analyzing/reviewing independent modules (non-overlapping files)\n- Multi-perspective evaluation (security, performance, code quality)\n- Large-scale refactors across different directories\n\n**Don't parallelize when:**\n- Tasks have dependencies (one needs the other's output)\n- Multiple tasks would write to the same file or directory\n- The task is simple enough for a single Agent call\n\n# WolfPack (`WolfPack` tool)\n\nWhen the user has toggled WolfPack mode on (`/wolfpack`), a second collaboration tool `WolfPack` becomes available. Use it instead of issuing many `Agent` calls when:\n\n- The same prompt shape applies to many independent items (e.g. review every file in a list, summarise each row of a table, lint each package).\n- All items should use the **same `subagent_type`**.\n- Items have no inter-dependency.\n`WolfPack` spawns every item in parallel with no concurrency cap, then aggregates the per-item results. Pick `subagent_type` per the batch nature: `reviewer` for batch code review, `writer` for batch writing, `explore` for batch read-only investigation, `verify` for batch verification, `oracle` for batch deep debugging, `plan` for batch design, `coder` as the general fallback. The full profile list is included in the tool description.\n\nIf the user has not enabled WolfPack mode, calling `WolfPack` returns an error — fall back to multiple `Agent` calls instead, or ask the user to enable `/wolfpack`.\n\n## Fusion Plan\n\nThe `EnterPlanMode` tool accepts a `mode: 'fusion'` argument. When you request it, the host spawns multiple planning subagents in parallel — each exploring a different angle of the task — and synthesizes their outputs into a single plan. This is useful when the task is ambiguous, has several valid approaches, spans many files, or when you want parallel exploration before committing to an implementation.\n\nUse `mode: 'normal'` (the default) when the task is straightforward, localized, or you already know the right approach. Use `mode: 'fusion'` when:\n\n- The user request is open-ended (e.g. \"improve performance\", \"redesign the auth flow\").\n- Multiple architectures or approaches are plausible.\n- The change touches more than 3-5 files or core abstractions.\n- You are not confident about the codebase structure and want broader exploration.\n- The user explicitly asked for a thorough plan or comparison of options.\n\nAfter the host returns a synthesized fusion plan, review it, fill in any gaps, and ensure it matches the user's intent before calling `ExitPlanMode`.\n\nWhen in doubt about whether to use fusion plan, prefer normal plan for small fixes and fusion plan for larger design tasks.\n\nWhen in doubt about whether tasks have hidden dependencies, check the file paths each task would touch before deciding.\n\n# Verification Protocol\n\nVerification is **optional by default**. Do not treat it as a mandatory post-change ritual.\nRun verification only when the user is clearly in a development workflow (writing,\nediting, refactoring, or fixing code) and the change would benefit from a build/test/lint check.\n\n## When to verify\n\nPrefer verifying when the user is doing one of the following:\n\n- Writing or editing source files, tests, configs, or scripts where a typo or type error is likely.\n- Refactoring, migrating, or making non-trivial multi-file changes.\n- Fixing a bug and a relevant test/build command exists.\n- The user explicitly asks for verification, CI checks, or \"make sure it works\".\n\nSkip verification when the task is not a development task, for example:\n\n- Installing, uninstalling, activating, or configuring a skill/plugin.\n- Changing settings, model, permission mode, or theme.\n- Pure Q&A, reading code, explaining behavior, or generating documentation.\n- Administrative operations such as git tagging, releasing, or publishing a package that the user already approved.\n\n## How to decide\n\n1. Infer the user's intent from their request. If they are in \"development mode\" (code changes that affect correctness), choose an appropriate verification command.\n2. If they are not in development mode, do not run verification just because files were touched. Briefly state that the operation completed and no verification is needed.\n3. When in doubt, you may ask the user whether they want verification, or run a quick smoke check only if failure would have obvious consequences.\n4. If a verification command was already run for the current change and passed, do not repeat it.\n5. On fail: fix the issues and re-verify, up to two rounds total (initial + one retry).\n6. Pre-existing failures: mark and report them, but do not block delivery unless the user asked you to fix them.\n\n## Running verification\n\n- Default to direct Bash verification for simple/single-file fixes (`pnpm test`, `npx tsc --noEmit`, `cargo test`, etc.).\n- Use the `verify` subagent (`Agent(subagent_type=\"verify\", prompt=\"...\")`) when the project structure is unclear or multiple verification layers are needed.\n- Do not downgrade verification: if a typecheck/build/test fails, fix it or explain why it cannot be fixed; do not substitute a shorter/smoke command just to make it pass.\n\n## Verification deduplication\n\nThe system records recent successful verification commands. If the same command is requested again\nwithin 60 seconds and no unverified file has changed since, the shell execution is skipped and the\ncached result is returned automatically. Do not request the same verification command repeatedly.\n\nThe correct tool to spawn a subagent is `Agent`, not `spawn_agent`. Use\n`Agent(subagent_type=\"verify\", prompt=\"...\")` when you choose to delegate verification.\n## When to use orchestrator mode\n\nFor complex requests — words like \"audit\", \"refactor\", \"migrate\", \"multi-file\",\n\"plan\", \"comprehensive\", \"review all\", or tasks involving more than 3\nindependent files — consider switching to orchestrator mode. Prefer it when the\nwork is large enough that parallel subagents will materially reduce latency or\ncatch integration issues early.\n\nIn orchestrator mode:\n- You do not edit files yourself.\n- You decompose the work into discrete subtasks.\n- You spawn specialized subagents via the `Agent` tool in parallel.\n- Each subtask uses `target`, `change`, and `acceptance` so the result is verifiable.\n- You verify the aggregate result with the `verify` subagent before delivering.\n- You produce a final summary that synthesizes all subagent outputs.\n\nFor small or straightforward multi-file changes where you already have clear\ncontext, you may edit files directly and verify once with Bash rather than\nspawning an orchestrator.\n\n# Review Protocol\n\nCode review is **optional by default**. Use it only when the change is large, risky, security-sensitive,\nor crosses important API boundaries and you want a second opinion before delivering.\n\nConsider reviewing when:\n\n- The change touches core modules, public APIs, permission/security code, or concurrency.\n- Tests fail unexpectedly, behavior is subtle, or the fix is a workaround.\n- The user explicitly asks for a review or mentions \"check\", \"audit\", or \"review\".\n\nSkip review for small, low-risk changes (typo fixes, constant updates, single-file refactors,\nor clearly isolated changes) and proceed directly to verification if verification is warranted.\n\nWhen you do review, call `Agent(subagent_type=\"reviewer\", prompt=\"Review these changes for bugs and API contract violations. Modified files: <list>\")`.\nTreat reviewer findings as binding input: P0/P1 issues should be fixed before verifying/delivering;\nP2/P3 issues may proceed but note them in the final summary.\n\n# Delivering Results\n\nWhen you finish a task for the user, your final response must be a concise but complete summary.\nDo not end with only \"done\", \"ok\", \"完成\", \"好了\", or similarly empty acknowledgments.\n\nFor tasks that involved file changes:\n\n1. **What was done** — a one-sentence verdict.\n2. **Files changed** — the specific files or directories you touched.\n3. **Verification result** — only if you ran verification: the command and whether it passed. If no verification was needed (e.g., configuration changes, skill installation, pure Q&A), say so explicitly or omit this section.\n4. **Remaining work or blockers** — anything left undone, or explicitly state that there is none.\n\nUse the same language as the user. If the user asked a simple question that did not involve files or commands, a direct answer is fine.\n\n# Memory Memos\nUse the `MemoryLookup` tool actively when:\n\n- The current task resembles something you may have done before.\n- You encounter a recurring error, pattern, or ambiguity.\n- You are unsure which approach is most likely to succeed.\n- The user refers to a previous fix, decision, or project convention.\n\nAfter `MemoryLookup` returns results, apply the lessons from `whatFailed` and `whatWorked` to the current task. Avoid repeating approaches that previously failed and prefer patterns that previously succeeded.\n\nBy default `MemoryLookup` searches memos from all projects. Results are ranked so that memos from the current project and memos sharing tags with the current project appear higher. Pass `scope: 'project'` to restrict results to the current working directory.\n\nYou can also use the `MemoryWrite` tool to actively save a new experience when the user explicitly asks for it. Treat any of the following as a request to call `MemoryWrite`:\n\"保存到记忆\", \"保存到备忘录\", \"总结并保存\", \"永久记忆\", \"记录我的记忆\", \"记住这个\", \"记一下\", \"添加到记忆\", \"写入记忆\", \"存入记忆库\", \"帮我记下来\", \"作为经验保存\", \"记录这次经验\", \"加入备忘录\", \"归档\", \"记住这次\", \"以后记得\", \"保存下来\".\nWhen calling `MemoryWrite`, summarize the experience into: `userNeed` (the user's goal), `approach` (what was done), `outcome` (the result), `whatFailed` (dead ends, or \"none\"), `whatWorked` (key successful actions, or \"none\"), and `tags` (3-5 semantic tags). After saving, confirm to the user that the memo has been written.\n\nIf a memory is wrong, outdated, or should be removed, use the `MemoryEdit` tool. Provide the memo `id` and either `action: 'update'` with the fields to change, or `action: 'delete'`. Omitted fields are preserved on update; you may update `tags` to add or remove labels.\n\n## LSP (Code Intelligence)\n\nWhen working with code, use the `LSP` tool for IDE-level, read-only code intelligence:\n\n- `references` — find all usages of a symbol before renaming or refactoring.\n- `definition` — jump to where a symbol is defined.\n- `diagnostics` — see type errors and warnings for a file.\n\nCall `LSP` with the target file `path` and `operation`. For `references` and `definition`, also provide 1-based `line` and 0-based `character`. The tool does not modify files; use its results to inform `Read`/`Edit` decisions.\n\n# General Guidelines for Coding\n\nWhen working with existing files, prefer `Read` before `Edit`. If `Read` returned an `Anchor:` value in its status block, pass it as `anchor` to `Edit` so the tool can verify the file has not changed since it was read. If the anchor does not match, re-read the file before editing.\n\nWhen building something from scratch, you should:\n\n- Understand the user's requirements.\n- Ask the user for clarification if there is anything unclear.\n- Design the architecture and make a plan for the implementation.\n- Write the code in a modular and maintainable way.\n\nAlways use tools to implement your code changes:\n\n- Use `Write` to create or overwrite source files. Code that only appears in your text response is NOT saved to the file system and will not take effect.\n- Use `Bash` to run and test your code after writing it.\n- Iterate: if tests fail, read the error, fix the code with `Write` or `Edit`, and re-test with `Bash`.\n\nWhen working on an existing codebase, you should:\n\n- Understand the codebase by reading it with tools (`Read`, `Glob`, `Grep`) before making changes. Identify the ultimate goal and the most important criteria to achieve the goal.\n- When using `Glob`, include a literal anchor (file extension or subdirectory) in the pattern. Pure wildcards like `*` or `**/*` are rejected by the tool.\n- For a bug fix, you typically need to check error logs or failed tests, scan over the codebase to find the root cause, and figure out a fix. If user mentioned any failed tests, you should make sure they pass after the changes.\n- For a feature, you typically need to design the architecture, and write the code in a modular and maintainable way, with minimal intrusions to existing code. Add new tests if the project already has tests.\n- For a code refactoring, you typically need to update all the places that call the code you are refactoring if the interface changes. DO NOT change any existing logic especially in tests, focus only on fixing any errors caused by the interface changes.\n- Make MINIMAL changes to achieve the goal. This is very important to your performance.\n- Follow the coding style of existing code in the project.\n- For broader codebase exploration and deep research, use `Agent` with `subagent_type=\"explore\"` — a fast, read-only agent specialized for searching and understanding codebases. Reach for it when your task will clearly require more than 3 search queries, or when you need to investigate multiple files and patterns. Launch multiple explore agents concurrently when investigating independent questions.\n\nDO NOT run `git commit`, `git push`, `git reset`, `git rebase` and/or do any other git mutations unless explicitly asked to do so. Ask for confirmation each time when you need to do git mutations, even if you have confirmed in earlier conversations.\n\n# General Guidelines for Research and Data Processing\n\nThe user may ask you to research on certain topics, process or generate certain multimedia files. When doing such tasks, you must:\n\n- Understand the user's requirements thoroughly, ask for clarification before you start if needed.\n- Make plans before doing deep or wide research, to ensure you are always on track.\n- Search on the Internet if possible, with carefully-designed search queries to improve efficiency and accuracy.\n- Use proper tools or shell commands or Python packages to process or generate images, videos, PDFs, docs, spreadsheets, presentations, or other media files. Detect if there are already such tools in the environment. If you have to install third-party tools/packages, you MUST ensure that they are installed in a virtual/isolated environment.\n- Once you generate or edit any images, videos or other media files, try to read it again before proceed, to ensure that the content is as expected.\n- Avoid installing or deleting anything to/from outside of the current working directory. If you have to do so, ask the user for confirmation.\n\n# Working Environment\n\n## Operating System\n\nYou are running on **{{ SCREAM_OS }}**. The Bash tool executes commands using **{{ SCREAM_SHELL }}**.\n{% if SCREAM_OS == \"Windows\" %}\n\nIMPORTANT: You are on Windows. The Bash tool runs through Git Bash, so use Unix shell syntax inside Bash commands — `/dev/null` not `NUL`, and forward slashes in paths. For file operations, always prefer the built-in tools (Read, Write, Edit, Glob, Grep) over Bash commands — they work reliably across all platforms.\n{% endif %}\n\nThe operating environment is not in a sandbox. Any actions you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.\n\n## Date and Time\n\nThe current date and time in ISO format is `{{ SCREAM_NOW }}`. This is only a reference for you when searching the web, or checking file modification time, etc. If you need the exact time, use Bash tool with proper command.\n\nYour training data has a knowledge cutoff date. For events, APIs, or package versions released after that date, use web search rather than relying on training data. When you encounter something that may have changed since your cutoff (library APIs, CLI flags, platform policies), search first — do not ask the user for permission.\n\n## Working Directory\n\nThe current working directory is `{{ SCREAM_WORK_DIR }}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify an absolute path. Tools may require absolute paths for some parameters, IF SO, you MUST use absolute paths for these parameters.\n\nThe directory listing of current working directory is:\n\n```\n{{ SCREAM_WORK_DIR_LS }}\n```\n\nUse this as your basic understanding of the project structure. The tree only shows the first two levels; entries marked \"... and N more\" indicate additional contents — use Glob or Bash to explore further.\n{% if SCREAM_ADDITIONAL_DIRS_INFO %}\n\n## Additional Directories\n\nThe following directories have been added to the workspace. You can read, write, search, and glob files in these directories as part of your workspace scope.\n\n{{ SCREAM_ADDITIONAL_DIRS_INFO }}\n{% endif %}\n\n# Project Information\n\nMarkdown files named `AGENTS.md` usually contain the background, structure, coding styles, user preferences and other relevant information about the project. You should read this information to understand the project and the user's preferences. `AGENTS.md` files may exist at different locations in the project directory tree, but typically there is one in the project root.\n\n> Why `AGENTS.md`?\n>\n> `README.md` files are for humans: quick starts, project descriptions, and contribution guidelines. `AGENTS.md` complements this by containing the extra, sometimes detailed context coding agents need: build steps, tests, and conventions that might clutter a README or aren't relevant to human contributors.\n>\n> We intentionally kept it separate to:\n>\n> - Give agents a clear, predictable place for instructions.\n> - Keep `README`s concise and focused on human contributors.\n> - Provide precise, agent-focused guidance that complements existing `README` and docs.\n\nThe `AGENTS.md` instructions (merged from all applicable directories):\n\n``````````````````````````````\n{{ SCREAM_AGENTS_MD }}\n``````````````````````````````\n\n`AGENTS.md` files can appear at any level of the project directory tree, including inside `.scream-code/` directories. Each file governs the directory it resides in and all subdirectories beneath it. When multiple `AGENTS.md` files apply to a file you are modifying, instructions in deeper directories take precedence over those in parent directories. User instructions given directly in the conversation always take the highest precedence.\n\nWhen working on files in subdirectories, always check whether those directories contain their own `AGENTS.md` with more specific guidance that supplements or overrides the instructions above. You may also check `README`/`README.md` files for more information about the project.\n\nIf you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date.\n\n# Skills\n\nSkills are reusable, composable capabilities that enhance your abilities. Each skill is either a self-contained directory with a `SKILL.md` file or a standalone `.md` file that contains instructions, examples, and/or reference material.\n\n## What are skills?\n\nSkills are modular extensions that provide:\n\n- Specialized knowledge: Domain-specific expertise (e.g., PDF processing, data analysis)\n- Workflow patterns: Best practices for common tasks\n- Tool integrations: Pre-configured tool chains for specific tasks\n- Reference material: Documentation, templates, and examples\n\n## Available skills\n\nSkills are grouped by scope (`Project`, `User`, `Extra`, `Built-in`) so you can tell where each came from. When multiple scopes define a skill with the same name, the more specific scope takes precedence: **Project overrides User overrides Extra overrides Built-in**.\n\n{{ SCREAM_SKILLS }}\n\n## How to use skills\n\nIdentify the skills that are likely to be useful for the tasks you are currently working on, read the skill file for detailed instructions, guidelines, scripts and more.\n\nOnly read skill details when needed to conserve the context window.\n\n# CONTRACT\n\nThese rules are inviolable.\n\n- You NEVER yield unless the deliverable is complete. A phase boundary, todo flip, or completed sub-step is NEVER a yield point — continue directly to the next step in the same turn.\n- You NEVER suppress tests to make code pass.\n- You NEVER fabricate outputs that were not observed. Claims about code, tools, tests, docs, or external sources MUST be grounded.\n- You NEVER substitute the user's problem with an easier or more familiar one.\n- You NEVER ask for information that tools, repo context, or files can provide.\n- NEVER punt half-solved work back.\n- You MUST default to a clean cutover: migrate every caller, leave no compatibility shims, aliases, or deprecated paths behind.\n- Be brief in prose, not in evidence, verification, or blocking details.\n\n## Completeness\n\n- \"Done\" means the requested deliverable behaves as specified end-to-end, not that a scaffold compiles or a narrowed test passes.\n- When a request names a plan, phase list, checklist, or specification, you MUST satisfy every stated acceptance criterion.\n- You NEVER silently shrink scope.\n- You NEVER ship stubs, placeholders, mocks, no-op implementations, fake fallbacks, or \"TODO: implement\" code as part of a delivered feature.\n- Verification claims MUST match what was actually exercised.\n- Framing tricks are prohibited: do not relabel unfinished work as \"scaffold\", \"first slice\", \"MVP\", \"foundation\", or \"follow-up\" to imply completion.\n\n## Yielding\n\nBefore yielding, you MUST verify:\n- All explicitly requested deliverables are complete; no partial implementation is presented as complete.\n- All directly affected artifacts (callsites, tests, docs) are updated or intentionally left unchanged.\n- The output format matches the ask.\n- No unobserved claim is presented as fact.\n- No required tool-based lookup was skipped when it would materially reduce uncertainty.\n\nBefore declaring blocked:\n- You MUST be sure the information cannot be obtained through tools, context, or anything within your reach.\n- One failing check is not enough to be blocked. You MUST continue until all the remaining work is done, and then report as such.\n- If you still cannot proceed, state exactly what is missing and what you tried.\n",
95980
96379
  "profile/default/verify.yaml": "extends: agent\nname: verify\npromptVars:\n roleAdditional: |\n You are now running as a sub-agent. All `user` messages are sent by the main agent.\n You are the Verify sub-agent. Use me when the main agent is unsure which verification\n command to run for a project, or when the project has multiple verification layers\n (typecheck, build, test, lint) that need coordinated execution.\n\n For simple / single-file fixes, the main agent should run the obvious command directly\n (e.g. `npx -p typescript tsc --noEmit --strict file.ts`, `python3 -m py_compile file.py`)\n instead of spawning this subagent.\n\n Your sole responsibility is to detect the project type and run verification commands.\n Do NOT try to fix anything. Do NOT repeat verification work the parent agent has already\n performed.\n # Phase 1: Detect project type (deterministic lookup — no guessing)\n\n Use `Read` to check for these files in order (first match wins).\n Read the file content, then look up the exact commands from this table:\n\n ## package.json exists — read it and check dependencies/devDependencies and scripts:\n\n | Condition | Type | Build | Test | Lint | Typecheck |\n |-----------|------|-------|------|------|-----------|\n | `dependencies.next` or `devDependencies.next` | Next.js | `npx next build` | `npm test` (if script exists) | `npx next lint` | `npx tsc --noEmit` or script `typecheck` |\n | `dependencies.react-scripts` | CRA | `npx react-scripts build` | `npm test` (if exists) | `npm run lint` (if exists) | `npx tsc --noEmit` or script `typecheck` |\n | `devDependencies.vite` or `dependencies.vite` | Vite | `npx vite build` | `npx vitest run` (if script exists) | `npm run lint` (if exists) | `npx tsc --noEmit` or script `typecheck` |\n | `devDependencies.@sveltejs/kit` | SvelteKit | `npx vite build` | `npm test` (if exists) | `npm run lint` (if exists) | `npx tsc --noEmit` or script `typecheck` |\n | `dependencies.astro` | Astro | `npx astro build` | `npm test` (if exists) | `npm run lint` (if exists) | `npx tsc --noEmit` or script `typecheck` |\n | none of the above | Node.js | `npm run build` (if script exists) | `npm test` (if script exists) | `npm run lint` (if script exists) | `npx tsc --noEmit` or script `typecheck` |\n\n Check `scripts` in package.json for `test`, `lint`, `build`, `typecheck` — only include commands whose scripts actually exist. Look for alternatives: `test:ci`, `test:unit`, `check`, `format:check`.\n\n IMPORTANT: If `tsconfig.json` exists in the project root or the directory you are verifying, you MUST run a TypeScript typecheck command. Prefer the script `typecheck` if it exists, otherwise run `npx tsc --noEmit` (or `pnpm tsc --noEmit` / `yarn tsc --noEmit` matching the package manager). Do NOT skip typechecking. Do NOT substitute a runtime test for a typecheck failure.\n\n ## Other ecosystems:\n\n | File | Type | Build | Test | Lint |\n |------|------|-------|------|------|\n | `requirements.txt` or `pyproject.toml` | Python | — | `python -m pytest` (if tests/ dir exists) or `python -m unittest` | `ruff check .` |\n | `go.mod` | Go | `go build ./...` | `go test ./...` | `go vet ./...` |\n | `Cargo.toml` | Rust | `cargo build` | `cargo test` | `cargo clippy` |\n | `pom.xml` | Maven | `mvn package -q` | `mvn test` | — |\n | `build.gradle` or `build.gradle.kts` | Gradle | `./gradlew build` (or `gradle build`) | `./gradlew test` (or `gradle test`) | — |\n | `Makefile` | Make | `make build` (if target exists) | `make test` (if target exists) | `make check` or `make lint` (if target exists) |\n\n ## Fallback:\n If none of the above match, report: \"No supported project type detected.\" and stop.\n\n # Phase 2: Run commands\n\n Run each command in order: typecheck → build → test → lint.\n For Python/Go/Rust, skip build if the command is not available.\n Capture stdout and stderr for each. Time each command.\n\n If a command fails because the binary is not found (e.g. `command not found: tsc`), report the exact error and stop — do not invent an alternative command. The parent agent must install or locate the correct binary.\n\n # Phase 3: Report\n\n Use this exact format (each command gets ONE line):\n\n ## Verify Report\n\n **Project:** <detected type>\n\n ✅ typecheck: passed (<N>s)\n ❌ typecheck: failed (<N>s)\n <first 30 lines of stderr/stdout with errors>\n ✅ build: passed (<N>s)\n ❌ test: <N> failed, <M> passed (<N>s)\n FAIL <file> > <test name>\n <error message>\n ⚠️ lint: <N> warnings, no errors (<N>s)\n ⏭️ lint: skipped: not configured\n\n If all pass:\n **Result:** ✅ All checks passed.\n\n If any fail:\n **Result:** ❌ <N> check(s) failed. See details above.\n\n # Phase 4: Machine-readable status\n\n You MUST end your response with a machine-readable `[verification_status]` block:\n\n On success:\n ```\n [verification_status]\n passed: true\n command: <the primary verification command that was run>\n exit_code: 0\n ```\n\n On failure:\n ```\n [verification_status]\n passed: false\n command: <command that failed>\n exit_code: <non-zero exit code>\n ```\n\n If no supported project type was detected:\n ```\n [verification_status]\n passed: true\n command: none\n exit_code: 0\n ```\n\n # Rules\n\n - Do NOT try to fix anything. Report only.\n - Do NOT ask questions. Run and report.\n - Do NOT run runtime smoke tests as a substitute for a failed typecheck/build/test.\n - Skip commands whose scripts/tools don't exist — mark as \"⏭️ skipped: not configured\".\n - If the SAME test was already failing before this change (the parent agent will tell you), mark it \"⏭️ pre-existing\" not \"❌\".\n\nwhenToUse: |\n Verification specialist. Detects project type deterministically and runs\n build, test, lint, and typecheck commands. Use after writing or modifying code to\n confirm correctness before delivering to the user.\ntools:\n - Bash\n - Read\n - Glob\n - Grep\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n",
95981
96380
  "profile/default/writer.yaml": "extends: agent\nname: writer\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n You are a content production and research specialist. Your output is not merely text — it is structured, evidence-based analysis presented in Markdown. Every piece of content you produce must demonstrate depth, traceability, and intellectual honesty.\n\n ## Core Methodology: Three-Layer Deep Analysis\n\n Before you write a single paragraph, you must perform a three-layer analysis of the request. This is your most important responsibility. Surface-level writing is not acceptable.\n\n **Layer 1 — The Ask:** What did the user explicitly request? What is the surface-level topic, format, and scope?\n\n **Layer 2 — The Purpose:** Why does the user want this? What decision will this content inform? What outcome are they trying to achieve? If the request is a report, who is the audience and what do they need to decide? If it is an analysis, what hypothesis is being tested?\n\n **Layer 3 — The Origin:** How did this purpose come to be? What is the broader context, market force, organizational pressure, or personal motivation that created this need? What would happen if this need were left unaddressed?\n\n Your final output must reflect all three layers. The content should not just describe — it should explain, contextualize, and anticipate. The reader should finish reading and think, \"This person truly understands why I needed this.\"\n\n ## Your Strengths\n\n - **Multi-dimensional analysis**: You do not settle for a single angle. You examine topics through multiple lenses — economic, technical, social, temporal, competitive — and synthesize them into a coherent narrative.\n - **Evidence-based writing**: Every significant claim has a source. You prefer primary sources and data over secondary opinion. You cite sources inline or in a dedicated Evidence section.\n - **Objective rigor**: You distinguish fact from inference and inference from speculation. You present counter-arguments. You flag uncertainty explicitly rather than hiding it behind confident language.\n - **Table precision**: When data is involved, you present it in clean, accurate Markdown tables. You verify column alignment, unit consistency, and mathematical correctness before outputting.\n\n ## Guidelines\n\n ### Deep Analysis\n - Start every substantial piece with a \"Why This Matters\" section that captures your three-layer analysis.\n - Do not merely list facts. Explain the relationships between them. Cause and effect, trade-offs, second-order consequences.\n - When comparing options, use a structured comparison table that covers all relevant dimensions, not just the obvious ones.\n - Anticipate the reader's next three questions and address them proactively.\n\n ### Sources and Evidence\n - For data claims, cite the source. Prefer: `SearchWeb`, `FetchURL`, or files provided by the caller.\n - If you cannot verify a claim, say so explicitly: \"This figure could not be independently verified.\"\n - Distinguish between \"confirmed\" (you checked it), \"reported\" (a source claims it), and \"estimated\" (your inference).\n - Include an Evidence section in your output listing sources and verification methods.\n\n ### Objectivity\n - Present both supporting and contradicting evidence.\n - Avoid adjectives that imply certainty without proof: \"obviously\", \"undoubtedly\", \"inevitably\".\n - Use probabilistic language when appropriate: \"based on current data, the most likely outcome is...\"\n - Separate \"what is\" (fact) from \"what it means\" (interpretation) from \"what should be done\" (recommendation).\n\n ### Markdown Tables (Mandatory for Data)\n - All tables use standard Markdown pipe syntax.\n - Headers are bold and semantically clear.\n - Numbers are right-aligned; text is left-aligned; status/tags are centered.\n - Every table has a descriptive caption above it (e.g., \"Table 1: Q1-Q4 Revenue by Region\").\n - Keep columns ≤ 8. If more are needed, split into related tables.\n - Verify arithmetic: totals, percentages, and growth rates must be correct.\n - Use consistent units within a column.\n\n ### Content Structure\n - Use clear heading hierarchies (`#`, `##`, `###`).\n - Each major section begins with a concise summary of what the section covers.\n - Each major section ends with a \"So What\" takeaway that connects the facts back to the reader's purpose.\n - Complex comparisons always use tables. Narrative descriptions of tabular data are insufficient.\n\n ## Output Format\n\n Your final response must include:\n\n ```markdown\n ## SUMMARY\n A concise executive summary capturing the three-layer analysis and key conclusions.\n\n ## WHY THIS MATTERS\n The three-layer deep analysis (Ask → Purpose → Origin) that frames everything below.\n\n ## [Main Content Sections]\n The body of the analysis, report, or document.\n\n ## EVIDENCE\n - Source A: description and verification method\n - Source B: description and verification method\n\n ## RISKS & LIMITATIONS\n What is uncertain, unverified, or context-dependent in this analysis.\n ```\n\n ## Important Reminders\n\n - Your only output is Markdown content. You do not generate .docx, .pdf, or any other format.\n - If the caller asks for a specific file format, output Markdown and note that format conversion is the caller's responsibility.\n - If the user provides a template or sample file, Read it first and match its depth, tone, and structure.\n - After writing, verify: logical self-consistency, source accuracy, table arithmetic, and structural completeness.\n - Never fabricate data. If data is missing, say so and explain the impact of the gap.\nwhenToUse: |\n Use this agent when the task involves producing substantial written content that requires depth: research reports, competitive analysis, data-driven documents, strategic proposals, or any work where understanding the \"why\" behind the request is as important as the \"what.\" This agent excels at multi-dimensional analysis, evidence-based reasoning, and structured Markdown output with precise tables.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - mcp__*\n"
95982
96381
  };
@@ -97699,6 +98098,7 @@ var LtodLLM = class {
97699
98098
  provider;
97700
98099
  generate;
97701
98100
  completionBudgetConfig;
98101
+ obfuscator;
97702
98102
  constructor(config) {
97703
98103
  this.provider = config.provider;
97704
98104
  this.modelName = config.modelName;
@@ -97706,6 +98106,7 @@ var LtodLLM = class {
97706
98106
  this.capability = config.capability;
97707
98107
  this.generate = config.generate ?? generate;
97708
98108
  this.completionBudgetConfig = config.completionBudgetConfig;
98109
+ this.obfuscator = config.obfuscator;
97709
98110
  }
97710
98111
  async chat(params) {
97711
98112
  let requestStartedAt = Date.now();
@@ -97720,22 +98121,26 @@ var LtodLLM = class {
97720
98121
  const markStreamOutput = () => {
97721
98122
  firstChunkAt ??= Date.now();
97722
98123
  };
97723
- const callbacks = buildLtodCallbacks(params, markStreamOutput);
98124
+ const { callbacks, flushPending } = buildLtodCallbacks(params, markStreamOutput, this.obfuscator);
97724
98125
  const effectiveProvider = applyCompletionBudget({
97725
98126
  provider: this.provider,
97726
98127
  budget: this.completionBudgetConfig,
97727
98128
  capability: this.capability
97728
98129
  });
97729
- const result = await this.generate(effectiveProvider, this.systemPrompt, [...params.tools], params.messages, callbacks, generateOptions(params, {
98130
+ const outboundMessages = this.obfuscator && this.obfuscator.hasSecrets() ? obfuscateMessages(this.obfuscator, params.messages) : params.messages;
98131
+ const result = await this.generate(effectiveProvider, this.systemPrompt, [...params.tools], outboundMessages, callbacks, generateOptions(params, {
97730
98132
  onRequestStart: markRequestStart,
97731
- onStreamEnd: markStreamEnd
98133
+ onStreamEnd: () => {
98134
+ markStreamEnd();
98135
+ flushPending();
98136
+ }
97732
98137
  }));
97733
98138
  if (params.onTextPart !== void 0 || params.onThinkPart !== void 0) {
97734
98139
  for (const part of result.message.content) if (part.type === "text" && params.onTextPart !== void 0) await params.onTextPart(part);
97735
98140
  else if (part.type === "think" && params.onThinkPart !== void 0) await params.onThinkPart(part);
97736
98141
  }
97737
98142
  return {
97738
- toolCalls: [...result.message.toolCalls],
98143
+ toolCalls: this.obfuscator && this.obfuscator.hasSecrets() ? deobfuscateToolCalls(this.obfuscator, result.message.toolCalls) : [...result.message.toolCalls],
97739
98144
  providerFinishReason: result.finishReason ?? void 0,
97740
98145
  rawFinishReason: result.rawFinishReason ?? void 0,
97741
98146
  usage: result.usage ?? emptyUsage(),
@@ -97761,78 +98166,129 @@ function generateOptions(params, hooks) {
97761
98166
  [GENERATE_REQUEST_LOG_CONTEXT]: params.requestLogContext
97762
98167
  };
97763
98168
  }
97764
- function buildLtodCallbacks(params, markStreamOutput) {
98169
+ function buildLtodCallbacks(params, markStreamOutput, obfuscator) {
97765
98170
  const toolCallIdentities = /* @__PURE__ */ new Map();
97766
98171
  const pendingIndexedToolCallDeltas = /* @__PURE__ */ new Map();
97767
98172
  let lastToolCallIdentity;
98173
+ let pendingTextTail = "";
98174
+ let pendingThinkTail = "";
98175
+ const pendingToolCallTails = /* @__PURE__ */ new Map();
98176
+ const PARTIAL_PLACEHOLDER_RE = /#[A-Z2-9]{0,6}$/;
98177
+ const deobfuscateWithBuffer = (text, setBuffer, getBuffer) => {
98178
+ if (obfuscator === void 0 || !obfuscator.hasSecrets()) return getBuffer() + text;
98179
+ const combined = getBuffer() + text;
98180
+ setBuffer("");
98181
+ const deobfuscated = obfuscator.deobfuscate(combined);
98182
+ const partialMatch = deobfuscated.match(PARTIAL_PLACEHOLDER_RE);
98183
+ if (partialMatch !== null && partialMatch[0].length > 0) {
98184
+ setBuffer(partialMatch[0]);
98185
+ return deobfuscated.slice(0, deobfuscated.length - partialMatch[0].length);
98186
+ }
98187
+ return deobfuscated;
98188
+ };
97768
98189
  const emitToolCallDelta = (delta) => {
97769
98190
  if (params.onToolCallDelta === void 0) return;
98191
+ if (delta.argumentsPart !== void 0) {
98192
+ const id = delta.toolCallId;
98193
+ const out = deobfuscateWithBuffer(delta.argumentsPart, (v) => pendingToolCallTails.set(id, v), () => pendingToolCallTails.get(id) ?? "");
98194
+ if (out.length > 0) delta = {
98195
+ ...delta,
98196
+ argumentsPart: out
98197
+ };
98198
+ else return;
98199
+ }
97770
98200
  params.onToolCallDelta(delta);
97771
98201
  };
97772
- return { onMessagePart: (part) => {
97773
- markStreamOutput();
97774
- if (part.type === "text") {
97775
- if (params.onTextDelta === void 0) return;
97776
- params.onTextDelta(part.text);
97777
- return;
97778
- }
97779
- if (part.type === "think") {
97780
- if (params.onThinkDelta === void 0) return;
97781
- params.onThinkDelta(part.think);
97782
- return;
97783
- }
97784
- if (part.type === "function") {
97785
- const identity = {
97786
- toolCallId: part.id,
97787
- name: part.name
97788
- };
97789
- lastToolCallIdentity = identity;
97790
- if (part._streamIndex !== void 0) toolCallIdentities.set(part._streamIndex, identity);
97791
- emitToolCallDelta({
97792
- toolCallId: part.id,
97793
- name: part.name,
97794
- ...part.arguments !== null ? { argumentsPart: part.arguments } : {}
97795
- });
97796
- if (part._streamIndex !== void 0) {
97797
- const pendingDeltas = pendingIndexedToolCallDeltas.get(part._streamIndex);
97798
- if (pendingDeltas !== void 0) {
97799
- pendingIndexedToolCallDeltas.delete(part._streamIndex);
97800
- for (const delta of pendingDeltas) emitToolCallDelta({
98202
+ return {
98203
+ callbacks: { onMessagePart: (part) => {
98204
+ markStreamOutput();
98205
+ if (part.type === "text") {
98206
+ if (params.onTextDelta === void 0) return;
98207
+ const out = deobfuscateWithBuffer(part.text, (v) => {
98208
+ pendingTextTail = v;
98209
+ }, () => pendingTextTail);
98210
+ if (out.length > 0) params.onTextDelta(out);
98211
+ return;
98212
+ }
98213
+ if (part.type === "think") {
98214
+ if (params.onThinkDelta === void 0) return;
98215
+ const out = deobfuscateWithBuffer(part.think, (v) => {
98216
+ pendingThinkTail = v;
98217
+ }, () => pendingThinkTail);
98218
+ if (out.length > 0) params.onThinkDelta(out);
98219
+ return;
98220
+ }
98221
+ if (part.type === "function") {
98222
+ const identity = {
98223
+ toolCallId: part.id,
98224
+ name: part.name
98225
+ };
98226
+ lastToolCallIdentity = identity;
98227
+ if (part._streamIndex !== void 0) toolCallIdentities.set(part._streamIndex, identity);
98228
+ emitToolCallDelta({
98229
+ toolCallId: part.id,
98230
+ name: part.name,
98231
+ ...part.arguments !== null ? { argumentsPart: part.arguments } : {}
98232
+ });
98233
+ if (part._streamIndex !== void 0) {
98234
+ const pendingDeltas = pendingIndexedToolCallDeltas.get(part._streamIndex);
98235
+ if (pendingDeltas !== void 0) {
98236
+ pendingIndexedToolCallDeltas.delete(part._streamIndex);
98237
+ for (const delta of pendingDeltas) emitToolCallDelta({
98238
+ toolCallId: identity.toolCallId,
98239
+ name: identity.name,
98240
+ ...delta
98241
+ });
98242
+ }
98243
+ }
98244
+ return;
98245
+ }
98246
+ if (part.type === "tool_call_part") {
98247
+ const argumentsPart = part.argumentsPart;
98248
+ const delta = argumentsPart !== null ? { argumentsPart } : {};
98249
+ if (part.index !== void 0) {
98250
+ const identity = toolCallIdentities.get(part.index);
98251
+ if (identity === void 0) {
98252
+ const pendingDeltas = pendingIndexedToolCallDeltas.get(part.index) ?? [];
98253
+ pendingDeltas.push(delta);
98254
+ pendingIndexedToolCallDeltas.set(part.index, pendingDeltas);
98255
+ return;
98256
+ }
98257
+ emitToolCallDelta({
97801
98258
  toolCallId: identity.toolCallId,
97802
98259
  name: identity.name,
97803
98260
  ...delta
97804
98261
  });
97805
- }
97806
- }
97807
- return;
97808
- }
97809
- if (part.type === "tool_call_part") {
97810
- const argumentsPart = part.argumentsPart;
97811
- const delta = argumentsPart !== null ? { argumentsPart } : {};
97812
- if (part.index !== void 0) {
97813
- const identity = toolCallIdentities.get(part.index);
97814
- if (identity === void 0) {
97815
- const pendingDeltas = pendingIndexedToolCallDeltas.get(part.index) ?? [];
97816
- pendingDeltas.push(delta);
97817
- pendingIndexedToolCallDeltas.set(part.index, pendingDeltas);
97818
98262
  return;
97819
98263
  }
98264
+ const identity = lastToolCallIdentity;
98265
+ if (identity === void 0) return;
97820
98266
  emitToolCallDelta({
97821
98267
  toolCallId: identity.toolCallId,
97822
98268
  name: identity.name,
97823
98269
  ...delta
97824
98270
  });
97825
- return;
97826
98271
  }
97827
- const identity = lastToolCallIdentity;
97828
- if (identity === void 0) return;
97829
- emitToolCallDelta({
97830
- toolCallId: identity.toolCallId,
97831
- name: identity.name,
97832
- ...delta
97833
- });
98272
+ } },
98273
+ flushPending: () => {
98274
+ if (pendingTextTail.length > 0 && params.onTextDelta !== void 0) {
98275
+ params.onTextDelta(pendingTextTail);
98276
+ pendingTextTail = "";
98277
+ }
98278
+ if (pendingThinkTail.length > 0 && params.onThinkDelta !== void 0) {
98279
+ params.onThinkDelta(pendingThinkTail);
98280
+ pendingThinkTail = "";
98281
+ }
98282
+ if (params.onToolCallDelta !== void 0) {
98283
+ for (const [id, tail] of pendingToolCallTails) if (tail.length > 0) params.onToolCallDelta({
98284
+ toolCallId: id,
98285
+ name: "",
98286
+ argumentsPart: tail
98287
+ });
98288
+ pendingToolCallTails.clear();
98289
+ }
97834
98290
  }
97835
- } };
98291
+ };
97836
98292
  }
97837
98293
  //#endregion
97838
98294
  //#region ../../packages/agent-core/src/agent/usage/index.ts
@@ -98033,9 +98489,19 @@ var Agent = class {
98033
98489
  systemPrompt: this.config.systemPrompt,
98034
98490
  capability: this.config.modelCapabilities,
98035
98491
  generate: this.generate,
98036
- completionBudgetConfig
98492
+ completionBudgetConfig,
98493
+ obfuscator: this.obfuscator
98037
98494
  });
98038
98495
  }
98496
+ _obfuscator;
98497
+ _obfuscatorConfigRef;
98498
+ get obfuscator() {
98499
+ if (this._obfuscatorConfigRef === this.screamConfig && this._obfuscator !== void 0) return this._obfuscator;
98500
+ this._obfuscatorConfigRef = this.screamConfig;
98501
+ const userEntries = this.screamConfig?.secrets ?? [];
98502
+ this._obfuscator = new SecretObfuscator([...DEFAULT_SECRET_PATTERNS, ...userEntries]);
98503
+ return this._obfuscator;
98504
+ }
98039
98505
  logLlmRequest(provider, systemPrompt, tools, history, options) {
98040
98506
  const context = buildLlmRequestContext(options);
98041
98507
  const configMetadata = buildLlmConfigMetadata(provider, this.config.modelAlias, systemPrompt, tools);
@@ -98301,10 +98767,12 @@ var Agent = class {
98301
98767
  this.emitEvent({
98302
98768
  type: "agent.status.updated",
98303
98769
  model,
98770
+ thinkingLevel: this.config.thinkingLevel,
98304
98771
  contextTokens,
98305
98772
  maxContextTokens,
98306
98773
  contextUsage,
98307
98774
  planMode: this.planMode.isActive,
98775
+ planStrategy: this.planMode.isActive ? this.planMode.strategy : void 0,
98308
98776
  permission: this.permission.mode,
98309
98777
  usage
98310
98778
  });
@@ -98402,20 +98870,6 @@ function isPlainObject$2(value) {
98402
98870
  return typeof value === "object" && value !== null && !Array.isArray(value);
98403
98871
  }
98404
98872
  //#endregion
98405
- //#region ../../packages/agent-core/src/config/path.ts
98406
- function resolveScreamHome(homeDir) {
98407
- return homeDir ?? process.env["SCREAM_CODE_HOME"] ?? join$1(homedir(), ".scream-code");
98408
- }
98409
- function resolveConfigPath$1(input) {
98410
- return input.configPath ?? join$1(resolveScreamHome(input.homeDir), "config.toml");
98411
- }
98412
- function ensureScreamHome(homeDir) {
98413
- mkdirSync(homeDir, {
98414
- recursive: true,
98415
- mode: 448
98416
- });
98417
- }
98418
- //#endregion
98419
98873
  //#region ../../packages/agent-core/src/config/env-model.ts
98420
98874
  /** Reserved keys for the env-driven synthetic provider / model alias. */
98421
98875
  const ENV_MODEL_PROVIDER_KEY = "__scream_env__";
@@ -102922,7 +103376,9 @@ var Session$1 = class {
102922
103376
  ...this.metadata.custom,
102923
103377
  recap
102924
103378
  };
102925
- this.writeMetadata();
103379
+ this.writeMetadata().catch((error) => {
103380
+ this.log.error("failed to write session recap metadata", error);
103381
+ });
102926
103382
  }
102927
103383
  }
102928
103384
  try {
@@ -102962,7 +103418,9 @@ var Session$1 = class {
102962
103418
  type,
102963
103419
  parentAgentId: parentAgentId ?? null
102964
103420
  };
102965
- this.writeMetadata();
103421
+ this.writeMetadata().catch((error) => {
103422
+ this.log.error("failed to write session metadata after agent creation", error);
103423
+ });
102966
103424
  return {
102967
103425
  id,
102968
103426
  agent
@@ -103018,7 +103476,7 @@ var Session$1 = class {
103018
103476
  });
103019
103477
  await this.options.jian.writeText(this.metadataPath, text);
103020
103478
  };
103021
- this.writeMetadataPromise = this.writeMetadataPromise.then(write, write);
103479
+ this.writeMetadataPromise = this.writeMetadataPromise.then(() => write());
103022
103480
  return this.writeMetadataPromise;
103023
103481
  }
103024
103482
  async readMetadata() {
@@ -120730,7 +121188,7 @@ var Session = class {
120730
121188
  }
120731
121189
  async compact(options = {}) {
120732
121190
  this.ensureOpen();
120733
- const instruction = normalizeOptionalString(options.instruction);
121191
+ const instruction = normalizeOptionalString$1(options.instruction);
120734
121192
  await this.rpc.compact({
120735
121193
  sessionId: this.id,
120736
121194
  ...instruction !== void 0 ? { instruction } : {}
@@ -120906,7 +121364,7 @@ var Session = class {
120906
121364
  async activateSkill(name, args) {
120907
121365
  this.ensureOpen();
120908
121366
  const skillName = normalizeRequiredString(name, "Skill name cannot be empty", ErrorCodes.SKILL_NAME_EMPTY);
120909
- const skillArgs = normalizeOptionalString(args);
121367
+ const skillArgs = normalizeOptionalString$1(args);
120910
121368
  await this.rpc.activateSkill({
120911
121369
  sessionId: this.id,
120912
121370
  name: skillName,
@@ -121033,7 +121491,7 @@ function normalizeRequiredString(value, message, code) {
121033
121491
  if (normalized.length === 0) throw new ScreamError(code, message);
121034
121492
  return normalized;
121035
121493
  }
121036
- function normalizeOptionalString(value) {
121494
+ function normalizeOptionalString$1(value) {
121037
121495
  if (value === void 0) return void 0;
121038
121496
  const normalized = value.trim();
121039
121497
  return normalized.length > 0 ? normalized : void 0;
@@ -121310,8 +121768,14 @@ function applyCatalogProvider(config, options) {
121310
121768
  for (const model of options.models) models[`${options.providerId}/${model.id}`] = catalogModelToAlias(options.providerId, model);
121311
121769
  config.models = models;
121312
121770
  const defaultModel = `${options.providerId}/${options.selectedModelId}`;
121771
+ const thinkingLevel = options.thinkingLevel ?? (options.thinking === true ? "medium" : "off");
121313
121772
  config.defaultModel = defaultModel;
121314
- config.defaultThinking = options.thinking;
121773
+ config.defaultThinking = thinkingLevel !== "off";
121774
+ config.thinking = {
121775
+ ...config.thinking,
121776
+ mode: thinkingLevel === "off" ? "off" : "on",
121777
+ effort: thinkingLevel
121778
+ };
121315
121779
  return { defaultModel };
121316
121780
  }
121317
121781
  //#endregion
@@ -121322,6 +121786,7 @@ const CLI_USER_AGENT_PRODUCT = "scream-code-cli";
121322
121786
  const CLI_UI_MODE = "shell";
121323
121787
  const SCREAM_CODE_HOME_ENV = "SCREAM_CODE_HOME";
121324
121788
  const SCREAM_CODE_DATA_DIR_NAME = ".scream-code";
121789
+ const SCREAM_CODE_LOG_DIR_NAME = "logs";
121325
121790
  const SCREAM_CODE_UPDATE_DIR_NAME = "updates";
121326
121791
  const SCREAM_CODE_UPDATE_STATE_FILE_NAME = "latest.json";
121327
121792
  const SCREAM_CODE_INPUT_HISTORY_DIR_NAME = "user-history";
@@ -121436,6 +121901,12 @@ function getDataDir() {
121436
121901
  return join(homedir(), SCREAM_CODE_DATA_DIR_NAME);
121437
121902
  }
121438
121903
  /**
121904
+ * Return the diagnostic log directory: `<dataDir>/logs/`.
121905
+ */
121906
+ function getLogDir() {
121907
+ return join(getDataDir(), SCREAM_CODE_LOG_DIR_NAME);
121908
+ }
121909
+ /**
121439
121910
  * Return the update cache file: `<dataDir>/updates/latest.json`.
121440
121911
  */
121441
121912
  function getUpdateStateFile() {
@@ -121617,13 +122088,14 @@ function createProgram(version, onMain, onMigrate, onPluginNodeRunner = () => {}
121617
122088
  program.addOption(new Option("-S, --session [id]", "恢复会话。带 ID:恢复该会话。不带 ID:交互式选择。").argParser((val) => val === true ? "" : val)).addOption(new Option("-r, --resume [id]").hideHelp().argParser((val) => val === true ? "" : val)).option("-C, --continue", "继续当前工作目录的上一个会话。", false).option("-y, --yolo", "自动批准所有操作。", false).option("--auto", "以自动权限模式启动。", false).addOption(new Option("-m, --model <model>", "本次调用使用的 LLM 模型别名。默认使用 config.toml 中的 default_model。")).addOption(new Option("-p, --prompt <prompt>", "非交互式运行一条提示并打印响应。")).addOption(new Option("--output-format <format>", "提示模式的输出格式。默认为 text。").choices(["text", "stream-json"])).addOption(new Option("--skills-dir <dir>", "从该目录加载技能,而不是自动发现的用户和项目目录。可多次指定。").argParser((value, previous) => [...previous ?? [], value]).default([])).addOption(new Option("--yes").hideHelp().default(false)).addOption(new Option("--auto-approve").hideHelp().default(false)).option("--plan", "以计划模式启动。", false);
121618
122089
  registerExportCommand(program);
121619
122090
  registerMigrateCommand(program, onMigrate);
121620
- program.command("stream-json", { hidden: true }).option("--input-format <fmt>", "stream-json").option("--output-format <fmt>", "stream-json").option("--resume <id>", "resume a previous session").option("--model <model>", "model to use").option("--permission-mode <mode>", "permission mode").option("--permission-prompt-tool <mode>", "(ignored, cc-connect compat)").option("--replay-user-messages", "(ignored, cc-connect compat)").option("--verbose", "(ignored, cc-connect compat)").option("--system-prompt <text>", "(ignored, cc-connect compat)").option("--append-system-prompt <text>", "(passed through to agent)").option("--allowedTools <list>", "(ignored, cc-connect compat)").option("--disallowedTools <list>", "(ignored, cc-connect compat)").option("--effort <value>", "(ignored, cc-connect compat)").option("--max-context-tokens <N>", "(ignored, cc-connect compat)").option("--skills-dir <dir>", "additional skills directory (repeatable)", (value, previous) => [...previous ?? [], value], []).action((subOpts) => {
122091
+ program.command("stream-json", { hidden: true }).option("--input-format <fmt>", "stream-json").option("--output-format <fmt>", "stream-json").option("--resume <id>", "resume a previous session").option("--model <model>", "model to use").option("--permission-mode <mode>", "permission mode").option("--permission-prompt-tool <mode>", "(ignored, cc-connect compat)").option("--replay-user-messages", "(ignored, cc-connect compat)").option("--verbose", "(ignored, cc-connect compat)").option("--system-prompt <text>", "(ignored, cc-connect compat)").option("--append-system-prompt <text>", "(passed through to agent)").option("--append-system-prompt-file <path>", "(passed through to agent; file contents are read and merged)").option("--allowedTools <list>", "(ignored, cc-connect compat)").option("--disallowedTools <list>", "(ignored, cc-connect compat)").option("--effort <value>", "(ignored, cc-connect compat)").option("--max-context-tokens <N>", "(ignored, cc-connect compat)").option("--skills-dir <dir>", "additional skills directory (repeatable)", (value, previous) => [...previous ?? [], value], []).option("--plugin-dir <dir>", "(ignored, cc-connect compat; repeatable)", (value, previous) => [...previous ?? [], value], []).action((subOpts) => {
121621
122092
  onStreamJson({
121622
122093
  resume: subOpts["resume"],
121623
122094
  model: subOpts["model"],
121624
122095
  permissionMode: subOpts["permissionMode"],
121625
122096
  skillsDirs: subOpts["skillsDir"] ?? [],
121626
- appendSystemPrompt: subOpts["appendSystemPrompt"]
122097
+ appendSystemPrompt: subOpts["appendSystemPrompt"],
122098
+ appendSystemPromptFile: subOpts["appendSystemPromptFile"]
121627
122099
  });
121628
122100
  });
121629
122101
  program.command("channel").description("管理 cc-connect 消息平台通道").command("setup").description("配置 cc-connect 并选择要连接的平台").action(() => {
@@ -122147,18 +122619,25 @@ const NotificationsConfigSchema = z.object({
122147
122619
  enabled: z.boolean(),
122148
122620
  condition: NotificationConditionSchema
122149
122621
  });
122622
+ const TuiLikePreferencesSchema = z.object({
122623
+ nickname: z.string().optional(),
122624
+ tone: z.string().optional(),
122625
+ other: z.string().optional()
122626
+ });
122150
122627
  const TuiConfigFileSchema = z.object({
122151
122628
  theme: TuiThemeSchema.optional(),
122152
122629
  editor: z.object({ command: z.string().optional() }).optional(),
122153
122630
  notifications: z.object({
122154
122631
  enabled: z.boolean().optional(),
122155
122632
  notification_condition: NotificationConditionSchema.optional()
122156
- }).optional()
122633
+ }).optional(),
122634
+ like: TuiLikePreferencesSchema.optional()
122157
122635
  });
122158
122636
  const TuiConfigSchema = z.object({
122159
122637
  theme: TuiThemeSchema,
122160
122638
  editorCommand: z.string().nullable(),
122161
- notifications: NotificationsConfigSchema
122639
+ notifications: NotificationsConfigSchema,
122640
+ like: TuiLikePreferencesSchema
122162
122641
  });
122163
122642
  const DEFAULT_NOTIFICATIONS_CONFIG = {
122164
122643
  enabled: true,
@@ -122167,7 +122646,8 @@ const DEFAULT_NOTIFICATIONS_CONFIG = {
122167
122646
  const DEFAULT_TUI_CONFIG = TuiConfigSchema.parse({
122168
122647
  theme: "auto",
122169
122648
  editorCommand: null,
122170
- notifications: DEFAULT_NOTIFICATIONS_CONFIG
122649
+ notifications: DEFAULT_NOTIFICATIONS_CONFIG,
122650
+ like: {}
122171
122651
  });
122172
122652
  /**
122173
122653
  * Thrown by `loadTuiConfig` when the on-disk TOML cannot be parsed.
@@ -122208,16 +122688,30 @@ async function saveTuiConfig(config, filePath = getTuiConfigPath()) {
122208
122688
  }
122209
122689
  function normalizeTuiConfig(config) {
122210
122690
  const command = config.editor?.command?.trim();
122691
+ const like = config.like ?? {};
122211
122692
  return TuiConfigSchema.parse({
122212
122693
  theme: config.theme ?? DEFAULT_TUI_CONFIG.theme,
122213
122694
  editorCommand: command === void 0 || command.length === 0 ? null : command,
122214
122695
  notifications: {
122215
122696
  enabled: config.notifications?.enabled ?? DEFAULT_NOTIFICATIONS_CONFIG.enabled,
122216
122697
  condition: config.notifications?.notification_condition ?? DEFAULT_NOTIFICATIONS_CONFIG.condition
122698
+ },
122699
+ like: {
122700
+ nickname: normalizeOptionalString(like.nickname),
122701
+ tone: normalizeOptionalString(like.tone),
122702
+ other: normalizeOptionalString(like.other)
122217
122703
  }
122218
122704
  });
122219
122705
  }
122706
+ function normalizeOptionalString(value) {
122707
+ if (value === void 0) return void 0;
122708
+ const trimmed = value.trim();
122709
+ return trimmed.length > 0 ? trimmed : void 0;
122710
+ }
122220
122711
  function renderTuiConfig(config) {
122712
+ const nickname = escapeTomlBasicString(config.like.nickname ?? "");
122713
+ const tone = escapeTomlBasicString(config.like.tone ?? "");
122714
+ const other = escapeTomlBasicString(config.like.other ?? "");
122221
122715
  return `# ~/.scream-code/tui.toml
122222
122716
  # Terminal UI preferences for scream-code.
122223
122717
  # Agent/runtime settings stay in ~/.scream-code/config.toml.
@@ -122230,6 +122724,11 @@ command = "${escapeTomlBasicString(config.editorCommand ?? "")}" # Empty uses $V
122230
122724
  [notifications]
122231
122725
  enabled = ${String(config.notifications.enabled)} # true | false
122232
122726
  notification_condition = "${config.notifications.condition}" # "unfocused" | "always"
122727
+
122728
+ [like]
122729
+ nickname = "${nickname}"
122730
+ tone = "${tone}"
122731
+ other = "${other}"
122233
122732
  `;
122234
122733
  }
122235
122734
  function escapeTomlBasicString(value) {
@@ -122390,6 +122889,13 @@ const BUILTIN_SLASH_COMMANDS = [
122390
122889
  priority: 118,
122391
122890
  availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
122392
122891
  },
122892
+ {
122893
+ name: "fusionplan",
122894
+ aliases: ["fp"],
122895
+ description: "切换融合计划模式(多子代理并行规划)",
122896
+ priority: 118,
122897
+ availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
122898
+ },
122393
122899
  {
122394
122900
  name: "tasks",
122395
122901
  aliases: ["task"],
@@ -122425,6 +122931,13 @@ const BUILTIN_SLASH_COMMANDS = [
122425
122931
  priority: 113,
122426
122932
  availability: "always"
122427
122933
  },
122934
+ {
122935
+ name: "like",
122936
+ aliases: [],
122937
+ description: "设置你的偏好(昵称、语气、其他偏好)",
122938
+ priority: 113,
122939
+ availability: "always"
122940
+ },
122428
122941
  {
122429
122942
  name: "mcp",
122430
122943
  aliases: [],
@@ -123136,6 +123649,12 @@ function optionLabelStyle(option, selected, colors) {
123136
123649
  }
123137
123650
  //#endregion
123138
123651
  //#region src/tui/components/dialogs/model-selector.ts
123652
+ const DEFAULT_THINKING_LEVELS = [
123653
+ "off",
123654
+ "low",
123655
+ "medium",
123656
+ "high"
123657
+ ];
123139
123658
  function modelDisplayName$1(alias, model) {
123140
123659
  return model?.displayName ?? model?.model ?? alias;
123141
123660
  }
@@ -123157,11 +123676,17 @@ function thinkingAvailability(model) {
123157
123676
  if (caps.includes("thinking") || model.adaptiveThinking === true) return "toggle";
123158
123677
  return "unsupported";
123159
123678
  }
123160
- function effectiveThinking(model, thinkingDraft) {
123679
+ function getThinkingLevels(model) {
123161
123680
  const availability = thinkingAvailability(model);
123162
- if (availability === "always-on") return true;
123163
- if (availability === "unsupported") return false;
123164
- return thinkingDraft;
123681
+ if (availability === "unsupported") return ["off"];
123682
+ const base = model.thinkingLevels ?? DEFAULT_THINKING_LEVELS;
123683
+ if (availability === "always-on") return base.filter((level) => level !== "off");
123684
+ return base;
123685
+ }
123686
+ function effectiveThinkingLevel(model, thinkingDraft) {
123687
+ const levels = getThinkingLevels(model);
123688
+ if (levels.includes(thinkingDraft)) return thinkingDraft;
123689
+ return levels[0] ?? "off";
123165
123690
  }
123166
123691
  var ModelSelectorComponent = class extends Container {
123167
123692
  focused = false;
@@ -123181,7 +123706,8 @@ var ModelSelectorComponent = class extends Container {
123181
123706
  initialIndex: Math.max(selectedIdx, 0),
123182
123707
  searchable: opts.searchable === true
123183
123708
  });
123184
- this.thinkingDraft = opts.currentThinking;
123709
+ const initialChoice = choices[selectedIdx];
123710
+ this.thinkingDraft = initialChoice !== void 0 ? effectiveThinkingLevel(initialChoice.model, opts.currentThinkingLevel) : opts.currentThinkingLevel;
123185
123711
  }
123186
123712
  handleInput(data) {
123187
123713
  if (matchesKey(data, Key.escape)) {
@@ -123190,13 +123716,17 @@ var ModelSelectorComponent = class extends Container {
123190
123716
  return;
123191
123717
  }
123192
123718
  const selected = this.list.selected();
123193
- if (selected !== void 0 && thinkingAvailability(selected.model) === "toggle") {
123719
+ if (selected !== void 0 && thinkingAvailability(selected.model) !== "unsupported") {
123720
+ const levels = getThinkingLevels(selected.model);
123721
+ const idx = levels.indexOf(this.thinkingDraft);
123194
123722
  if (matchesKey(data, Key.left)) {
123195
- this.thinkingDraft = true;
123723
+ const nextIdx = idx <= 0 ? levels.length - 1 : idx - 1;
123724
+ this.thinkingDraft = levels[nextIdx];
123196
123725
  return;
123197
123726
  }
123198
123727
  if (matchesKey(data, Key.right)) {
123199
- this.thinkingDraft = false;
123728
+ const nextIdx = idx === -1 || idx >= levels.length - 1 ? 0 : idx + 1;
123729
+ this.thinkingDraft = levels[nextIdx];
123200
123730
  return;
123201
123731
  }
123202
123732
  }
@@ -123204,7 +123734,7 @@ var ModelSelectorComponent = class extends Container {
123204
123734
  if (selected === void 0) return;
123205
123735
  this.opts.onSelect({
123206
123736
  alias: selected.alias,
123207
- thinking: effectiveThinking(selected.model, this.thinkingDraft)
123737
+ thinkingLevel: effectiveThinkingLevel(selected.model, this.thinkingDraft)
123208
123738
  });
123209
123739
  return;
123210
123740
  }
@@ -123215,7 +123745,7 @@ var ModelSelectorComponent = class extends Container {
123215
123745
  const searchable = this.opts.searchable === true;
123216
123746
  const view = this.list.view();
123217
123747
  const choices = view.items;
123218
- const navParts = ["↑↓ 模型", "←→ 思考"];
123748
+ const navParts = ["↑↓ 模型", "←→ 思考等级"];
123219
123749
  if (view.page.pageCount > 1) navParts.push("PgUp/PgDn 翻页");
123220
123750
  navParts.push("Enter 应用", "Esc 取消");
123221
123751
  const titleSuffix = searchable && view.query.length === 0 ? chalk.hex(colors.textMuted)(" (输入搜索)") : "";
@@ -123246,11 +123776,14 @@ var ModelSelectorComponent = class extends Container {
123246
123776
  }
123247
123777
  renderThinkingControl(model) {
123248
123778
  const { colors } = this.opts;
123249
- const segment = (label, active) => active ? chalk.hex(colors.primary).bold(`[ ${label} ]`) : chalk.hex(colors.text)(` ${label} `);
123250
123779
  const availability = thinkingAvailability(model);
123251
- if (availability === "always-on") return ` ${segment("Always on", true)}`;
123252
- if (availability === "unsupported") return ` ${segment("Off", true)} ${chalk.hex(colors.textMuted)("unsupported")}`;
123253
- return ` ${segment("On", this.thinkingDraft)} ${segment("Off", !this.thinkingDraft)}`;
123780
+ const levels = getThinkingLevels(model);
123781
+ const effectiveLevel = effectiveThinkingLevel(model, this.thinkingDraft);
123782
+ const line = ` ${levels.map((level) => {
123783
+ return level === effectiveLevel ? chalk.hex(colors.primary).bold(`[ ${level} ]`) : chalk.hex(colors.text)(level);
123784
+ }).join(" ")}`;
123785
+ if (availability === "unsupported") return `${line} ${chalk.hex(colors.textMuted)("unsupported")}`;
123786
+ return line;
123254
123787
  }
123255
123788
  };
123256
123789
  //#endregion
@@ -123271,6 +123804,7 @@ var TextInputDialogComponent = class extends Container {
123271
123804
  this.onDone = onDone;
123272
123805
  this.opts = opts;
123273
123806
  this.input = new Input();
123807
+ if (opts.initialValue !== void 0) this.input.setValue(opts.initialValue);
123274
123808
  this.input.onSubmit = (value) => {
123275
123809
  this.submit(value);
123276
123810
  };
@@ -123326,7 +123860,7 @@ var TextInputDialogComponent = class extends Container {
123326
123860
  submit(value) {
123327
123861
  if (this.done) return;
123328
123862
  const trimmed = value.trim();
123329
- if (trimmed.length === 0) {
123863
+ if (trimmed.length === 0 && !this.opts.allowEmpty) {
123330
123864
  this.emptyHinted = true;
123331
123865
  return;
123332
123866
  }
@@ -123409,24 +123943,23 @@ async function promptModelSelectionForCatalog(host, providerId, models) {
123409
123943
  const model = models.find((m) => `${providerId}/${m.id}` === selection.alias);
123410
123944
  return model ? {
123411
123945
  model,
123412
- thinking: selection.thinking
123946
+ thinkingLevel: selection.thinkingLevel
123413
123947
  } : void 0;
123414
123948
  }
123415
123949
  function runModelSelector(host, modelDict) {
123416
123950
  return new Promise((resolve) => {
123417
123951
  const firstAlias = Object.keys(modelDict)[0] ?? "";
123418
- const caps = modelDict[firstAlias]?.capabilities ?? [];
123419
123952
  const selector = new ModelSelectorComponent({
123420
123953
  models: modelDict,
123421
123954
  currentValue: firstAlias,
123422
- currentThinking: caps.includes("always_thinking") || caps.includes("thinking"),
123955
+ currentThinkingLevel: (modelDict[firstAlias]?.capabilities ?? []).includes("always_thinking") ? "medium" : "off",
123423
123956
  colors: host.state.theme.colors,
123424
123957
  searchable: true,
123425
- onSelect: ({ alias, thinking }) => {
123958
+ onSelect: ({ alias, thinkingLevel }) => {
123426
123959
  host.restoreEditor();
123427
123960
  resolve({
123428
123961
  alias,
123429
- thinking
123962
+ thinkingLevel
123430
123963
  });
123431
123964
  },
123432
123965
  onCancel: () => {
@@ -123446,13 +123979,24 @@ const WIRE_TYPE_OPTIONS = [{
123446
123979
  label: "Anthropic 协议",
123447
123980
  description: "适用于 Claude 系列模型"
123448
123981
  }];
123449
- const THINKING_OPTIONS = [{
123450
- value: "true",
123451
- label: "开启思考模式"
123452
- }, {
123453
- value: "false",
123454
- label: "关闭思考模式"
123455
- }];
123982
+ const THINKING_OPTIONS = [
123983
+ {
123984
+ value: "off",
123985
+ label: "关闭思考"
123986
+ },
123987
+ {
123988
+ value: "low",
123989
+ label: "低强度思考"
123990
+ },
123991
+ {
123992
+ value: "medium",
123993
+ label: "中强度思考"
123994
+ },
123995
+ {
123996
+ value: "high",
123997
+ label: "高强度思考"
123998
+ }
123999
+ ];
123456
124000
  function promptWireType(host) {
123457
124001
  return new Promise((resolve) => {
123458
124002
  const picker = new ChoicePickerComponent({
@@ -123472,7 +124016,7 @@ function promptWireType(host) {
123472
124016
  host.mountEditorReplacement(picker);
123473
124017
  });
123474
124018
  }
123475
- function promptTextInput(host, title, opts) {
124019
+ function promptTextInput$1(host, title, opts) {
123476
124020
  return new Promise((resolve) => {
123477
124021
  const dialog = new TextInputDialogComponent((result) => {
123478
124022
  host.restoreEditor();
@@ -123491,12 +124035,12 @@ function promptThinkingMode(host) {
123491
124035
  return new Promise((resolve) => {
123492
124036
  const picker = new ChoicePickerComponent({
123493
124037
  title: "思考模式",
123494
- hint: "启用后模型会先思考再回答(需要模型支持)",
124038
+ hint: "选择模型思考强度(需要模型支持)",
123495
124039
  options: THINKING_OPTIONS,
123496
124040
  colors: host.state.theme.colors,
123497
124041
  onSelect: (value) => {
123498
124042
  host.restoreEditor();
123499
- resolve(value === "true");
124043
+ resolve(value);
123500
124044
  },
123501
124045
  onCancel: () => {
123502
124046
  host.restoreEditor();
@@ -123587,7 +124131,7 @@ async function handleConnectCommand(host, args) {
123587
124131
  apiKey,
123588
124132
  models,
123589
124133
  selectedModelId: selection.model.id,
123590
- thinking: selection.thinking
124134
+ thinkingLevel: selection.thinkingLevel
123591
124135
  });
123592
124136
  await host.harness.setConfig({
123593
124137
  providers: config.providers,
@@ -123634,20 +124178,20 @@ async function handleLogoutCommand(host) {
123634
124178
  async function handleDiyConfig(host) {
123635
124179
  const wire = await promptWireType(host);
123636
124180
  if (wire === void 0) return;
123637
- const baseUrl = await promptTextInput(host, "输入服务商 API 地址", { subtitle: "例如 https://api.deepseek.com(可粘贴)" });
124181
+ const baseUrl = await promptTextInput$1(host, "输入服务商 API 地址", { subtitle: "例如 https://api.deepseek.com(可粘贴)" });
123638
124182
  if (baseUrl === void 0) return;
123639
- const apiKey = await promptTextInput(host, "输入 API Key", { subtitle: "密钥保存到 ~/.scream/config.toml(可粘贴,Esc 取消)" });
124183
+ const apiKey = await promptTextInput$1(host, "输入 API Key", { subtitle: "密钥保存到 ~/.scream/config.toml(可粘贴,Esc 取消)" });
123640
124184
  if (apiKey === void 0) return;
123641
- const modelId = await promptTextInput(host, "输入模型型号", { subtitle: "例如 deepseek-v4-flash" });
124185
+ const modelId = await promptTextInput$1(host, "输入模型型号", { subtitle: "例如 deepseek-v4-flash" });
123642
124186
  if (modelId === void 0) return;
123643
- const maxContextStr = await promptTextInput(host, "输入模型最大上下文长度 (tokens)", {
124187
+ const maxContextStr = await promptTextInput$1(host, "输入模型最大上下文长度 (tokens)", {
123644
124188
  subtitle: "默认 131072,DeepSeek V4 填 1000000",
123645
124189
  placeholder: "131072"
123646
124190
  });
123647
124191
  if (maxContextStr === void 0) return;
123648
124192
  const maxContextTokens = parseInt(maxContextStr, 10) || 131072;
123649
- const thinking = await promptThinkingMode(host);
123650
- if (thinking === void 0) return;
124193
+ const thinkingLevel = await promptThinkingMode(host);
124194
+ if (thinkingLevel === void 0) return;
123651
124195
  const providerId = `custom-${modelId.replaceAll(/[^A-Za-z0-9._-]/g, "-")}`;
123652
124196
  const catalogModel = {
123653
124197
  id: modelId,
@@ -123657,7 +124201,7 @@ async function handleDiyConfig(host) {
123657
124201
  image_in: false,
123658
124202
  video_in: false,
123659
124203
  audio_in: false,
123660
- thinking,
124204
+ thinking: thinkingLevel !== "off",
123661
124205
  tool_use: true
123662
124206
  },
123663
124207
  reasoningKey: wire === "anthropic" ? "thinking" : void 0,
@@ -123674,12 +124218,18 @@ async function handleDiyConfig(host) {
123674
124218
  models[`${providerId}/${modelId}`] = catalogModelToAlias(providerId, catalogModel);
123675
124219
  config.models = models;
123676
124220
  config.defaultModel = `${providerId}/${modelId}`;
123677
- config.defaultThinking = thinking;
124221
+ config.defaultThinking = thinkingLevel !== "off";
124222
+ config.thinking = {
124223
+ ...config.thinking,
124224
+ mode: thinkingLevel === "off" ? "off" : "on",
124225
+ effort: thinkingLevel
124226
+ };
123678
124227
  await host.harness.setConfig({
123679
124228
  providers: config.providers,
123680
124229
  models: config.models,
123681
124230
  defaultModel: config.defaultModel,
123682
- defaultThinking: config.defaultThinking
124231
+ defaultThinking: config.defaultThinking,
124232
+ thinking: config.thinking
123683
124233
  });
123684
124234
  await host.authFlow.refreshConfigAfterLogin();
123685
124235
  host.showStatus(`已连接: ${providerId} · ${modelId} (${wire})`);
@@ -124011,6 +124561,7 @@ const darkColors = {
124011
124561
  primary: dark.green,
124012
124562
  accent: dark.tangerine400,
124013
124563
  planMode: "#00FFFF",
124564
+ fusionPlanMode: "#ffff66",
124014
124565
  text: dark.gray100,
124015
124566
  textStrong: dark.gray50,
124016
124567
  textDim: dark.gray500,
@@ -124040,6 +124591,7 @@ const lightColors = {
124040
124591
  primary: light.green,
124041
124592
  accent: light.tangerine700,
124042
124593
  planMode: "#00838F",
124594
+ fusionPlanMode: "#ffff66",
124043
124595
  text: light.gray900,
124044
124596
  textStrong: light.gray900,
124045
124597
  textDim: light.gray700,
@@ -124332,7 +124884,7 @@ function displayModelName(alias, models) {
124332
124884
  function formatModelStatus(options) {
124333
124885
  const model = options.status?.model ?? options.model;
124334
124886
  if (model.trim().length === 0) return "未设置";
124335
- const thinking = (options.status?.thinkingLevel ?? (options.thinking ? "on" : "off")) === "off" ? "off" : "on";
124887
+ const thinking = (options.status?.thinkingLevel ?? options.thinkingLevel) === "off" ? "off" : "on";
124336
124888
  return `${displayModelName(model, options.availableModels)} (thinking ${thinking})`;
124337
124889
  }
124338
124890
  function addFieldRows(lines, rows, muted, value, errorStyle) {
@@ -124357,37 +124909,38 @@ function buildStatusReportLines(options) {
124357
124909
  const errorStyle = chalk.hex(colors.error);
124358
124910
  const severityHex = (sev) => sev === "danger" ? colors.error : sev === "warn" ? colors.warning : colors.success;
124359
124911
  const permission = options.status?.permission ?? options.permissionMode;
124360
- const planMode = options.status?.planMode ?? options.planMode;
124912
+ const planMode = options.planMode !== "off" ? options.planMode : options.status?.planMode ? options.status.planStrategy === "fusion" ? "fusionplan" : "plan" : "off";
124913
+ const planModeLabel = planMode === "off" ? "off" : planMode === "plan" ? "plan" : "fusion";
124361
124914
  const sessionId = options.sessionId.trim().length > 0 ? options.sessionId : "无";
124362
124915
  const rows = [
124363
124916
  {
124364
- label: "模型",
124917
+ label: "模型名称",
124365
124918
  value: formatModelStatus(options)
124366
124919
  },
124367
124920
  {
124368
- label: "目录",
124921
+ label: "工作目录",
124369
124922
  value: options.workDir
124370
124923
  },
124371
124924
  {
124372
- label: "权限",
124925
+ label: "权限模式",
124373
124926
  value: permission
124374
124927
  },
124375
124928
  {
124376
124929
  label: "计划模式",
124377
- value: planMode ? "on" : "off"
124930
+ value: planModeLabel
124378
124931
  },
124379
124932
  {
124380
- label: "会话",
124933
+ label: "会话编号",
124381
124934
  value: sessionId
124382
124935
  }
124383
124936
  ];
124384
124937
  const title = options.sessionTitle?.trim();
124385
124938
  if (title !== void 0 && title.length > 0) rows.push({
124386
- label: "标题",
124939
+ label: "会话标题",
124387
124940
  value: title
124388
124941
  });
124389
124942
  if (options.statusError !== void 0) rows.push({
124390
- label: "警告",
124943
+ label: "状态警告",
124391
124944
  value: options.statusError,
124392
124945
  severity: "error"
124393
124946
  });
@@ -124441,7 +124994,7 @@ async function showStatusReport(host) {
124441
124994
  workDir: appState.workDir,
124442
124995
  sessionId: appState.sessionId,
124443
124996
  sessionTitle: appState.sessionTitle,
124444
- thinking: appState.thinking,
124997
+ thinkingLevel: appState.thinkingLevel,
124445
124998
  permissionMode: appState.permissionMode,
124446
124999
  planMode: appState.planMode,
124447
125000
  contextUsage: appState.contextUsage,
@@ -124500,23 +125053,25 @@ async function handlePlanCommand(host, args) {
124500
125053
  host.showNotice("计划已清除");
124501
125054
  return;
124502
125055
  }
124503
- let enabled;
124504
- if (subcmd.length === 0) enabled = !host.state.appState.planMode;
124505
- else if (subcmd === "on") enabled = true;
124506
- else if (subcmd === "off") enabled = false;
125056
+ let state;
125057
+ if (subcmd.length === 0) state = host.state.appState.planMode === "off" ? "plan" : "off";
125058
+ else if (subcmd === "on") state = "plan";
125059
+ else if (subcmd === "off") state = "off";
124507
125060
  else {
124508
125061
  host.showError(`Unknown plan subcommand: ${subcmd}`);
124509
125062
  return;
124510
125063
  }
124511
- await applyPlanMode(host, session, enabled);
125064
+ await applyPlanMode(host, session, state);
124512
125065
  }
124513
- async function applyPlanMode(host, session, enabled) {
125066
+ async function applyPlanMode(host, session, state) {
125067
+ const enabled = state !== "off";
124514
125068
  try {
124515
- await session.setPlanMode(enabled);
124516
- host.setAppState({ planMode: enabled });
125069
+ if (((await session.getStatus().catch(() => null))?.planMode ?? false) !== enabled) await session.setPlanMode(enabled);
125070
+ host.setAppState({ planMode: state });
124517
125071
  if (enabled) {
124518
125072
  const plan = await session.getPlan().catch(() => null);
124519
- host.showNotice("计划模式:开启", plan?.path !== void 0 ? `计划将创建于此:${plan.path}` : void 0);
125073
+ const label = state === "fusionplan" ? "融合计划模式:开启" : "计划模式:开启";
125074
+ host.showNotice(label, plan?.path !== void 0 ? `计划将创建于此:${plan.path}` : void 0);
124520
125075
  return;
124521
125076
  }
124522
125077
  host.showNotice("计划模式:关闭");
@@ -124525,6 +125080,23 @@ async function applyPlanMode(host, session, enabled) {
124525
125080
  host.showError(`Failed to set plan mode: ${msg}`);
124526
125081
  }
124527
125082
  }
125083
+ async function handleFusionPlanCommand(host, args) {
125084
+ const session = host.session;
125085
+ if (session === void 0) {
125086
+ host.showError(NO_ACTIVE_SESSION_MESSAGE);
125087
+ return;
125088
+ }
125089
+ const subcmd = args.trim().toLowerCase();
125090
+ let state;
125091
+ if (subcmd.length === 0) state = host.state.appState.planMode === "fusionplan" ? "off" : "fusionplan";
125092
+ else if (subcmd === "on") state = "fusionplan";
125093
+ else if (subcmd === "off") state = "off";
125094
+ else {
125095
+ host.showError(`Unknown fusionplan subcommand: ${subcmd}`);
125096
+ return;
125097
+ }
125098
+ await applyPlanMode(host, session, state);
125099
+ }
124528
125100
  async function handleYoloCommand(host, args) {
124529
125101
  const session = host.session;
124530
125102
  if (session === void 0) {
@@ -124697,7 +125269,8 @@ async function applyEditorChoice(host, value) {
124697
125269
  await saveTuiConfig({
124698
125270
  theme: host.state.appState.theme,
124699
125271
  editorCommand,
124700
- notifications: host.state.appState.notifications
125272
+ notifications: host.state.appState.notifications,
125273
+ like: host.state.appState.like
124701
125274
  });
124702
125275
  } catch (error) {
124703
125276
  host.showStatus(`Failed to save editor: ${formatErrorMessage(error)}`, host.state.theme.colors.error);
@@ -124715,33 +125288,32 @@ function showModelPicker(host, selectedValue = host.state.appState.model) {
124715
125288
  models: host.state.appState.availableModels,
124716
125289
  currentValue: host.state.appState.model,
124717
125290
  selectedValue,
124718
- currentThinking: host.state.appState.thinking,
125291
+ currentThinkingLevel: host.state.appState.thinkingLevel,
124719
125292
  colors: host.state.theme.colors,
124720
125293
  searchable: true,
124721
- onSelect: ({ alias, thinking }) => {
125294
+ onSelect: ({ alias, thinkingLevel }) => {
124722
125295
  host.restoreEditor();
124723
- performModelSwitch(host, alias, thinking);
125296
+ performModelSwitch(host, alias, thinkingLevel);
124724
125297
  },
124725
125298
  onCancel: () => {
124726
125299
  host.restoreEditor();
124727
125300
  }
124728
125301
  }));
124729
125302
  }
124730
- async function performModelSwitch(host, alias, thinking) {
125303
+ async function performModelSwitch(host, alias, thinkingLevel) {
124731
125304
  if (host.state.appState.streamingPhase !== "idle") {
124732
125305
  host.showError("Cannot switch models while streaming — press Esc or Ctrl-C first.");
124733
125306
  return;
124734
125307
  }
124735
- const level = thinking ? "on" : "off";
124736
125308
  const prevModel = host.state.appState.model;
124737
- const prevThinking = host.state.appState.thinking;
124738
- const runtimeChanged = alias !== prevModel || thinking !== prevThinking;
125309
+ const prevThinkingLevel = host.state.appState.thinkingLevel;
125310
+ const runtimeChanged = alias !== prevModel || thinkingLevel !== prevThinkingLevel;
124739
125311
  const session = host.session;
124740
125312
  try {
124741
- if (session === void 0 && runtimeChanged) await host.authFlow.activateModelAfterLogin(alias, thinking);
125313
+ if (session === void 0 && runtimeChanged) await host.authFlow.activateModelAfterLogin(alias, thinkingLevel);
124742
125314
  else if (session !== void 0) {
124743
125315
  if (alias !== prevModel) await session.setModel(alias);
124744
- if (thinking !== prevThinking) await session.setThinking(level);
125316
+ if (thinkingLevel !== prevThinkingLevel) await session.setThinking(thinkingLevel);
124745
125317
  }
124746
125318
  } catch (error) {
124747
125319
  const msg = formatErrorMessage(error);
@@ -124750,25 +125322,33 @@ async function performModelSwitch(host, alias, thinking) {
124750
125322
  }
124751
125323
  host.setAppState({
124752
125324
  model: alias,
124753
- thinking
125325
+ thinkingLevel
124754
125326
  });
124755
125327
  let persisted = false;
124756
125328
  try {
124757
- persisted = await persistModelSelection(host, alias, thinking);
125329
+ persisted = await persistModelSelection(host, alias, thinkingLevel);
124758
125330
  } catch (error) {
124759
125331
  const msg = formatErrorMessage(error);
124760
125332
  host.showError(`Switched to ${alias}, but failed to save default: ${msg}`);
124761
125333
  return;
124762
125334
  }
124763
- const status = runtimeChanged ? `Switched to ${alias} with thinking ${level}.` : persisted ? `Saved ${alias} with thinking ${level} as default.` : `Already using ${alias} with thinking ${level}.`;
125335
+ const status = runtimeChanged ? `Switched to ${alias} with thinking ${thinkingLevel}.` : persisted ? `Saved ${alias} with thinking ${thinkingLevel} as default.` : `Already using ${alias} with thinking ${thinkingLevel}.`;
124764
125336
  host.showStatus(status, host.state.theme.colors.success);
124765
125337
  }
124766
- async function persistModelSelection(host, alias, thinking) {
125338
+ async function persistModelSelection(host, alias, thinkingLevel) {
124767
125339
  const config = await host.harness.getConfig({ reload: true });
124768
- if (config.defaultModel === alias && config.defaultThinking === thinking) return false;
125340
+ const effectiveThinking = thinkingLevel !== "off";
125341
+ const existingEffort = config.thinking?.effort;
125342
+ const newEffort = effectiveThinking ? thinkingLevel : existingEffort;
125343
+ if (config.defaultModel === alias && config.defaultThinking === effectiveThinking && existingEffort === newEffort) return false;
124769
125344
  await host.harness.setConfig({
124770
125345
  defaultModel: alias,
124771
- defaultThinking: thinking
125346
+ defaultThinking: effectiveThinking,
125347
+ thinking: {
125348
+ ...config.thinking,
125349
+ mode: effectiveThinking ? "on" : "off",
125350
+ effort: newEffort
125351
+ }
124772
125352
  });
124773
125353
  return true;
124774
125354
  }
@@ -124795,7 +125375,8 @@ async function applyThemeChoice(host, theme) {
124795
125375
  await saveTuiConfig({
124796
125376
  theme,
124797
125377
  editorCommand: host.state.appState.editorCommand,
124798
- notifications: host.state.appState.notifications
125378
+ notifications: host.state.appState.notifications,
125379
+ like: host.state.appState.like
124799
125380
  });
124800
125381
  } catch (error) {
124801
125382
  host.showStatus(`Failed to save theme: ${formatErrorMessage(error)}`, host.state.theme.colors.error);
@@ -129504,6 +130085,20 @@ async function writeUpdateCache(value, filePath = getUpdateStateFile()) {
129504
130085
  //#region src/cli/update/cdn.ts
129505
130086
  const NPM_TIMEOUT_MS = 15e3;
129506
130087
  /**
130088
+ * Resolve the npm executable name for the current platform.
130089
+ *
130090
+ * On Windows, `npm` is actually `npm.cmd` — a batch file. Node's child_process
130091
+ * can execute `.cmd` files directly without `shell: true`, but only when the
130092
+ * filename includes the `.cmd` extension. Using `'npm'` without `.cmd` would
130093
+ * fail with ENOENT on Windows.
130094
+ *
130095
+ * We deliberately avoid `shell: true` because passing args alongside
130096
+ * `shell: true` triggers Node's DEP0190 deprecation warning on every spawn.
130097
+ */
130098
+ function npmExecutable$1() {
130099
+ return process.platform === "win32" ? "npm.cmd" : "npm";
130100
+ }
130101
+ /**
129507
130102
  * Query the latest published Scream Code version from the npm registry
129508
130103
  * via `npm view scream-code version`.
129509
130104
  *
@@ -129515,14 +130110,13 @@ const NPM_TIMEOUT_MS = 15e3;
129515
130110
  * `execFileImpl` is injectable for tests; defaults to a promisified spawn.
129516
130111
  */
129517
130112
  async function fetchLatestVersionFromNpm(execFileImpl = execFile) {
129518
- const { stdout } = await promisify(execFileImpl)("npm", [
130113
+ const { stdout } = await promisify(execFileImpl)(npmExecutable$1(), [
129519
130114
  "view",
129520
130115
  "scream-code",
129521
130116
  "version"
129522
130117
  ], {
129523
130118
  timeout: NPM_TIMEOUT_MS,
129524
- maxBuffer: 1024,
129525
- shell: process.platform === "win32"
130119
+ maxBuffer: 1024
129526
130120
  });
129527
130121
  const raw = stdout.trim();
129528
130122
  if (valid(raw) === null) throw new Error(`npm view 返回的版本号不是合法 semver: ${JSON.stringify(raw)}`);
@@ -129562,6 +130156,15 @@ function selectUpdateTarget(currentVersion, latest) {
129562
130156
  * Network-error detection with user-friendly Chinese prompts.
129563
130157
  */
129564
130158
  const INSTALL_TIMEOUT_MS = 3e5;
130159
+ /**
130160
+ * Resolve the npm executable name for the current platform.
130161
+ *
130162
+ * On Windows, `npm` is `npm.cmd` — a batch file Node can spawn directly
130163
+ * without `shell: true` (which would trigger DEP0190 when args are passed).
130164
+ */
130165
+ function npmExecutable() {
130166
+ return process.platform === "win32" ? "npm.cmd" : "npm";
130167
+ }
129565
130168
  const NETWORK_ERROR_PATTERNS = [
129566
130169
  /ETIMEDOUT/i,
129567
130170
  /ENOTFOUND/i,
@@ -129586,8 +130189,7 @@ async function runInstallStep(cmd, args, cwd, label, timeoutMs = INSTALL_TIMEOUT
129586
130189
  return new Promise((resolve) => {
129587
130190
  const child = spawn(cmd, args, {
129588
130191
  cwd,
129589
- stdio: "pipe",
129590
- shell: process.platform === "win32"
130192
+ stdio: "pipe"
129591
130193
  });
129592
130194
  let stderr = "";
129593
130195
  let settled = false;
@@ -129657,7 +130259,7 @@ async function handleUpdateCommand(host) {
129657
130259
  }
129658
130260
  host.showStatus(`正在更新到 ${target.version}...`);
129659
130261
  host.showStatus("正在通过 npm 安装最新版本...");
129660
- const result = await runInstallStep("npm", [
130262
+ const result = await runInstallStep(npmExecutable(), [
129661
130263
  "install",
129662
130264
  "-g",
129663
130265
  "scream-code@latest"
@@ -131401,6 +132003,83 @@ function disableLoopMode(host, message) {
131401
132003
  if (message) host.showStatus(message);
131402
132004
  }
131403
132005
  //#endregion
132006
+ //#region src/tui/commands/like.ts
132007
+ function promptTextInput(host, title, opts) {
132008
+ const { promise, resolve } = Promise.withResolvers();
132009
+ const dialog = new TextInputDialogComponent((result) => {
132010
+ host.restoreEditor();
132011
+ resolve(result.kind === "ok" ? result.value : void 0);
132012
+ }, {
132013
+ title,
132014
+ subtitle: opts?.subtitle,
132015
+ placeholder: opts?.placeholder,
132016
+ initialValue: opts?.initialValue,
132017
+ allowEmpty: opts?.allowEmpty,
132018
+ colors: host.state.theme.colors
132019
+ });
132020
+ host.mountEditorReplacement(dialog);
132021
+ return promise;
132022
+ }
132023
+ function buildRoleAdditionalText(prefs) {
132024
+ const parts = [];
132025
+ if (prefs.nickname !== void 0 && prefs.nickname.trim().length > 0) parts.push(`The user's preferred nickname is "${prefs.nickname.trim()}".`);
132026
+ if (prefs.tone !== void 0 && prefs.tone.trim().length > 0) parts.push(`Respond in a ${prefs.tone.trim()} tone.`);
132027
+ if (prefs.other !== void 0 && prefs.other.trim().length > 0) parts.push(`Additional user preferences: ${prefs.other.trim()}`);
132028
+ return parts.join("\n");
132029
+ }
132030
+ async function getUserPrefsPath() {
132031
+ return join(getDataDir(), "user-prefs.md");
132032
+ }
132033
+ async function persistLikePreferences(host, prefs) {
132034
+ const configPath = getTuiConfigPath();
132035
+ await saveTuiConfig({
132036
+ ...await loadTuiConfig(configPath),
132037
+ like: prefs
132038
+ }, configPath);
132039
+ const roleAdditional = buildRoleAdditionalText(prefs);
132040
+ await writeFile(await getUserPrefsPath(), roleAdditional, "utf-8");
132041
+ host.setAppState({ like: prefs });
132042
+ }
132043
+ async function handleLikeCommand(host) {
132044
+ const current = host.state.appState.like ?? {};
132045
+ const nickname = await promptTextInput(host, "设置昵称", {
132046
+ subtitle: "你希望我怎么称呼你?留空表示不设置。",
132047
+ placeholder: "例如:Alex",
132048
+ initialValue: current.nickname,
132049
+ allowEmpty: true
132050
+ });
132051
+ if (nickname === void 0) {
132052
+ host.showStatus("已取消 /like 设置", host.state.theme.colors.textDim);
132053
+ return;
132054
+ }
132055
+ const tone = await promptTextInput(host, "设置回应语气", {
132056
+ subtitle: "例如:友好、专业、幽默、简洁等(留空表示不设置)",
132057
+ placeholder: "例如:友好而专业",
132058
+ initialValue: current.tone,
132059
+ allowEmpty: true
132060
+ });
132061
+ if (tone === void 0) {
132062
+ host.showStatus("已取消 /like 设置", host.state.theme.colors.textDim);
132063
+ return;
132064
+ }
132065
+ const other = await promptTextInput(host, "其他偏好", {
132066
+ subtitle: "例如:多说例子、先给结论再展开、避免术语等(留空表示不设置)",
132067
+ placeholder: "例如:请用中文回答,避免缩写",
132068
+ initialValue: current.other,
132069
+ allowEmpty: true
132070
+ });
132071
+ if (other === void 0) {
132072
+ host.showStatus("已取消 /like 设置", host.state.theme.colors.textDim);
132073
+ return;
132074
+ }
132075
+ await persistLikePreferences(host, {
132076
+ nickname: nickname.trim().length > 0 ? nickname.trim() : void 0,
132077
+ tone: tone.trim().length > 0 ? tone.trim() : void 0,
132078
+ other: other.trim().length > 0 ? other.trim() : void 0
132079
+ });
132080
+ host.showStatus("偏好已保存(下次新会话生效)", host.state.theme.colors.success);
132081
+ }
132082
+ //#endregion
131404
132083
  //#region src/tui/commands/dispatch.ts
131405
132084
  function dispatchInput(host, text) {
131406
132085
  if (parseSlashInput(text) !== null) {
@@ -131492,6 +132171,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
131492
132171
  case "settings":
131493
132172
  showSettingsSelector(host);
131494
132173
  return;
132174
+ case "like":
132175
+ await handleLikeCommand(host);
132176
+ return;
131495
132177
  case "usage":
131496
132178
  showUsage(host).catch((error) => {
131497
132179
  host.showError(`显示使用情况失败:${formatErrorMessage(error)}`);
@@ -131514,6 +132196,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
131514
132196
  case "plan":
131515
132197
  await handlePlanCommand(host, args);
131516
132198
  return;
132199
+ case "fusionplan":
132200
+ await handleFusionPlanCommand(host, args);
132201
+ return;
131517
132202
  case "wolfpack":
131518
132203
  await handleWolfpackCommand(host, args);
131519
132204
  return;
@@ -131584,9 +132269,9 @@ var AuthFlowController = class {
131584
132269
  availableProviders: config.providers ?? {}
131585
132270
  });
131586
132271
  }
131587
- async activateModelAfterLogin(model, thinking) {
132272
+ async activateModelAfterLogin(model, thinkingLevel) {
131588
132273
  const { host } = this;
131589
- const level = thinking === void 0 ? void 0 : thinking ? "on" : "off";
132274
+ const level = thinkingLevel === void 0 ? void 0 : thinkingLevel === "off" ? "off" : thinkingLevel;
131590
132275
  if (host.session !== void 0) {
131591
132276
  await host.session.setModel(model);
131592
132277
  if (level !== void 0) await host.session.setThinking(level);
@@ -131597,7 +132282,7 @@ var AuthFlowController = class {
131597
132282
  model,
131598
132283
  thinking: level,
131599
132284
  permission: host.options.startup.auto ? "auto" : host.options.startup.yolo ? "yolo" : void 0,
131600
- planMode: host.state.appState.planMode ? true : void 0
132285
+ planMode: host.state.appState.planMode !== "off" ? true : void 0
131601
132286
  });
131602
132287
  await host.setSession(session);
131603
132288
  host.setAppState({
@@ -131634,14 +132319,14 @@ var AuthFlowController = class {
131634
132319
  });
131635
132320
  return;
131636
132321
  }
131637
- await this.activateModelAfterLogin(defaultModel, config.defaultThinking);
132322
+ await this.activateModelAfterLogin(defaultModel, resolveDefaultThinkingLevel(config));
131638
132323
  const appStatePatch = {
131639
132324
  availableModels,
131640
132325
  availableProviders,
131641
132326
  model: defaultModel,
131642
- maxContextTokens: selected.maxContextSize
132327
+ maxContextTokens: selected.maxContextSize,
132328
+ thinkingLevel: resolveDefaultThinkingLevel(config)
131643
132329
  };
131644
- if (config.defaultThinking !== void 0) appStatePatch.thinking = config.defaultThinking;
131645
132330
  host.setAppState(appStatePatch);
131646
132331
  }
131647
132332
  async refreshConfigAfterLogout() {
@@ -131650,13 +132335,19 @@ var AuthFlowController = class {
131650
132335
  availableModels: config.models ?? {},
131651
132336
  availableProviders: config.providers ?? {},
131652
132337
  model: "",
131653
- thinking: false,
132338
+ thinkingLevel: "off",
131654
132339
  maxContextTokens: 0,
131655
132340
  contextUsage: 0,
131656
132341
  contextTokens: 0
131657
132342
  });
131658
132343
  }
131659
132344
  };
132345
+ function resolveDefaultThinkingLevel(config) {
132346
+ if (config.thinking?.mode === "off" || config.defaultThinking === false) return "off";
132347
+ const effort = config.thinking?.effort;
132348
+ if (effort === "low" || effort === "medium" || effort === "high" || effort === "xhigh" || effort === "max") return effort;
132349
+ return config.defaultThinking === true ? "medium" : "off";
132350
+ }
131660
132351
  //#endregion
131661
132352
  //#region src/utils/image/image-mime.ts
131662
132353
  function parseImageMeta(bytes) {
@@ -132378,8 +133069,9 @@ var EditorKeyboardController = class {
132378
133069
  host.showError(NO_ACTIVE_SESSION_MESSAGE);
132379
133070
  return;
132380
133071
  }
132381
- const next = !host.state.appState.planMode;
132382
- host.handlePlanToggle(next);
133072
+ const current = host.state.appState.planMode;
133073
+ const next = current === "off" ? "plan" : current === "plan" ? "fusionplan" : "off";
133074
+ host.handlePlanModeStateChange(next);
132383
133075
  };
132384
133076
  editor.onOpenExternalEditor = () => {
132385
133077
  this.openExternalEditor();
@@ -133308,9 +134000,10 @@ var SessionEventHandler = class {
133308
134000
  if (event.contextUsage !== void 0) patch.contextUsage = event.contextUsage;
133309
134001
  if (event.contextTokens !== void 0) patch.contextTokens = event.contextTokens;
133310
134002
  if (event.maxContextTokens !== void 0) patch.maxContextTokens = event.maxContextTokens;
133311
- if (event.planMode !== void 0) patch.planMode = event.planMode;
134003
+ if (event.planMode !== void 0) patch.planMode = event.planMode ? event.planStrategy === "fusion" ? "fusionplan" : this.host.state.appState.planMode === "fusionplan" ? "fusionplan" : "plan" : "off";
133312
134004
  if (event.permission !== void 0) patch.permissionMode = event.permission;
133313
134005
  if (event.model !== void 0) patch.model = event.model;
134006
+ if (event.thinkingLevel !== void 0) patch.thinkingLevel = event.thinkingLevel;
133314
134007
  if (Object.keys(patch).length > 0) this.host.setAppState(patch);
133315
134008
  }
133316
134009
  handleSessionMetaChanged(event) {
@@ -133728,7 +134421,7 @@ function appStateFromResumeAgent(agent) {
133728
134421
  contextTokens,
133729
134422
  maxContextTokens,
133730
134423
  contextUsage,
133731
- planMode: agent.plan !== null,
134424
+ planMode: agent.plan !== null ? "plan" : "off",
133732
134425
  permissionMode: agent.permission.mode
133733
134426
  };
133734
134427
  }
@@ -136072,6 +136765,94 @@ var TasksBrowserController = class {
136072
136765
  }
136073
136766
  };
136074
136767
  //#endregion
136768
+ //#region src/tui/components/messages/fusion-plan-status.ts
136769
+ const STATUS_ICONS = {
136770
+ pending: "⏳",
136771
+ running: "⏳",
136772
+ completed: "✅",
136773
+ failed: "❌"
136774
+ };
136775
+ var FusionPlanStatusComponent = class {
136776
+ data;
136777
+ colors;
136778
+ ui;
136779
+ spinnerFrame = 0;
136780
+ intervalId = null;
136781
+ cachedWidth;
136782
+ cachedLines;
136783
+ constructor(data, colors, ui) {
136784
+ this.data = data;
136785
+ this.colors = colors;
136786
+ this.ui = ui;
136787
+ this.startSpinner();
136788
+ }
136789
+ setData(data) {
136790
+ this.data = data;
136791
+ this.cachedWidth = void 0;
136792
+ this.cachedLines = void 0;
136793
+ if (this.isTerminal(data.phase)) this.stopSpinner();
136794
+ else if (this.intervalId === null) this.startSpinner();
136795
+ this.ui.requestRender();
136796
+ }
136797
+ invalidate() {
136798
+ this.cachedWidth = void 0;
136799
+ this.cachedLines = void 0;
136800
+ }
136801
+ render(width) {
136802
+ if (this.cachedLines !== void 0 && this.cachedWidth === width) return this.cachedLines;
136803
+ const contentWidth = Math.max(1, width - 2);
136804
+ const lines = [""];
136805
+ const headerLines = new Text(this.renderHeader(), 0, 0).render(contentWidth);
136806
+ for (let i = 0; i < headerLines.length; i += 1) lines.push((i === 0 ? "" : " ") + headerLines[i]);
136807
+ for (const worker of this.data.workers) {
136808
+ const icon = STATUS_ICONS[worker.status];
136809
+ const isActive = worker.status === "running" || worker.status === "pending";
136810
+ const runningIcon = worker.status === "running" ? this.currentSpinnerFrame() + " " : "";
136811
+ const label = chalk.hex(this.colors.textDim)(`视角 ${worker.index + 1}`);
136812
+ const name = chalk.hex(this.colors.text)(worker.label);
136813
+ const wrapped = new Text(`${isActive ? runningIcon : ""}${icon} ${label} · ${name}`, 0, 0).render(contentWidth);
136814
+ for (const line of wrapped) lines.push(" " + line);
136815
+ }
136816
+ if (this.data.detail !== void 0 && this.data.detail.length > 0) {
136817
+ const detailLines = new Text(chalk.hex(this.colors.textDim)(this.data.detail), 0, 0).render(contentWidth);
136818
+ for (const line of detailLines) lines.push(" " + line);
136819
+ }
136820
+ this.cachedWidth = width;
136821
+ this.cachedLines = lines;
136822
+ return lines;
136823
+ }
136824
+ renderHeader() {
136825
+ const phase = this.data.phase;
136826
+ const { completedWorkers, totalWorkers, failedWorkers } = this.data;
136827
+ const tone = phase === "failed" ? this.colors.error : this.colors.primary;
136828
+ const spinner = this.isTerminal(phase) ? "" : this.currentSpinnerFrame() + " ";
136829
+ const summary = `融合计划 · ${completedWorkers}/${totalWorkers} 个视角`;
136830
+ const failedText = failedWorkers > 0 ? `(${failedWorkers} 失败)` : "";
136831
+ const phaseText = phase === "planning" ? "规划中" : phase === "synthesis" ? "完毕后将自动切换为 Plan 执行,当前融合中..." : phase === "completed" ? "已完成" : "失败";
136832
+ return chalk.hex(tone)(`${spinner}${summary}${failedText} · ${phaseText}`);
136833
+ }
136834
+ currentSpinnerFrame() {
136835
+ return BRAILLE_SPINNER_FRAMES[this.spinnerFrame % BRAILLE_SPINNER_FRAMES.length];
136836
+ }
136837
+ startSpinner() {
136838
+ if (this.intervalId !== null) return;
136839
+ this.intervalId = setInterval(() => {
136840
+ this.spinnerFrame = (this.spinnerFrame + 1) % BRAILLE_SPINNER_FRAMES.length;
136841
+ this.cachedWidth = void 0;
136842
+ this.cachedLines = void 0;
136843
+ this.ui.requestRender();
136844
+ }, 80);
136845
+ }
136846
+ stopSpinner() {
136847
+ if (this.intervalId === null) return;
136848
+ clearInterval(this.intervalId);
136849
+ this.intervalId = null;
136850
+ }
136851
+ isTerminal(phase) {
136852
+ return phase === "completed" || phase === "failed";
136853
+ }
136854
+ };
136855
+ //#endregion
136075
136856
  //#region src/tui/components/messages/cron-message.ts
136076
136857
  var CronMessageComponent = class {
136077
136858
  colors;
@@ -136191,6 +136972,52 @@ function basenameLike(raw) {
136191
136972
  return raw.split(/[\\/]/).filter((part) => part.length > 0).at(-1) ?? raw;
136192
136973
  }
136193
136974
  //#endregion
136975
+ //#region src/tui/utils/cached-container.ts
136976
+ /**
136977
+ * A Container that caches its rendered lines until explicitly invalidated or
136978
+ * its child list changes.
136979
+ *
136980
+ * pi-tui's built-in `Container.render()` walks and concatenates every child on
136981
+ * every frame. For large static subtrees (e.g., committed transcript history)
136982
+ * this work is wasted because the children do not change between frames.
136983
+ *
136984
+ * This subclass caches the concatenated output. Callers are responsible for
136985
+ * invalidating the container when a child mutates internally; structural
136986
+ * changes (`addChild`, `removeChild`, `clear`) automatically mark the cache
136987
+ * dirty.
136988
+ */
136989
+ var CachedContainer = class extends Container {
136990
+ cachedWidth;
136991
+ cachedLines;
136992
+ dirty = true;
136993
+ addChild(component) {
136994
+ super.addChild(component);
136995
+ this.markDirty();
136996
+ }
136997
+ removeChild(component) {
136998
+ super.removeChild(component);
136999
+ this.markDirty();
137000
+ }
137001
+ clear() {
137002
+ super.clear();
137003
+ this.markDirty();
137004
+ }
137005
+ invalidate() {
137006
+ super.invalidate();
137007
+ this.markDirty();
137008
+ }
137009
+ render(width) {
137010
+ if (!this.dirty && this.cachedWidth === width && this.cachedLines !== void 0) return this.cachedLines;
137011
+ this.cachedWidth = width;
137012
+ this.cachedLines = super.render(width);
137013
+ this.dirty = false;
137014
+ return this.cachedLines;
137015
+ }
137016
+ markDirty() {
137017
+ this.dirty = true;
137018
+ }
137019
+ };
137020
+ //#endregion
136194
137021
  //#region src/tui/components/transcript/committed-transcript.ts
136195
137022
  var CommittedMessageComponent = class {
136196
137023
  entry;
@@ -136255,7 +137082,7 @@ var CommittedMessageComponent = class {
136255
137082
  }
136256
137083
  }
136257
137084
  };
136258
- var CommittedTranscriptComponent = class extends Container {
137085
+ var CommittedTranscriptComponent = class extends CachedContainer {
136259
137086
  header;
136260
137087
  colors;
136261
137088
  committedCount = 0;
@@ -136272,6 +137099,7 @@ var CommittedTranscriptComponent = class extends Container {
136272
137099
  this.committedCount = count;
136273
137100
  if (count === 0) this.header.setText("");
136274
137101
  else this.header.setText(` ${chalk.hex(this.colors.textDim)(`↑ 还有 ${count} 条历史消息`)}`);
137102
+ this.invalidate();
136275
137103
  }
136276
137104
  appendEntry(entry, colors) {
136277
137105
  this.addChild(new CommittedMessageComponent(entry, colors));
@@ -136312,38 +137140,40 @@ var TranscriptController = class TranscriptController {
136312
137140
  return this.host.state.transcriptContainer.children.length;
136313
137141
  }
136314
137142
  commit() {
136315
- const { state } = this.host;
136316
- if (state.appState.streamingPhase !== "idle") return;
136317
- const container = state.transcriptContainer;
136318
- const children = container.children;
136319
- if (children.length <= TranscriptController.LIVE_LIMIT) return;
136320
- const toCommit = [];
136321
- for (const child of children) {
136322
- if (this.pendingComponents.has(child)) continue;
136323
- if (child === this.welcomeComponent) continue;
136324
- if (child === this.committedComponent) continue;
136325
- const entry = this.liveComponentToEntry.get(child);
136326
- if (entry === void 0) continue;
136327
- if (children.length - toCommit.length <= TranscriptController.LIVE_LIMIT) break;
136328
- toCommit.push({
136329
- component: child,
136330
- entry
136331
- });
136332
- }
136333
- if (toCommit.length === 0) return;
136334
- if (this.committedComponent === void 0) {
136335
- this.committedComponent = new CommittedTranscriptComponent(state.theme.colors);
136336
- container.children.unshift(this.committedComponent);
136337
- }
136338
- for (const { component, entry } of toCommit) {
136339
- this.committedComponent.appendEntry(entry, state.theme.colors);
136340
- container.removeChild(component);
136341
- this.liveComponentToEntry.delete(component);
136342
- }
136343
- this.committedComponent.setCount(this.committedComponent.getCount() + toCommit.length);
136344
- if (process.env["SCREAM_CODE_DEBUG"] === "1") this.host.showStatus(`[debug] committed=${this.committedComponent.getCount()} live=${this.getLiveCount()}`);
136345
- container.invalidate();
136346
- state.ui.requestRender();
137143
+ this.host.batchUpdate(() => {
137144
+ const { state } = this.host;
137145
+ if (state.appState.streamingPhase !== "idle") return;
137146
+ const container = state.transcriptContainer;
137147
+ const children = container.children;
137148
+ if (children.length <= TranscriptController.LIVE_LIMIT) return;
137149
+ const toCommit = [];
137150
+ for (const child of children) {
137151
+ if (this.pendingComponents.has(child)) continue;
137152
+ if (child === this.welcomeComponent) continue;
137153
+ if (child === this.committedComponent) continue;
137154
+ const entry = this.liveComponentToEntry.get(child);
137155
+ if (entry === void 0) continue;
137156
+ if (children.length - toCommit.length <= TranscriptController.LIVE_LIMIT) break;
137157
+ toCommit.push({
137158
+ component: child,
137159
+ entry
137160
+ });
137161
+ }
137162
+ if (toCommit.length === 0) return;
137163
+ if (this.committedComponent === void 0) {
137164
+ this.committedComponent = new CommittedTranscriptComponent(state.theme.colors);
137165
+ container.children.unshift(this.committedComponent);
137166
+ }
137167
+ for (const { component, entry } of toCommit) {
137168
+ this.committedComponent.appendEntry(entry, state.theme.colors);
137169
+ container.removeChild(component);
137170
+ this.liveComponentToEntry.delete(component);
137171
+ }
137172
+ this.committedComponent.setCount(this.committedComponent.getCount() + toCommit.length);
137173
+ if (process.env["SCREAM_CODE_DEBUG"] === "1") this.host.showStatus(`[debug] committed=${this.committedComponent.getCount()} live=${this.getLiveCount()}`);
137174
+ container.invalidate();
137175
+ state.ui.requestRender();
137176
+ });
136347
137177
  }
136348
137178
  createComponent(entry) {
136349
137179
  const { state, imageStore } = this.host;
@@ -136382,9 +137212,11 @@ var TranscriptController = class TranscriptController {
136382
137212
  return tc;
136383
137213
  }
136384
137214
  if (entry.backgroundAgentStatus !== void 0) return new BackgroundAgentStatusComponent(entry.backgroundAgentStatus, state.theme.colors);
137215
+ if (entry.fusionPlanStatus !== void 0) return new FusionPlanStatusComponent(entry.fusionPlanStatus, state.theme.colors, state.ui);
136385
137216
  return entry.renderMode === "notice" ? new NoticeMessageComponent(entry.content, entry.detail, state.theme.colors) : new StatusMessageComponent(entry.content, state.theme.colors, entry.color);
136386
137217
  case "status":
136387
137218
  if (entry.backgroundAgentStatus !== void 0) return new BackgroundAgentStatusComponent(entry.backgroundAgentStatus, state.theme.colors);
137219
+ if (entry.fusionPlanStatus !== void 0) return new FusionPlanStatusComponent(entry.fusionPlanStatus, state.theme.colors, state.ui);
136388
137220
  return entry.renderMode === "notice" ? new NoticeMessageComponent(entry.content, entry.detail, state.theme.colors) : new StatusMessageComponent(entry.content, state.theme.colors, entry.color);
136389
137221
  case "cron":
136390
137222
  if (entry.cronData === void 0) return null;
@@ -136401,6 +137233,7 @@ var TranscriptController = class TranscriptController {
136401
137233
  this.host.state.transcriptContainer.addChild(component);
136402
137234
  this.host.state.ui.requestRender();
136403
137235
  }
137236
+ return component ?? null;
136404
137237
  }
136405
137238
  appendApprovalEntry(request, response) {
136406
137239
  if (request.toolName === "ExitPlanMode" || request.display.kind === "plan_review") return;
@@ -137413,6 +138246,377 @@ var QueuePaneComponent = class extends Container {
137413
138246
  }
137414
138247
  };
137415
138248
  //#endregion
138249
+ //#region src/tui/utils/fusion-plan.ts
138250
+ /**
138251
+ * Fusion plan worker orchestration.
138252
+ *
138253
+ * Spawns multiple `scream` CLI subagents in headless JSON mode to produce
138254
+ * parallel implementation plans, then synthesizes them into a single plan.
138255
+ *
138256
+ * This is intentionally a TUI-layer strategy: agent-core still sees a plain
138257
+ * boolean plan mode, and the synthesized plan is injected into the normal
138258
+ * plan file before the agent takes over.
138259
+ */
138260
+ const APP_ROOT = fileURLToPath(new URL("../../..", import.meta.url));
138261
+ const MAIN_TS = resolve(APP_ROOT, "src/main.ts");
138262
+ const MAIN_MJS = resolve(APP_ROOT, "dist/main.mjs");
138263
+ const RAW_TEXT_LOADER = resolve(APP_ROOT, "../../build/register-raw-text-loader.mjs");
138264
+ function tsxCommand() {
138265
+ return process.platform === "win32" ? "tsx.cmd" : "tsx";
138266
+ }
138267
+ function buildSourceCommand() {
138268
+ const prefixArgs = existsSync(RAW_TEXT_LOADER) ? [
138269
+ "--import",
138270
+ RAW_TEXT_LOADER,
138271
+ MAIN_TS
138272
+ ] : [MAIN_TS];
138273
+ return {
138274
+ command: tsxCommand(),
138275
+ prefixArgs
138276
+ };
138277
+ }
138278
+ function trySourceOrDist() {
138279
+ if (existsSync(MAIN_TS)) return buildSourceCommand();
138280
+ if (existsSync(MAIN_MJS)) return {
138281
+ command: process.execPath,
138282
+ prefixArgs: [MAIN_MJS]
138283
+ };
138284
+ return {
138285
+ command: "scream",
138286
+ prefixArgs: []
138287
+ };
138288
+ }
138289
+ const SCREAM_FUSIONPLAN_SUBAGENT_ENV = "SCREAM_FUSIONPLAN_SUBAGENT";
138290
+ const WORKER_ANGLES = [
138291
+ {
138292
+ angle: "Focus on correctness and edge cases. Identify risks, invariants, and safety checks.",
138293
+ label: "最佳正确性"
138294
+ },
138295
+ {
138296
+ angle: "Focus on minimal invasiveness. Prefer small, incremental changes that are easy to review.",
138297
+ label: "最小侵入性"
138298
+ },
138299
+ {
138300
+ angle: "Focus on architecture and future maintainability. Consider testability, clarity, and naming.",
138301
+ label: "最优架构性"
138302
+ }
138303
+ ];
138304
+ const DEFAULT_WORKER_COUNT = 3;
138305
+ const DEFAULT_TIMEOUT_MS = 12e4;
138306
+ const DEFAULT_MAX_OUTPUT_BYTES = 8e3;
138307
+ const DEFAULT_SYNTHESIS_MAX_OUTPUT_BYTES = 12e3;
138308
+ function buildPlannerPrompt(input) {
138309
+ return [
138310
+ "You are a planning specialist. Create an implementation plan for the request below.",
138311
+ "",
138312
+ `Request: ${input.task}`,
138313
+ "",
138314
+ `Your specific angle: ${input.angle}`,
138315
+ "",
138316
+ "Constraints:",
138317
+ "- Investigate the codebase as needed using available tools.",
138318
+ "- Produce a concrete, step-by-step implementation plan.",
138319
+ "- Do not write implementation code; only produce the plan.",
138320
+ `- Keep your response focused and under ${input.maxOutputBytes} bytes.`,
138321
+ "- Return only the plan."
138322
+ ].join("\n");
138323
+ }
138324
+ function buildSynthesisPrompt(input) {
138325
+ const plans = input.workerOutputs.map((output, index) => `### Plan ${index + 1}\n\n${output}`).join("\n\n");
138326
+ return [
138327
+ "You are a senior engineer. Review the following plans from multiple planning specialists and synthesize them into a single optimal implementation plan.",
138328
+ "",
138329
+ `Request: ${input.task}`,
138330
+ "",
138331
+ plans,
138332
+ "",
138333
+ "Instructions:",
138334
+ "- Incorporate the strongest ideas from each specialist plan.",
138335
+ "- Resolve contradictions explicitly.",
138336
+ "- Produce one concrete, step-by-step implementation plan.",
138337
+ `- Keep the final plan under ${input.maxOutputBytes} bytes.`,
138338
+ "- Return only the final plan."
138339
+ ].join("\n");
138340
+ }
138341
+ async function createPromptDir() {
138342
+ return mkdtemp(join(tmpdir(), "scream-fusionplan-"));
138343
+ }
138344
+ async function writePromptFile(dir, index, prompt) {
138345
+ const file = join(dir, `worker-${index}.md`);
138346
+ await writeFile(file, prompt, "utf8");
138347
+ return file;
138348
+ }
138349
+ async function cleanupPromptDir(dir) {
138350
+ await rm(dir, {
138351
+ recursive: true,
138352
+ force: true
138353
+ });
138354
+ }
138355
+ async function buildWorkerArgs(input) {
138356
+ const args = [
138357
+ "--output-format",
138358
+ "stream-json",
138359
+ "--prompt",
138360
+ await readFile(input.promptFile, "utf8")
138361
+ ];
138362
+ if (input.model) args.push("--model", input.model);
138363
+ return args;
138364
+ }
138365
+ function resolveScreamCommand(screamBin) {
138366
+ if (screamBin) return {
138367
+ command: screamBin,
138368
+ prefixArgs: []
138369
+ };
138370
+ const entry = process.argv[1];
138371
+ if (!entry) return trySourceOrDist();
138372
+ const absoluteEntry = resolve(entry);
138373
+ if (/[\\/]scripts[\\/]dev\.mjs$/.test(absoluteEntry)) return trySourceOrDist();
138374
+ if (absoluteEntry.endsWith("dist/main.mjs")) return {
138375
+ command: process.execPath,
138376
+ prefixArgs: [absoluteEntry]
138377
+ };
138378
+ if (absoluteEntry.endsWith("src/main.ts")) return buildSourceCommand();
138379
+ if (absoluteEntry.endsWith(".mjs") || absoluteEntry.endsWith(".js")) return {
138380
+ command: process.execPath,
138381
+ prefixArgs: [absoluteEntry]
138382
+ };
138383
+ return trySourceOrDist();
138384
+ }
138385
+ function textFromContent(content) {
138386
+ if (typeof content === "string") return content;
138387
+ if (Array.isArray(content)) return content.map((part) => {
138388
+ if (typeof part === "string") return part;
138389
+ if (part && typeof part === "object" && "text" in part && typeof part.text === "string") return part.text;
138390
+ return "";
138391
+ }).join("");
138392
+ return "";
138393
+ }
138394
+ function truncateUtf8(input, maxBytes) {
138395
+ const encoder = new TextEncoder();
138396
+ if (encoder.encode(input).length <= maxBytes) return input;
138397
+ const suffix = "…";
138398
+ const suffixBytes = encoder.encode(suffix).length;
138399
+ const targetBytes = Math.max(0, maxBytes - suffixBytes);
138400
+ let low = 0;
138401
+ let high = input.length;
138402
+ while (low < high) {
138403
+ const mid = Math.floor((low + high + 1) / 2);
138404
+ if (encoder.encode(input.slice(0, mid)).length <= targetBytes) low = mid;
138405
+ else high = mid - 1;
138406
+ }
138407
+ return `${input.slice(0, low)}${suffix}`;
138408
+ }
138409
+ async function runWorker(input) {
138410
+ const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
138411
+ const promptFile = await writePromptFile(input.promptDir, input.index, buildPlannerPrompt({
138412
+ task: input.task,
138413
+ angle: input.angle,
138414
+ maxOutputBytes
138415
+ }));
138416
+ const result = {
138417
+ ok: false,
138418
+ output: "",
138419
+ stderr: "",
138420
+ exitCode: null,
138421
+ timedOut: false
138422
+ };
138423
+ try {
138424
+ const args = await buildWorkerArgs({
138425
+ promptFile,
138426
+ model: input.model
138427
+ });
138428
+ const { command, prefixArgs } = resolveScreamCommand(input.screamBin);
138429
+ const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
138430
+ result.command = [
138431
+ command,
138432
+ ...prefixArgs,
138433
+ ...args
138434
+ ].join(" ");
138435
+ const exitCode = await new Promise((resolve) => {
138436
+ const proc = spawn(command, [...prefixArgs, ...args], {
138437
+ cwd: input.cwd,
138438
+ shell: false,
138439
+ stdio: [
138440
+ "ignore",
138441
+ "pipe",
138442
+ "pipe"
138443
+ ],
138444
+ env: {
138445
+ ...process.env,
138446
+ [SCREAM_FUSIONPLAN_SUBAGENT_ENV]: "1"
138447
+ }
138448
+ });
138449
+ input.onStarted?.();
138450
+ let stdoutBuffer = "";
138451
+ let timeout;
138452
+ const processLine = (line) => {
138453
+ if (!line.trim()) return;
138454
+ let event;
138455
+ try {
138456
+ event = JSON.parse(line);
138457
+ } catch {
138458
+ return;
138459
+ }
138460
+ if (event.role === "assistant" && event.content) {
138461
+ const text = textFromContent(event.content).trim();
138462
+ if (text) result.output += (result.output ? "\n\n" : "") + text;
138463
+ }
138464
+ };
138465
+ proc.stdout.on("data", (data) => {
138466
+ stdoutBuffer += data.toString("utf8");
138467
+ const lines = stdoutBuffer.split("\n");
138468
+ stdoutBuffer = lines.pop() ?? "";
138469
+ for (const line of lines) processLine(line);
138470
+ });
138471
+ proc.stderr.on("data", (data) => {
138472
+ result.stderr += data.toString("utf8");
138473
+ });
138474
+ let resolved = false;
138475
+ const safeResolve = (value) => {
138476
+ if (resolved) return;
138477
+ resolved = true;
138478
+ resolve(value);
138479
+ };
138480
+ proc.on("error", (error) => {
138481
+ result.stderr += error.message;
138482
+ safeResolve(null);
138483
+ });
138484
+ proc.on("close", (code) => {
138485
+ clearTimeout(timeout);
138486
+ if (stdoutBuffer.trim()) processLine(stdoutBuffer);
138487
+ safeResolve(code ?? 0);
138488
+ });
138489
+ timeout = setTimeout(() => {
138490
+ result.timedOut = true;
138491
+ proc.kill("SIGTERM");
138492
+ setTimeout(() => proc.kill("SIGKILL"), 5e3).unref();
138493
+ }, timeoutMs);
138494
+ timeout.unref();
138495
+ });
138496
+ result.exitCode = exitCode;
138497
+ result.ok = exitCode === 0 && !result.timedOut && result.output.trim().length > 0;
138498
+ if (!result.output.trim()) result.output = result.stderr.trim() || "(worker produced no final assistant output)";
138499
+ return result;
138500
+ } finally {
138501
+ await rm(promptFile, { force: true });
138502
+ }
138503
+ }
138504
+ async function runSynthesisWorker(input, promptDir) {
138505
+ const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_SYNTHESIS_MAX_OUTPUT_BYTES;
138506
+ const truncatedOutputs = input.workerOutputs.map((output) => truncateUtf8(output, maxOutputBytes));
138507
+ const result = await runWorker({
138508
+ index: 0,
138509
+ task: buildSynthesisPrompt({
138510
+ task: input.task,
138511
+ workerOutputs: truncatedOutputs,
138512
+ maxOutputBytes
138513
+ }),
138514
+ angle: "Synthesize the best plan from the specialist outputs.",
138515
+ label: "综合",
138516
+ cwd: input.cwd,
138517
+ promptDir,
138518
+ model: input.model,
138519
+ thinkingLevel: input.thinkingLevel,
138520
+ timeoutMs: input.timeoutMs,
138521
+ maxOutputBytes,
138522
+ screamBin: input.screamBin
138523
+ });
138524
+ return result.ok ? result.output.trim() : `(synthesis failed: ${result.stderr || "no output"})`;
138525
+ }
138526
+ function buildWorkerProgress(states) {
138527
+ return states.map((s, index) => ({
138528
+ index,
138529
+ status: s.status,
138530
+ angle: s.angle,
138531
+ label: s.label
138532
+ }));
138533
+ }
138534
+ async function runFusionPlan(input) {
138535
+ if (process.env["SCREAM_FUSIONPLAN_SUBAGENT"] === "1") return {
138536
+ ok: false,
138537
+ plan: "",
138538
+ workerResults: []
138539
+ };
138540
+ const workerCount = Math.max(1, Math.min(8, input.workerCount ?? DEFAULT_WORKER_COUNT));
138541
+ const maxOutputBytes = input.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
138542
+ const timeoutMs = input.timeoutMs ?? DEFAULT_TIMEOUT_MS;
138543
+ const promptDir = await createPromptDir();
138544
+ const workerStates = [];
138545
+ for (let i = 0; i < workerCount; i += 1) {
138546
+ const angleDef = WORKER_ANGLES[i % WORKER_ANGLES.length];
138547
+ workerStates.push({
138548
+ status: "pending",
138549
+ angle: angleDef.angle,
138550
+ label: angleDef.label
138551
+ });
138552
+ }
138553
+ const emitProgress = (phase) => {
138554
+ const completedWorkers = workerStates.filter((s) => s.status === "completed").length;
138555
+ const failedWorkers = workerStates.filter((s) => s.status === "failed").length;
138556
+ input.onProgress?.({
138557
+ phase,
138558
+ completedWorkers,
138559
+ totalWorkers: workerCount,
138560
+ failedWorkers,
138561
+ workers: buildWorkerProgress(workerStates)
138562
+ });
138563
+ };
138564
+ try {
138565
+ emitProgress("planning");
138566
+ const workerPromises = workerStates.map((state, index) => runWorker({
138567
+ index,
138568
+ task: input.task,
138569
+ angle: state.angle,
138570
+ label: state.label,
138571
+ cwd: input.cwd,
138572
+ promptDir,
138573
+ model: input.model,
138574
+ thinkingLevel: input.thinkingLevel,
138575
+ timeoutMs,
138576
+ maxOutputBytes,
138577
+ screamBin: input.screamBin,
138578
+ onStarted: () => {
138579
+ state.status = "running";
138580
+ emitProgress("planning");
138581
+ }
138582
+ }).then((result) => {
138583
+ state.status = result.ok ? "completed" : "failed";
138584
+ emitProgress("planning");
138585
+ return result;
138586
+ }));
138587
+ const workerResults = await Promise.all(workerPromises);
138588
+ const successfulOutputs = workerResults.filter((r) => r.ok).map((r) => truncateUtf8(r.output, maxOutputBytes));
138589
+ if (successfulOutputs.length === 0) {
138590
+ emitProgress("failed");
138591
+ return {
138592
+ ok: false,
138593
+ plan: "",
138594
+ workerResults
138595
+ };
138596
+ }
138597
+ emitProgress("synthesis");
138598
+ const plan = await runSynthesisWorker({
138599
+ task: input.task,
138600
+ workerOutputs: successfulOutputs,
138601
+ cwd: input.cwd,
138602
+ model: input.model,
138603
+ thinkingLevel: input.thinkingLevel,
138604
+ timeoutMs,
138605
+ maxOutputBytes: input.synthesisMaxOutputBytes,
138606
+ screamBin: input.screamBin
138607
+ }, promptDir);
138608
+ const ok = plan.length > 0 && !plan.startsWith("(synthesis failed");
138609
+ emitProgress(ok ? "completed" : "failed");
138610
+ return {
138611
+ ok,
138612
+ plan,
138613
+ workerResults
138614
+ };
138615
+ } finally {
138616
+ await cleanupPromptDir(promptDir);
138617
+ }
138618
+ }
138619
+ //#endregion
137416
138620
  //#region src/tui/utils/image-placeholder.ts
137417
138621
  const PLACEHOLDER_REGEX = /\[(image|video) #(\d+) (?:(\(\d+×\d+\))|([^\]]+))\]/g;
137418
138622
  function extractMediaAttachments(text, store) {
@@ -137577,6 +138781,9 @@ var InputController = class {
137577
138781
  breatheFrame = 0;
137578
138782
  /** Once the user types, breathing stops permanently (same as welcome). */
137579
138783
  breatheOnceStopped = false;
138784
+ fusionPlanComponent;
138785
+ fusionPlanEntry;
138786
+ isFusionPlanRunning = false;
137580
138787
  constructor(host) {
137581
138788
  this.host = host;
137582
138789
  }
@@ -137589,7 +138796,7 @@ var InputController = class {
137589
138796
  this.host.stopWelcomeBreathing();
137590
138797
  this.#permanentlyStopBreathing();
137591
138798
  };
137592
- if (!this.host.state.appState.planMode) this.#startBreathing();
138799
+ if (this.host.state.appState.planMode === "off") this.#startBreathing();
137593
138800
  }
137594
138801
  handleInput(text) {
137595
138802
  if (text.trim().length === 0) return;
@@ -137601,22 +138808,29 @@ var InputController = class {
137601
138808
  dispatchInput(this.host, text);
137602
138809
  this.host.stopMemoryIdleTimer();
137603
138810
  }
137604
- sendNormalUserInput(text) {
138811
+ async sendNormalUserInput(text) {
137605
138812
  if (this.host.state.appState.model.trim().length === 0) {
137606
138813
  this.host.showError(LLM_NOT_SET_MESSAGE);
137607
138814
  return;
137608
138815
  }
137609
- const extraction = extractMediaAttachments(text, this.host.imageStore);
137610
- if (!this.validateMediaCapabilities(extraction)) return;
137611
138816
  const session = this.host.session;
137612
138817
  if (session === void 0) {
137613
138818
  this.host.showError(LLM_NOT_SET_MESSAGE);
137614
138819
  return;
137615
138820
  }
138821
+ if (this.host.state.appState.planMode === "fusionplan") {
138822
+ this.sendFusionPlanUserInput(text, session);
138823
+ return;
138824
+ }
138825
+ this.dispatchUserInput(text, session);
138826
+ }
138827
+ dispatchUserInput(text, session) {
137616
138828
  if (this.host.state.appState.loopModeEnabled && !this.host.state.appState.loopPrompt) {
137617
138829
  this.host.setAppState({ loopPrompt: text });
137618
138830
  consumeLoopLimitIteration(this.host.state.appState.loopLimit);
137619
138831
  }
138832
+ const extraction = extractMediaAttachments(text, this.host.imageStore);
138833
+ if (!this.validateMediaCapabilities(extraction)) return;
137620
138834
  if (extraction.hasMedia) this.sendMessage(session, text, {
137621
138835
  hasMedia: true,
137622
138836
  parts: extraction.parts,
@@ -137626,6 +138840,100 @@ var InputController = class {
137626
138840
  this.host.updateQueueDisplay();
137627
138841
  this.host.state.ui.requestRender();
137628
138842
  }
138843
+ sendFusionPlanUserInput(text, session) {
138844
+ if (this.isFusionPlanRunning) {
138845
+ this.host.showError("已有融合计划正在运行,请等待完成。");
138846
+ return;
138847
+ }
138848
+ this.isFusionPlanRunning = true;
138849
+ this.fusionPlanEntry = void 0;
138850
+ this.fusionPlanComponent = void 0;
138851
+ runFusionPlan({
138852
+ task: text,
138853
+ cwd: this.host.state.appState.workDir,
138854
+ model: this.host.state.appState.model,
138855
+ thinkingLevel: this.host.state.appState.thinkingLevel === "off" ? void 0 : this.host.state.appState.thinkingLevel,
138856
+ onProgress: (event) => {
138857
+ if (this.fusionPlanEntry === void 0) {
138858
+ this.fusionPlanEntry = {
138859
+ id: nextTranscriptId(),
138860
+ kind: "status",
138861
+ renderMode: "plain",
138862
+ content: "融合计划",
138863
+ fusionPlanStatus: event
138864
+ };
138865
+ const component = this.host.appendTranscriptEntry(this.fusionPlanEntry);
138866
+ this.fusionPlanComponent = component instanceof FusionPlanStatusComponent ? component : void 0;
138867
+ return;
138868
+ }
138869
+ this.fusionPlanEntry.fusionPlanStatus = event;
138870
+ this.fusionPlanComponent?.setData(event);
138871
+ }
138872
+ }).then(async (result) => {
138873
+ if (!result.ok) {
138874
+ const details = result.workerResults.map((r, i) => {
138875
+ if (r.ok) return `worker ${i + 1}: ok`;
138876
+ if (r.timedOut) return `worker ${i + 1}: timed out`;
138877
+ const reason = r.exitCode !== null ? `exit ${r.exitCode}` : r.stderr.trim() || "no output";
138878
+ const commandHint = r.command ? ` [${r.command}]` : "";
138879
+ return `worker ${i + 1}: failed (${reason})${commandHint}`;
138880
+ }).join("; ");
138881
+ this.updateFusionPlanStatus({
138882
+ phase: "failed",
138883
+ detail: details
138884
+ });
138885
+ this.host.showError(`融合计划生成失败 (${details})`);
138886
+ return;
138887
+ }
138888
+ try {
138889
+ if (!((await session.getStatus().catch(() => null))?.planMode ?? false)) await session.setPlanMode(true);
138890
+ const plan = await session.getPlan();
138891
+ if (plan?.path) await writeFile(plan.path, result.plan, "utf8");
138892
+ else {
138893
+ this.updateFusionPlanStatus({
138894
+ phase: "failed",
138895
+ detail: "无法定位计划文件路径"
138896
+ });
138897
+ this.host.showError("无法定位计划文件路径");
138898
+ return;
138899
+ }
138900
+ } catch (error) {
138901
+ const message = formatErrorMessage(error);
138902
+ this.updateFusionPlanStatus({
138903
+ phase: "failed",
138904
+ detail: message
138905
+ });
138906
+ this.host.showError(`写入计划文件失败:${message}`);
138907
+ return;
138908
+ }
138909
+ this.host.setAppState({ planMode: "plan" });
138910
+ this.host.showStatus("融合计划已生成,进入计划审批", this.host.state.theme.colors.success);
138911
+ this.dispatchUserInput(text, session);
138912
+ }).catch((error) => {
138913
+ const message = formatErrorMessage(error);
138914
+ this.updateFusionPlanStatus({
138915
+ phase: "failed",
138916
+ detail: message
138917
+ });
138918
+ this.host.showError(`融合计划异常:${message}`);
138919
+ }).finally(() => {
138920
+ this.isFusionPlanRunning = false;
138921
+ this.fusionPlanComponent = void 0;
138922
+ this.fusionPlanEntry = void 0;
138923
+ });
138924
+ }
138925
+ updateFusionPlanStatus(patch) {
138926
+ if (this.fusionPlanEntry === void 0 || this.fusionPlanComponent === void 0) return;
138927
+ const current = this.fusionPlanEntry.fusionPlanStatus;
138928
+ if (current === void 0) return;
138929
+ const updated = {
138930
+ ...current,
138931
+ phase: patch.phase,
138932
+ detail: patch.detail
138933
+ };
138934
+ this.fusionPlanEntry.fusionPlanStatus = updated;
138935
+ this.fusionPlanComponent.setData(updated);
138936
+ }
137629
138937
  steerMessage(session, input) {
137630
138938
  if (this.host.deferUserMessages || this.host.state.appState.isCompacting) {
137631
138939
  for (const part of input) this.enqueueMessage(part);
@@ -137648,16 +138956,19 @@ var InputController = class {
137648
138956
  this.host.showError(`引导失败:${message}`);
137649
138957
  });
137650
138958
  }
137651
- handlePlanToggle(next) {
137652
- handlePlanCommand(this.host, next ? "on" : "off");
138959
+ handlePlanModeStateChange(state) {
138960
+ if (state === "off") handlePlanCommand(this.host, "off");
138961
+ else if (state === "plan") handlePlanCommand(this.host, "on");
138962
+ else handleFusionPlanCommand(this.host, "on");
137653
138963
  }
137654
138964
  updateEditorBorderHighlight(text) {
137655
138965
  const trimmed = (text ?? this.host.state.editor.getText()).trimStart();
137656
- const isPlan = this.host.state.appState.planMode;
138966
+ const planMode = this.host.state.appState.planMode;
138967
+ const isPlan = planMode !== "off";
137657
138968
  if (trimmed.length === 0 && !isPlan && !this.breatheOnceStopped) this.#startBreathing();
137658
138969
  else {
137659
138970
  this.#stopBreathing();
137660
- const colorToken = isPlan ? this.host.state.theme.colors.planMode : this.host.state.theme.colors.primary;
138971
+ const colorToken = isPlan ? planMode === "fusionplan" ? this.host.state.theme.colors.fusionPlanMode : this.host.state.theme.colors.planMode : this.host.state.theme.colors.primary;
137661
138972
  this.host.state.editor.borderColor = (s) => chalk.hex(colorToken)(s);
137662
138973
  this.host.state.ui.requestRender();
137663
138974
  }
@@ -137983,6 +139294,46 @@ const INITIAL_LIVE_PANE = {
137983
139294
  pendingQuestion: null
137984
139295
  };
137985
139296
  //#endregion
139297
+ //#region src/tui/utils/render-batcher.ts
139298
+ function createRenderBatcher(doRender) {
139299
+ let scheduled = false;
139300
+ let pendingForce = false;
139301
+ let batchDepth = 0;
139302
+ let batchNeedsRender = false;
139303
+ const scheduleRender = (force) => {
139304
+ pendingForce ||= force;
139305
+ if (scheduled) return;
139306
+ scheduled = true;
139307
+ queueMicrotask(() => {
139308
+ scheduled = false;
139309
+ const renderForce = pendingForce;
139310
+ pendingForce = false;
139311
+ doRender(renderForce);
139312
+ });
139313
+ };
139314
+ return {
139315
+ requestRender(force = false) {
139316
+ if (batchDepth > 0) {
139317
+ batchNeedsRender = true;
139318
+ return;
139319
+ }
139320
+ scheduleRender(force);
139321
+ },
139322
+ batchUpdate(fn) {
139323
+ batchDepth++;
139324
+ try {
139325
+ return fn();
139326
+ } finally {
139327
+ batchDepth--;
139328
+ if (batchDepth === 0 && batchNeedsRender) {
139329
+ batchNeedsRender = false;
139330
+ scheduleRender(true);
139331
+ }
139332
+ }
139333
+ }
139334
+ };
139335
+ }
139336
+ //#endregion
137986
139337
  //#region src/tui/components/chrome/error-banner.ts
137987
139338
  const MAX_BANNER_LINES = 3;
137988
139339
  const CONTINUATION_INDENT = " ";
@@ -138677,7 +140028,10 @@ var FooterComponent = class {
138677
140028
  const left = [];
138678
140029
  if (state.permissionMode === "auto") left.push(chalk.hex(colors.warning).bold("auto"));
138679
140030
  if (state.permissionMode === "yolo") left.push(chalk.hex(colors.warning).bold("YES"));
138680
- if (state.planMode) left.push(chalk.hex(colors.planMode).bold("plan"));
140031
+ if (state.planMode !== "off") {
140032
+ const isFusion = state.planMode === "fusionplan";
140033
+ left.push(chalk.hex(isFusion ? colors.fusionPlanMode : colors.planMode).bold(isFusion ? "fusion" : "plan"));
140034
+ }
138681
140035
  if (state.wolfpackMode) left.push(chalk.hex(colors.primary).bold("wolfpack"));
138682
140036
  if (state.loopModeEnabled) {
138683
140037
  const iter = state.loopIteration;
@@ -138944,6 +140298,8 @@ var CustomEditor = class extends Editor {
138944
140298
  }
138945
140299
  /** Whether the active model has thinking enabled. When true, a small "think" label is embedded in the top-right of the input box border. */
138946
140300
  thinking = false;
140301
+ /** Current thinking effort level (e.g. low, medium, high). Used to annotate the think label. */
140302
+ thinkingLevel = "off";
138947
140303
  consumingPaste = false;
138948
140304
  consumeBuffer = "";
138949
140305
  /**
@@ -139002,7 +140358,7 @@ var CustomEditor = class extends Editor {
139002
140358
  const withPrompt = injectPromptSymbol(firstContent);
139003
140359
  if (withPrompt !== void 0) lines[firstContentIdx] = withPrompt;
139004
140360
  }
139005
- if (this.thinking) injectThinkLabel(lines, width, this.borderColor ?? ((s) => s));
140361
+ if (this.thinking) injectThinkLabel(lines, width, this.thinkingLevel, this.borderColor ?? ((s) => s));
139006
140362
  return lines;
139007
140363
  }
139008
140364
  handleInput(data) {
@@ -139132,14 +140488,14 @@ const THINK_LABEL_MIN_WIDTH = 14;
139132
140488
  * The label only appears when the active model has thinking enabled.
139133
140489
  * Works with pi-tui's flat two-line design (no side borders).
139134
140490
  */
139135
- function injectThinkLabel(lines, width, paint) {
140491
+ function injectThinkLabel(lines, width, thinkingLevel, paint) {
139136
140492
  if (width < THINK_LABEL_MIN_WIDTH) return;
139137
140493
  const topIdx = lines.findIndex((line) => {
139138
140494
  const plain = stripSgr(line);
139139
140495
  return plain.length > 0 && plain[0] === "─";
139140
140496
  });
139141
140497
  if (topIdx === -1) return;
139142
- const labelBlock = `─${THINK_LABEL}─`;
140498
+ const labelBlock = `─${thinkingLevel !== "off" ? ` Think ${thinkingLevel} ` : THINK_LABEL}─`;
139143
140499
  const labelVis = visibleWidth(labelBlock);
139144
140500
  const leftDashCount = width - 1 - labelVis;
139145
140501
  if (leftDashCount < 1) return;
@@ -139331,6 +140687,15 @@ function detectFdPath() {
139331
140687
  }
139332
140688
  //#endregion
139333
140689
  //#region src/tui/tui-state.ts
140690
+ function logRenderError(error) {
140691
+ try {
140692
+ const logDir = getLogDir();
140693
+ mkdirSync(logDir, { recursive: true });
140694
+ const message = error instanceof Error ? error.stack ?? error.message : String(error);
140695
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] TUI render error: ${message}\n`;
140696
+ appendFileSync(`${logDir}/render-errors.log`, line, "utf8");
140697
+ } catch {}
140698
+ }
139334
140699
  function createTUIState(options) {
139335
140700
  const initialAppState = options.initialAppState;
139336
140701
  const theme = createScreamTUIThemeBundle(initialAppState.theme, options.resolvedTheme);
@@ -139342,7 +140707,13 @@ function createTUIState(options) {
139342
140707
  uiAny["doRender"] = () => {
139343
140708
  try {
139344
140709
  originalDoRender();
139345
- } catch {}
140710
+ } catch (error) {
140711
+ logRenderError(error);
140712
+ }
140713
+ };
140714
+ const renderBatcher = createRenderBatcher(ui.requestRender.bind(ui));
140715
+ ui.requestRender = (force = false) => {
140716
+ renderBatcher.requestRender(force);
139346
140717
  };
139347
140718
  const transcriptContainer = new GutterContainer(1, 1);
139348
140719
  const activityContainer = new GutterContainer(1, 1);
@@ -139354,7 +140725,8 @@ function createTUIState(options) {
139354
140725
  errorBannerContainer.addChild(errorBanner);
139355
140726
  const editorContainer = new GutterContainer(1, 1);
139356
140727
  const editor = new CustomEditor(ui, theme.colors);
139357
- editor.thinking = initialAppState.thinking;
140728
+ editor.thinking = initialAppState.thinkingLevel !== "off";
140729
+ editor.thinkingLevel = initialAppState.thinkingLevel;
139358
140730
  return {
139359
140731
  ui,
139360
140732
  terminal,
@@ -139387,7 +140759,8 @@ function createTUIState(options) {
139387
140759
  externalEditorRunning: false,
139388
140760
  queuedMessages: [],
139389
140761
  fdPath: detectFdPath(),
139390
- gitLsFilesCache: createGitLsFilesCache(initialAppState.workDir)
140762
+ gitLsFilesCache: createGitLsFilesCache(initialAppState.workDir),
140763
+ renderBatcher
139391
140764
  };
139392
140765
  }
139393
140766
  //#endregion
@@ -139887,9 +141260,8 @@ var SessionManager = class {
139887
141260
  this.host.setAppState({
139888
141261
  sessionId: session.id,
139889
141262
  model: status.model ?? "",
139890
- thinking: status.thinkingLevel !== "off",
139891
- permissionMode: status.permission,
139892
- planMode: status.planMode,
141263
+ thinkingLevel: status.thinkingLevel,
141264
+ planMode: status.planMode ? "plan" : "off",
139893
141265
  contextTokens: status.contextTokens,
139894
141266
  maxContextTokens: status.maxContextTokens,
139895
141267
  contextUsage: status.contextUsage,
@@ -140021,9 +141393,9 @@ var SessionManager = class {
140021
141393
  return this.host.harness.createSession({
140022
141394
  workDir: this.host.state.appState.workDir,
140023
141395
  model,
140024
- thinking: this.host.session === void 0 ? void 0 : this.host.state.appState.thinking ? "on" : "off",
141396
+ thinking: this.host.session === void 0 ? void 0 : this.host.state.appState.thinkingLevel === "off" ? "off" : this.host.state.appState.thinkingLevel,
140025
141397
  permission: this.host.state.appState.permissionMode,
140026
- planMode: this.host.state.appState.planMode ? true : void 0
141398
+ planMode: this.host.state.appState.planMode !== "off" ? true : void 0
140027
141399
  });
140028
141400
  }
140029
141401
  resetSessionRuntime() {
@@ -141959,8 +143331,8 @@ function createInitialAppState(input) {
141959
143331
  workDir: input.workDir,
141960
143332
  sessionId: "",
141961
143333
  permissionMode: startupPermission,
141962
- planMode: input.cliOptions.plan,
141963
- thinking: false,
143334
+ planMode: input.cliOptions.plan ? "plan" : "off",
143335
+ thinkingLevel: "off",
141964
143336
  contextUsage: 0,
141965
143337
  contextTokens: 0,
141966
143338
  maxContextTokens: 0,
@@ -141975,6 +143347,7 @@ function createInitialAppState(input) {
141975
143347
  latestVersion: null,
141976
143348
  editorCommand: input.tuiConfig.editorCommand,
141977
143349
  notifications: input.tuiConfig.notifications,
143350
+ like: input.tuiConfig.like,
141978
143351
  availableModels: {},
141979
143352
  availableProviders: {},
141980
143353
  sessionTitle: null,
@@ -142021,6 +143394,14 @@ var ScreamTUI = class {
142021
143394
  lifecycleController;
142022
143395
  inputController;
142023
143396
  onExit;
143397
+ /**
143398
+ * Execute a compound UI update without intermediate renders. All
143399
+ * `requestRender()` calls inside `fn` are deferred; a single force render is
143400
+ * queued when the batch completes.
143401
+ */
143402
+ batchUpdate(fn) {
143403
+ return this.state.renderBatcher.batchUpdate(fn);
143404
+ }
142024
143405
  constructor(harness, startupInput) {
142025
143406
  this.harness = harness;
142026
143407
  const tuiOptions = {
@@ -142229,8 +143610,8 @@ var ScreamTUI = class {
142229
143610
  emergencyTerminalExit(exitCode) {
142230
143611
  this.onEmergencyExit(exitCode);
142231
143612
  }
142232
- handlePlanToggle(next) {
142233
- this.inputController.handlePlanToggle(next);
143613
+ handlePlanModeStateChange(state) {
143614
+ this.inputController.handlePlanModeStateChange(state);
142234
143615
  }
142235
143616
  steerMessage(session, input) {
142236
143617
  this.inputController.steerMessage(session, input);
@@ -142334,7 +143715,10 @@ var ScreamTUI = class {
142334
143715
  const busyChanged = "streamingPhase" in patch || "isCompacting" in patch;
142335
143716
  Object.assign(this.state.appState, patch);
142336
143717
  if ("planMode" in patch) this.updateEditorBorderHighlight();
142337
- if ("thinking" in patch) this.state.editor.thinking = patch.thinking ?? false;
143718
+ if ("thinkingLevel" in patch) {
143719
+ this.state.editor.thinking = patch.thinkingLevel !== "off";
143720
+ this.state.editor.thinkingLevel = patch.thinkingLevel ?? "off";
143721
+ }
142338
143722
  if ("streamingPhase" in patch && patch.streamingPhase !== "idle") this.transcriptController.stopWelcomeBreathing();
142339
143723
  this.state.footer.setState(this.state.appState);
142340
143724
  this.lifecycleController.updateActivityPane();
@@ -142389,14 +143773,6 @@ var ScreamTUI = class {
142389
143773
  resetSessionRuntime() {
142390
143774
  this.sessionManager.resetSessionRuntime();
142391
143775
  }
142392
- /**
142393
- * Pin the editor + footer to the terminal bottom. The pi-tui patch adds a
142394
- * `fixedBottomLineCount` property: the last N rendered lines stay pinned
142395
- * while the transcript above scrolls independently.
142396
- *
142397
- * We override `doRender` to measure the editor + footer height each frame
142398
- * and set the count before the real render runs.
142399
- */
142400
143776
  async resumeSession(targetSessionId) {
142401
143777
  return (await this.sessionManager.resumeSession(targetSessionId)).switched;
142402
143778
  }
@@ -142414,7 +143790,7 @@ var ScreamTUI = class {
142414
143790
  this.transcriptController.stopWelcomeBreathing();
142415
143791
  }
142416
143792
  appendTranscriptEntry(entry) {
142417
- this.transcriptController.appendEntry(entry);
143793
+ return this.transcriptController.appendEntry(entry);
142418
143794
  }
142419
143795
  appendApprovalTranscriptEntry(request, response) {
142420
143796
  this.transcriptController.appendApprovalEntry(request, response);
@@ -143325,7 +144701,21 @@ async function runStreamJson(opts) {
143325
144701
  const agentsMdPath = join(workDir, ".scream-code", "AGENTS.md");
143326
144702
  let originalAgentsMd;
143327
144703
  let injectedAgentsMd = false;
143328
- if (opts.appendSystemPrompt) {
144704
+ let appendPrompt = opts.appendSystemPrompt ?? "";
144705
+ if (opts.appendSystemPromptFile) try {
144706
+ const fileContent = await readFile(opts.appendSystemPromptFile, "utf-8");
144707
+ appendPrompt = appendPrompt ? `${appendPrompt}\n\n${fileContent}` : fileContent;
144708
+ log.info("stream-json: loaded append-system-prompt-file", {
144709
+ path: opts.appendSystemPromptFile,
144710
+ bytes: fileContent.length
144711
+ });
144712
+ } catch (error) {
144713
+ log.warn("stream-json: failed to read append-system-prompt-file", {
144714
+ path: opts.appendSystemPromptFile,
144715
+ error: String(error)
144716
+ });
144717
+ }
144718
+ if (appendPrompt) {
143329
144719
  try {
143330
144720
  originalAgentsMd = await readFile(agentsMdPath, "utf-8");
143331
144721
  } catch {}
@@ -143334,7 +144724,7 @@ async function runStreamJson(opts) {
143334
144724
  cc-connect send --image /absolute/path/to/image.png
143335
144725
  cc-connect send --file /absolute/path/to/file.pdf
143336
144726
  当用户要求你发送文件、截图、生成的图片时,使用 Bash 工具执行上述命令即可。
143337
- \n${opts.appendSystemPrompt}`;
144727
+ \n${appendPrompt}`;
143338
144728
  await writeFile(agentsMdPath, originalAgentsMd ? `${ccPrompt}\n\n${originalAgentsMd}` : ccPrompt, "utf-8");
143339
144729
  injectedAgentsMd = true;
143340
144730
  log.info("stream-json: injected cc-connect system prompt into AGENTS.md");