scream-code 0.13.8 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,8 +4,8 @@ import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
- import { $t as isTodoItemShape, A as renderDiffLinesClustered, An as CLI_COMMAND_NAME, At as ENABLE_TERMINAL_FOCUS_REPORTING, B as BackgroundAgentStatusComponent, Bn as MemoryMemoStore, Bt as TERMINAL_THEME_LIGHT, Cn as loadTuiConfig, Ct as createThemeStyles, D as toggleEmptySessionHint, Dn as getInputHistoryFile, Dt as parseOsc11BackgroundTheme, E as isTurnElapsedEnabled, En as getDataDir, Et as detectTerminalTheme, F as estimateTokens, Fn as fetchCatalog, Ft as OSC11_RESPONSE_PREFIX_NO_ESC, G as getBreathingFrame, Gn as isOrphanedToolCallError, H as AgentGroupComponent, Hn as log, Ht as isStreaming, I as getSharedSpeedTracker, In as saveCatalogCache, It as QUERY_TERMINAL_THEME, J as refineGoal, Jt as printableChar, K as resetBreathingClock, Kn as ErrorCodes, L as SkillActivationComponent, Ln as ScreamHarness, Lt as TERMINAL_FOCUS_IN, M as langFromPath, Mn as CLI_USER_AGENT_PRODUCT, Mt as OSC11_QUERY, N as CachedContainer, Nn as PRODUCT_NAME, Nt as OSC11_RESPONSE, O as ToolCallComponent, On as getLogDir, Ot as DISABLE_TERMINAL_FOCUS_REPORTING, P as ThinkingComponent, Pn as DEFAULT_CATALOG_URL, Pt as OSC11_RESPONSE_PREFIX, Qt as formatErrorMessage, R as ReadGroupComponent, Rt as TERMINAL_FOCUS_OUT, S as getDaemonInstructions, Sn as TuiLikePreferencesSchema, St as createMarkdownTheme, T as isEmptySessionHintDismissed, Tn as detectShellEnvironment, Tt as getColorPalette, U as WelcomeComponent, Un as resolveGlobalLogPath, Ut as FooterComponent, V as AssistantMessageComponent, Vn as flushDiagnosticLogs, Vt as isBusy, W as BREATHE_CYCLE_MS, Wn as isScreamError, Xt as appendStreamingArgsPreview, Yt as STATUS_BULLET, Zt as argsRecord, _ as refreshUpdateCache, _n as isExperimentalFlagEnabled, _t as clearInfoPanelState, an as EMPTY_SESSION_HINT_URL, b as readJsonlFile, bn as PULSE_WAVE_FRAMES, bt as resolveThemeSync, c as hasDispose, cn as SESSION_TIPS, ct as handlePlanCommand, d as formatMemoryMemoForInjection, dn as getCtrlDHint, en as parseStreamingArgs, et as toTerminalHyperlink, fn as getLlmNotSetMessage, g as selectUpdateTarget, gn as sortSlashCommands, gt as supportsBalance, hn as BUILTIN_SLASH_COMMANDS, ht as refreshProviderBalance, i as buildRoleAdditionalText, in as CHARS_PER_TOKEN, j as highlightLines, jn as CLI_UI_MODE, jt as ENABLE_TERMINAL_THEME_REPORTING, k as renderDiffLines, kn as detectInstallSource, kt as DISABLE_TERMINAL_THEME_REPORTING, l as isPlanExpandable, ln as TIP_ROTATION_INTERVAL_MS, mn as buildSkillSlashCommands, n as clearEvalPanelState, nn as stringValue, nt as getModelCycleLevel, on as EXIT_CONFIRM_WINDOW_MS, ot as handleFusionPlanCommand, pn as getNoActiveSessionMessage, q as clearGoalState, qn as SCREAM_ERROR_INFO, r as openUrl, rn as truncateErrorMessage, s as disposeChildren, sn as MAIN_AGENT_ID, t as dispatchInput, tn as serializeToolResultOutput, tt as changeThinkingLevel, u as MoonLoader, un as getCtrlCHint, v as readUpdateCache, vn as setExperimentalFlags, w as UserMessageComponent, wn as saveTuiConfig, wt as contrastTextHex, xn as TuiConfigParseError, xt as createEditorTheme, y as appendJsonlLine, yn as PIXEL_PULSE_FRAMES, z as parseReadGroupOutput, zn as resolveScreamHome, zt as TERMINAL_THEME_DARK } from "./dispatch-DevL-OSj.mjs";
8
- import { a as setLocale, o as t } from "./text-input-dialog-B9Klg3re.mjs";
7
+ import { $t as isTodoItemShape, A as renderDiffLinesClustered, An as CLI_COMMAND_NAME, At as ENABLE_TERMINAL_FOCUS_REPORTING, B as BackgroundAgentStatusComponent, Bn as MemoryMemoStore, Bt as TERMINAL_THEME_LIGHT, Cn as loadTuiConfig, Ct as createThemeStyles, D as toggleEmptySessionHint, Dn as getInputHistoryFile, Dt as parseOsc11BackgroundTheme, E as isTurnElapsedEnabled, En as getDataDir, Et as detectTerminalTheme, F as estimateTokens, Fn as fetchCatalog, Ft as OSC11_RESPONSE_PREFIX_NO_ESC, G as getBreathingFrame, Gn as isOrphanedToolCallError, H as AgentGroupComponent, Hn as log, Ht as isStreaming, I as getSharedSpeedTracker, In as saveCatalogCache, It as QUERY_TERMINAL_THEME, J as refineGoal, Jt as printableChar, K as resetBreathingClock, Kn as ErrorCodes, L as SkillActivationComponent, Ln as ScreamHarness, Lt as TERMINAL_FOCUS_IN, M as langFromPath, Mn as CLI_USER_AGENT_PRODUCT, Mt as OSC11_QUERY, N as CachedContainer, Nn as PRODUCT_NAME, Nt as OSC11_RESPONSE, O as ToolCallComponent, On as getLogDir, Ot as DISABLE_TERMINAL_FOCUS_REPORTING, P as ThinkingComponent, Pn as DEFAULT_CATALOG_URL, Pt as OSC11_RESPONSE_PREFIX, Qt as formatErrorMessage, R as ReadGroupComponent, Rt as TERMINAL_FOCUS_OUT, S as getDaemonInstructions, Sn as TuiLikePreferencesSchema, St as createMarkdownTheme, T as isEmptySessionHintDismissed, Tn as detectShellEnvironment, Tt as getColorPalette, U as WelcomeComponent, Un as resolveGlobalLogPath, Ut as FooterComponent, V as AssistantMessageComponent, Vn as flushDiagnosticLogs, Vt as isBusy, W as BREATHE_CYCLE_MS, Wn as isScreamError, Xt as appendStreamingArgsPreview, Yt as STATUS_BULLET, Zt as argsRecord, _ as refreshUpdateCache, _n as isExperimentalFlagEnabled, _t as clearInfoPanelState, an as EMPTY_SESSION_HINT_URL, b as readJsonlFile, bn as PULSE_WAVE_FRAMES, bt as resolveThemeSync, c as hasDispose, cn as SESSION_TIPS, ct as handlePlanCommand, d as formatMemoryMemoForInjection, dn as getCtrlDHint, en as parseStreamingArgs, et as toTerminalHyperlink, fn as getLlmNotSetMessage, g as selectUpdateTarget, gn as sortSlashCommands, gt as supportsBalance, hn as BUILTIN_SLASH_COMMANDS, ht as refreshProviderBalance, i as buildRoleAdditionalText, in as CHARS_PER_TOKEN, j as highlightLines, jn as CLI_UI_MODE, jt as ENABLE_TERMINAL_THEME_REPORTING, k as renderDiffLines, kn as detectInstallSource, kt as DISABLE_TERMINAL_THEME_REPORTING, l as isPlanExpandable, ln as TIP_ROTATION_INTERVAL_MS, mn as buildSkillSlashCommands, n as clearEvalPanelState, nn as stringValue, nt as getModelCycleLevel, on as EXIT_CONFIRM_WINDOW_MS, ot as handleFusionPlanCommand, pn as getNoActiveSessionMessage, q as clearGoalState, qn as SCREAM_ERROR_INFO, r as openUrl, rn as truncateErrorMessage, s as disposeChildren, sn as MAIN_AGENT_ID, t as dispatchInput, tn as serializeToolResultOutput, tt as changeThinkingLevel, u as MoonLoader, un as getCtrlCHint, v as readUpdateCache, vn as setExperimentalFlags, w as UserMessageComponent, wn as saveTuiConfig, wt as contrastTextHex, xn as TuiConfigParseError, xt as createEditorTheme, y as appendJsonlLine, yn as PIXEL_PULSE_FRAMES, z as parseReadGroupOutput, zn as resolveScreamHome, zt as TERMINAL_THEME_DARK } from "./dispatch-202xtik3.mjs";
8
+ import { a as setLocale, o as t } from "./text-input-dialog-BzVVVncg.mjs";
9
9
  import { createRequire } from "node:module";
10
10
  import { randomUUID } from "node:crypto";
11
11
  import { access, mkdir, mkdtemp, readFile, readdir, realpath, rm, unlink, writeFile } from "node:fs/promises";
@@ -30,7 +30,7 @@ function optionalBuildString(value) {
30
30
  return typeof value === "string" && value.length > 0 ? value : void 0;
31
31
  }
32
32
  const SCREAM_BUILD_INFO = {
33
- version: optionalBuildString("0.13.8"),
33
+ version: optionalBuildString("0.14.0"),
34
34
  channel: optionalBuildString(""),
35
35
  commit: optionalBuildString(""),
36
36
  buildTarget: optionalBuildString("darwin-arm64")
@@ -2909,9 +2909,9 @@ var SessionEventHandler = class {
2909
2909
  const { name, purpose } = event.candidate;
2910
2910
  if (name.length === 0) return;
2911
2911
  if (this.promptedSkillCandidates.has(name)) return;
2912
- this.promptedSkillCandidates.add(name);
2913
2912
  const session = this.host.session;
2914
2913
  if (session === void 0) return;
2914
+ this.promptedSkillCandidates.add(name);
2915
2915
  if (this.host.streamingUI.hasActiveTurn()) {
2916
2916
  this.pendingSkillCandidates.push({
2917
2917
  name,
@@ -12192,7 +12192,7 @@ var ScreamTUI = class {
12192
12192
  const card = new OnboardingCardComponent({
12193
12193
  colors: this.state.theme.colors,
12194
12194
  onConfigure: () => {
12195
- import("./dispatch-D5doFUQU.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
12195
+ import("./dispatch-D71h_08g.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
12196
12196
  this.restoreEditor();
12197
12197
  });
12198
12198
  },
@@ -13237,6 +13237,44 @@ function mapCcConnectMode(mode) {
13237
13237
  };
13238
13238
  }
13239
13239
  }
13240
+ /**
13241
+ * Install the stdout EPIPE guard and return the line writer handed to
13242
+ * {@link ClaudeStreamJsonWriter}.
13243
+ *
13244
+ * cc-connect closes our stdout when it kills/restarts the agent (idle
13245
+ * timeout, /restart, config change). Writing after that raises EPIPE as an
13246
+ * unhandled error and crashes the process. The pipe is gone — results can
13247
+ * no longer be delivered — so exit quietly instead of burning tokens as a
13248
+ * zombie waiting for a signal that may never come.
13249
+ *
13250
+ * Both the `error` event on stdout and a throw from `stdout.write` are
13251
+ * covered: EPIPE → process.exit(0); any other error is surfaced (rethrown or
13252
+ * logged) so real I/O failures are not masked.
13253
+ */
13254
+ function installStdoutEpipeGuard() {
13255
+ const handleStdoutClosed = () => {
13256
+ log.debug("stream-json: stdout closed (EPIPE), exiting");
13257
+ process.exit(0);
13258
+ };
13259
+ process.stdout.on("error", (error) => {
13260
+ if (error.code === "EPIPE") {
13261
+ handleStdoutClosed();
13262
+ return;
13263
+ }
13264
+ log.error("stream-json: stdout error", { error: String(error) });
13265
+ });
13266
+ return (line) => {
13267
+ try {
13268
+ process.stdout.write(`${line}\n`);
13269
+ } catch (error) {
13270
+ if (error.code === "EPIPE") {
13271
+ handleStdoutClosed();
13272
+ return;
13273
+ }
13274
+ throw error;
13275
+ }
13276
+ };
13277
+ }
13240
13278
  async function runStreamJson(opts) {
13241
13279
  const homeDir = resolveScreamHome();
13242
13280
  const workDir = opts.workDir ?? process.cwd();
@@ -13257,9 +13295,7 @@ async function runStreamJson(opts) {
13257
13295
  }
13258
13296
  const streamSubagentModels = streamTuiConfig.subagentModels;
13259
13297
  harness.setSubagentModelBindings(() => streamSubagentModels);
13260
- const writer = new ClaudeStreamJsonWriter((line) => {
13261
- process.stdout.write(`${line}\n`);
13262
- });
13298
+ const writer = new ClaudeStreamJsonWriter(installStdoutEpipeGuard());
13263
13299
  let session;
13264
13300
  let currentSessionId;
13265
13301
  let sessionKey = "cc-connect-main";
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } from "./chunk-D90kvbyJ.mjs";
7
7
  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";
8
8
  import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
9
- import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-B9Klg3re.mjs";
9
+ import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-BzVVVncg.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
12
12
  import * as fs$1 from "node:fs/promises";
@@ -64654,7 +64654,23 @@ function renderPrompt(template, vars) {
64654
64654
  }
64655
64655
  //#endregion
64656
64656
  //#region ../../packages/agent-core/src/tools/builtin/collaboration/skill-tool.md
64657
- var skill_tool_default = "Invoke a registered skill from the current skill listing. BLOCKING REQUIREMENT: when a skill from the listing matches the user's request, you MUST call this tool (not free-form text). Do NOT call the same skill repeatedly inside one turn — recursive depth is capped at {{ MAX_SKILL_QUERY_DEPTH }}.";
64657
+ var skill_tool_default = "Invoke a registered skill from the current skill listing. BLOCKING REQUIREMENT: when a skill from the listing matches the user's request, you MUST call this tool (not free-form text). Do NOT call the same skill repeatedly inside one turn — recursive depth is capped at {{ MAX_SKILL_QUERY_DEPTH }}.\n\n## Currently model-invocable skills\n{{ AVAILABLE_SKILLS }}\nThis list is a construction-time snapshot; for the live catalog inspect the skill registry.";
64658
+ /** Max skills to list inline in the tool description; rest are summarized. */
64659
+ const MAX_INLINE_SKILLS = 15;
64660
+ /** Max description length per skill line. */
64661
+ const MAX_SKILL_DESC_CHARS = 100;
64662
+ function buildSkillToolDescription(agent) {
64663
+ const skills = agent.skills?.registry.listInvocableSkills() ?? [];
64664
+ const lines = skills.slice(0, MAX_INLINE_SKILLS).map((s) => {
64665
+ const desc = s.description.trim().replaceAll(/\n/g, " ").slice(0, MAX_SKILL_DESC_CHARS);
64666
+ return `- ${s.name}: ${desc}`;
64667
+ });
64668
+ if (skills.length > MAX_INLINE_SKILLS) lines.push(`…${String(skills.length - MAX_INLINE_SKILLS)} more (see skill registry)`);
64669
+ return renderPrompt(skill_tool_default, {
64670
+ MAX_SKILL_QUERY_DEPTH: 3,
64671
+ AVAILABLE_SKILLS: lines.length > 0 ? lines.join("\n") : "(none currently registered)"
64672
+ });
64673
+ }
64658
64674
  var NestedSkillTooDeepError = class extends Error {
64659
64675
  skillName;
64660
64676
  depth;
@@ -64674,11 +64690,12 @@ var SkillTool = class SkillTool {
64674
64690
  agent;
64675
64691
  options;
64676
64692
  name = "Skill";
64677
- description = renderPrompt(skill_tool_default, { MAX_SKILL_QUERY_DEPTH: 3 });
64693
+ description;
64678
64694
  parameters = toInputJsonSchema(SkillToolInputSchema);
64679
64695
  constructor(agent, options = {}) {
64680
64696
  this.agent = agent;
64681
64697
  this.options = options;
64698
+ this.description = buildSkillToolDescription(agent);
64682
64699
  }
64683
64700
  resolveExecution(args) {
64684
64701
  return {
@@ -76706,10 +76723,10 @@ function stripContextMetadata(message) {
76706
76723
  }
76707
76724
  //#endregion
76708
76725
  //#region ../../packages/agent-core/src/agent/compaction/compaction-instruction.md
76709
- 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";
76726
+ 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) after the summary — 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 after the summary (the skill-candidate marker line described below comes after all memory-memo blocks):\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). Then output exactly\none marker as the very LAST line of your response — after all memory-memo\nblocks:\n\n- If a process is genuinely worth capturing as a reusable skill (reusable\n beyond this one task do not emit for one-off actions; the evidence must\n be concrete and verifiable: exact file paths, commands, or step sequences\n from the conversation, e.g. \"run `pnpm vitest run -t compaction` in\n packages/agent-core to reproduce the compaction snapshots\". Vague evidence\n like \"used a script\" is not acceptable):\n\n[[skill-candidate: <name>|<one-line purpose>|<evidence>]]\n\n- If none exists, output instead:\n\n[[skill-candidate: none]]\n\nThis marker line is MANDATORY never omit it, and output at most one\ncandidate marker.\n";
76710
76727
  //#endregion
76711
76728
  //#region ../../packages/agent-core/src/agent/compaction/compaction-update-instruction.md
76712
- 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";
76729
+ 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) after the updated summary — 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 after the updated summary (the skill-candidate marker line described below comes after all memory-memo blocks):\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<!-- Skill Candidate Detection (after memory-memo) -->\n\n## Skill candidates\n\nAFTER the memory-memo block(s), scan ONLY the new messages being compacted\n(not the content already covered by the previous summary — candidates from\nolder messages were already considered) for reusable processes\n(project-specific build steps, recurring debugging patterns, or tool\nworkflows). Then output exactly one marker as the very LAST line of your\nresponse — after all memory-memo blocks:\n\n- If a process is genuinely worth capturing as a reusable skill (reusable\n beyond this one task — do not emit for one-off actions; the evidence must\n be concrete and verifiable: exact file paths, commands, or step sequences\n from the new messages):\n\n[[skill-candidate: <name>|<one-line purpose>|<evidence>]]\n\n- If none exists, output instead:\n\n[[skill-candidate: none]]\n\nThis marker line is MANDATORY — never omit it, and output at most one\ncandidate marker.\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";
76713
76730
  //#endregion
76714
76731
  //#region ../../packages/agent-core/src/agent/compaction/render-messages.ts
76715
76732
  const TOOL_RESULT_MAX_CHARS = 2e3;
@@ -77157,7 +77174,7 @@ var FullCompaction = class {
77157
77174
  blockedByTurn: false
77158
77175
  };
77159
77176
  this.compacting = active;
77160
- active.promise = this.compactionWorker(abortController.signal, data, compactedCount);
77177
+ active.promise = this.compactionWorker(abortController.signal, data, compactedCount, active);
77161
77178
  }
77162
77179
  cancel() {
77163
77180
  this.markCanceled();
@@ -77186,6 +77203,13 @@ var FullCompaction = class {
77186
77203
  this.lowWaterMark = 0;
77187
77204
  this.reactiveAttempted = false;
77188
77205
  }
77206
+ /** The low-water mark is measured against the model's context window, so a
77207
+ * model switch (different max_context_tokens) invalidates it. Without a
77208
+ * reset, a stale mark from a large model can mask the overflow threshold
77209
+ * of a smaller model and suppress compaction until the provider overflows. */
77210
+ resetLowWaterMark() {
77211
+ this.lowWaterMark = 0;
77212
+ }
77189
77213
  async handleOverflowError(signal, error) {
77190
77214
  if (this.reactiveAttempted) throw error;
77191
77215
  if (!this.beginAutoCompaction(false) && !this.compacting) {
@@ -77197,6 +77221,12 @@ var FullCompaction = class {
77197
77221
  throw error;
77198
77222
  }
77199
77223
  this.reactiveAttempted = true;
77224
+ if (this.compacting) try {
77225
+ await this.block(signal);
77226
+ } catch (blockError) {
77227
+ if (signal.aborted || isAbortError$1(blockError)) throw blockError;
77228
+ throw error;
77229
+ }
77200
77230
  }
77201
77231
  async beforeStep(signal) {
77202
77232
  this.agent.microCompaction.detect();
@@ -77246,13 +77276,14 @@ var FullCompaction = class {
77246
77276
  async block(signal) {
77247
77277
  const active = this.compacting;
77248
77278
  if (!active) return;
77279
+ signal.throwIfAborted();
77249
77280
  active.blockedByTurn = true;
77250
77281
  const timeoutId = setTimeout(() => {
77251
77282
  if (this.compacting === active) {
77252
77283
  this.compactionTimedOut = true;
77253
- this.markCanceled("压缩超时(60秒),已取消。请使用 /compact 手动重试。");
77284
+ this.markCanceled("压缩超时(120秒),已取消。请使用 /compact 手动重试。");
77254
77285
  }
77255
- }, 6e4);
77286
+ }, 12e4);
77256
77287
  const onAbort = () => {
77257
77288
  clearTimeout(timeoutId);
77258
77289
  if (this.compacting === active) this.cancel();
@@ -77269,7 +77300,7 @@ var FullCompaction = class {
77269
77300
  signal.removeEventListener("abort", onAbort);
77270
77301
  }
77271
77302
  }
77272
- async compactionWorker(signal, data, compactedCount) {
77303
+ async compactionWorker(signal, data, compactedCount, owner) {
77273
77304
  const originalHistory = [...this.agent.context.history];
77274
77305
  const systemPromptTokens = estimateTokens$1(this.agent.getRuntimeSystemPrompt());
77275
77306
  const toolTokens = estimateTokensForTools(this.agent.tools.loopTools);
@@ -77309,24 +77340,27 @@ var FullCompaction = class {
77309
77340
  } catch (error) {
77310
77341
  if (error instanceof APIContextOverflowError || error instanceof TruncatedError) {
77311
77342
  const reduced = this.strategy.reduceCompactOnOverflow(messagesToCompact);
77312
- if (reduced < compactedCount) compactedCount = reduced;
77313
- else {
77314
- this.agent.log.warn("compaction overflow at minimum split, falling back to re-summarize", {
77315
- compactedCount,
77316
- tokensBefore: estimateTokensForMessages(messagesToCompact)
77317
- });
77318
- const result = await summarizeWithFallback(messagesToCompact, summarizeOnce);
77319
- summary = result.summary;
77320
- usage = result.usage;
77321
- break;
77343
+ if (reduced < compactedCount) {
77344
+ compactedCount = reduced;
77345
+ continue;
77322
77346
  }
77323
- } else if (!isRetryableGenerateError(error)) throw error;
77347
+ this.agent.log.warn("compaction overflow at minimum split, falling back to re-summarize", {
77348
+ compactedCount,
77349
+ tokensBefore: estimateTokensForMessages(messagesToCompact)
77350
+ });
77351
+ const result = await summarizeWithFallback(messagesToCompact, summarizeOnce);
77352
+ summary = result.summary;
77353
+ usage = result.usage;
77354
+ break;
77355
+ }
77356
+ if (!isRetryableGenerateError(error)) throw error;
77324
77357
  if (retryCount + 1 >= 5) throw error;
77325
- await sleepForRetry(delays[retryCount], signal);
77358
+ await sleepForRetry(computeDelayMs(error, delays, retryCount + 1), signal);
77326
77359
  retryCount += 1;
77327
77360
  }
77328
77361
  }
77329
77362
  if (usage !== null) this.agent.usage.record(model, usage);
77363
+ if (this.compacting !== owner) return void 0;
77330
77364
  const newHistory = this.agent.context.history;
77331
77365
  for (let i = 0; i < originalHistory.length; i++) if (newHistory[i] !== originalHistory[i]) {
77332
77366
  this.markCanceled("上下文已被更改(如 /revoke),压缩已取消");
@@ -77341,6 +77375,7 @@ var FullCompaction = class {
77341
77375
  } else this.lastCompactionFiles = void 0;
77342
77376
  for (const msg of messagesToCompactForOps) extractFileOpsFromMessage(msg, fileOps);
77343
77377
  const toolCallHistory = formatToolCallHistory(messagesToCompactForOps);
77378
+ const skillCandidateSummary = summary;
77344
77379
  const processedSummary = this.postProcessSummary(summary, fileOps, toolCallHistory, compactedCount);
77345
77380
  const tokensAfter = systemPromptTokens + toolTokens + estimateTokens$1(processedSummary) + estimateTokensForMessages(recent);
77346
77381
  const fileLists = computeFileLists(fileOps);
@@ -77366,20 +77401,23 @@ var FullCompaction = class {
77366
77401
  this.lowWaterMark = Math.floor(this.effectiveTokenCount * 1.1);
77367
77402
  await this.extractAndStoreMemos(processedSummary, messagesToCompactForOps);
77368
77403
  this.triggerPostCompactHook(data, result);
77369
- this.detectSkillCandidates(processedSummary);
77404
+ this.detectSkillCandidates(skillCandidateSummary);
77370
77405
  this.consecutiveCompactionFailures = 0;
77371
77406
  this._shouldInjectSessionSummary = true;
77372
77407
  } catch (error) {
77373
- const wasTimedOut = this.compactionTimedOut;
77374
- this.compactionTimedOut = false;
77408
+ const stale = this.compacting !== null && this.compacting !== owner;
77409
+ const wasTimedOut = stale ? false : this.compactionTimedOut;
77410
+ if (!stale) this.compactionTimedOut = false;
77375
77411
  if (!isAbortError$1(error) || wasTimedOut) {
77376
- const blockedByTurn = this.compacting?.blockedByTurn === true;
77377
- this.consecutiveCompactionFailures += 1;
77378
- if (this.consecutiveCompactionFailures >= MAX_CONSECUTIVE_FAILURES) this.agent.emitEvent({
77379
- type: "warning",
77380
- message: `压缩连续失败 ${String(this.consecutiveCompactionFailures)} 次,已自动暂停本回合的自动压缩。使用 /compact 手动重试。`,
77381
- code: "compaction_circuit_open"
77382
- });
77412
+ const blockedByTurn = stale || owner.blockedByTurn === true;
77413
+ if (!stale) {
77414
+ this.consecutiveCompactionFailures += 1;
77415
+ if (this.consecutiveCompactionFailures >= MAX_CONSECUTIVE_FAILURES) this.agent.emitEvent({
77416
+ type: "warning",
77417
+ message: `压缩连续失败 ${String(this.consecutiveCompactionFailures)} 次,已自动暂停本回合的自动压缩。使用 /compact 手动重试。`,
77418
+ code: "compaction_circuit_open"
77419
+ });
77420
+ }
77383
77421
  this.agent.log.error("compaction failed", {
77384
77422
  code: isScreamError(error) ? error.code : void 0,
77385
77423
  error,
@@ -77388,7 +77426,7 @@ var FullCompaction = class {
77388
77426
  compactedCount,
77389
77427
  tokensBefore
77390
77428
  });
77391
- this.markCanceled();
77429
+ if (!stale) this.markCanceled();
77392
77430
  if (!blockedByTurn) {
77393
77431
  const details = {
77394
77432
  model,
@@ -77400,7 +77438,7 @@ var FullCompaction = class {
77400
77438
  ...payload
77401
77439
  });
77402
77440
  }
77403
- if (blockedByTurn) {
77441
+ if (blockedByTurn && !stale) {
77404
77442
  if (isScreamError(error) && error.code === ErrorCodes.AUTH_LOGIN_REQUIRED) throw error;
77405
77443
  throw new ScreamError(ErrorCodes.COMPACTION_FAILED, String(error), { cause: error });
77406
77444
  }
@@ -77491,27 +77529,41 @@ var FullCompaction = class {
77491
77529
  }
77492
77530
  /**
77493
77531
  * Detects [[skill-candidate: <name>|<purpose>|<evidence>]] markers in the
77494
- * compaction summary and emits a `skill_candidate` event for each one (at
77495
- * most one per compact). Best-effort and fully isolated: a parse failure or
77496
- * a missing marker never affects compaction or memory extraction — this
77497
- * method only reads the summary and emits an event.
77532
+ * compaction summary and emits a `skill_candidate` event (at most one per
77533
+ * compact). Best-effort and fully isolated: a parse failure or a missing
77534
+ * marker never affects compaction or memory extraction — this method only
77535
+ * reads the summary and emits an event.
77536
+ *
77537
+ * Tolerant parsing: the evidence segment may contain `|` characters and
77538
+ * newlines, and the explicit `[[skill-candidate: none]]` marker (emitted
77539
+ * when the model found nothing) is recognized and skipped. When several
77540
+ * markers appear (e.g. an old one carried over into an update summary),
77541
+ * the LAST valid candidate wins — the templates mandate the marker as the
77542
+ * final line of the response, so the trailing one is the model's verdict
77543
+ * for this round.
77498
77544
  */
77499
77545
  detectSkillCandidates(summary) {
77500
77546
  try {
77501
- const matches = summary.matchAll(/\[\[skill-candidate:\s*([^|\]]+)\|([^|\]]+)\|([^|\]]+)\]\]/g);
77547
+ const matches = summary.matchAll(/\[\[skill-candidate:\s*([^|\]]+?)(?:\|([^|\]]*)\|([\s\S]*?))?\]\]/g);
77548
+ let chosen;
77502
77549
  for (const m of matches) {
77503
- const name = m[1].trim();
77550
+ const name = m[1]?.trim() ?? "";
77504
77551
  if (name.length === 0) continue;
77505
- this.agent.emitEvent({
77506
- type: "skill_candidate",
77507
- candidate: {
77508
- name,
77509
- purpose: m[2].trim(),
77510
- evidence: m[3].trim()
77511
- }
77512
- });
77513
- break;
77552
+ if (name.toLowerCase() === "none") {
77553
+ chosen = void 0;
77554
+ continue;
77555
+ }
77556
+ if (m[2] === void 0) continue;
77557
+ chosen = {
77558
+ name,
77559
+ purpose: m[2].trim(),
77560
+ evidence: (m[3] ?? "").trim()
77561
+ };
77514
77562
  }
77563
+ if (chosen !== void 0) this.agent.emitEvent({
77564
+ type: "skill_candidate",
77565
+ candidate: chosen
77566
+ });
77515
77567
  } catch (error) {
77516
77568
  this.agent.log.warn("Skill candidate detection failed", { error: String(error) });
77517
77569
  }
@@ -77529,7 +77581,7 @@ var FullCompaction = class {
77529
77581
  */
77530
77582
  postProcessSummary(summary, fileOps, toolCallHistory, compactedCount) {
77531
77583
  const todos = this.agent.tools.storeData()["todo"] ?? [];
77532
- 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();
77584
+ const base = summary.trim().replaceAll(/<files>[\s\S]*?<\/files>\s*/g, "").replaceAll(/\[\[skill-candidate:[\s\S]*?\]\]\s*/g, "").replace(/^> The conversation before this point was compacted:[\s\S]*?continue from here\.\s*\n+/m, "").trimEnd();
77533
77585
  const sections = [];
77534
77586
  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.`);
77535
77587
  if (base.length > 0) sections.push(base);
@@ -78855,7 +78907,10 @@ var ConfigState = class {
78855
78907
  this._cwd = changed.cwd;
78856
78908
  this.agent.jian.chdir(changed.cwd);
78857
78909
  }
78858
- if (changed.modelAlias) this._modelAlias = changed.modelAlias;
78910
+ if (changed.modelAlias && changed.modelAlias !== this._modelAlias) {
78911
+ this._modelAlias = changed.modelAlias;
78912
+ this.agent.fullCompaction.resetLowWaterMark();
78913
+ }
78859
78914
  if (changed.profileName) this._profileName = changed.profileName;
78860
78915
  if (changed.thinkingLevel !== void 0) this._thinkingLevel = resolveThinkingEffort(changed.thinkingLevel, this.agent.screamConfig?.thinking);
78861
78916
  if (changed.systemPrompt !== void 0) this._systemPrompt = changed.systemPrompt;
@@ -129417,7 +129472,7 @@ async function guidedGoalSetup(host) {
129417
129472
  host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
129418
129473
  return;
129419
129474
  }
129420
- const { TextInputDialogComponent } = await import("./text-input-dialog-Bnu3s0gP.mjs");
129475
+ const { TextInputDialogComponent } = await import("./text-input-dialog-DVWsE7Gh.mjs");
129421
129476
  const initialDesc = await promptText(host, TextInputDialogComponent, {
129422
129477
  title: t("goal.setup_title_initial"),
129423
129478
  subtitle: t("goal.setup_desc_hint"),
@@ -129438,7 +129493,7 @@ async function guidedGoalSetup(host) {
129438
129493
  await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
129439
129494
  }
129440
129495
  async function showGoalConfigWizard(host, session, objective, replace) {
129441
- const { TextInputDialogComponent } = await import("./text-input-dialog-Bnu3s0gP.mjs");
129496
+ const { TextInputDialogComponent } = await import("./text-input-dialog-DVWsE7Gh.mjs");
129442
129497
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
129443
129498
  title: t("goal.wizard_title", { objective }),
129444
129499
  subtitle: t("goal.budget_turns_hint"),
@@ -131524,16 +131579,16 @@ const renderTruncated = (_toolCall, result, ctx) => {
131524
131579
  var ShellExecutionComponent = class extends Container {
131525
131580
  constructor(options) {
131526
131581
  super();
131527
- if (options.showCommand === true) this.addCommandPreview(options.command ?? "", options.commandPreviewLines);
131582
+ if (options.showCommand === true) this.addCommandPreview(options.command ?? "", options.commandPreviewLines, options.colors);
131528
131583
  if (options.result !== void 0) this.addResultPreview(options.result, options.colors, options.expanded ?? false, options.resultPreviewLines ?? 15);
131529
131584
  }
131530
- addCommandPreview(command, previewLines) {
131585
+ addCommandPreview(command, previewLines, colors) {
131531
131586
  if (command.length === 0) return;
131532
- const allLines = command.split("\n");
131587
+ const allLines = highlightLines(command, "bash", colors);
131533
131588
  const lines = previewLines === void 0 ? allLines : allLines.slice(0, previewLines);
131534
131589
  for (const [i, line] of lines.entries()) {
131535
131590
  const prefix = i === 0 ? "$ " : " ";
131536
- this.addChild(new Text(chalk.dim(prefix + line), 2, 0));
131591
+ this.addChild(new Text(`${chalk.dim(prefix)}${line}`, 2, 0));
131537
131592
  }
131538
131593
  }
131539
131594
  addResultPreview(result, colors, expanded, previewLines) {
@@ -131554,8 +131609,8 @@ const shellExecutionResultRenderer = (toolCall, result, ctx) => [new ShellExecut
131554
131609
  result,
131555
131610
  colors: ctx.colors,
131556
131611
  expanded: ctx.expanded,
131557
- showCommand: ctx.expanded,
131558
- commandPreviewLines: void 0
131612
+ showCommand: true,
131613
+ commandPreviewLines: ctx.expanded ? void 0 : 3
131559
131614
  })];
131560
131615
  //#endregion
131561
131616
  //#region src/tui/components/media/image-thumbnail.ts
@@ -131834,6 +131889,26 @@ function pickChip(toolName) {
131834
131889
  return REGISTRY[toolName];
131835
131890
  }
131836
131891
  //#endregion
131892
+ //#region src/tui/components/messages/tool-renderers/glance-lines.ts
131893
+ /**
131894
+ * Renders pre-composed glance lines (ANSI-colored) with hard per-line
131895
+ * truncation to the actual render width. Unlike `Text`, long lines never
131896
+ * wrap — each logical line always occupies exactly one visual row, so
131897
+ * column-aligned samples (Grep matches) never break their alignment on
131898
+ * narrow terminals.
131899
+ */
131900
+ var GlanceLinesComponent = class {
131901
+ lines;
131902
+ constructor(lines) {
131903
+ this.lines = lines;
131904
+ }
131905
+ invalidate() {}
131906
+ render(width) {
131907
+ const safeWidth = Math.max(width, 1);
131908
+ return this.lines.map((line) => truncateToWidth(` ${line}`, safeWidth, "…"));
131909
+ }
131910
+ };
131911
+ //#endregion
131837
131912
  //#region src/tui/components/messages/tool-renderers/summary.ts
131838
131913
  const GLANCE_SAMPLES = 3;
131839
131914
  const MAX_EXTENSION_COUNTS = 4;
@@ -131843,11 +131918,13 @@ function withGlance(glance) {
131843
131918
  if (result.is_error) return renderTruncated(toolCall, result, ctx);
131844
131919
  const out = [];
131845
131920
  if (glance !== null) {
131846
- const line = glance(toolCall, result, ctx.colors);
131847
- if (line.length > 0) {
131848
- const indented = line.split("\n").map((l) => ` ${l}`).join("\n");
131849
- out.push(new Text(indented, 0, 0));
131850
- }
131921
+ const glanceOut = glance(toolCall, result, ctx.colors);
131922
+ if (typeof glanceOut === "string") {
131923
+ if (glanceOut.length > 0) {
131924
+ const indented = glanceOut.split("\n").map((l) => ` ${l}`).join("\n");
131925
+ out.push(new Text(indented, 0, 0));
131926
+ }
131927
+ } else out.push(glanceOut);
131851
131928
  }
131852
131929
  if (ctx.expanded && result.output.length > 0) out.push(new Text(chalk.dim(result.output), 4, 0));
131853
131930
  return out;
@@ -131870,29 +131947,35 @@ function readSearchResultsMatches(result) {
131870
131947
  if (display.kind !== "search_results") return void 0;
131871
131948
  return display.matches;
131872
131949
  }
131873
- function truncateMatchText(text, max = 80) {
131874
- if (text.length <= max) return text;
131875
- return `${text.slice(0, max - 1)}…`;
131950
+ function styleFilePath(path) {
131951
+ const idx = path.lastIndexOf("/");
131952
+ if (idx < 0) return path;
131953
+ return `${chalk.dim(path.slice(0, idx + 1))}${path.slice(idx + 1)}`;
131876
131954
  }
131877
131955
  const grepGlance = (_toolCall, result, colors) => {
131878
131956
  const matches = readSearchResultsMatches(result);
131879
131957
  if (matches !== void 0 && matches.length > 0) {
131880
- const fileColor = chalk.hex(colors.roleTool);
131881
- const lines = matches.slice(0, GLANCE_SAMPLES).map((m) => {
131882
- return `${m.line > 0 ? `${fileColor(m.file)}${chalk.dim(":")}${chalk.hex(colors.primary)(String(m.line))}` : fileColor(m.file)}${chalk.dim(` ${truncateMatchText(m.text.trim())}`)}`;
131958
+ const samples = matches.slice(0, GLANCE_SAMPLES);
131959
+ const plainLocs = samples.map((m) => m.line > 0 ? `${m.file}:${String(m.line)}` : m.file);
131960
+ const colWidth = Math.max(...plainLocs.map((s) => visibleWidth(s)));
131961
+ const lineNoColor = chalk.hex(colors.roleTool);
131962
+ const lines = samples.map((m, i) => {
131963
+ const loc = m.line > 0 ? `${styleFilePath(m.file)}${chalk.dim(":")}${lineNoColor(String(m.line))}` : styleFilePath(m.file);
131964
+ const pad = " ".repeat(Math.max(0, colWidth - visibleWidth(plainLocs[i] ?? "")));
131965
+ const text = m.text.trim();
131966
+ return text.length > 0 ? `${loc}${pad} ${chalk.dim(text)}` : `${loc}${pad}`;
131883
131967
  });
131884
131968
  const remaining = matches.length - GLANCE_SAMPLES;
131885
131969
  if (remaining > 0) lines.push(chalk.dim(`+${String(remaining)} more`));
131886
- return lines.join("\n");
131970
+ return new GlanceLinesComponent(lines);
131887
131971
  }
131888
131972
  const lines = nonEmptyLines(result.output);
131889
131973
  if (lines.length === 0) return "";
131890
131974
  const samples = lines.slice(0, GLANCE_SAMPLES).map(pathFromGrepLine);
131891
131975
  const remaining = lines.length - samples.length;
131892
- const fileColor = chalk.hex(colors.roleTool);
131893
- const out = samples.map((s) => fileColor(s));
131976
+ const out = samples.map((s) => styleFilePath(s));
131894
131977
  if (remaining > 0) out.push(chalk.dim(`+${String(remaining)} more`));
131895
- return out.join("\n");
131978
+ return new GlanceLinesComponent(out);
131896
131979
  };
131897
131980
  function fileBasename(path) {
131898
131981
  const idx = path.lastIndexOf("/");
@@ -134037,7 +134120,7 @@ async function findCcConnectResidualPaths(excludePath) {
134037
134120
  existing.push(p);
134038
134121
  } catch {}
134039
134122
  }
134040
- return existing.sort();
134123
+ return existing.toSorted();
134041
134124
  }
134042
134125
  async function handleCcCommand(host) {
134043
134126
  const daemon = resolveDaemonMode();
@@ -134077,7 +134160,7 @@ function runLifecycleAction(host, daemon, action) {
134077
134160
  host.refreshCcStatus();
134078
134161
  } else host.showError(t("cc.start_failed", {
134079
134162
  label,
134080
- output: output || "未知错误"
134163
+ output: output || t("cc.unknown_error")
134081
134164
  }));
134082
134165
  })();
134083
134166
  }
@@ -134087,7 +134170,7 @@ function buildUninstallSummary(daemon, install, residualPaths = []) {
134087
134170
  t("cc.will_clean"),
134088
134171
  t("cc.clean_daemon", { label: daemon.method }),
134089
134172
  t("cc.clean_config", { detail: t("cc.clean_config.detail") }),
134090
- "· 执行 npm uninstall -g cc-connect"
134173
+ t("cc.uninstall_step_npm")
134091
134174
  ];
134092
134175
  if (install.version) lines.push(t("cc.current_version", { version: install.version }));
134093
134176
  if (install.entry) lines.push(t("cc.install_path", { path: install.entry }));
@@ -135034,9 +135117,92 @@ function getPlatforms() {
135034
135117
  type: "wecom",
135035
135118
  setupCmd: "wecom setup --project default",
135036
135119
  note: t("ccconnect.note_wecom")
135120
+ },
135121
+ {
135122
+ name: "QQ 官方 bot",
135123
+ type: "qqbot",
135124
+ setupCmd: "qqbot setup --project default"
135125
+ },
135126
+ {
135127
+ name: "LINE",
135128
+ type: "line",
135129
+ setupCmd: "line setup --project default"
135130
+ },
135131
+ {
135132
+ name: "微博",
135133
+ type: "weibo",
135134
+ setupCmd: "weibo setup --project default"
135135
+ },
135136
+ {
135137
+ name: "WPS 协作",
135138
+ type: "wps-xiezuo",
135139
+ setupCmd: "wps-xiezuo setup --project default"
135140
+ },
135141
+ {
135142
+ name: "Matrix",
135143
+ type: "matrix",
135144
+ setupCmd: "matrix setup --project default",
135145
+ note: t("ccconnect.note_matrix")
135146
+ },
135147
+ {
135148
+ name: "Cisco Webex",
135149
+ type: "webex",
135150
+ setupCmd: "webex setup --project default",
135151
+ note: t("ccconnect.note_webex")
135152
+ },
135153
+ {
135154
+ name: "MAX",
135155
+ type: "max",
135156
+ setupCmd: "max setup --project default",
135157
+ note: t("ccconnect.note_max")
135158
+ },
135159
+ {
135160
+ name: "Google Chat",
135161
+ type: "googlechat",
135162
+ setupCmd: "googlechat setup --project default",
135163
+ note: t("ccconnect.note_googlechat")
135164
+ },
135165
+ {
135166
+ name: "Cloud Web",
135167
+ type: "cloud_web",
135168
+ setupCmd: "cloud_web setup --project default",
135169
+ note: t("ccconnect.note_cloud_web")
135170
+ },
135171
+ {
135172
+ name: "腾讯元宝",
135173
+ type: "yuanbao",
135174
+ setupCmd: "yuanbao setup --project default",
135175
+ note: t("ccconnect.note_yuanbao")
135176
+ },
135177
+ {
135178
+ name: "Tuitui",
135179
+ type: "tuitui",
135180
+ setupCmd: "tuitui setup --project default",
135181
+ note: t("ccconnect.note_tuitui")
135182
+ },
135183
+ {
135184
+ name: "WPS 数字员工",
135185
+ type: "wps-agentspace",
135186
+ setupCmd: "wps-agentspace setup --project default",
135187
+ note: t("ccconnect.note_wps_agentspace")
135037
135188
  }
135038
135189
  ];
135039
135190
  }
135191
+ /**
135192
+ * Platform types introduced by cc-connect v1.4/v1.5. Older cc-connect
135193
+ * binaries do not recognise these, so the post-config notice tells the user
135194
+ * to update cc-connect first.
135195
+ */
135196
+ const NEW_PLATFORM_TYPES = new Set([
135197
+ "matrix",
135198
+ "webex",
135199
+ "max",
135200
+ "googlechat",
135201
+ "cloud_web",
135202
+ "yuanbao",
135203
+ "tuitui",
135204
+ "wps-agentspace"
135205
+ ]);
135040
135206
  const CONFIG_PATH = join(homedir(), ".cc-connect", "config.toml");
135041
135207
  function checkCcConnect() {
135042
135208
  try {
@@ -135051,43 +135217,80 @@ function checkCcConnect() {
135051
135217
  return { installed: false };
135052
135218
  }
135053
135219
  }
135220
+ /** Auto-detect the path to the scream binary, including the stream-json subcommand. */
135054
135221
  function detectScreamPath() {
135222
+ const execBase = process.execPath.toLowerCase();
135223
+ if (execBase.endsWith("/scream") || execBase.endsWith("\\scream") || execBase.endsWith("scream.exe")) return `${quoteShellPath(process.execPath)} stream-json`;
135224
+ if (execBase.includes("node") && process.argv[1]) {
135225
+ const arg1 = process.argv[1];
135226
+ if (arg1.includes("scream-code") || arg1.includes("scream")) return `node ${quoteShellPath(arg1)} stream-json`;
135227
+ }
135055
135228
  try {
135056
135229
  const first = execSync(process.platform === "win32" ? "where scream" : "which scream 2>/dev/null", {
135057
135230
  encoding: "utf-8",
135058
135231
  timeout: 3e3
135059
135232
  }).trim().split(/[\r\n]+/)[0]?.trim() ?? "";
135060
- if (first) return `${first} stream-json`;
135233
+ if (first) return `${quoteShellPath(first)} stream-json`;
135061
135234
  } catch {}
135062
135235
  return "scream stream-json";
135063
135236
  }
135064
- function readConfiguredType() {
135065
- if (!existsSync(CONFIG_PATH)) return void 0;
135066
- try {
135067
- const content = readFileSync(CONFIG_PATH, "utf-8");
135068
- let inPlatforms = false;
135069
- for (const line of content.split("\n")) {
135070
- const trimmed = line.trim();
135071
- if (trimmed === "[[projects.platforms]]") {
135072
- inPlatforms = true;
135073
- continue;
135074
- }
135075
- if (trimmed.startsWith("[[") && trimmed !== "[[projects.platforms]]") {
135076
- inPlatforms = false;
135077
- continue;
135078
- }
135079
- if (inPlatforms) {
135080
- const m = line.match(/^type\s*=\s*"(\S+)"/);
135081
- if (m) return m[1];
135082
- }
135237
+ /**
135238
+ * Double-quote a path for shell parsing. cc-connect runs `cmd` through a
135239
+ * shell, so an unquoted path containing spaces would be split apart.
135240
+ */
135241
+ function quoteShellPath(path) {
135242
+ return `"${path.replaceAll(/"/g, "\\\"")}"`;
135243
+ }
135244
+ /** Parse every configured platform type from config.toml content. */
135245
+ function parseConfiguredTypes(content) {
135246
+ const types = [];
135247
+ let inPlatforms = false;
135248
+ for (const line of content.split("\n")) {
135249
+ const trimmed = line.trim();
135250
+ if (trimmed.startsWith("[[projects.platforms]]")) {
135251
+ inPlatforms = true;
135252
+ continue;
135083
135253
  }
135084
- return;
135254
+ if (trimmed.startsWith("[")) {
135255
+ inPlatforms = false;
135256
+ continue;
135257
+ }
135258
+ if (inPlatforms) {
135259
+ const m = trimmed.match(/^type\s*=\s*["']([^"']+)["']/);
135260
+ if (m?.[1]) types.push(m[1]);
135261
+ }
135262
+ }
135263
+ return types;
135264
+ }
135265
+ function readConfiguredTypes() {
135266
+ if (!existsSync(CONFIG_PATH)) return [];
135267
+ try {
135268
+ return parseConfiguredTypes(readFileSync(CONFIG_PATH, "utf-8"));
135085
135269
  } catch {
135086
- return;
135270
+ return [];
135087
135271
  }
135088
135272
  }
135089
- function escapeSingleQuotes(str) {
135090
- return str.replaceAll("'", "\\'");
135273
+ /** True when config content already has an active (non-comment) platform of this type. */
135274
+ function hasPlatformConfigured(content, type) {
135275
+ const escaped = type.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
135276
+ return new RegExp(`^\\s*type\\s*=\\s*["']${escaped}["']`, "m").test(content);
135277
+ }
135278
+ /** Minimal semver-ish check: "1.5.0" >= "1.5.0". */
135279
+ function isVersionAtLeast(version, min) {
135280
+ if (!version) return false;
135281
+ const parse = (v) => v.split(".").map((n) => Number.parseInt(n, 10) || 0);
135282
+ const [a = 0, b = 0, c = 0] = parse(version);
135283
+ const [x = 0, y = 0, z = 0] = parse(min);
135284
+ return a > x || a === x && (b > y || b === y && c >= z);
135285
+ }
135286
+ /**
135287
+ * Quote a value for TOML. Literal strings ('...') cannot contain a single
135288
+ * quote at all (no escape mechanism), so values with one fall back to a
135289
+ * basic string ("...") where backslash and double-quote are escaped.
135290
+ */
135291
+ function tomlString(value) {
135292
+ if (!value.includes("'")) return `'${value}'`;
135293
+ return `"${value.replaceAll(/\\/g, "\\\\").replaceAll(/"/g, "\\\"")}"`;
135091
135294
  }
135092
135295
  function generateConfig(platform) {
135093
135296
  const dir = dirname$1(CONFIG_PATH);
@@ -135095,11 +135298,15 @@ function generateConfig(platform) {
135095
135298
  const platformBlock = `\n[[projects.platforms]]\ntype = "${platform.type}"\n`;
135096
135299
  if (existsSync(CONFIG_PATH)) {
135097
135300
  const existing = readFileSync(CONFIG_PATH, "utf-8");
135098
- if (existing.includes(`type = "${platform.type}"`)) return;
135301
+ if (hasPlatformConfigured(existing, platform.type)) return;
135099
135302
  writeFileSync(CONFIG_PATH, existing + platformBlock, "utf-8");
135100
135303
  return;
135101
135304
  }
135102
- writeFileSync(CONFIG_PATH, [
135305
+ writeFileSync(CONFIG_PATH, buildConfigContent(platform, detectScreamPath(), process.cwd()), "utf-8");
135306
+ }
135307
+ /** Build the full config.toml content for a fresh setup. */
135308
+ function buildConfigContent(platform, screamCmd, cwd) {
135309
+ return [
135103
135310
  t("ccconnect.config_comment_attachment"),
135104
135311
  "attachment_send = \"on\"",
135105
135312
  "",
@@ -135110,21 +135317,22 @@ function generateConfig(platform) {
135110
135317
  "type = \"claudecode\"",
135111
135318
  "",
135112
135319
  "[projects.agent.options]",
135113
- `cmd = '${escapeSingleQuotes(detectScreamPath())}'`,
135114
- `work_dir = '${escapeSingleQuotes(process.cwd())}'`,
135115
- "mode = \"default\"",
135320
+ `cmd = ${tomlString(screamCmd)}`,
135321
+ `work_dir = ${tomlString(cwd)}`,
135322
+ "# Permission mode: default (confirm each tool) | acceptEdits | plan | auto | bypassPermissions (yolo) | dontAsk",
135323
+ "mode = \"auto\"",
135116
135324
  "",
135117
135325
  "[[projects.platforms]]",
135118
135326
  `type = "${platform.type}"`,
135119
135327
  ""
135120
- ].join("\n"), "utf-8");
135328
+ ].join("\n");
135121
135329
  }
135122
135330
  const SEP = "──".repeat(20);
135123
135331
  /**
135124
135332
  * Build the full notice text shown after platform selection.
135125
135333
  * Common management commands come first; detailed setup steps follow.
135126
135334
  */
135127
- function buildNoticeText(platform, isReconfigure) {
135335
+ function buildNoticeText(platform, isReconfigure, installedVersion) {
135128
135336
  const daemon = getDaemonInstructions(dirname$1(CONFIG_PATH));
135129
135337
  const parts = [];
135130
135338
  if (isReconfigure) {
@@ -135136,6 +135344,10 @@ function buildNoticeText(platform, isReconfigure) {
135136
135344
  parts.push("");
135137
135345
  parts.push(t("ccconnect.config_written", { path: CONFIG_PATH }));
135138
135346
  }
135347
+ if (NEW_PLATFORM_TYPES.has(platform.type) && !isVersionAtLeast(installedVersion, "1.5.0")) {
135348
+ parts.push("");
135349
+ parts.push(t("ccconnect.new_platform_version_hint"));
135350
+ }
135139
135351
  parts.push("");
135140
135352
  parts.push(t("ccconnect.quick_ref"));
135141
135353
  parts.push("");
@@ -135189,13 +135401,14 @@ function buildNoticeText(platform, isReconfigure) {
135189
135401
  return parts.join("\n");
135190
135402
  }
135191
135403
  async function handleChannelCommand(host, _args) {
135192
- if (!checkCcConnect().installed) {
135404
+ const cc = checkCcConnect();
135405
+ if (!cc.installed) {
135193
135406
  host.showNotice(t("ccconnect.not_installed"), t("ccconnect.install_guide"));
135194
135407
  return;
135195
135408
  }
135196
- const configuredType = readConfiguredType();
135409
+ const configuredTypes = readConfiguredTypes();
135197
135410
  const options = getPlatforms().map((p) => {
135198
- const isConfigured = configuredType === p.type;
135411
+ const isConfigured = configuredTypes.includes(p.type);
135199
135412
  return {
135200
135413
  value: p.type,
135201
135414
  label: isConfigured ? t("ccconnect.already_configured", { name: p.name }) : p.name,
@@ -135206,7 +135419,7 @@ async function handleChannelCommand(host, _args) {
135206
135419
  title: t("ccconnect.picker_title"),
135207
135420
  hint: t("ccconnect.picker_hint"),
135208
135421
  options,
135209
- currentValue: configuredType,
135422
+ currentValue: configuredTypes[0],
135210
135423
  colors: host.state.theme.colors,
135211
135424
  onSelect: (value) => {
135212
135425
  host.restoreEditor();
@@ -135215,12 +135428,12 @@ async function handleChannelCommand(host, _args) {
135215
135428
  host.showError(t("error.internal"));
135216
135429
  return;
135217
135430
  }
135218
- if (configuredType === value) {
135219
- host.showNotice(t("ccconnect.reconfigured", { name: platform.name }), buildNoticeText(platform, true));
135431
+ if (configuredTypes.includes(value)) {
135432
+ host.showNotice(t("ccconnect.reconfigured", { name: platform.name }), buildNoticeText(platform, true, cc.version));
135220
135433
  return;
135221
135434
  }
135222
135435
  generateConfig(platform);
135223
- host.showNotice(t("ccconnect.config_done", { name: platform.name }), buildNoticeText(platform, false));
135436
+ host.showNotice(t("ccconnect.config_done", { name: platform.name }), buildNoticeText(platform, false, cc.version));
135224
135437
  },
135225
135438
  onCancel: () => {
135226
135439
  host.restoreEditor();
@@ -136072,7 +136285,7 @@ function buildRoleAdditionalText(prefs) {
136072
136285
  if (items.length === 0 && (doNot === void 0 || doNot.length === 0) && !hasToolPriority) return "";
136073
136286
  lines.push("", ...items);
136074
136287
  if (doNot !== void 0 && doNot.length > 0) lines.push("", "## Do NOT (explicit prohibitions — NEVER do these)", doNot);
136075
- if (hasToolPriority) lines.push("", "## Tool priority (set via /like — HIGHEST PRIORITY)", prefs.toolPriority === "skill" ? "For every user request, first analyze the intent, then check all installed skills and try to solve the problem with a matching one. Only when no installed skill fits should you fall back to MCP tools or decide on your own how to solve it." : "For every user request, first analyze the intent, then check all available MCP tools and try to solve the problem with one. Only when no MCP tool fits should you fall back to the Skill tool or decide on your own how to solve it.");
136288
+ if (hasToolPriority) lines.push("", "## Tool priority (set via /like — HIGHEST PRIORITY)", prefs.toolPriority === "skill" ? "For every user request: (1) first analyze the intent, (2) then identify whether any installed skill matches THIS request, (3) if one matches, invoke the Skill tool with it as your FIRST action before MCP tools or doing it yourself, (4) if none matches, proceed with MCP tools or solve it yourself. Consult the skill list shown in the Skill tool description whenever you are unsure what is installed." : "For every user request: (1) first analyze the intent, (2) then identify whether any available MCP tool matches THIS request, (3) if one matches, use it as your FIRST action before the Skill tool or doing it yourself, (4) if none matches, proceed with the Skill tool or solve it yourself.");
136076
136289
  lines.push("", t("like.priority"));
136077
136290
  return lines.join("\n");
136078
136291
  }
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
3
3
  import { dirname as __cjsShimDirname } from 'node:path';
4
4
  const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
- import { Kt as handleConnectCommand } from "./dispatch-DevL-OSj.mjs";
6
+ import { Kt as handleConnectCommand } from "./dispatch-202xtik3.mjs";
7
7
  export { handleConnectCommand };
package/dist/main.mjs CHANGED
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
6
6
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
7
7
  //#region src/main.ts
8
8
  try {
9
- (await import("./app-CJGX8qct.mjs")).main();
9
+ (await import("./app-BXslKpYT.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);
@@ -410,6 +410,8 @@ const dictionaries = {
410
410
  "cc.confirm_uninstall": "确认彻底卸载 cc-connect?",
411
411
  "cc.uninstall_irreversible": "此操作不可撤销,所有 cc-connect 数据将被清除",
412
412
  "cc.confirm_uninstall_btn": "确认卸载",
413
+ "cc.uninstall_step_npm": "· 执行 npm uninstall -g cc-connect",
414
+ "cc.unknown_error": "未知错误",
413
415
  "auth.fetching_models": "正在拉取最新模型目录...",
414
416
  "auth.catalog_fetch_failed": "拉取远程模型目录失败: {error}。请检查网络(目录站点在你的网络环境下可能无法访问)后重试,或使用 /config diy 自行配置服务商。",
415
417
  "auth.no_providers": "没有已配置的模型商。",
@@ -959,6 +961,15 @@ const dictionaries = {
959
961
  "ccconnect.note_botfather": "需先在 @BotFather 创建 bot",
960
962
  "ccconnect.note_napcat": "需要 NapCat/OneBot",
961
963
  "ccconnect.note_wecom": "需要公网 IP",
964
+ "ccconnect.note_matrix": "E2EE 加密,需 homeserver + access token",
965
+ "ccconnect.note_webex": "需在 developer.webex.com 创建 Bot",
966
+ "ccconnect.note_max": "需通过 @MasterBot 创建 bot",
967
+ "ccconnect.note_googlechat": "需 GCP 服务账号 JSON",
968
+ "ccconnect.note_cloud_web": "自建 IM 网关",
969
+ "ccconnect.note_yuanbao": "腾讯元宝,需 app-key:app-secret",
970
+ "ccconnect.note_tuitui": "需 app_id + app_secret",
971
+ "ccconnect.note_wps_agentspace": "需 app_id + wps_sid",
972
+ "ccconnect.new_platform_version_hint": "⚠ 该平台需要 cc-connect v1.5.0 及以上,请先更新 cc-connect",
962
973
  "ccconnect.config_comment_attachment": "# 全局:允许/禁止图片和文件回传到聊天(on = 开启,off = 关闭)",
963
974
  "ccconnect.reconfigured": "{name} 已配置(配置不会丢失)",
964
975
  "ccconnect.config_path": "配置文件:{path}",
@@ -1490,6 +1501,8 @@ const dictionaries = {
1490
1501
  "cc.confirm_uninstall": "Completely uninstall cc-connect?",
1491
1502
  "cc.uninstall_irreversible": "This cannot be undone. All cc-connect data will be removed.",
1492
1503
  "cc.confirm_uninstall_btn": "Confirm Uninstall",
1504
+ "cc.uninstall_step_npm": "· Run npm uninstall -g cc-connect",
1505
+ "cc.unknown_error": "Unknown error",
1493
1506
  "auth.fetching_models": "Fetching latest model catalog...",
1494
1507
  "auth.catalog_fetch_failed": "Failed to fetch the remote model catalog: {error}. Check your network (the catalog host may be unreachable in your region) and retry, or use /config diy to set up a provider manually.",
1495
1508
  "auth.no_providers": "No model providers configured.",
@@ -2039,6 +2052,15 @@ const dictionaries = {
2039
2052
  "ccconnect.note_botfather": "Create a bot at @BotFather first",
2040
2053
  "ccconnect.note_napcat": "Requires NapCat/OneBot",
2041
2054
  "ccconnect.note_wecom": "Requires public IP",
2055
+ "ccconnect.note_matrix": "E2EE encrypted, needs homeserver + access token",
2056
+ "ccconnect.note_webex": "Create a bot at developer.webex.com",
2057
+ "ccconnect.note_max": "Create a bot via @MasterBot",
2058
+ "ccconnect.note_googlechat": "Requires GCP service account JSON",
2059
+ "ccconnect.note_cloud_web": "Self-hosted IM gateway",
2060
+ "ccconnect.note_yuanbao": "Tencent Yuanbao, needs app-key:app-secret",
2061
+ "ccconnect.note_tuitui": "Needs app_id + app_secret",
2062
+ "ccconnect.note_wps_agentspace": "Needs app_id + wps_sid",
2063
+ "ccconnect.new_platform_version_hint": "⚠ This platform requires cc-connect v1.5.0 or newer. Please update cc-connect first.",
2042
2064
  "ccconnect.config_comment_attachment": "# Global: allow/disallow image & file backhaul to chat (on = enabled, off = disabled)",
2043
2065
  "ccconnect.reconfigured": "{name} already configured (config preserved)",
2044
2066
  "ccconnect.config_path": "Config file: {path}",
@@ -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-B9Klg3re.mjs";
6
+ import { t as TextInputDialogComponent } from "./text-input-dialog-BzVVVncg.mjs";
7
7
  export { TextInputDialogComponent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.13.8",
3
+ "version": "0.14.0",
4
4
  "description": "A terminal-native AI agent for builders",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",