scream-code 0.12.7 → 0.12.9

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.
@@ -7,7 +7,7 @@ import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } fr
7
7
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
8
8
  import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-BH9W5k24.mjs";
9
9
  import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
10
- import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-9ywV88Lh.mjs";
10
+ import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-CsPSlTrQ.mjs";
11
11
  import { createRequire } from "node:module";
12
12
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
13
13
  import * as fs$1 from "node:fs/promises";
@@ -3033,7 +3033,7 @@ const buildHeaders$2 = (newHeaders) => {
3033
3033
  function encodeURIPath$2(str) {
3034
3034
  return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
3035
3035
  }
3036
- const EMPTY$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
3036
+ const EMPTY$4 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
3037
3037
  const createPathTagFunction$2 = (pathEncoder = encodeURIPath$2) => function path(statics, ...params) {
3038
3038
  if (statics.length === 1) return statics[0];
3039
3039
  let postPath = false;
@@ -3042,7 +3042,7 @@ const createPathTagFunction$2 = (pathEncoder = encodeURIPath$2) => function path
3042
3042
  if (/[?#]/.test(currentValue)) postPath = true;
3043
3043
  const value = params[index];
3044
3044
  let encoded = (postPath ? encodeURIComponent : pathEncoder)("" + value);
3045
- if (index !== params.length && (value == null || typeof value === "object" && value.toString === Object.getPrototypeOf(Object.getPrototypeOf(value.hasOwnProperty ?? EMPTY$3) ?? EMPTY$3)?.toString)) {
3045
+ if (index !== params.length && (value == null || typeof value === "object" && value.toString === Object.getPrototypeOf(Object.getPrototypeOf(value.hasOwnProperty ?? EMPTY$4) ?? EMPTY$4)?.toString)) {
3046
3046
  encoded = value + "";
3047
3047
  invalidSegments.push({
3048
3048
  start: previousValue.length + currentValue.length,
@@ -31529,7 +31529,7 @@ APIResource$1._key = [];
31529
31529
  function encodeURIPath$1(str) {
31530
31530
  return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
31531
31531
  }
31532
- const EMPTY$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
31532
+ const EMPTY$3 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
31533
31533
  const createPathTagFunction$1 = (pathEncoder = encodeURIPath$1) => (function path(statics, ...params) {
31534
31534
  if (statics.length === 1) return statics[0];
31535
31535
  let postPath = false;
@@ -31539,7 +31539,7 @@ const createPathTagFunction$1 = (pathEncoder = encodeURIPath$1) => (function pat
31539
31539
  if (/[?#]/.test(currentValue)) postPath = true;
31540
31540
  const value = params[index];
31541
31541
  let encoded = (postPath ? encodeURIComponent : pathEncoder)("" + value);
31542
- if (index !== params.length && (value == null || typeof value === "object" && value.toString === ((_c = Object.getPrototypeOf((_b = Object.getPrototypeOf((_a = value.hasOwnProperty) !== null && _a !== void 0 ? _a : EMPTY$2)) !== null && _b !== void 0 ? _b : EMPTY$2)) === null || _c === void 0 ? void 0 : _c.toString))) {
31542
+ if (index !== params.length && (value == null || typeof value === "object" && value.toString === ((_c = Object.getPrototypeOf((_b = Object.getPrototypeOf((_a = value.hasOwnProperty) !== null && _a !== void 0 ? _a : EMPTY$3)) !== null && _b !== void 0 ? _b : EMPTY$3)) === null || _c === void 0 ? void 0 : _c.toString))) {
31543
31543
  encoded = value + "";
31544
31544
  invalidSegments.push({
31545
31545
  start: previousValue.length + currentValue.length,
@@ -36516,7 +36516,7 @@ var APIResource = class {
36516
36516
  function encodeURIPath(str) {
36517
36517
  return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
36518
36518
  }
36519
- const EMPTY$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
36519
+ const EMPTY$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
36520
36520
  const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(statics, ...params) {
36521
36521
  if (statics.length === 1) return statics[0];
36522
36522
  let postPath = false;
@@ -36525,7 +36525,7 @@ const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(sta
36525
36525
  if (/[?#]/.test(currentValue)) postPath = true;
36526
36526
  const value = params[index];
36527
36527
  let encoded = (postPath ? encodeURIComponent : pathEncoder)("" + value);
36528
- if (index !== params.length && (value == null || typeof value === "object" && value.toString === Object.getPrototypeOf(Object.getPrototypeOf(value.hasOwnProperty ?? EMPTY$1) ?? EMPTY$1)?.toString)) {
36528
+ if (index !== params.length && (value == null || typeof value === "object" && value.toString === Object.getPrototypeOf(Object.getPrototypeOf(value.hasOwnProperty ?? EMPTY$2) ?? EMPTY$2)?.toString)) {
36529
36529
  encoded = value + "";
36530
36530
  invalidSegments.push({
36531
36531
  start: previousValue.length + currentValue.length,
@@ -66924,7 +66924,7 @@ async function isDir(p) {
66924
66924
  //#region ../../packages/agent-core/src/plugin/store.ts
66925
66925
  const PLUGINS_DIR_REL = path.join("plugins");
66926
66926
  const INSTALLED_REL = path.join(PLUGINS_DIR_REL, "installed.json");
66927
- const EMPTY = {
66927
+ const EMPTY$1 = {
66928
66928
  version: 1,
66929
66929
  plugins: []
66930
66930
  };
@@ -66934,7 +66934,7 @@ async function readInstalled(screamHomeDir) {
66934
66934
  try {
66935
66935
  text = await readFile(filePath, "utf8");
66936
66936
  } catch (error) {
66937
- if (error.code === "ENOENT") return EMPTY;
66937
+ if (error.code === "ENOENT") return EMPTY$1;
66938
66938
  throw error;
66939
66939
  }
66940
66940
  try {
@@ -76465,10 +76465,10 @@ function stripContextMetadata(message) {
76465
76465
  }
76466
76466
  //#endregion
76467
76467
  //#region ../../packages/agent-core/src/agent/compaction/compaction-instruction.md
76468
- var compaction_instruction_default = "\n--- This message is a direct task, not part of the above conversation ---\n\nYou are now given a task to compact this conversation context according to specific priorities and output requirements.\n\nOutput text only. DO NOT CALL ANY TOOLS. Calling tools will be rejected and fails the task. You already have all the information you need in the conversation history. You have only one chance.\n\nThe goal of compaction is to keep essential code patterns, technical details, and architectural decisions for continuing development without losing context after the above messages are cleared work.\n\n{{ customInstruction }}\n\n<!-- Memory Memo Extraction (PRIORITY — do not skip) -->\n\n## 任务经验提取\n\nAFTER completing the compaction summary below, scan the messages being compacted for **completed task loops**. A task loop is \"completed\" when:\n- The user made a clear request or asked a specific question\n- You provided a solution or answer\n- The outcome is clear (success, partial success, or failure)\n\nFor each completed task loop found, output a structured experience record **at the very end of your response**:\n\n```memory-memo\n{\n \"userNeed\": \"<the user's need or goal, one sentence>\",\n \"approach\": \"<what was done — the approach taken, 2-4 sentences>\",\n \"outcome\": \"<final result, e.g. '完成', '部分完成', '失败: reason'>\",\n \"whatFailed\": \"<dead ends tried — things that didn't work, or 'none'>\",\n \"whatWorked\": \"<key actions that ultimately worked, or 'none'>\",\n \"tags\": [\"<tag1>\", \"<tag2>\", \"<tag3>\"],\n \"note\": \"<optional: a soft, AI-readable note/suggestion that makes the record easier for future turns to understand — e.g. '这类任务先查接口可用性再写代码'. Omit if none>\"\n}\n```\n\nGuidelines:\n- Record important failed attempts in \"whatFailed\" to help avoid repeating mistakes.\n- Record key successful actions in \"whatWorked\" to help reuse effective approaches.\n- Include 3-5 semantic \"tags\" summarizing the task domain, tech stack, or action type (e.g. [\"react\", \"auth\", \"部署\"]).\n- \"note\" is optional: a one-sentence advisory note that helps future AI understand/reuse the record faster. It is a soft suggestion, not a user-enforced rule.\n- Skip in-progress work unless it contains a valuable error+fix experience.\n- Merge closely related sub-tasks into a single record.\n- Use the exact field names and JSON format shown above (no extra fields beyond \"note\").\n\nIf no completed task loops are found in the compacted messages, output:\n```memory-memo\n{\"none\": true}\n```\n\n<!-- Compression Priorities (in order) -->\n\n1. **Current Task State**: What is being worked on RIGHT NOW\n2. **Errors & Solutions**: All encountered errors and their resolutions\n3. **Code Evolution**: Final working versions only (remove intermediate attempts)\n4. **System Context**: Project structure, dependencies, environment setup\n5. **Design Decisions**: Architectural choices and their rationale\n6. **TODO Items**: Unfinished tasks and known issues\n\n<!-- Required Output Structure -->\n\n## Current Focus\n\n[What we're working on now]\n\n## Environment\n\n- [Key setup/config points]\n- ...\n\n## Completed Tasks\n\n- [Task]: [Brief outcome]\n- ...\n\n## Active Issues\n\n- [Issue]: [Status/Next steps]\n- ...\n\n## Code State\n\n### [Critical file name]\n\n[Brief description of the file's purpose and current state]\n\n```\n[The latest version of critical code snippets in this file, <20 lines]\n```\n\n### [Critical file name]\n\n- [Useful classes/methods/functions]: [Brief description/usage]\n- ...\n\n<!-- Omit non-critical code, intermediate attempts, and resolved errors -->\n\n## Important Context\n\n- [Any crucial information not covered above]\n- ...\n\n## All User Messages\n\n- [Detailed non tool use user message]\n- ...\n\n## Skill candidates\n\nScan the compressed messages for reusable processes (project-specific build\nsteps, recurring debugging patterns, or tool workflows). If any is genuinely\nworth capturing as a reusable skill, output at the very end, at most one:\n\n[[skill-candidate: <name>|<one-line purpose>|<evidence>]]\n\nOmit if none.\n";
76468
+ var compaction_instruction_default = "\n--- This message is a direct task, not part of the above conversation ---\n\nYou are now given a task to compact this conversation context according to specific priorities and output requirements.\n\nOutput text only. DO NOT CALL ANY TOOLS. Calling tools will be rejected and fails the task. You already have all the information you need in the conversation history. You have only one chance.\n\nThe goal of compaction is to keep essential code patterns, technical details, and architectural decisions for continuing development without losing context after the above messages are cleared work.\n\n{{ customInstruction }}\n\n<!-- Memory Memo Extraction (PRIORITY — do not skip) -->\n\n## 任务经验提取\n\nAFTER completing the compaction summary below, scan the messages being compacted for **task loops**. A task loop is \"completed\" when:\n- The user made a clear request or asked a specific question\n- You provided a solution or answer\n- The outcome is clear (success, partial success, or failure)\n\n**You MUST output at least one memory-memo block** (or the `{\"none\": true}` marker) at the very end of your response — omitting the section entirely is not allowed. Record completed task loops as full experience records; record **in-progress work** as a lower-priority record whose \"outcome\" is \"进行中\" (so the ongoing task survives compaction and can be resumed later).\n\nFor each task loop found, output a structured experience record **at the very end of your response**:\n\n```memory-memo\n{\n \"userNeed\": \"<the user's need or goal, one sentence>\",\n \"approach\": \"<what was done — the approach taken, 2-4 sentences>\",\n \"outcome\": \"<final result, e.g. '完成', '部分完成', '失败: reason', or '进行中' for in-progress work>\",\n \"whatFailed\": \"<dead ends tried — things that didn't work, or 'none'>\",\n \"whatWorked\": \"<key actions that ultimately worked, or 'none'>\",\n \"tags\": [\"<tag1>\", \"<tag2>\", \"<tag3>\"],\n \"note\": \"<optional: a soft, AI-readable note/suggestion that makes the record easier for future turns to understand — e.g. '这类任务先查接口可用性再写代码'. Omit if none>\"\n}\n```\n\nGuidelines:\n- Record important failed attempts in \"whatFailed\" to help avoid repeating mistakes.\n- Record key successful actions in \"whatWorked\" to help reuse effective approaches.\n- Include 3-5 semantic \"tags\" summarizing the task domain, tech stack, or action type (e.g. [\"react\", \"auth\", \"部署\"]).\n- \"note\" is optional: a one-sentence advisory note that helps future AI understand/reuse the record faster. It is a soft suggestion, not a user-enforced rule.\n- For in-progress work: record it with \"outcome\": \"进行中\" and describe where the task stands and what remains — this is what lets the agent resume seamlessly after compaction.\n- Merge closely related sub-tasks into a single record.\n- Use the exact field names and JSON format shown above (no extra fields beyond \"note\").\n\nIf no task loops (completed or in-progress) are found in the compacted messages, output:\n```memory-memo\n{\"none\": true}\n```\n\n<!-- Compression Priorities (in order) -->\n\n1. **Current Task State**: What is being worked on RIGHT NOW\n2. **Errors & Solutions**: All encountered errors and their resolutions\n3. **Code Evolution**: Final working versions only (remove intermediate attempts)\n4. **System Context**: Project structure, dependencies, environment setup\n5. **Design Decisions**: Architectural choices and their rationale\n6. **Next Steps**: The ordered, actionable plan going forward\n7. **TODO Items**: Unfinished tasks and known issues\n\n<!-- Required Output Structure -->\n\n## Current Focus\n\n[What we're working on now]\n\n## Environment\n\n- [Key setup/config points]\n- ...\n\n## Completed Tasks\n\n- [Task]: [Brief outcome]\n- ...\n\n## Active Issues\n\n- [Issue]: [Status/Next steps]\n- ...\n\n## Key Decisions\n\n- [Decision]: [rationale] — preserve architectural choices and why they were made\n- ...\n\n## Next Steps\n\n1. [The very next actionable step]\n2. [Then this]\n3. ...\n\n## Code State\n\n### [Critical file name]\n\n[Brief description of the file's purpose and current state]\n\n```\n[The latest version of critical code snippets in this file, <20 lines]\n```\n\n### [Critical file name]\n\n- [Useful classes/methods/functions]: [Brief description/usage]\n- ...\n\n<!-- Omit non-critical code, intermediate attempts, and resolved errors -->\n\n## Important Context\n\n- [Any crucial information not covered above]\n- ...\n\n## All User Messages\n\n- [Detailed non tool use user message]\n- ...\n\n## Skill candidates\n\nScan the compressed messages for reusable processes (project-specific build\nsteps, recurring debugging patterns, or tool workflows). If any is genuinely\nworth capturing as a reusable skill, output at the very end, at most one:\n\n[[skill-candidate: <name>|<one-line purpose>|<evidence>]]\n\n- The evidence must be concrete and verifiable: exact file paths, commands, or\n step sequences from the conversation (e.g. \"run `pnpm vitest run -t compaction`\n in packages/agent-core to reproduce the compaction snapshots\"). Vague evidence\n like \"used a script\" is not acceptable.\n- Only emit a candidate when the process is genuinely reusable beyond this one\n task; do not emit for one-off actions.\n\nOmit if none.\n";
76469
76469
  //#endregion
76470
76470
  //#region ../../packages/agent-core/src/agent/compaction/compaction-update-instruction.md
76471
- var compaction_update_instruction_default = "\n--- This message is a direct task, not part of the above conversation ---\n\nYou are now given a task to UPDATE an existing compaction summary with new messages that came in after the last compaction.\n\nA previous compaction summary already exists at the top of the conversation (the first assistant message). You must NOT discard it. Instead, merge the new information into the existing structure, keeping all previously captured state intact unless explicitly contradicted or resolved by the new messages.\n\nOutput text only. DO NOT CALL ANY TOOLS. Calling tools will be rejected and fails the task. You have only one chance.\n\n{{ customInstruction }}\n\n<!-- Previous Summary Reference -->\n\nThe first assistant message above is the PREVIOUS summary. Treat it as the source of truth for everything that happened before the new messages. Do not restate it verbatim — produce an updated, merged summary.\n\n<!-- Memory Memo Extraction (PRIORITY — do not skip) -->\n\n## 任务经验提取\n\nAFTER completing the updated summary below, scan ONLY the new messages being compacted (not those already covered by the previous summary) for **completed task loops**. A task loop is \"completed\" when:\n- The user made a clear request or asked a specific question\n- You provided a solution or answer\n- The outcome is clear (success, partial success, or failure)\n\nFor each completed task loop found, output a structured experience record **at the very end of your response**:\n\n```memory-memo\n{\n \"userNeed\": \"<the user's need or goal, one sentence>\",\n \"approach\": \"<what was done — the approach taken, 2-4 sentences>\",\n \"outcome\": \"<final result, e.g. '完成', '部分完成', '失败: reason'>\",\n \"whatFailed\": \"<dead ends tried — things that didn't work, or 'none'>\",\n \"whatWorked\": \"<key actions that ultimately worked, or 'none'>\",\n \"tags\": [\"<tag1>\", \"<tag2>\", \"<tag3>\"]\n}\n```\n\nGuidelines:\n- Record important failed attempts in \"whatFailed\" to help avoid repeating mistakes.\n- Record key successful actions in \"whatWorked\" to help reuse effective approaches.\n- Include 3-5 semantic \"tags\" summarizing the task domain, tech stack, or action type.\n- Skip in-progress work unless it contains a valuable error+fix experience.\n- Merge closely related sub-tasks into a single record.\n- Use the exact field names and JSON format shown above.\n\nIf no completed task loops are found in the new compacted messages, output:\n```memory-memo\n{\"none\": true}\n```\n\n<!-- Update Rules -->\n\n1. Preserve the section structure of the previous summary (Current Focus, Environment, Completed Tasks, Active Issues, Code State, Important Context, All User Messages).\n2. Move newly-completed tasks from \"Current Focus\" / \"Active Issues\" into \"Completed Tasks\".\n3. Update \"Current Focus\" to reflect what is being worked on RIGHT NOW.\n4. Append new user messages to \"All User Messages\" — do not repeat those already captured.\n5. Refresh \"Code State\" code snippets only if newer versions exist in the new messages.\n6. Drop resolved issues from \"Active Issues\"; add newly-discovered ones.\n7. Do not invent new information. If the new messages say nothing about a section, carry the previous content forward unchanged.\n\n<!-- Required Output Structure -->\n\n## Current Focus\n\n[What we're working on now]\n\n## Environment\n\n- [Key setup/config points]\n- ...\n\n## Completed Tasks\n\n- [Task]: [Brief outcome]\n- ...\n\n## Active Issues\n\n- [Issue]: [Status/Next steps]\n- ...\n\n## Code State\n\n### [Critical file name]\n\n[Brief description of the file's purpose and current state]\n\n```\n[The latest version of critical code snippets in this file, <20 lines]\n```\n\n## Important Context\n\n- [Any crucial information not covered above]\n- ...\n\n## All User Messages\n\n- [Detailed non tool use user message]\n- ...\n";
76471
+ var compaction_update_instruction_default = "\n--- This message is a direct task, not part of the above conversation ---\n\nYou are now given a task to UPDATE an existing compaction summary with new messages that came in after the last compaction.\n\nA previous compaction summary already exists at the top of the conversation (the first assistant message). You must NOT discard it. Instead, merge the new information into the existing structure, keeping all previously captured state intact unless explicitly contradicted or resolved by the new messages.\n\nOutput text only. DO NOT CALL ANY TOOLS. Calling tools will be rejected and fails the task. You have only one chance.\n\n{{ customInstruction }}\n\n<!-- Previous Summary Reference -->\n\nThe first assistant message above is the PREVIOUS summary. Treat it as the source of truth for everything that happened before the new messages. Do not restate it verbatim — produce an updated, merged summary.\n\n<!-- Memory Memo Extraction (PRIORITY — do not skip) -->\n\n## 任务经验提取\n\nAFTER completing the updated summary below, scan ONLY the new messages being compacted (not those already covered by the previous summary) for **task loops**. A task loop is \"completed\" when:\n- The user made a clear request or asked a specific question\n- You provided a solution or answer\n- The outcome is clear (success, partial success, or failure)\n\n**You MUST output at least one memory-memo block** (or the `{\"none\": true}` marker) at the very end of your response — omitting the section entirely is not allowed. Record completed task loops as full experience records; record **in-progress work** as a lower-priority record whose \"outcome\" is \"进行中\" (so the ongoing task survives compaction and can be resumed later).\n\nFor each task loop found, output a structured experience record **at the very end of your response**:\n\n```memory-memo\n{\n \"userNeed\": \"<the user's need or goal, one sentence>\",\n \"approach\": \"<what was done — the approach taken, 2-4 sentences>\",\n \"outcome\": \"<final result, e.g. '完成', '部分完成', '失败: reason', or '进行中' for in-progress work>\",\n \"whatFailed\": \"<dead ends tried — things that didn't work, or 'none'>\",\n \"whatWorked\": \"<key actions that ultimately worked, or 'none'>\",\n \"tags\": [\"<tag1>\", \"<tag2>\", \"<tag3>\"],\n \"note\": \"<optional: a soft, AI-readable note/suggestion that makes the record easier for future turns to understand — e.g. '这类任务先查接口可用性再写代码'. Omit if none>\"\n}\n```\n\nGuidelines:\n- Record important failed attempts in \"whatFailed\" to help avoid repeating mistakes.\n- Record key successful actions in \"whatWorked\" to help reuse effective approaches.\n- Include 3-5 semantic \"tags\" summarizing the task domain, tech stack, or action type.\n- For in-progress work: record it with \"outcome\": \"进行中\" and describe where the task stands and what remains — this is what lets the agent resume seamlessly after compaction.\n- Merge closely related sub-tasks into a single record.\n- Use the exact field names and JSON format shown above.\n\nIf no task loops (completed or in-progress) are found in the new compacted messages, output:\n```memory-memo\n{\"none\": true}\n```\n\n<!-- Update Rules -->\n\n1. Preserve the section structure of the previous summary (Current Focus, Environment, Completed Tasks, Active Issues, Key Decisions, Next Steps, Code State, Important Context, All User Messages).\n2. Move newly-completed tasks from \"Current Focus\" / \"Active Issues\" into \"Completed Tasks\".\n3. Update \"Current Focus\" to reflect what is being worked on RIGHT NOW.\n4. Append new user messages to \"All User Messages\" — do not repeat those already captured.\n5. Refresh \"Code State\" code snippets only if newer versions exist in the new messages.\n6. Drop resolved issues from \"Active Issues\"; add newly-discovered ones.\n7. Merge new decisions into \"Key Decisions\" (keep prior decisions unless explicitly overturned); merge new steps into \"Next Steps\" (reorder if priorities changed).\n8. Do not invent new information. If the new messages say nothing about a section, carry the previous content forward unchanged.\n\n<!-- Required Output Structure -->\n\n## Current Focus\n\n[What we're working on now]\n\n## Environment\n\n- [Key setup/config points]\n- ...\n\n## Completed Tasks\n\n- [Task]: [Brief outcome]\n- ...\n\n## Active Issues\n\n- [Issue]: [Status/Next steps]\n- ...\n\n## Key Decisions\n\n- [Decision]: [rationale] — preserve architectural choices and why they were made\n- ...\n\n## Next Steps\n\n1. [The very next actionable step]\n2. [Then this]\n3. ...\n\n## Code State\n\n### [Critical file name]\n\n[Brief description of the file's purpose and current state]\n\n```\n[The latest version of critical code snippets in this file, <20 lines]\n```\n\n## Important Context\n\n- [Any crucial information not covered above]\n- ...\n\n## All User Messages\n\n- [Detailed non tool use user message]\n- ...\n";
76472
76472
  //#endregion
76473
76473
  //#region ../../packages/agent-core/src/agent/compaction/render-messages.ts
76474
76474
  const TOOL_RESULT_MAX_CHARS = 2e3;
@@ -76556,13 +76556,14 @@ function stringifyJsonish(value) {
76556
76556
  //#endregion
76557
76557
  //#region ../../packages/agent-core/src/agent/compaction/strategy.ts
76558
76558
  const DEFAULT_COMPACTION_CONFIG = {
76559
- triggerRatio: .75,
76560
- blockRatio: .85,
76561
- reservedContextSize: 5e4,
76559
+ triggerRatio: .85,
76560
+ blockRatio: .9,
76561
+ reservedContextSize: 2e4,
76562
76562
  maxCompactionPerTurn: 3,
76563
- maxRecentMessages: 4,
76564
- maxRecentUserMessages: 2,
76565
- maxRecentSizeRatio: .2,
76563
+ maxRecentMessages: 24,
76564
+ maxRecentUserMessages: 12,
76565
+ maxRecentTokens: 3e4,
76566
+ maxRecentSizeRatio: .25,
76566
76567
  minOverflowReductionRatio: .05,
76567
76568
  turnGrowthMultiplier: 2.5
76568
76569
  };
@@ -76603,7 +76604,7 @@ var DefaultCompactionStrategy = class {
76603
76604
  if (m2.role === "user") recentUserMessages++;
76604
76605
  recentSize += estimateTokensForMessage(m2);
76605
76606
  if (canSplitAfter(messages, splitIndex)) bestN = splitIndex + 1;
76606
- if ((recentMessages >= this.config.maxRecentMessages || recentUserMessages >= this.config.maxRecentUserMessages || recentSize >= this.maxSize * this.config.maxRecentSizeRatio) && bestN !== void 0) break;
76607
+ if ((recentMessages >= this.config.maxRecentMessages || recentUserMessages >= this.config.maxRecentUserMessages || recentSize >= this.config.maxRecentTokens || recentSize >= this.maxSize * this.config.maxRecentSizeRatio) && bestN !== void 0) break;
76607
76608
  }
76608
76609
  return bestN ?? 0;
76609
76610
  }
@@ -77091,7 +77092,7 @@ var FullCompaction = class {
77091
77092
  } else this.lastCompactionFiles = void 0;
77092
77093
  for (const msg of messagesToCompactForOps) extractFileOpsFromMessage(msg, fileOps);
77093
77094
  const toolCallHistory = formatToolCallHistory(messagesToCompactForOps);
77094
- const processedSummary = this.postProcessSummary(summary, fileOps, toolCallHistory);
77095
+ const processedSummary = this.postProcessSummary(summary, fileOps, toolCallHistory, compactedCount);
77095
77096
  const tokensAfter = estimateTokens$1(processedSummary) + estimateTokensForMessages(recent);
77096
77097
  const fileLists = computeFileLists(fileOps);
77097
77098
  const MAX_PERSISTED_FILES = 100;
@@ -77114,7 +77115,7 @@ var FullCompaction = class {
77114
77115
  this.agent.context.applyCompaction(result);
77115
77116
  this.lastCompactionFiles = fileLists;
77116
77117
  this.lowWaterMark = Math.floor(this.effectiveTokenCount * 1.1);
77117
- await this.extractAndStoreMemos(processedSummary);
77118
+ await this.extractAndStoreMemos(processedSummary, messagesToCompactForOps);
77118
77119
  this.triggerPostCompactHook(data, result);
77119
77120
  this.detectSkillCandidates(processedSummary);
77120
77121
  this.consecutiveCompactionFailures = 0;
@@ -77178,16 +77179,27 @@ var FullCompaction = class {
77178
77179
  }
77179
77180
  });
77180
77181
  }
77181
- /** Extract memory memos from compaction summary and store them. */
77182
- async extractAndStoreMemos(summary) {
77182
+ /** Extract memory memos from compaction summary and store them. When the
77183
+ * summary carries no memory-memo block, fall back to recording the most
77184
+ * recent real user message from the compacted history as a low-confidence
77185
+ * in-progress memo, so an ongoing task survives compaction even if the
77186
+ * model omitted the extraction section. */
77187
+ async extractAndStoreMemos(summary, messagesToCompact) {
77183
77188
  const memoStore = this.agent.memoStore;
77184
77189
  if (!memoStore) {
77185
77190
  this.agent.log.info("Memory memo store not available, skipping extraction");
77186
77191
  return;
77187
77192
  }
77188
77193
  this.agent.log.info("Scanning compaction summary for memory memos", { summaryLen: summary.length });
77189
- const memos = parseMemoryMemos(summary);
77194
+ let memos = parseMemoryMemos(summary);
77190
77195
  this.agent.log.info("Memory memo parse result", { memoCount: memos.length });
77196
+ if (memos.length === 0 && messagesToCompact !== void 0) {
77197
+ const fallback = /```memory-memo[\s\S]*?"none"\s*:\s*true[\s\S]*?```/.test(summary) ? void 0 : this.buildFallbackMemo(messagesToCompact);
77198
+ if (fallback !== void 0) {
77199
+ memos = [fallback];
77200
+ this.agent.log.info("Compaction summary carried no memory-memo; stored fallback memo", { userNeed: fallback.userNeed.slice(0, 120) });
77201
+ }
77202
+ }
77191
77203
  if (memos.length === 0) return;
77192
77204
  const sessionId = this.agent.homedir ? basename$1(dirname$2(dirname$2(this.agent.homedir))) : "unknown";
77193
77205
  const sessionTitle = await this.agent.getSessionTitle();
@@ -77206,6 +77218,28 @@ var FullCompaction = class {
77206
77218
  sessionId
77207
77219
  });
77208
77220
  }
77221
+ /** Build a low-confidence fallback memo from the most recent real user
77222
+ * message in the compacted history (origin.kind === 'user'), so an ongoing
77223
+ * task is not lost when the summary omits the memory-memo section. */
77224
+ buildFallbackMemo(messages) {
77225
+ for (let i = messages.length - 1; i >= 0; i--) {
77226
+ const msg = messages[i];
77227
+ if (msg === void 0 || msg.role !== "user" || msg.origin?.kind !== "user") continue;
77228
+ const text = msg.content.filter((p) => p.type === "text").map((p) => p.text).join("").trim();
77229
+ if (text.length === 0) continue;
77230
+ return createMemoryMemo({
77231
+ sourceSessionId: "",
77232
+ userNeed: text.slice(0, 300),
77233
+ approach: "(压缩时自动兜底记录:压缩摘要未生成完整任务经验提取,此处记录最近一条用户请求,用于跨压缩恢复进行中任务。请结合会话上下文核实。)",
77234
+ outcome: "进行中",
77235
+ whatFailed: "none",
77236
+ whatWorked: "none",
77237
+ tags: ["compaction-fallback"],
77238
+ note: "低置信度自动记录:建议后续在上下文恢复后核实并补充完整经验。",
77239
+ extractionSource: "compaction"
77240
+ });
77241
+ }
77242
+ }
77209
77243
  /**
77210
77244
  * Detects [[skill-candidate: <name>|<purpose>|<evidence>]] markers in the
77211
77245
  * compaction summary and emits a `skill_candidate` event for each one (at
@@ -77238,11 +77272,17 @@ var FullCompaction = class {
77238
77272
  * the compaction summary so active tasks and file context survive
77239
77273
  * compression. Without this, both are lost after compaction because the
77240
77274
  * original messages containing them are removed from the context window.
77275
+ *
77276
+ * When messages were actually compacted (`compactedCount > 0`), a leading
77277
+ * elision note tells the model how much of the conversation is missing and
77278
+ * that the summary is working notes, not verbatim history — so the tail
77279
+ * messages after the summary are not mistaken for a continuous conversation.
77241
77280
  */
77242
- postProcessSummary(summary, fileOps, toolCallHistory) {
77281
+ postProcessSummary(summary, fileOps, toolCallHistory, compactedCount) {
77243
77282
  const todos = this.agent.tools.storeData()["todo"] ?? [];
77244
- const base = summary.trim().replaceAll(/<files>[\s\S]*?<\/files>\s*/g, "").trimEnd();
77283
+ const base = summary.trim().replaceAll(/<files>[\s\S]*?<\/files>\s*/g, "").replace(/^> The conversation before this point was compacted:[\s\S]*?continue from here\.\s*\n+/m, "").trimEnd();
77245
77284
  const sections = [];
77285
+ if (compactedCount > 0) sections.push(`> The conversation before this point was compacted: ${String(compactedCount)} earlier message(s) were omitted and are covered by this summary. Treat the summary below as working notes, not verbatim history — the messages after it continue from here.`);
77246
77286
  if (base.length > 0) sections.push(base);
77247
77287
  if (todos.length > 0) {
77248
77288
  const lines = todos.map((t) => {
@@ -121699,6 +121739,7 @@ const SCREAM_CODE_LOG_DIR_NAME = "logs";
121699
121739
  const SCREAM_CODE_UPDATE_DIR_NAME = "updates";
121700
121740
  const SCREAM_CODE_UPDATE_STATE_FILE_NAME = "latest.json";
121701
121741
  const SCREAM_CODE_INPUT_HISTORY_DIR_NAME = "user-history";
121742
+ const SCREAM_CODE_UI_PREFERENCES_FILE_NAME = "ui-preferences.json";
121702
121743
  const DEFAULT_OAUTH_PROVIDER_NAME = "managed:scream-code";
121703
121744
  ErrorCodes.AUTH_LOGIN_REQUIRED;
121704
121745
  const SCREAM_CODE_PLUGIN_MARKETPLACE_URL_ENV = "SCREAM_CODE_PLUGIN_MARKETPLACE_URL";
@@ -121775,7 +121816,7 @@ function optionalBuildString(value) {
121775
121816
  return typeof value === "string" && value.length > 0 ? value : void 0;
121776
121817
  }
121777
121818
  const SCREAM_BUILD_INFO = {
121778
- version: optionalBuildString("0.12.7"),
121819
+ version: optionalBuildString("0.12.9"),
121779
121820
  channel: optionalBuildString(""),
121780
121821
  commit: optionalBuildString(""),
121781
121822
  buildTarget: optionalBuildString("darwin-arm64")
@@ -122762,28 +122803,46 @@ const BUILTIN_SLASH_COMMANDS = [
122762
122803
  name: "auto",
122763
122804
  aliases: [],
122764
122805
  description: "registry.auto_desc",
122765
- priority: 125,
122806
+ priority: 220,
122766
122807
  availability: "always"
122767
122808
  },
122768
122809
  {
122769
122810
  name: "yes",
122770
122811
  aliases: ["yolo"],
122771
122812
  description: "registry.yolo_desc",
122772
- priority: 124,
122813
+ priority: 219,
122773
122814
  availability: "always"
122774
122815
  },
122775
122816
  {
122776
122817
  name: "ask",
122777
122818
  aliases: ["ask"],
122778
122819
  description: "registry.ask_desc",
122779
- priority: 123,
122820
+ priority: 218,
122821
+ availability: "always"
122822
+ },
122823
+ {
122824
+ name: "goal",
122825
+ aliases: ["goaloff"],
122826
+ description: "registry.goal_desc",
122827
+ argumentHint: "[objective]",
122828
+ priority: 217,
122829
+ availability: (args) => {
122830
+ const trimmed = args.trim();
122831
+ return trimmed === "" || trimmed === "status" || trimmed === "pause" || trimmed === "off" ? "always" : "idle-only";
122832
+ }
122833
+ },
122834
+ {
122835
+ name: "wolfpack",
122836
+ aliases: ["wp"],
122837
+ description: "registry.wolfpack_desc",
122838
+ priority: 216,
122780
122839
  availability: "always"
122781
122840
  },
122782
122841
  {
122783
122842
  name: "rlm",
122784
122843
  aliases: ["rlm"],
122785
122844
  description: "registry.rlm_desc",
122786
- priority: 121,
122845
+ priority: 215,
122787
122846
  availability: "always"
122788
122847
  },
122789
122848
  {
@@ -122791,39 +122850,21 @@ const BUILTIN_SLASH_COMMANDS = [
122791
122850
  aliases: [],
122792
122851
  description: "registry.rlm_max_depth_desc",
122793
122852
  argumentHint: "[N]",
122794
- priority: 119,
122795
- availability: "always"
122796
- },
122797
- {
122798
- name: "wolfpack",
122799
- aliases: ["wp"],
122800
- description: "registry.wolfpack_desc",
122801
- priority: 122,
122853
+ priority: 214,
122802
122854
  availability: "always"
122803
122855
  },
122804
122856
  {
122805
122857
  name: "sessions",
122806
122858
  aliases: ["resume"],
122807
122859
  description: "registry.sessions_desc",
122808
- priority: 119
122809
- },
122810
- {
122811
- name: "goal",
122812
- aliases: ["goaloff"],
122813
- description: "registry.goal_desc",
122814
- argumentHint: "[objective]",
122815
- priority: 120,
122816
- availability: (args) => {
122817
- const trimmed = args.trim();
122818
- return trimmed === "" || trimmed === "status" || trimmed === "pause" || trimmed === "off" ? "always" : "idle-only";
122819
- }
122860
+ priority: 213
122820
122861
  },
122821
122862
  {
122822
122863
  name: "memory",
122823
122864
  aliases: ["memo", "mem"],
122824
122865
  description: "registry.memory_desc",
122825
122866
  argumentHint: "[query]",
122826
- priority: 120,
122867
+ priority: 212,
122827
122868
  availability: "always"
122828
122869
  },
122829
122870
  {
@@ -122831,96 +122872,61 @@ const BUILTIN_SLASH_COMMANDS = [
122831
122872
  aliases: ["know"],
122832
122873
  description: "registry.knowledge_desc",
122833
122874
  argumentHint: "[query]",
122834
- priority: 119,
122875
+ priority: 211,
122835
122876
  availability: "always"
122836
122877
  },
122837
- {
122838
- name: "new",
122839
- aliases: ["clear"],
122840
- description: "registry.new_desc",
122841
- priority: 120
122842
- },
122843
122878
  {
122844
122879
  name: "model",
122845
122880
  aliases: [],
122846
122881
  description: "registry.model_desc",
122847
122882
  argumentHint: "[alias]",
122848
- priority: 120
122883
+ priority: 210
122884
+ },
122885
+ {
122886
+ name: "new",
122887
+ aliases: ["clear"],
122888
+ description: "registry.new_desc",
122889
+ priority: 209
122849
122890
  },
122850
122891
  {
122851
122892
  name: "compact",
122852
122893
  aliases: [],
122853
122894
  description: "registry.compact_desc",
122854
- priority: 119
122895
+ priority: 208
122855
122896
  },
122856
122897
  {
122857
- name: "make-skill",
122858
- aliases: ["makeskill", "craftskill"],
122859
- description: "registry.make_skill_desc",
122860
- priority: 118,
122861
- availability: "idle-only"
122898
+ name: "fusionplan",
122899
+ aliases: ["fp"],
122900
+ description: "registry.fusionplan_desc",
122901
+ priority: 207,
122902
+ availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
122862
122903
  },
122863
122904
  {
122864
122905
  name: "plan",
122865
122906
  aliases: [],
122866
122907
  description: "registry.plan_desc",
122867
- priority: 118,
122868
- availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
122869
- },
122870
- {
122871
- name: "fusionplan",
122872
- aliases: ["fp"],
122873
- description: "registry.fusionplan_desc",
122874
- priority: 118,
122908
+ priority: 206,
122875
122909
  availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
122876
122910
  },
122877
122911
  {
122878
122912
  name: "tasks",
122879
122913
  aliases: ["task"],
122880
122914
  description: "registry.tasks_desc",
122881
- priority: 117,
122882
- availability: "always"
122883
- },
122884
- {
122885
- name: "help",
122886
- aliases: ["h", "?"],
122887
- description: "registry.help_desc",
122888
- priority: 116,
122889
- availability: "always"
122890
- },
122891
- {
122892
- name: "status",
122893
- aliases: [],
122894
- description: "registry.status_desc",
122895
- priority: 115,
122896
- availability: "always"
122897
- },
122898
- {
122899
- name: "usage",
122900
- aliases: [],
122901
- description: "registry.usage_desc",
122902
- priority: 114,
122915
+ priority: 205,
122903
122916
  availability: "always"
122904
122917
  },
122905
122918
  {
122906
122919
  name: "btw",
122907
122920
  aliases: [],
122908
122921
  description: "registry.btw_desc",
122909
- priority: 113,
122922
+ priority: 204,
122910
122923
  availability: "always"
122911
122924
  },
122912
122925
  {
122913
122926
  name: "like",
122914
122927
  aliases: [],
122915
122928
  description: "registry.like_desc",
122916
- priority: 113,
122917
- availability: "always"
122918
- },
122919
- {
122920
- name: "mcp",
122921
- aliases: [],
122922
- description: "registry.mcp_desc",
122923
- priority: 112,
122929
+ priority: 203,
122924
122930
  availability: "always"
122925
122931
  },
122926
122932
  {
@@ -122931,127 +122937,162 @@ const BUILTIN_SLASH_COMMANDS = [
122931
122937
  "plugins"
122932
122938
  ],
122933
122939
  description: "registry.skill_desc",
122934
- priority: 110,
122940
+ priority: 202,
122935
122941
  availability: "always"
122936
122942
  },
122937
122943
  {
122938
- name: "cc",
122944
+ name: "fork",
122939
122945
  aliases: [],
122940
- description: "registry.cc_desc",
122941
- priority: 109,
122946
+ description: "registry.fork_desc",
122947
+ priority: 201
122948
+ },
122949
+ {
122950
+ name: "title",
122951
+ aliases: ["rename"],
122952
+ description: "registry.title_desc",
122953
+ priority: 200,
122942
122954
  availability: "always"
122943
122955
  },
122944
122956
  {
122945
- name: "cc-connect",
122957
+ name: "config",
122946
122958
  aliases: [],
122947
- description: "registry.cc_connect_desc",
122948
- priority: 109,
122959
+ description: "registry.config_desc",
122960
+ priority: 199
122961
+ },
122962
+ {
122963
+ name: "help",
122964
+ aliases: ["h", "?"],
122965
+ description: "registry.help_desc",
122966
+ priority: 198,
122949
122967
  availability: "always"
122950
122968
  },
122951
122969
  {
122952
- name: "revoke",
122953
- aliases: [],
122954
- description: "registry.revoke_desc",
122955
- priority: 108,
122970
+ name: "make-skill",
122971
+ aliases: ["makeskill", "craftskill"],
122972
+ description: "registry.make_skill_desc",
122973
+ priority: 197,
122956
122974
  availability: "idle-only"
122957
122975
  },
122958
122976
  {
122959
- name: "fork",
122977
+ name: "mcp",
122960
122978
  aliases: [],
122961
- description: "registry.fork_desc",
122962
- priority: 105
122979
+ description: "registry.mcp_desc",
122980
+ priority: 196,
122981
+ availability: "always"
122963
122982
  },
122964
122983
  {
122965
- name: "title",
122966
- aliases: ["rename"],
122967
- description: "registry.title_desc",
122968
- priority: 104,
122984
+ name: "status",
122985
+ aliases: [],
122986
+ description: "registry.status_desc",
122987
+ priority: 195,
122969
122988
  availability: "always"
122970
122989
  },
122971
122990
  {
122972
- name: "config",
122991
+ name: "usage",
122973
122992
  aliases: [],
122974
- description: "registry.config_desc",
122975
- priority: 103
122993
+ description: "registry.usage_desc",
122994
+ priority: 194,
122995
+ availability: "always"
122976
122996
  },
122977
122997
  {
122978
- name: "permission",
122998
+ name: "revoke",
122979
122999
  aliases: [],
122980
- description: "registry.permission_desc",
122981
- priority: 102,
123000
+ description: "registry.revoke_desc",
123001
+ priority: 193,
123002
+ availability: "idle-only"
123003
+ },
123004
+ {
123005
+ name: "cc",
123006
+ aliases: [],
123007
+ description: "registry.cc_desc",
123008
+ priority: 192,
123009
+ availability: "always"
123010
+ },
123011
+ {
123012
+ name: "cc-connect",
123013
+ aliases: [],
123014
+ description: "registry.cc_connect_desc",
123015
+ priority: 191,
122982
123016
  availability: "always"
122983
123017
  },
122984
123018
  {
122985
123019
  name: "theme",
122986
123020
  aliases: [],
122987
123021
  description: "registry.theme_desc",
122988
- priority: 101,
123022
+ priority: 190,
122989
123023
  availability: "always"
122990
123024
  },
122991
123025
  {
122992
123026
  name: "language",
122993
123027
  aliases: ["lang"],
122994
123028
  description: "registry.language_desc",
122995
- priority: 102,
123029
+ priority: 189,
123030
+ availability: "always"
123031
+ },
123032
+ {
123033
+ name: "permission",
123034
+ aliases: [],
123035
+ description: "registry.permission_desc",
123036
+ priority: 188,
122996
123037
  availability: "always"
122997
123038
  },
122998
123039
  {
122999
123040
  name: "editor",
123000
123041
  aliases: [],
123001
123042
  description: "registry.editor_desc",
123002
- priority: 100,
123043
+ priority: 187,
123003
123044
  availability: "always"
123004
123045
  },
123005
123046
  {
123006
123047
  name: "settings",
123007
123048
  aliases: [],
123008
123049
  description: "registry.settings_desc",
123009
- priority: 99,
123050
+ priority: 186,
123010
123051
  availability: "always"
123011
123052
  },
123012
123053
  {
123013
123054
  name: "init",
123014
123055
  aliases: [],
123015
123056
  description: "registry.init_desc",
123016
- priority: 98
123057
+ priority: 185
123017
123058
  },
123018
123059
  {
123019
123060
  name: "export-md",
123020
123061
  aliases: ["export"],
123021
123062
  description: "registry.export_md_desc",
123022
- priority: 97
123063
+ priority: 184
123023
123064
  },
123024
123065
  {
123025
123066
  name: "export-debug-zip",
123026
123067
  aliases: [],
123027
123068
  description: "registry.export_debug_desc",
123028
- priority: 96
123069
+ priority: 183
123070
+ },
123071
+ {
123072
+ name: "eval",
123073
+ aliases: [],
123074
+ description: "registry.eval_desc",
123075
+ priority: 182
123029
123076
  },
123030
123077
  {
123031
123078
  name: "update",
123032
123079
  aliases: [],
123033
123080
  description: "registry.update_desc",
123034
- priority: 95,
123081
+ priority: 181,
123035
123082
  availability: "idle-only"
123036
123083
  },
123037
123084
  {
123038
123085
  name: "version",
123039
123086
  aliases: [],
123040
123087
  description: "registry.version_desc",
123041
- priority: 94,
123088
+ priority: 180,
123042
123089
  availability: "always"
123043
123090
  },
123044
123091
  {
123045
123092
  name: "logout",
123046
123093
  aliases: ["disconnect"],
123047
123094
  description: "registry.logout_desc",
123048
- priority: 93
123049
- },
123050
- {
123051
- name: "eval",
123052
- aliases: [],
123053
- description: "registry.eval_desc",
123054
- priority: 92
123095
+ priority: 179
123055
123096
  },
123056
123097
  {
123057
123098
  name: "exit",
@@ -123171,6 +123212,8 @@ function getCtrlCHint() {
123171
123212
  }
123172
123213
  const MAIN_AGENT_ID$1 = "main";
123173
123214
  const EXIT_CONFIRM_WINDOW_MS = 1500;
123215
+ /** Partner model-provider page opened by Ctrl+F while the chat is empty. */
123216
+ const EMPTY_SESSION_HINT_URL = "https://opencode.ai/go?ref=75NKVRZQCY";
123174
123217
  function isManagedUsageProvider(providerKey) {
123175
123218
  return providerKey === DEFAULT_OAUTH_PROVIDER_NAME;
123176
123219
  }
@@ -125693,7 +125736,15 @@ const HEADING_HASH_PREFIX = /^((?:\u001B\[[0-9;]*m)*)#{1,6}[ \t]+/;
125693
125736
  * takes one formatter function per token; tokens not listed here fall back to
125694
125737
  * its DEFAULT_THEME.
125695
125738
  */
125696
- function createCodeHighlightTheme(colors) {
125739
+ /**
125740
+ * Markdown code-block highlight theme: green-dominant mapping (keyword,
125741
+ * function, built_in → primary; strings → success; numbers → warning;
125742
+ * comments → textDim). Kept distinct from the shared preview theme in
125743
+ * code-highlight-theme.ts on purpose — markdown code blocks use the green
125744
+ * primary hue, while file-preview panels use the classic blue/red/yellow
125745
+ * scheme mapped to the same palette. Both follow the active theme.
125746
+ */
125747
+ function createMarkdownCodeHighlightTheme(colors) {
125697
125748
  const keyword = chalk.hex(colors.primary);
125698
125749
  const str = chalk.hex(colors.success);
125699
125750
  const comment = chalk.hex(colors.textDim);
@@ -125743,7 +125794,7 @@ function createMarkdownTheme(colors) {
125743
125794
  const stripHash = (text) => text.replace(HEADING_HASH_PREFIX, "$1");
125744
125795
  const muted = chalk.hex(colors.textMuted);
125745
125796
  const border = chalk.hex(colors.border);
125746
- const codeTheme = createCodeHighlightTheme(colors);
125797
+ const codeTheme = createMarkdownCodeHighlightTheme(colors);
125747
125798
  return {
125748
125799
  heading: (text) => chalk.bold.hex(colors.text)(stripHash(text)),
125749
125800
  link: (text) => chalk.hex(colors.mdLink)(text),
@@ -127595,7 +127646,7 @@ async function guidedGoalSetup(host) {
127595
127646
  host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
127596
127647
  return;
127597
127648
  }
127598
- const { TextInputDialogComponent } = await import("./text-input-dialog-BIXonNNb.mjs");
127649
+ const { TextInputDialogComponent } = await import("./text-input-dialog-DXLBq_rF.mjs");
127599
127650
  const initialDesc = await promptText(host, TextInputDialogComponent, {
127600
127651
  title: t("goal.setup_title_initial"),
127601
127652
  subtitle: t("goal.setup_desc_hint"),
@@ -127616,7 +127667,7 @@ async function guidedGoalSetup(host) {
127616
127667
  await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
127617
127668
  }
127618
127669
  async function showGoalConfigWizard(host, session, objective, replace) {
127619
- const { TextInputDialogComponent } = await import("./text-input-dialog-BIXonNNb.mjs");
127670
+ const { TextInputDialogComponent } = await import("./text-input-dialog-DXLBq_rF.mjs");
127620
127671
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
127621
127672
  title: t("goal.wizard_title", { objective }),
127622
127673
  subtitle: t("goal.budget_turns_hint"),
@@ -128981,6 +129032,60 @@ var CachedContainer = class extends Container {
128981
129032
  }
128982
129033
  };
128983
129034
  //#endregion
129035
+ //#region src/tui/theme/code-highlight-theme.ts
129036
+ function createCodeHighlightTheme(colors) {
129037
+ const keyword = chalk.hex(colors.mdCodeBlock);
129038
+ const str = chalk.hex(colors.error);
129039
+ const num = chalk.hex(colors.primary);
129040
+ const fn = chalk.hex(colors.warning);
129041
+ const builtin = chalk.hex(colors.planMode);
129042
+ const cls = chalk.hex(colors.accent);
129043
+ const text = chalk.hex(colors.text);
129044
+ return {
129045
+ keyword,
129046
+ built_in: builtin,
129047
+ type: keyword,
129048
+ literal: builtin,
129049
+ number: num,
129050
+ regexp: str,
129051
+ string: str,
129052
+ subst: str,
129053
+ symbol: num,
129054
+ class: cls,
129055
+ function: fn,
129056
+ title: fn,
129057
+ params: text,
129058
+ comment: num,
129059
+ doctag: num,
129060
+ meta: chalk.hex(colors.textMuted),
129061
+ "meta-keyword": keyword,
129062
+ "meta-string": str,
129063
+ section: keyword,
129064
+ tag: keyword,
129065
+ name: fn,
129066
+ "builtin-name": builtin,
129067
+ attr: fn,
129068
+ attribute: fn,
129069
+ variable: text,
129070
+ bullet: num,
129071
+ code: str,
129072
+ emphasis: (s) => chalk.italic(s),
129073
+ strong: (s) => chalk.bold(s),
129074
+ formula: text,
129075
+ link: chalk.hex(colors.mdLink),
129076
+ quote: chalk.hex(colors.mdQuote),
129077
+ addition: chalk.hex(colors.diffAdded),
129078
+ deletion: chalk.hex(colors.diffRemoved),
129079
+ "selector-tag": keyword,
129080
+ "selector-id": cls,
129081
+ "selector-class": cls,
129082
+ "selector-attr": fn,
129083
+ "selector-pseudo": fn,
129084
+ "template-tag": str,
129085
+ "template-variable": fn
129086
+ };
129087
+ }
129088
+ //#endregion
128984
129089
  //#region src/tui/components/media/code-highlight.ts
128985
129090
  /**
128986
129091
  * Shared syntax-highlighting helpers for code previews
@@ -129018,13 +129123,14 @@ function langFromPath(filePath) {
129018
129123
  const lang = EXT_LANG_MAP[ext] ?? ext;
129019
129124
  return supportsLanguage(lang) ? lang : void 0;
129020
129125
  }
129021
- function highlightLines(code, lang) {
129126
+ function highlightLines(code, lang, colors) {
129022
129127
  const normalizedLang = lang?.trim().toLowerCase();
129023
129128
  if (!normalizedLang || !supportsLanguage(normalizedLang)) return code.split("\n");
129024
129129
  try {
129025
129130
  return highlight(code, {
129026
129131
  language: normalizedLang,
129027
- ignoreIllegals: true
129132
+ ignoreIllegals: true,
129133
+ theme: createCodeHighlightTheme(colors)
129028
129134
  }).split("\n");
129029
129135
  } catch {
129030
129136
  return code.split("\n");
@@ -129086,10 +129192,12 @@ function visualizeIndent(line) {
129086
129192
  }
129087
129193
  /**
129088
129194
  * Render a diff line's code with leading-whitespace visualization (tabs as
129089
- * `->`, spaces as `·`, dimmed) and optional syntax highlighting. Highlighting
129090
- * runs on the raw code first; indent visualization then runs on the
129091
- * highlighted string - leading whitespace carries no token color, so it is
129092
- * still plain and can be replaced and dimmed without disturbing the tokens.
129195
+ * `->`, spaces as `·`, dimmed) and optional syntax highlighting. Indent
129196
+ * visualization runs on the raw code FIRST, then highlighting runs on the
129197
+ * stripped remainder: once the text is ANSI-tinted (the theme's `default`
129198
+ * color wraps whitespace too), a leading tab is no longer the first raw
129199
+ * character and would be missed. The dimmed indent itself carries no token
129200
+ * color.
129093
129201
  *
129094
129202
  * When highlighting is on and produced token colors, the diff line color is
129095
129203
  * layered as the base foreground: syntax tokens override it, but every ANSI
@@ -129098,17 +129206,18 @@ function visualizeIndent(line) {
129098
129206
  * When highlighting is off (streaming) or produced no token colors, the code
129099
129207
  * part is colored with the diff line color instead.
129100
129208
  */
129101
- function renderDiffCode(code, colorFn, highlight, lang) {
129102
- const { text, indentEnd } = visualizeIndent(highlight ? highlightLines(code, lang)[0] ?? code : code);
129209
+ function renderDiffCode(code, colorFn, highlight, lang, colors) {
129210
+ const { text, indentEnd } = visualizeIndent(code);
129103
129211
  const indent = text.slice(0, indentEnd);
129104
129212
  const rest = text.slice(indentEnd);
129105
129213
  const dimIndent = indent.length > 0 ? chalk.dim(indent) : indent;
129106
- if (highlight && ANSI_RE.test(rest)) {
129214
+ const source = highlight ? highlightLines(rest, lang, colors)[0] ?? rest : rest;
129215
+ if (highlight && ANSI_RE.test(source)) {
129107
129216
  const prefix = extractAnsiPrefix(colorFn);
129108
- if (prefix.length > 0) return dimIndent + prefix + rest.replace(ANSI_RESET_RE, (m) => m + prefix) + "\x1B[39m";
129109
- return dimIndent + rest;
129217
+ if (prefix.length > 0) return dimIndent + prefix + source.replace(ANSI_RESET_RE, (m) => m + prefix) + "\x1B[39m";
129218
+ return dimIndent + source;
129110
129219
  }
129111
- return dimIndent + colorFn(rest);
129220
+ return dimIndent + colorFn(source);
129112
129221
  }
129113
129222
  /**
129114
129223
  * Compute word-level diff between two single lines and highlight the changed
@@ -129242,7 +129351,7 @@ function renderDiffLines(oldText, newText, path, colors, isIncomplete = false, o
129242
129351
  const line = shown[i];
129243
129352
  const marker = line.kind === "add" ? "+" : "-";
129244
129353
  const color = line.kind === "add" ? s.add : s.del;
129245
- output.push(s.gutter(String(line.lineNum).padStart(4) + " ") + color(`${marker} `) + renderDiffCode(line.code, color, doHighlight, lang));
129354
+ output.push(s.gutter(String(line.lineNum).padStart(4) + " ") + color(`${marker} `) + renderDiffCode(line.code, color, doHighlight, lang, colors));
129246
129355
  i += 1;
129247
129356
  }
129248
129357
  const hidden = changedLines.length - shown.length;
@@ -129293,11 +129402,11 @@ function buildClusters(diffLines, contextLines) {
129293
129402
  removedCount: removed
129294
129403
  };
129295
129404
  }
129296
- function formatDiffRow(line, s, doHighlight, lang) {
129405
+ function formatDiffRow(line, s, doHighlight, lang, colors) {
129297
129406
  const gutter = s.gutter(String(line.lineNum).padStart(4) + " ");
129298
- if (line.kind === "add") return gutter + s.add("+ ") + renderDiffCode(line.code, s.add, doHighlight, lang);
129299
- if (line.kind === "delete") return gutter + s.del("- ") + renderDiffCode(line.code, s.del, doHighlight, lang);
129300
- return gutter + " " + renderDiffCode(line.code, (x) => x, doHighlight, lang);
129407
+ if (line.kind === "add") return gutter + s.add("+ ") + renderDiffCode(line.code, s.add, doHighlight, lang, colors);
129408
+ if (line.kind === "delete") return gutter + s.del("- ") + renderDiffCode(line.code, s.del, doHighlight, lang, colors);
129409
+ return gutter + " " + renderDiffCode(line.code, (x) => x, doHighlight, lang, colors);
129301
129410
  }
129302
129411
  /**
129303
129412
  * Render a diff with surrounding context, eliding unchanged middle
@@ -129368,7 +129477,7 @@ function renderDiffLinesClustered(oldText, newText, path, colors, opts = {}) {
129368
129477
  i += 2;
129369
129478
  continue;
129370
129479
  }
129371
- output.push(formatDiffRow(line, s, doHighlight, lang));
129480
+ output.push(formatDiffRow(line, s, doHighlight, lang, colors));
129372
129481
  body++;
129373
129482
  if (line.kind !== "context") shownChanges++;
129374
129483
  prevEnd = i;
@@ -131251,7 +131360,7 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
131251
131360
  if (name === "Write") {
131252
131361
  const content = str(this.toolCall.args["content"]);
131253
131362
  if (content.length === 0) return;
131254
- const allLines = highlightLines(content, langFromPath(str(this.toolCall.args["file_path"] ?? this.toolCall.args["path"])));
131363
+ const allLines = highlightLines(content, langFromPath(str(this.toolCall.args["file_path"] ?? this.toolCall.args["path"])), this.colors);
131255
131364
  const writeShouldCap = !this.expanded;
131256
131365
  const shown = writeShouldCap ? allLines.slice(0, 10) : allLines;
131257
131366
  const remaining = allLines.length - shown.length;
@@ -131356,7 +131465,7 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
131356
131465
  const fragmentEnd = Math.min(streamText.length, fragmentStart + ToolCallComponent.WRITE_STREAM_TAIL_RAW_CHARS);
131357
131466
  const fragment = unescapeJsonStringValue(streamText, fragmentStart, fragmentEnd);
131358
131467
  if (fragment.length === 0) return;
131359
- const lines = highlightLines(fragment, this.writeStreamLang);
131468
+ const lines = highlightLines(fragment, this.writeStreamLang, this.colors);
131360
131469
  const displayLines = lines.slice(-10);
131361
131470
  const firstFragmentLineNo = this.writeStreamNlCount + (tailStart > contentStart ? 2 : 1);
131362
131471
  const skipped = lines.length - displayLines.length;
@@ -137552,6 +137661,51 @@ function shellQuote(path) {
137552
137661
  return `'${path.replaceAll("'", "'\\''")}'`;
137553
137662
  }
137554
137663
  //#endregion
137664
+ //#region src/tui/utils/ui-preferences.ts
137665
+ /**
137666
+ * TUI-local UI preferences persisted to `<dataDir>/ui-preferences.json`.
137667
+ * Distinct from agent/SDK config files (which are owned by core) — this file
137668
+ * stores lightweight view-state knobs owned by the TUI itself, e.g. whether
137669
+ * the empty-session provider hint has been dismissed by the user.
137670
+ */
137671
+ const EMPTY = {};
137672
+ function getUiPreferencesPath() {
137673
+ return join(getDataDir(), SCREAM_CODE_UI_PREFERENCES_FILE_NAME);
137674
+ }
137675
+ function readUiPreferences() {
137676
+ try {
137677
+ const file = getUiPreferencesPath();
137678
+ if (!existsSync(file)) return { ...EMPTY };
137679
+ const raw = readFileSync(file, "utf8");
137680
+ const parsed = JSON.parse(raw);
137681
+ return {
137682
+ ...EMPTY,
137683
+ ...parsed
137684
+ };
137685
+ } catch {
137686
+ return { ...EMPTY };
137687
+ }
137688
+ }
137689
+ function writeUiPreferences(prefs) {
137690
+ try {
137691
+ const file = getUiPreferencesPath();
137692
+ mkdirSync(dirname$1(file), { recursive: true });
137693
+ writeFileSync(file, JSON.stringify(prefs, null, 2), "utf8");
137694
+ } catch {}
137695
+ }
137696
+ function isEmptySessionHintDismissed() {
137697
+ return readUiPreferences().emptySessionHintDismissed === true;
137698
+ }
137699
+ /** Toggle the empty-session hint on/off (Ctrl+B). Returns the new state:
137700
+ * true = hidden, false = shown. Persisted immediately. */
137701
+ function toggleEmptySessionHint() {
137702
+ const prefs = readUiPreferences();
137703
+ const dismissed = prefs.emptySessionHintDismissed !== true;
137704
+ prefs.emptySessionHintDismissed = dismissed;
137705
+ writeUiPreferences(prefs);
137706
+ return dismissed;
137707
+ }
137708
+ //#endregion
137555
137709
  //#region src/tui/controllers/editor-keyboard.ts
137556
137710
  var EditorKeyboardController = class {
137557
137711
  host;
@@ -137605,6 +137759,18 @@ var EditorKeyboardController = class {
137605
137759
  }
137606
137760
  this.armPendingExit("ctrl-d", getCtrlDHint());
137607
137761
  };
137762
+ editor.onCtrlF = () => {
137763
+ if (host.state.transcriptEntries.length > 0) return false;
137764
+ if (isEmptySessionHintDismissed()) return false;
137765
+ openUrl(EMPTY_SESSION_HINT_URL);
137766
+ return true;
137767
+ };
137768
+ editor.onCtrlB = () => {
137769
+ if (host.state.transcriptEntries.length > 0) return false;
137770
+ toggleEmptySessionHint();
137771
+ host.forceUpdateStatusBar();
137772
+ return true;
137773
+ };
137608
137774
  editor.onEscape = () => {
137609
137775
  if (this.pendingExit) this.clearPendingExit();
137610
137776
  if (host.state.activeDialog === "session-picker") {
@@ -138164,6 +138330,8 @@ var SessionEventHandler = class {
138164
138330
  this.renderedSkillActivationIds.clear();
138165
138331
  this.renderedMcpServerStatusKeys.clear();
138166
138332
  this.stopAllMcpServerStatusSpinners();
138333
+ this.pendingSkillCandidates = [];
138334
+ this.promptedSkillCandidates.clear();
138167
138335
  this.host.setAppState({ subagentUsage: {} });
138168
138336
  }
138169
138337
  startSubscription() {
@@ -138419,6 +138587,12 @@ var SessionEventHandler = class {
138419
138587
  const todos = this.host.state.todoPanel.getTodos();
138420
138588
  if (todos.length > 0 && todos.every((todo) => todo.status === "done")) this.host.streamingUI.setTodoList([]);
138421
138589
  this.host.streamingUI.resetToolUi();
138590
+ if (this.pendingSkillCandidates.length > 0 && this.host.state.queuedMessages.length === 0) {
138591
+ const pending = this.pendingSkillCandidates;
138592
+ this.pendingSkillCandidates = [];
138593
+ const session = this.host.session;
138594
+ if (session !== void 0) for (const candidate of pending) this.promptSkillCandidate(session, candidate.name, candidate.purpose);
138595
+ }
138422
138596
  this.host.streamingUI.finalizeTurn(sendQueued);
138423
138597
  }
138424
138598
  handleStepBegin(event) {
@@ -138793,6 +138967,10 @@ var SessionEventHandler = class {
138793
138967
  }
138794
138968
  /** Candidates already prompted this session, to avoid repeating the request. */
138795
138969
  promptedSkillCandidates = /* @__PURE__ */ new Set();
138970
+ /** Skill candidates detected while a turn was still active. Flushed at the
138971
+ * next turn end so the AskUserQuestion dialog never interrupts an ongoing
138972
+ * response. */
138973
+ pendingSkillCandidates = [];
138796
138974
  /**
138797
138975
  * Handles a `skill_candidate` event emitted after compaction detected a
138798
138976
  * reusable process in the summary. Best-effort and non-fatal: if the session
@@ -138800,6 +138978,10 @@ var SessionEventHandler = class {
138800
138978
  * no confirmation. When it works, the model runs an AskUserQuestion dialog
138801
138979
  * (确定/忽略) so the user can confirm or dismiss with a single keystroke,
138802
138980
  * then generates the skill via MakeSkillPlanTool on confirmation.
138981
+ *
138982
+ * If a turn is still streaming (the compaction finished while the agent was
138983
+ * mid-response), the candidate is queued and prompted at the next turn end —
138984
+ * never interrupting the running turn.
138803
138985
  */
138804
138986
  handleSkillCandidate(event) {
138805
138987
  try {
@@ -138809,10 +138991,25 @@ var SessionEventHandler = class {
138809
138991
  this.promptedSkillCandidates.add(name);
138810
138992
  const session = this.host.session;
138811
138993
  if (session === void 0) return;
138812
- const request = `检测到可复用过程「${name}」${purpose ? `(${purpose})` : ""}。请用 AskUserQuestion 工具询问用户是否将其保存为可复用技能(选项:生成 / 忽略)。用户选择「生成」后,用 MakeSkillPlanTool 生成该技能;选择「忽略」则直接说明已跳过。`;
138813
- session.prompt(request).catch(() => {});
138994
+ if (this.host.streamingUI.hasActiveTurn()) {
138995
+ this.pendingSkillCandidates.push({
138996
+ name,
138997
+ purpose
138998
+ });
138999
+ return;
139000
+ }
139001
+ this.promptSkillCandidate(session, name, purpose);
138814
139002
  } catch {}
138815
139003
  }
139004
+ /** Fire the AskUserQuestion prompt for a skill candidate. On failure, surface
139005
+ * a status hint instead of failing silently so the user still learns the
139006
+ * candidate was detected. */
139007
+ promptSkillCandidate(session, name, purpose) {
139008
+ const request = `检测到可复用过程「${name}」${purpose ? `(${purpose})` : ""}。请用 AskUserQuestion 工具询问用户是否将其保存为可复用技能(选项:生成 / 忽略)。用户选择「生成」后,用 MakeSkillPlanTool 生成该技能;选择「忽略」则直接说明已跳过。`;
139009
+ session.prompt(request).catch(() => {
139010
+ this.host.showStatus(`检测到可复用过程「${name}」,但询问失败。可稍后手动处理。`, this.host.state.theme.colors.warning);
139011
+ });
139012
+ }
138816
139013
  finishCompaction(sendQueued) {
138817
139014
  if (!this.host.streamingUI.hasActiveTurn()) {
138818
139015
  this.host.setAppState({
@@ -139293,6 +139490,7 @@ var SessionReplayRenderer = class {
139293
139490
  return false;
139294
139491
  } finally {
139295
139492
  this.host.setAppState({ isReplaying: false });
139493
+ this.host.forceUpdateStatusBar();
139296
139494
  }
139297
139495
  }
139298
139496
  hydrateSnapshot(agent) {
@@ -141831,6 +142029,7 @@ var TranscriptController = class TranscriptController {
141831
142029
  state.errorBanner.clear();
141832
142030
  imageStore.clear();
141833
142031
  this.renderWelcome();
142032
+ this.host.forceUpdateStatusBar();
141834
142033
  }
141835
142034
  showStatus(message, color) {
141836
142035
  this.host.state.transcriptContainer.addChild(new StatusMessageComponent(message, this.host.state.theme.colors, color));
@@ -141937,7 +142136,13 @@ var StatusBarPaneComponent = class extends Container {
141937
142136
  * swaps which children are visible. */
141938
142137
  update(options) {
141939
142138
  this.clear();
141940
- if (options.mode === "idle") return;
142139
+ if (options.mode === "idle") {
142140
+ if (options.label.length > 0) {
142141
+ const text = options.labelColor !== void 0 ? chalk.hex(options.labelColor)(options.label) : options.label;
142142
+ this.addChild(new Text(text, 1, 0));
142143
+ }
142144
+ return;
142145
+ }
141941
142146
  if (options.mode === "waiting" || options.mode === "tool" || options.mode === "composing" || options.mode === "thinking") {
141942
142147
  if (options.pulseWave !== void 0) {
141943
142148
  if (options.label.length > 0) {
@@ -142552,10 +142757,23 @@ var LifecycleController = class LifecycleController {
142552
142757
  break;
142553
142758
  case "thinking": break;
142554
142759
  case "idle":
142555
- case "hidden": break;
142760
+ case "hidden":
142761
+ if (mode === "idle" && state.transcriptEntries.length === 0 && !isEmptySessionHintDismissed()) state.statusBarContainer.addChild(new StatusBarPaneComponent({
142762
+ mode: "idle",
142763
+ label: t("editor.empty_session_hint"),
142764
+ labelColor: state.theme.colors.textDim
142765
+ }));
142766
+ break;
142556
142767
  }
142557
142768
  state.ui.requestRender();
142558
142769
  }
142770
+ /** Force a status-bar refresh even when the mode is unchanged. Used after
142771
+ * session switches/clears: the transcript is emptied, so the empty-session
142772
+ * hint must (re)appear even though idle -> idle normally short-circuits. */
142773
+ forceUpdateStatusBar() {
142774
+ this.lastActivityMode = void 0;
142775
+ this.updateActivityPane();
142776
+ }
142559
142777
  resolveActivityPaneMode() {
142560
142778
  const { state } = this.host;
142561
142779
  if (state.activeDialog !== null) return "hidden";
@@ -143720,7 +143938,7 @@ function imagePathBoundaryEnd(payload, segmentStart, extensionEnd) {
143720
143938
  function normalizePastedImagePath(path) {
143721
143939
  const trimmed = path.trim();
143722
143940
  const first = trimmed[0];
143723
- const last = trimmed[trimmed.length - 1];
143941
+ const last = trimmed.at(-1);
143724
143942
  return (trimmed.length > 1 && (first === "\"" || first === "'") && last === first ? trimmed.slice(1, -1) : trimmed).replace(SHELL_ESCAPED_PATH_CHAR_REGEX, "$1");
143725
143943
  }
143726
143944
  /**
@@ -143818,6 +144036,11 @@ var CustomEditor = class extends Editor {
143818
144036
  onEscape;
143819
144037
  onCtrlD;
143820
144038
  onCtrlC;
144039
+ /** Return true when the key was handled (consumed); false falls through to
144040
+ * the base editor's own binding (pi-tui maps Ctrl+F to cursorRight). */
144041
+ onCtrlF;
144042
+ /** Same contract as onCtrlF: true consumes the key, false falls through. */
144043
+ onCtrlB;
143821
144044
  onToggleToolExpand;
143822
144045
  onTogglePlanExpand;
143823
144046
  onOpenExternalEditor;
@@ -143978,6 +144201,12 @@ var CustomEditor = class extends Editor {
143978
144201
  this.onCtrlC?.();
143979
144202
  return;
143980
144203
  }
144204
+ if (matchesKey(normalized, Key.ctrl("b"))) {
144205
+ if (this.onCtrlB?.() ?? false) return;
144206
+ }
144207
+ if (matchesKey(normalized, Key.ctrl("f"))) {
144208
+ if (this.onCtrlF?.() ?? false) return;
144209
+ }
143981
144210
  if (matchesKey(normalized, Key.ctrl("g"))) {
143982
144211
  this.onOpenExternalEditor?.();
143983
144212
  return;
@@ -145163,7 +145392,7 @@ function renderDisplayBlock(block, s, colors) {
145163
145392
  });
145164
145393
  case "file_content": {
145165
145394
  const lang = block.language ?? langFromPath(block.path);
145166
- const allLines = highlightLines(replaceTabs(block.content), lang);
145395
+ const allLines = highlightLines(replaceTabs(block.content), lang, colors);
145167
145396
  const shown = allLines.slice(0, CONTENT_SUMMARY_MAX_LINES);
145168
145397
  const lines = [s.strong(block.path)];
145169
145398
  for (const [i, line] of shown.entries()) lines.push(s.gutter(String(i + 1).padStart(4) + " ") + line);
@@ -145574,7 +145803,7 @@ function buildDiffBody(block, colors) {
145574
145803
  }
145575
145804
  function buildFileContentBody(block, colors) {
145576
145805
  const lang = block.language ?? langFromPath(block.path);
145577
- const highlighted = highlightLines(block.content, lang);
145806
+ const highlighted = highlightLines(block.content, lang, colors);
145578
145807
  const gutter = chalk.hex(colors.diffGutter);
145579
145808
  return {
145580
145809
  lines: highlighted.map((line, i) => gutter(String(i + 1).padStart(4) + " ") + line),
@@ -147030,6 +147259,7 @@ var DialogManager = class {
147030
147259
  this.host.state.editorContainer.addChild(panel);
147031
147260
  this.host.state.ui.setFocus(panel);
147032
147261
  this.host.state.ui.requestRender();
147262
+ this.host.forceUpdateStatusBar();
147033
147263
  }
147034
147264
  restoreEditor() {
147035
147265
  disposeChildren(this.host.state.editorContainer);
@@ -147037,6 +147267,7 @@ var DialogManager = class {
147037
147267
  this.host.state.ui.setFocus(this.host.state.editor);
147038
147268
  this.host.state.activeDialog = null;
147039
147269
  this.host.state.ui.requestRender();
147270
+ this.host.forceUpdateStatusBar();
147040
147271
  }
147041
147272
  showHelpPanel(commands) {
147042
147273
  this.host.state.activeDialog = "help";
@@ -147775,6 +148006,9 @@ var ScreamTUI = class {
147775
148006
  showStatus(message, color) {
147776
148007
  this.transcriptController.showStatus(message, color);
147777
148008
  }
148009
+ forceUpdateStatusBar() {
148010
+ this.lifecycleController.forceUpdateStatusBar();
148011
+ }
147778
148012
  showNotice(title, detail) {
147779
148013
  this.transcriptController.showNotice(title, detail);
147780
148014
  }
package/dist/main.mjs CHANGED
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
7
  //#region src/main.ts
8
8
  try {
9
- (await import("./app-DoxADJlo.mjs")).main();
9
+ (await import("./app-DB0yphR2.mjs")).main();
10
10
  } catch (error) {
11
11
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
12
12
  process.exit(1);
@@ -892,6 +892,7 @@ const dictionaries = {
892
892
  "textinput.empty_hint": "输入不能为空。",
893
893
  "editor.auto_detect": "自动检测 ($VISUAL / $EDITOR)",
894
894
  "editor.select_title": "选择外部编辑器",
895
+ "editor.empty_session_hint": "Tips:推荐使用 OpencodeGo 作为模型提供商,可免费调用各种模型或超低价包月计划,按 Ctrl+F 获取 API,按 Ctrl+B 切换显示",
895
896
  "editorkey.cancel_compaction_failed": "取消压缩失败: {msg}",
896
897
  "editorkey.editor_not_configured": "未配置编辑器。请设置 $VISUAL / $EDITOR,或运行 /editor <命令>。",
897
898
  "editorkey.external_editor_failed": "外部编辑器失败: {msg}",
@@ -1967,6 +1968,7 @@ const dictionaries = {
1967
1968
  "textinput.empty_hint": "Input cannot be empty.",
1968
1969
  "editor.auto_detect": "Auto detect ($VISUAL / $EDITOR)",
1969
1970
  "editor.select_title": "Select external editor",
1971
+ "editor.empty_session_hint": "Tips: Use OpencodeGo as your model provider: free models or a low-cost plan. Ctrl+F: get an API key. Ctrl+B: toggle this tip.",
1970
1972
  "editorkey.cancel_compaction_failed": "Cancel compaction failed: {msg}",
1971
1973
  "editorkey.editor_not_configured": "Editor not configured. Set $VISUAL / $EDITOR, or run /editor <command>.",
1972
1974
  "editorkey.external_editor_failed": "External editor failed: {msg}",
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
- import { t as TextInputDialogComponent } from "./text-input-dialog-9ywV88Lh.mjs";
6
+ import { t as TextInputDialogComponent } from "./text-input-dialog-CsPSlTrQ.mjs";
7
7
  export { TextInputDialogComponent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.12.7",
3
+ "version": "0.12.9",
4
4
  "description": "A terminal-native AI agent for builders",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",