scream-code 0.12.7 → 0.12.8

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.
@@ -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;
@@ -76560,8 +76560,9 @@ const DEFAULT_COMPACTION_CONFIG = {
76560
76560
  blockRatio: .85,
76561
76561
  reservedContextSize: 5e4,
76562
76562
  maxCompactionPerTurn: 3,
76563
- maxRecentMessages: 4,
76564
- maxRecentUserMessages: 2,
76563
+ maxRecentMessages: 16,
76564
+ maxRecentUserMessages: 8,
76565
+ maxRecentTokens: 2e4,
76565
76566
  maxRecentSizeRatio: .2,
76566
76567
  minOverflowReductionRatio: .05,
76567
76568
  turnGrowthMultiplier: 2.5
@@ -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) => {
@@ -121775,7 +121815,7 @@ function optionalBuildString(value) {
121775
121815
  return typeof value === "string" && value.length > 0 ? value : void 0;
121776
121816
  }
121777
121817
  const SCREAM_BUILD_INFO = {
121778
- version: optionalBuildString("0.12.7"),
121818
+ version: optionalBuildString("0.12.8"),
121779
121819
  channel: optionalBuildString(""),
121780
121820
  commit: optionalBuildString(""),
121781
121821
  buildTarget: optionalBuildString("darwin-arm64")
@@ -122762,28 +122802,46 @@ const BUILTIN_SLASH_COMMANDS = [
122762
122802
  name: "auto",
122763
122803
  aliases: [],
122764
122804
  description: "registry.auto_desc",
122765
- priority: 125,
122805
+ priority: 220,
122766
122806
  availability: "always"
122767
122807
  },
122768
122808
  {
122769
122809
  name: "yes",
122770
122810
  aliases: ["yolo"],
122771
122811
  description: "registry.yolo_desc",
122772
- priority: 124,
122812
+ priority: 219,
122773
122813
  availability: "always"
122774
122814
  },
122775
122815
  {
122776
122816
  name: "ask",
122777
122817
  aliases: ["ask"],
122778
122818
  description: "registry.ask_desc",
122779
- priority: 123,
122819
+ priority: 218,
122820
+ availability: "always"
122821
+ },
122822
+ {
122823
+ name: "goal",
122824
+ aliases: ["goaloff"],
122825
+ description: "registry.goal_desc",
122826
+ argumentHint: "[objective]",
122827
+ priority: 217,
122828
+ availability: (args) => {
122829
+ const trimmed = args.trim();
122830
+ return trimmed === "" || trimmed === "status" || trimmed === "pause" || trimmed === "off" ? "always" : "idle-only";
122831
+ }
122832
+ },
122833
+ {
122834
+ name: "wolfpack",
122835
+ aliases: ["wp"],
122836
+ description: "registry.wolfpack_desc",
122837
+ priority: 216,
122780
122838
  availability: "always"
122781
122839
  },
122782
122840
  {
122783
122841
  name: "rlm",
122784
122842
  aliases: ["rlm"],
122785
122843
  description: "registry.rlm_desc",
122786
- priority: 121,
122844
+ priority: 215,
122787
122845
  availability: "always"
122788
122846
  },
122789
122847
  {
@@ -122791,39 +122849,21 @@ const BUILTIN_SLASH_COMMANDS = [
122791
122849
  aliases: [],
122792
122850
  description: "registry.rlm_max_depth_desc",
122793
122851
  argumentHint: "[N]",
122794
- priority: 119,
122795
- availability: "always"
122796
- },
122797
- {
122798
- name: "wolfpack",
122799
- aliases: ["wp"],
122800
- description: "registry.wolfpack_desc",
122801
- priority: 122,
122852
+ priority: 214,
122802
122853
  availability: "always"
122803
122854
  },
122804
122855
  {
122805
122856
  name: "sessions",
122806
122857
  aliases: ["resume"],
122807
122858
  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
- }
122859
+ priority: 213
122820
122860
  },
122821
122861
  {
122822
122862
  name: "memory",
122823
122863
  aliases: ["memo", "mem"],
122824
122864
  description: "registry.memory_desc",
122825
122865
  argumentHint: "[query]",
122826
- priority: 120,
122866
+ priority: 212,
122827
122867
  availability: "always"
122828
122868
  },
122829
122869
  {
@@ -122831,96 +122871,61 @@ const BUILTIN_SLASH_COMMANDS = [
122831
122871
  aliases: ["know"],
122832
122872
  description: "registry.knowledge_desc",
122833
122873
  argumentHint: "[query]",
122834
- priority: 119,
122874
+ priority: 211,
122835
122875
  availability: "always"
122836
122876
  },
122837
- {
122838
- name: "new",
122839
- aliases: ["clear"],
122840
- description: "registry.new_desc",
122841
- priority: 120
122842
- },
122843
122877
  {
122844
122878
  name: "model",
122845
122879
  aliases: [],
122846
122880
  description: "registry.model_desc",
122847
122881
  argumentHint: "[alias]",
122848
- priority: 120
122882
+ priority: 210
122883
+ },
122884
+ {
122885
+ name: "new",
122886
+ aliases: ["clear"],
122887
+ description: "registry.new_desc",
122888
+ priority: 209
122849
122889
  },
122850
122890
  {
122851
122891
  name: "compact",
122852
122892
  aliases: [],
122853
122893
  description: "registry.compact_desc",
122854
- priority: 119
122894
+ priority: 208
122855
122895
  },
122856
122896
  {
122857
- name: "make-skill",
122858
- aliases: ["makeskill", "craftskill"],
122859
- description: "registry.make_skill_desc",
122860
- priority: 118,
122861
- availability: "idle-only"
122897
+ name: "fusionplan",
122898
+ aliases: ["fp"],
122899
+ description: "registry.fusionplan_desc",
122900
+ priority: 207,
122901
+ availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
122862
122902
  },
122863
122903
  {
122864
122904
  name: "plan",
122865
122905
  aliases: [],
122866
122906
  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,
122907
+ priority: 206,
122875
122908
  availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
122876
122909
  },
122877
122910
  {
122878
122911
  name: "tasks",
122879
122912
  aliases: ["task"],
122880
122913
  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,
122914
+ priority: 205,
122903
122915
  availability: "always"
122904
122916
  },
122905
122917
  {
122906
122918
  name: "btw",
122907
122919
  aliases: [],
122908
122920
  description: "registry.btw_desc",
122909
- priority: 113,
122921
+ priority: 204,
122910
122922
  availability: "always"
122911
122923
  },
122912
122924
  {
122913
122925
  name: "like",
122914
122926
  aliases: [],
122915
122927
  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,
122928
+ priority: 203,
122924
122929
  availability: "always"
122925
122930
  },
122926
122931
  {
@@ -122931,127 +122936,162 @@ const BUILTIN_SLASH_COMMANDS = [
122931
122936
  "plugins"
122932
122937
  ],
122933
122938
  description: "registry.skill_desc",
122934
- priority: 110,
122939
+ priority: 202,
122935
122940
  availability: "always"
122936
122941
  },
122937
122942
  {
122938
- name: "cc",
122943
+ name: "fork",
122939
122944
  aliases: [],
122940
- description: "registry.cc_desc",
122941
- priority: 109,
122945
+ description: "registry.fork_desc",
122946
+ priority: 201
122947
+ },
122948
+ {
122949
+ name: "title",
122950
+ aliases: ["rename"],
122951
+ description: "registry.title_desc",
122952
+ priority: 200,
122942
122953
  availability: "always"
122943
122954
  },
122944
122955
  {
122945
- name: "cc-connect",
122956
+ name: "config",
122946
122957
  aliases: [],
122947
- description: "registry.cc_connect_desc",
122948
- priority: 109,
122958
+ description: "registry.config_desc",
122959
+ priority: 199
122960
+ },
122961
+ {
122962
+ name: "help",
122963
+ aliases: ["h", "?"],
122964
+ description: "registry.help_desc",
122965
+ priority: 198,
122949
122966
  availability: "always"
122950
122967
  },
122951
122968
  {
122952
- name: "revoke",
122953
- aliases: [],
122954
- description: "registry.revoke_desc",
122955
- priority: 108,
122969
+ name: "make-skill",
122970
+ aliases: ["makeskill", "craftskill"],
122971
+ description: "registry.make_skill_desc",
122972
+ priority: 197,
122956
122973
  availability: "idle-only"
122957
122974
  },
122958
122975
  {
122959
- name: "fork",
122976
+ name: "mcp",
122960
122977
  aliases: [],
122961
- description: "registry.fork_desc",
122962
- priority: 105
122978
+ description: "registry.mcp_desc",
122979
+ priority: 196,
122980
+ availability: "always"
122963
122981
  },
122964
122982
  {
122965
- name: "title",
122966
- aliases: ["rename"],
122967
- description: "registry.title_desc",
122968
- priority: 104,
122983
+ name: "status",
122984
+ aliases: [],
122985
+ description: "registry.status_desc",
122986
+ priority: 195,
122969
122987
  availability: "always"
122970
122988
  },
122971
122989
  {
122972
- name: "config",
122990
+ name: "usage",
122973
122991
  aliases: [],
122974
- description: "registry.config_desc",
122975
- priority: 103
122992
+ description: "registry.usage_desc",
122993
+ priority: 194,
122994
+ availability: "always"
122976
122995
  },
122977
122996
  {
122978
- name: "permission",
122997
+ name: "revoke",
122979
122998
  aliases: [],
122980
- description: "registry.permission_desc",
122981
- priority: 102,
122999
+ description: "registry.revoke_desc",
123000
+ priority: 193,
123001
+ availability: "idle-only"
123002
+ },
123003
+ {
123004
+ name: "cc",
123005
+ aliases: [],
123006
+ description: "registry.cc_desc",
123007
+ priority: 192,
123008
+ availability: "always"
123009
+ },
123010
+ {
123011
+ name: "cc-connect",
123012
+ aliases: [],
123013
+ description: "registry.cc_connect_desc",
123014
+ priority: 191,
122982
123015
  availability: "always"
122983
123016
  },
122984
123017
  {
122985
123018
  name: "theme",
122986
123019
  aliases: [],
122987
123020
  description: "registry.theme_desc",
122988
- priority: 101,
123021
+ priority: 190,
122989
123022
  availability: "always"
122990
123023
  },
122991
123024
  {
122992
123025
  name: "language",
122993
123026
  aliases: ["lang"],
122994
123027
  description: "registry.language_desc",
122995
- priority: 102,
123028
+ priority: 189,
123029
+ availability: "always"
123030
+ },
123031
+ {
123032
+ name: "permission",
123033
+ aliases: [],
123034
+ description: "registry.permission_desc",
123035
+ priority: 188,
122996
123036
  availability: "always"
122997
123037
  },
122998
123038
  {
122999
123039
  name: "editor",
123000
123040
  aliases: [],
123001
123041
  description: "registry.editor_desc",
123002
- priority: 100,
123042
+ priority: 187,
123003
123043
  availability: "always"
123004
123044
  },
123005
123045
  {
123006
123046
  name: "settings",
123007
123047
  aliases: [],
123008
123048
  description: "registry.settings_desc",
123009
- priority: 99,
123049
+ priority: 186,
123010
123050
  availability: "always"
123011
123051
  },
123012
123052
  {
123013
123053
  name: "init",
123014
123054
  aliases: [],
123015
123055
  description: "registry.init_desc",
123016
- priority: 98
123056
+ priority: 185
123017
123057
  },
123018
123058
  {
123019
123059
  name: "export-md",
123020
123060
  aliases: ["export"],
123021
123061
  description: "registry.export_md_desc",
123022
- priority: 97
123062
+ priority: 184
123023
123063
  },
123024
123064
  {
123025
123065
  name: "export-debug-zip",
123026
123066
  aliases: [],
123027
123067
  description: "registry.export_debug_desc",
123028
- priority: 96
123068
+ priority: 183
123069
+ },
123070
+ {
123071
+ name: "eval",
123072
+ aliases: [],
123073
+ description: "registry.eval_desc",
123074
+ priority: 182
123029
123075
  },
123030
123076
  {
123031
123077
  name: "update",
123032
123078
  aliases: [],
123033
123079
  description: "registry.update_desc",
123034
- priority: 95,
123080
+ priority: 181,
123035
123081
  availability: "idle-only"
123036
123082
  },
123037
123083
  {
123038
123084
  name: "version",
123039
123085
  aliases: [],
123040
123086
  description: "registry.version_desc",
123041
- priority: 94,
123087
+ priority: 180,
123042
123088
  availability: "always"
123043
123089
  },
123044
123090
  {
123045
123091
  name: "logout",
123046
123092
  aliases: ["disconnect"],
123047
123093
  description: "registry.logout_desc",
123048
- priority: 93
123049
- },
123050
- {
123051
- name: "eval",
123052
- aliases: [],
123053
- description: "registry.eval_desc",
123054
- priority: 92
123094
+ priority: 179
123055
123095
  },
123056
123096
  {
123057
123097
  name: "exit",
@@ -125693,7 +125733,15 @@ const HEADING_HASH_PREFIX = /^((?:\u001B\[[0-9;]*m)*)#{1,6}[ \t]+/;
125693
125733
  * takes one formatter function per token; tokens not listed here fall back to
125694
125734
  * its DEFAULT_THEME.
125695
125735
  */
125696
- function createCodeHighlightTheme(colors) {
125736
+ /**
125737
+ * Markdown code-block highlight theme: green-dominant mapping (keyword,
125738
+ * function, built_in → primary; strings → success; numbers → warning;
125739
+ * comments → textDim). Kept distinct from the shared preview theme in
125740
+ * code-highlight-theme.ts on purpose — markdown code blocks use the green
125741
+ * primary hue, while file-preview panels use the classic blue/red/yellow
125742
+ * scheme mapped to the same palette. Both follow the active theme.
125743
+ */
125744
+ function createMarkdownCodeHighlightTheme(colors) {
125697
125745
  const keyword = chalk.hex(colors.primary);
125698
125746
  const str = chalk.hex(colors.success);
125699
125747
  const comment = chalk.hex(colors.textDim);
@@ -125743,7 +125791,7 @@ function createMarkdownTheme(colors) {
125743
125791
  const stripHash = (text) => text.replace(HEADING_HASH_PREFIX, "$1");
125744
125792
  const muted = chalk.hex(colors.textMuted);
125745
125793
  const border = chalk.hex(colors.border);
125746
- const codeTheme = createCodeHighlightTheme(colors);
125794
+ const codeTheme = createMarkdownCodeHighlightTheme(colors);
125747
125795
  return {
125748
125796
  heading: (text) => chalk.bold.hex(colors.text)(stripHash(text)),
125749
125797
  link: (text) => chalk.hex(colors.mdLink)(text),
@@ -128981,6 +129029,60 @@ var CachedContainer = class extends Container {
128981
129029
  }
128982
129030
  };
128983
129031
  //#endregion
129032
+ //#region src/tui/theme/code-highlight-theme.ts
129033
+ function createCodeHighlightTheme(colors) {
129034
+ const keyword = chalk.hex(colors.mdCodeBlock);
129035
+ const str = chalk.hex(colors.error);
129036
+ const num = chalk.hex(colors.primary);
129037
+ const fn = chalk.hex(colors.warning);
129038
+ const builtin = chalk.hex(colors.planMode);
129039
+ const cls = chalk.hex(colors.accent);
129040
+ const text = chalk.hex(colors.text);
129041
+ return {
129042
+ keyword,
129043
+ built_in: builtin,
129044
+ type: keyword,
129045
+ literal: builtin,
129046
+ number: num,
129047
+ regexp: str,
129048
+ string: str,
129049
+ subst: str,
129050
+ symbol: num,
129051
+ class: cls,
129052
+ function: fn,
129053
+ title: fn,
129054
+ params: text,
129055
+ comment: num,
129056
+ doctag: num,
129057
+ meta: chalk.hex(colors.textMuted),
129058
+ "meta-keyword": keyword,
129059
+ "meta-string": str,
129060
+ section: keyword,
129061
+ tag: keyword,
129062
+ name: fn,
129063
+ "builtin-name": builtin,
129064
+ attr: fn,
129065
+ attribute: fn,
129066
+ variable: text,
129067
+ bullet: num,
129068
+ code: str,
129069
+ emphasis: (s) => chalk.italic(s),
129070
+ strong: (s) => chalk.bold(s),
129071
+ formula: text,
129072
+ link: chalk.hex(colors.mdLink),
129073
+ quote: chalk.hex(colors.mdQuote),
129074
+ addition: chalk.hex(colors.diffAdded),
129075
+ deletion: chalk.hex(colors.diffRemoved),
129076
+ "selector-tag": keyword,
129077
+ "selector-id": cls,
129078
+ "selector-class": cls,
129079
+ "selector-attr": fn,
129080
+ "selector-pseudo": fn,
129081
+ "template-tag": str,
129082
+ "template-variable": fn
129083
+ };
129084
+ }
129085
+ //#endregion
128984
129086
  //#region src/tui/components/media/code-highlight.ts
128985
129087
  /**
128986
129088
  * Shared syntax-highlighting helpers for code previews
@@ -129018,13 +129120,14 @@ function langFromPath(filePath) {
129018
129120
  const lang = EXT_LANG_MAP[ext] ?? ext;
129019
129121
  return supportsLanguage(lang) ? lang : void 0;
129020
129122
  }
129021
- function highlightLines(code, lang) {
129123
+ function highlightLines(code, lang, colors) {
129022
129124
  const normalizedLang = lang?.trim().toLowerCase();
129023
129125
  if (!normalizedLang || !supportsLanguage(normalizedLang)) return code.split("\n");
129024
129126
  try {
129025
129127
  return highlight(code, {
129026
129128
  language: normalizedLang,
129027
- ignoreIllegals: true
129129
+ ignoreIllegals: true,
129130
+ theme: createCodeHighlightTheme(colors)
129028
129131
  }).split("\n");
129029
129132
  } catch {
129030
129133
  return code.split("\n");
@@ -129086,10 +129189,12 @@ function visualizeIndent(line) {
129086
129189
  }
129087
129190
  /**
129088
129191
  * 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.
129192
+ * `->`, spaces as `·`, dimmed) and optional syntax highlighting. Indent
129193
+ * visualization runs on the raw code FIRST, then highlighting runs on the
129194
+ * stripped remainder: once the text is ANSI-tinted (the theme's `default`
129195
+ * color wraps whitespace too), a leading tab is no longer the first raw
129196
+ * character and would be missed. The dimmed indent itself carries no token
129197
+ * color.
129093
129198
  *
129094
129199
  * When highlighting is on and produced token colors, the diff line color is
129095
129200
  * layered as the base foreground: syntax tokens override it, but every ANSI
@@ -129098,17 +129203,18 @@ function visualizeIndent(line) {
129098
129203
  * When highlighting is off (streaming) or produced no token colors, the code
129099
129204
  * part is colored with the diff line color instead.
129100
129205
  */
129101
- function renderDiffCode(code, colorFn, highlight, lang) {
129102
- const { text, indentEnd } = visualizeIndent(highlight ? highlightLines(code, lang)[0] ?? code : code);
129206
+ function renderDiffCode(code, colorFn, highlight, lang, colors) {
129207
+ const { text, indentEnd } = visualizeIndent(code);
129103
129208
  const indent = text.slice(0, indentEnd);
129104
129209
  const rest = text.slice(indentEnd);
129105
129210
  const dimIndent = indent.length > 0 ? chalk.dim(indent) : indent;
129106
- if (highlight && ANSI_RE.test(rest)) {
129211
+ const source = highlight ? highlightLines(rest, lang, colors)[0] ?? rest : rest;
129212
+ if (highlight && ANSI_RE.test(source)) {
129107
129213
  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;
129214
+ if (prefix.length > 0) return dimIndent + prefix + source.replace(ANSI_RESET_RE, (m) => m + prefix) + "\x1B[39m";
129215
+ return dimIndent + source;
129110
129216
  }
129111
- return dimIndent + colorFn(rest);
129217
+ return dimIndent + colorFn(source);
129112
129218
  }
129113
129219
  /**
129114
129220
  * Compute word-level diff between two single lines and highlight the changed
@@ -129242,7 +129348,7 @@ function renderDiffLines(oldText, newText, path, colors, isIncomplete = false, o
129242
129348
  const line = shown[i];
129243
129349
  const marker = line.kind === "add" ? "+" : "-";
129244
129350
  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));
129351
+ output.push(s.gutter(String(line.lineNum).padStart(4) + " ") + color(`${marker} `) + renderDiffCode(line.code, color, doHighlight, lang, colors));
129246
129352
  i += 1;
129247
129353
  }
129248
129354
  const hidden = changedLines.length - shown.length;
@@ -129293,11 +129399,11 @@ function buildClusters(diffLines, contextLines) {
129293
129399
  removedCount: removed
129294
129400
  };
129295
129401
  }
129296
- function formatDiffRow(line, s, doHighlight, lang) {
129402
+ function formatDiffRow(line, s, doHighlight, lang, colors) {
129297
129403
  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);
129404
+ if (line.kind === "add") return gutter + s.add("+ ") + renderDiffCode(line.code, s.add, doHighlight, lang, colors);
129405
+ if (line.kind === "delete") return gutter + s.del("- ") + renderDiffCode(line.code, s.del, doHighlight, lang, colors);
129406
+ return gutter + " " + renderDiffCode(line.code, (x) => x, doHighlight, lang, colors);
129301
129407
  }
129302
129408
  /**
129303
129409
  * Render a diff with surrounding context, eliding unchanged middle
@@ -129368,7 +129474,7 @@ function renderDiffLinesClustered(oldText, newText, path, colors, opts = {}) {
129368
129474
  i += 2;
129369
129475
  continue;
129370
129476
  }
129371
- output.push(formatDiffRow(line, s, doHighlight, lang));
129477
+ output.push(formatDiffRow(line, s, doHighlight, lang, colors));
129372
129478
  body++;
129373
129479
  if (line.kind !== "context") shownChanges++;
129374
129480
  prevEnd = i;
@@ -131251,7 +131357,7 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
131251
131357
  if (name === "Write") {
131252
131358
  const content = str(this.toolCall.args["content"]);
131253
131359
  if (content.length === 0) return;
131254
- const allLines = highlightLines(content, langFromPath(str(this.toolCall.args["file_path"] ?? this.toolCall.args["path"])));
131360
+ const allLines = highlightLines(content, langFromPath(str(this.toolCall.args["file_path"] ?? this.toolCall.args["path"])), this.colors);
131255
131361
  const writeShouldCap = !this.expanded;
131256
131362
  const shown = writeShouldCap ? allLines.slice(0, 10) : allLines;
131257
131363
  const remaining = allLines.length - shown.length;
@@ -131356,7 +131462,7 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
131356
131462
  const fragmentEnd = Math.min(streamText.length, fragmentStart + ToolCallComponent.WRITE_STREAM_TAIL_RAW_CHARS);
131357
131463
  const fragment = unescapeJsonStringValue(streamText, fragmentStart, fragmentEnd);
131358
131464
  if (fragment.length === 0) return;
131359
- const lines = highlightLines(fragment, this.writeStreamLang);
131465
+ const lines = highlightLines(fragment, this.writeStreamLang, this.colors);
131360
131466
  const displayLines = lines.slice(-10);
131361
131467
  const firstFragmentLineNo = this.writeStreamNlCount + (tailStart > contentStart ? 2 : 1);
131362
131468
  const skipped = lines.length - displayLines.length;
@@ -138164,6 +138270,8 @@ var SessionEventHandler = class {
138164
138270
  this.renderedSkillActivationIds.clear();
138165
138271
  this.renderedMcpServerStatusKeys.clear();
138166
138272
  this.stopAllMcpServerStatusSpinners();
138273
+ this.pendingSkillCandidates = [];
138274
+ this.promptedSkillCandidates.clear();
138167
138275
  this.host.setAppState({ subagentUsage: {} });
138168
138276
  }
138169
138277
  startSubscription() {
@@ -138419,6 +138527,12 @@ var SessionEventHandler = class {
138419
138527
  const todos = this.host.state.todoPanel.getTodos();
138420
138528
  if (todos.length > 0 && todos.every((todo) => todo.status === "done")) this.host.streamingUI.setTodoList([]);
138421
138529
  this.host.streamingUI.resetToolUi();
138530
+ if (this.pendingSkillCandidates.length > 0 && this.host.state.queuedMessages.length === 0) {
138531
+ const pending = this.pendingSkillCandidates;
138532
+ this.pendingSkillCandidates = [];
138533
+ const session = this.host.session;
138534
+ if (session !== void 0) for (const candidate of pending) this.promptSkillCandidate(session, candidate.name, candidate.purpose);
138535
+ }
138422
138536
  this.host.streamingUI.finalizeTurn(sendQueued);
138423
138537
  }
138424
138538
  handleStepBegin(event) {
@@ -138793,6 +138907,10 @@ var SessionEventHandler = class {
138793
138907
  }
138794
138908
  /** Candidates already prompted this session, to avoid repeating the request. */
138795
138909
  promptedSkillCandidates = /* @__PURE__ */ new Set();
138910
+ /** Skill candidates detected while a turn was still active. Flushed at the
138911
+ * next turn end so the AskUserQuestion dialog never interrupts an ongoing
138912
+ * response. */
138913
+ pendingSkillCandidates = [];
138796
138914
  /**
138797
138915
  * Handles a `skill_candidate` event emitted after compaction detected a
138798
138916
  * reusable process in the summary. Best-effort and non-fatal: if the session
@@ -138800,6 +138918,10 @@ var SessionEventHandler = class {
138800
138918
  * no confirmation. When it works, the model runs an AskUserQuestion dialog
138801
138919
  * (确定/忽略) so the user can confirm or dismiss with a single keystroke,
138802
138920
  * then generates the skill via MakeSkillPlanTool on confirmation.
138921
+ *
138922
+ * If a turn is still streaming (the compaction finished while the agent was
138923
+ * mid-response), the candidate is queued and prompted at the next turn end —
138924
+ * never interrupting the running turn.
138803
138925
  */
138804
138926
  handleSkillCandidate(event) {
138805
138927
  try {
@@ -138809,10 +138931,25 @@ var SessionEventHandler = class {
138809
138931
  this.promptedSkillCandidates.add(name);
138810
138932
  const session = this.host.session;
138811
138933
  if (session === void 0) return;
138812
- const request = `检测到可复用过程「${name}」${purpose ? `(${purpose})` : ""}。请用 AskUserQuestion 工具询问用户是否将其保存为可复用技能(选项:生成 / 忽略)。用户选择「生成」后,用 MakeSkillPlanTool 生成该技能;选择「忽略」则直接说明已跳过。`;
138813
- session.prompt(request).catch(() => {});
138934
+ if (this.host.streamingUI.hasActiveTurn()) {
138935
+ this.pendingSkillCandidates.push({
138936
+ name,
138937
+ purpose
138938
+ });
138939
+ return;
138940
+ }
138941
+ this.promptSkillCandidate(session, name, purpose);
138814
138942
  } catch {}
138815
138943
  }
138944
+ /** Fire the AskUserQuestion prompt for a skill candidate. On failure, surface
138945
+ * a status hint instead of failing silently so the user still learns the
138946
+ * candidate was detected. */
138947
+ promptSkillCandidate(session, name, purpose) {
138948
+ const request = `检测到可复用过程「${name}」${purpose ? `(${purpose})` : ""}。请用 AskUserQuestion 工具询问用户是否将其保存为可复用技能(选项:生成 / 忽略)。用户选择「生成」后,用 MakeSkillPlanTool 生成该技能;选择「忽略」则直接说明已跳过。`;
138949
+ session.prompt(request).catch(() => {
138950
+ this.host.showStatus(`检测到可复用过程「${name}」,但询问失败。可稍后手动处理。`, this.host.state.theme.colors.warning);
138951
+ });
138952
+ }
138816
138953
  finishCompaction(sendQueued) {
138817
138954
  if (!this.host.streamingUI.hasActiveTurn()) {
138818
138955
  this.host.setAppState({
@@ -145163,7 +145300,7 @@ function renderDisplayBlock(block, s, colors) {
145163
145300
  });
145164
145301
  case "file_content": {
145165
145302
  const lang = block.language ?? langFromPath(block.path);
145166
- const allLines = highlightLines(replaceTabs(block.content), lang);
145303
+ const allLines = highlightLines(replaceTabs(block.content), lang, colors);
145167
145304
  const shown = allLines.slice(0, CONTENT_SUMMARY_MAX_LINES);
145168
145305
  const lines = [s.strong(block.path)];
145169
145306
  for (const [i, line] of shown.entries()) lines.push(s.gutter(String(i + 1).padStart(4) + " ") + line);
@@ -145574,7 +145711,7 @@ function buildDiffBody(block, colors) {
145574
145711
  }
145575
145712
  function buildFileContentBody(block, colors) {
145576
145713
  const lang = block.language ?? langFromPath(block.path);
145577
- const highlighted = highlightLines(block.content, lang);
145714
+ const highlighted = highlightLines(block.content, lang, colors);
145578
145715
  const gutter = chalk.hex(colors.diffGutter);
145579
145716
  return {
145580
145717
  lines: highlighted.map((line, i) => gutter(String(i + 1).padStart(4) + " ") + line),
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-CyJPMDaK.mjs")).main();
10
10
  } catch (error) {
11
11
  process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
12
12
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.12.7",
3
+ "version": "0.12.8",
4
4
  "description": "A terminal-native AI agent for builders",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",