scream-code 0.12.6 → 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.
|
@@ -7,7 +7,7 @@ import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } fr
|
|
|
7
7
|
import "./suppress-sqlite-warning-C2VB0doZ.mjs";
|
|
8
8
|
import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-BH9W5k24.mjs";
|
|
9
9
|
import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
|
|
10
|
-
import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-
|
|
10
|
+
import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-9ywV88Lh.mjs";
|
|
11
11
|
import { createRequire } from "node:module";
|
|
12
12
|
import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
|
|
13
13
|
import * as fs$1 from "node:fs/promises";
|
|
@@ -5600,7 +5600,7 @@ var _BetaToolRunner_instances, _BetaToolRunner_consumed, _BetaToolRunner_mutated
|
|
|
5600
5600
|
/**
|
|
5601
5601
|
* Just Promise.withResolvers(), which is not available in all environments.
|
|
5602
5602
|
*/
|
|
5603
|
-
function promiseWithResolvers() {
|
|
5603
|
+
function promiseWithResolvers$1() {
|
|
5604
5604
|
let resolve;
|
|
5605
5605
|
let reject;
|
|
5606
5606
|
return {
|
|
@@ -5646,7 +5646,7 @@ var BetaToolRunner = class {
|
|
|
5646
5646
|
...options,
|
|
5647
5647
|
headers: buildHeaders$2([{ "x-stainless-helper": helperValue }, options?.headers])
|
|
5648
5648
|
}, "f");
|
|
5649
|
-
__classPrivateFieldSet$1(this, _BetaToolRunner_completion, promiseWithResolvers(), "f");
|
|
5649
|
+
__classPrivateFieldSet$1(this, _BetaToolRunner_completion, promiseWithResolvers$1(), "f");
|
|
5650
5650
|
if (params.compactionControl?.enabled) console.warn("Anthropic: The `compactionControl` parameter is deprecated and will be removed in a future version. Use server-side compaction instead by passing `edits: [{ type: \"compact_20260112\" }]` in the params passed to `toolRunner()`. See https://platform.claude.com/docs/en/build-with-claude/compaction");
|
|
5651
5651
|
}
|
|
5652
5652
|
async *[(_BetaToolRunner_consumed = /* @__PURE__ */ new WeakMap(), _BetaToolRunner_mutated = /* @__PURE__ */ new WeakMap(), _BetaToolRunner_state = /* @__PURE__ */ new WeakMap(), _BetaToolRunner_options = /* @__PURE__ */ new WeakMap(), _BetaToolRunner_message = /* @__PURE__ */ new WeakMap(), _BetaToolRunner_toolResponse = /* @__PURE__ */ new WeakMap(), _BetaToolRunner_completion = /* @__PURE__ */ new WeakMap(), _BetaToolRunner_iterationCount = /* @__PURE__ */ new WeakMap(), _BetaToolRunner_instances = /* @__PURE__ */ new WeakSet(), _BetaToolRunner_checkAndCompact = async function _BetaToolRunner_checkAndCompact() {
|
|
@@ -5742,7 +5742,7 @@ var BetaToolRunner = class {
|
|
|
5742
5742
|
__classPrivateFieldSet$1(this, _BetaToolRunner_consumed, false, "f");
|
|
5743
5743
|
__classPrivateFieldGet$1(this, _BetaToolRunner_completion, "f").promise.catch(() => {});
|
|
5744
5744
|
__classPrivateFieldGet$1(this, _BetaToolRunner_completion, "f").reject(error);
|
|
5745
|
-
__classPrivateFieldSet$1(this, _BetaToolRunner_completion, promiseWithResolvers(), "f");
|
|
5745
|
+
__classPrivateFieldSet$1(this, _BetaToolRunner_completion, promiseWithResolvers$1(), "f");
|
|
5746
5746
|
throw error;
|
|
5747
5747
|
}
|
|
5748
5748
|
}
|
|
@@ -76344,14 +76344,84 @@ function maybeStatusCode(error) {
|
|
|
76344
76344
|
}
|
|
76345
76345
|
//#endregion
|
|
76346
76346
|
//#region ../../packages/agent-core/src/agent/context/projector.ts
|
|
76347
|
-
|
|
76348
|
-
|
|
76347
|
+
/** Synthetic error text used when a tool result is missing and must be
|
|
76348
|
+
* filled in so the provider accepts the message sequence. */
|
|
76349
|
+
const SYNTHETIC_TOOL_RESULT_TEXT = "<system>ERROR: The tool call did not complete because of an interruption. Do not assume the tool executed successfully, and do not invent its result.</system>";
|
|
76350
|
+
function project(history, options) {
|
|
76351
|
+
return repairToolExchangeAdjacency(mergeAdjacentUserMessages(history.filter((message) => {
|
|
76349
76352
|
return message.partial !== true && !(message.role === "assistant" && message.content.length === 0 && message.toolCalls.length === 0);
|
|
76350
|
-
});
|
|
76351
|
-
const last = usable.at(-1);
|
|
76352
|
-
return mergeAdjacentUserMessages(last?.role === "assistant" && last.toolCalls.length > 0 ? usable.slice(0, -1) : usable);
|
|
76353
|
+
}), options?.onAnomaly), options);
|
|
76353
76354
|
}
|
|
76354
|
-
|
|
76355
|
+
/**
|
|
76356
|
+
* Closes every tool exchange whose assistant `tool_use` is not fully answered
|
|
76357
|
+
* by a matching `tool_result`. A mid-history orphan (a later user/assistant
|
|
76358
|
+
* message follows) can never be in-flight, so it is always closed by
|
|
76359
|
+
* synthesizing an error result. A trailing orphan is closed only when
|
|
76360
|
+
* `synthesizeMissing` is set — otherwise it is left for the trim / replay
|
|
76361
|
+
* synthesis. This prevents the provider error "must be followed by tool
|
|
76362
|
+
* messages responding to each tool_call_id" after an interruption (e.g. a
|
|
76363
|
+
* network drop mid-batch).
|
|
76364
|
+
*/
|
|
76365
|
+
function repairToolExchangeAdjacency(messages, options) {
|
|
76366
|
+
let lastNonToolIndex = messages.length - 1;
|
|
76367
|
+
while (lastNonToolIndex >= 0 && messages[lastNonToolIndex]?.role === "tool") lastNonToolIndex -= 1;
|
|
76368
|
+
const out = [];
|
|
76369
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
76370
|
+
for (let i = 0; i < messages.length; i++) {
|
|
76371
|
+
if (consumed.has(i)) continue;
|
|
76372
|
+
const message = messages[i];
|
|
76373
|
+
if (message.role !== "assistant" || message.toolCalls.length === 0) {
|
|
76374
|
+
out.push(message);
|
|
76375
|
+
continue;
|
|
76376
|
+
}
|
|
76377
|
+
out.push(message);
|
|
76378
|
+
const pending = new Set(message.toolCalls.map((toolCall) => toolCall.id));
|
|
76379
|
+
let foreignBetween = false;
|
|
76380
|
+
for (let j = i + 1; j < messages.length && pending.size > 0; j++) {
|
|
76381
|
+
if (consumed.has(j)) continue;
|
|
76382
|
+
const next = messages[j];
|
|
76383
|
+
const toolCallId = next.toolCallId;
|
|
76384
|
+
if (next.role === "tool" && toolCallId !== void 0 && pending.has(toolCallId)) {
|
|
76385
|
+
out.push(next);
|
|
76386
|
+
consumed.add(j);
|
|
76387
|
+
pending.delete(toolCallId);
|
|
76388
|
+
if (foreignBetween) options?.onAnomaly?.({
|
|
76389
|
+
kind: "tool_result_reordered",
|
|
76390
|
+
toolCallId
|
|
76391
|
+
});
|
|
76392
|
+
} else foreignBetween = true;
|
|
76393
|
+
}
|
|
76394
|
+
const isMidHistory = i < lastNonToolIndex;
|
|
76395
|
+
if (options?.synthesizeMissing === true || isMidHistory) for (const missingId of pending) {
|
|
76396
|
+
out.push(makeSyntheticToolResult(missingId));
|
|
76397
|
+
options?.onAnomaly?.({
|
|
76398
|
+
kind: "tool_result_synthesized",
|
|
76399
|
+
toolCallId: missingId,
|
|
76400
|
+
trailing: !isMidHistory
|
|
76401
|
+
});
|
|
76402
|
+
}
|
|
76403
|
+
}
|
|
76404
|
+
return out;
|
|
76405
|
+
}
|
|
76406
|
+
function makeSyntheticToolResult(toolCallId) {
|
|
76407
|
+
return {
|
|
76408
|
+
role: "tool",
|
|
76409
|
+
content: [{
|
|
76410
|
+
type: "text",
|
|
76411
|
+
text: SYNTHETIC_TOOL_RESULT_TEXT
|
|
76412
|
+
}],
|
|
76413
|
+
toolCalls: [],
|
|
76414
|
+
toolCallId
|
|
76415
|
+
};
|
|
76416
|
+
}
|
|
76417
|
+
/**
|
|
76418
|
+
* Drops a trailing open tool exchange from a projected message list: when the
|
|
76419
|
+
* last assistant message carries tool calls whose results never arrived and
|
|
76420
|
+
* nothing follows, truncate from that batch so the compacted history does not
|
|
76421
|
+
* carry an unterminated exchange (which would be rejected or, if synthesized,
|
|
76422
|
+
* pollute the compaction prompt).
|
|
76423
|
+
*/
|
|
76424
|
+
function mergeAdjacentUserMessages(history, _onAnomaly) {
|
|
76355
76425
|
const out = [];
|
|
76356
76426
|
for (const message of history) {
|
|
76357
76427
|
const previous = out.at(-1);
|
|
@@ -76395,10 +76465,10 @@ function stripContextMetadata(message) {
|
|
|
76395
76465
|
}
|
|
76396
76466
|
//#endregion
|
|
76397
76467
|
//#region ../../packages/agent-core/src/agent/compaction/compaction-instruction.md
|
|
76398
|
-
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 **
|
|
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";
|
|
76399
76469
|
//#endregion
|
|
76400
76470
|
//#region ../../packages/agent-core/src/agent/compaction/compaction-update-instruction.md
|
|
76401
|
-
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 **
|
|
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";
|
|
76402
76472
|
//#endregion
|
|
76403
76473
|
//#region ../../packages/agent-core/src/agent/compaction/render-messages.ts
|
|
76404
76474
|
const TOOL_RESULT_MAX_CHARS = 2e3;
|
|
@@ -76490,8 +76560,9 @@ const DEFAULT_COMPACTION_CONFIG = {
|
|
|
76490
76560
|
blockRatio: .85,
|
|
76491
76561
|
reservedContextSize: 5e4,
|
|
76492
76562
|
maxCompactionPerTurn: 3,
|
|
76493
|
-
maxRecentMessages:
|
|
76494
|
-
maxRecentUserMessages:
|
|
76563
|
+
maxRecentMessages: 16,
|
|
76564
|
+
maxRecentUserMessages: 8,
|
|
76565
|
+
maxRecentTokens: 2e4,
|
|
76495
76566
|
maxRecentSizeRatio: .2,
|
|
76496
76567
|
minOverflowReductionRatio: .05,
|
|
76497
76568
|
turnGrowthMultiplier: 2.5
|
|
@@ -76533,7 +76604,7 @@ var DefaultCompactionStrategy = class {
|
|
|
76533
76604
|
if (m2.role === "user") recentUserMessages++;
|
|
76534
76605
|
recentSize += estimateTokensForMessage(m2);
|
|
76535
76606
|
if (canSplitAfter(messages, splitIndex)) bestN = splitIndex + 1;
|
|
76536
|
-
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;
|
|
76537
76608
|
}
|
|
76538
76609
|
return bestN ?? 0;
|
|
76539
76610
|
}
|
|
@@ -76621,12 +76692,23 @@ function extractFileOpsFromMessage(message, ops) {
|
|
|
76621
76692
|
}
|
|
76622
76693
|
}
|
|
76623
76694
|
}
|
|
76695
|
+
/** Projects a `FileOperations` accumulator into the persistent, sorted
|
|
76696
|
+
* file lists stored on `CompactionResult`. Unlike `formatFileOperations`
|
|
76697
|
+
* this keeps the full list (no `FILE_LIMIT` elision) so later compactions
|
|
76698
|
+
* can merge the previous round's file context without losing entries. */
|
|
76699
|
+
function computeFileLists(ops) {
|
|
76700
|
+
const modified = new Set([...ops.edited, ...ops.written]);
|
|
76701
|
+
return {
|
|
76702
|
+
readFiles: [...ops.read].toSorted(),
|
|
76703
|
+
modifiedFiles: [...modified].toSorted()
|
|
76704
|
+
};
|
|
76705
|
+
}
|
|
76624
76706
|
const FILE_LIMIT = 20;
|
|
76625
76707
|
function formatFileOperations(ops) {
|
|
76626
76708
|
const modified = new Set([...ops.edited, ...ops.written]);
|
|
76627
|
-
const readOnly = [...ops.read].filter((f) => !modified.has(f)).
|
|
76628
|
-
const modifiedFiles = [...modified].
|
|
76629
|
-
const all = [...new Set([...readOnly, ...modifiedFiles])].
|
|
76709
|
+
const readOnly = [...ops.read].filter((f) => !modified.has(f)).toSorted();
|
|
76710
|
+
const modifiedFiles = [...modified].toSorted();
|
|
76711
|
+
const all = [...new Set([...readOnly, ...modifiedFiles])].toSorted();
|
|
76630
76712
|
if (all.length === 0) return "";
|
|
76631
76713
|
const mode = /* @__PURE__ */ new Map();
|
|
76632
76714
|
for (const f of readOnly) mode.set(f, "Read");
|
|
@@ -76769,6 +76851,10 @@ var FullCompaction = class {
|
|
|
76769
76851
|
* limit → overflow → compact cycle from consuming the entire
|
|
76770
76852
|
* maxCompactionPerTurn budget with marginal savings. */
|
|
76771
76853
|
reactiveAttempted = false;
|
|
76854
|
+
/** File lists from the most recent successful compaction. Merged into the
|
|
76855
|
+
* next compaction's file operations so file context accumulates across
|
|
76856
|
+
* repeated compactions instead of being reset each round. */
|
|
76857
|
+
lastCompactionFiles;
|
|
76772
76858
|
compacting = null;
|
|
76773
76859
|
_compactedHistory = [];
|
|
76774
76860
|
strategy;
|
|
@@ -77000,15 +77086,25 @@ var FullCompaction = class {
|
|
|
77000
77086
|
const recent = originalHistory.slice(compactedCount);
|
|
77001
77087
|
const messagesToCompactForOps = originalHistory.slice(0, compactedCount);
|
|
77002
77088
|
const fileOps = createFileOps();
|
|
77089
|
+
if (this.lastCompactionFiles !== void 0 && extractPreviousSummary(originalHistory) !== null) {
|
|
77090
|
+
for (const f of this.lastCompactionFiles.readFiles) fileOps.read.add(f);
|
|
77091
|
+
for (const f of this.lastCompactionFiles.modifiedFiles) fileOps.edited.add(f);
|
|
77092
|
+
} else this.lastCompactionFiles = void 0;
|
|
77003
77093
|
for (const msg of messagesToCompactForOps) extractFileOpsFromMessage(msg, fileOps);
|
|
77004
77094
|
const toolCallHistory = formatToolCallHistory(messagesToCompactForOps);
|
|
77005
|
-
const processedSummary = this.postProcessSummary(summary, fileOps, toolCallHistory);
|
|
77095
|
+
const processedSummary = this.postProcessSummary(summary, fileOps, toolCallHistory, compactedCount);
|
|
77006
77096
|
const tokensAfter = estimateTokens$1(processedSummary) + estimateTokensForMessages(recent);
|
|
77097
|
+
const fileLists = computeFileLists(fileOps);
|
|
77098
|
+
const MAX_PERSISTED_FILES = 100;
|
|
77099
|
+
const readFiles = fileLists.readFiles.slice(0, MAX_PERSISTED_FILES);
|
|
77100
|
+
const modifiedFiles = fileLists.modifiedFiles.slice(0, MAX_PERSISTED_FILES);
|
|
77007
77101
|
const result = {
|
|
77008
77102
|
summary: processedSummary,
|
|
77009
77103
|
compactedCount,
|
|
77010
77104
|
tokensBefore,
|
|
77011
77105
|
tokensAfter,
|
|
77106
|
+
...readFiles.length > 0 ? { readFiles } : {},
|
|
77107
|
+
...modifiedFiles.length > 0 ? { modifiedFiles } : {},
|
|
77012
77108
|
...isUpdate ? { isUpdate: true } : {}
|
|
77013
77109
|
};
|
|
77014
77110
|
this.markCompleted();
|
|
@@ -77017,8 +77113,9 @@ var FullCompaction = class {
|
|
|
77017
77113
|
result
|
|
77018
77114
|
});
|
|
77019
77115
|
this.agent.context.applyCompaction(result);
|
|
77116
|
+
this.lastCompactionFiles = fileLists;
|
|
77020
77117
|
this.lowWaterMark = Math.floor(this.effectiveTokenCount * 1.1);
|
|
77021
|
-
await this.extractAndStoreMemos(processedSummary);
|
|
77118
|
+
await this.extractAndStoreMemos(processedSummary, messagesToCompactForOps);
|
|
77022
77119
|
this.triggerPostCompactHook(data, result);
|
|
77023
77120
|
this.detectSkillCandidates(processedSummary);
|
|
77024
77121
|
this.consecutiveCompactionFailures = 0;
|
|
@@ -77082,16 +77179,27 @@ var FullCompaction = class {
|
|
|
77082
77179
|
}
|
|
77083
77180
|
});
|
|
77084
77181
|
}
|
|
77085
|
-
/** Extract memory memos from compaction summary and store them.
|
|
77086
|
-
|
|
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) {
|
|
77087
77188
|
const memoStore = this.agent.memoStore;
|
|
77088
77189
|
if (!memoStore) {
|
|
77089
77190
|
this.agent.log.info("Memory memo store not available, skipping extraction");
|
|
77090
77191
|
return;
|
|
77091
77192
|
}
|
|
77092
77193
|
this.agent.log.info("Scanning compaction summary for memory memos", { summaryLen: summary.length });
|
|
77093
|
-
|
|
77194
|
+
let memos = parseMemoryMemos(summary);
|
|
77094
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
|
+
}
|
|
77095
77203
|
if (memos.length === 0) return;
|
|
77096
77204
|
const sessionId = this.agent.homedir ? basename$1(dirname$2(dirname$2(this.agent.homedir))) : "unknown";
|
|
77097
77205
|
const sessionTitle = await this.agent.getSessionTitle();
|
|
@@ -77110,6 +77218,28 @@ var FullCompaction = class {
|
|
|
77110
77218
|
sessionId
|
|
77111
77219
|
});
|
|
77112
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
|
+
}
|
|
77113
77243
|
/**
|
|
77114
77244
|
* Detects [[skill-candidate: <name>|<purpose>|<evidence>]] markers in the
|
|
77115
77245
|
* compaction summary and emits a `skill_candidate` event for each one (at
|
|
@@ -77142,10 +77272,18 @@ var FullCompaction = class {
|
|
|
77142
77272
|
* the compaction summary so active tasks and file context survive
|
|
77143
77273
|
* compression. Without this, both are lost after compaction because the
|
|
77144
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.
|
|
77145
77280
|
*/
|
|
77146
|
-
postProcessSummary(summary, fileOps, toolCallHistory) {
|
|
77281
|
+
postProcessSummary(summary, fileOps, toolCallHistory, compactedCount) {
|
|
77147
77282
|
const todos = this.agent.tools.storeData()["todo"] ?? [];
|
|
77148
|
-
const
|
|
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();
|
|
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.`);
|
|
77286
|
+
if (base.length > 0) sections.push(base);
|
|
77149
77287
|
if (todos.length > 0) {
|
|
77150
77288
|
const lines = todos.map((t) => {
|
|
77151
77289
|
return `- [${t.status === "done" ? "x" : t.status === "in_progress" ? "-" : " "}] ${t.title}`;
|
|
@@ -78804,12 +78942,15 @@ var ContextMemory = class {
|
|
|
78804
78942
|
* message mutated (compaction summary, micro-compaction truncation, or a
|
|
78805
78943
|
* projection repair) and the cache broke from that index.
|
|
78806
78944
|
*
|
|
78807
|
-
*
|
|
78808
|
-
*
|
|
78945
|
+
* Unlike the read-only `messages` getter, this path closes any trailing
|
|
78946
|
+
* in-flight tool call by synthesizing an error result (synthesizeMissing):
|
|
78947
|
+
* these messages go straight to the provider, which rejects an assistant
|
|
78948
|
+
* tool_calls message with no matching tool result (e.g. after a network
|
|
78949
|
+
* drop mid-batch).
|
|
78809
78950
|
*/
|
|
78810
78951
|
messagesForLLM() {
|
|
78811
78952
|
this.agent.microCompaction.detect();
|
|
78812
|
-
const messages = project(this.agent.microCompaction.compact(this.history));
|
|
78953
|
+
const messages = project(this.agent.microCompaction.compact(this.history), { synthesizeMissing: true });
|
|
78813
78954
|
this.observePrefixStability(messages);
|
|
78814
78955
|
return messages;
|
|
78815
78956
|
}
|
|
@@ -78911,6 +79052,31 @@ var ContextMemory = class {
|
|
|
78911
79052
|
hasOpenToolExchange() {
|
|
78912
79053
|
return this.pendingToolResultIds.size > 0;
|
|
78913
79054
|
}
|
|
79055
|
+
/**
|
|
79056
|
+
* Defensive teardown for a live turn that ended — normally, cancelled, or
|
|
79057
|
+
* failed — while recorded tool calls were still awaiting results (e.g. the
|
|
79058
|
+
* batch's result dispatch died after a `tool.call` was already recorded,
|
|
79059
|
+
* like a network drop mid-execution). Synthesizes an error result for each
|
|
79060
|
+
* dangling call so the exchange closes: left open, the assistant tool_calls
|
|
79061
|
+
* message would have no matching tool message and the next request would be
|
|
79062
|
+
* rejected by the provider ("must be followed by tool messages responding
|
|
79063
|
+
* to each tool_call_id"). No-op when the exchange is already closed.
|
|
79064
|
+
*/
|
|
79065
|
+
closeAbandonedToolExchange(output) {
|
|
79066
|
+
if (this.pendingToolResultIds.size === 0) return 0;
|
|
79067
|
+
const interruptedToolCallIds = [...this.pendingToolResultIds];
|
|
79068
|
+
for (const toolCallId of interruptedToolCallIds) this.appendLoopEvent({
|
|
79069
|
+
type: "tool.result",
|
|
79070
|
+
parentUuid: toolCallId,
|
|
79071
|
+
toolCallId,
|
|
79072
|
+
result: {
|
|
79073
|
+
output,
|
|
79074
|
+
isError: true
|
|
79075
|
+
}
|
|
79076
|
+
});
|
|
79077
|
+
this.flushDeferredMessagesIfToolExchangeClosed();
|
|
79078
|
+
return interruptedToolCallIds.length;
|
|
79079
|
+
}
|
|
78914
79080
|
pushHistory(...messages) {
|
|
78915
79081
|
this._history.push(...messages);
|
|
78916
79082
|
for (const message of messages) {
|
|
@@ -96333,6 +96499,11 @@ var ToolCallDeduplicator = class {
|
|
|
96333
96499
|
};
|
|
96334
96500
|
//#endregion
|
|
96335
96501
|
//#region ../../packages/agent-core/src/agent/turn/index.ts
|
|
96502
|
+
/** Builds the error text synthesized for tool calls abandoned when a live
|
|
96503
|
+
* turn ends (cancelled, failed, or completed) before their results arrived. */
|
|
96504
|
+
function abandonedToolResultOutput(ended) {
|
|
96505
|
+
return `Tool call did not complete: ${ended.reason === "cancelled" ? "the turn was cancelled" : ended.reason === "failed" ? `the turn failed${ended.error !== void 0 ? ` (${ended.error.message})` : ""}` : "the turn ended"} before its result was recorded. Do not assume the tool completed successfully.`;
|
|
96506
|
+
}
|
|
96336
96507
|
const GOAL_CONTINUATION_PROMPT = [
|
|
96337
96508
|
"Continue working toward the active goal.",
|
|
96338
96509
|
"Keep the self-audit brief. Do not explore unrelated interpretations once the goal can be",
|
|
@@ -96567,6 +96738,11 @@ var TurnFlow = class {
|
|
|
96567
96738
|
turnId,
|
|
96568
96739
|
reason: "completed"
|
|
96569
96740
|
};
|
|
96741
|
+
try {
|
|
96742
|
+
this.agent.context.closeAbandonedToolExchange(abandonedToolResultOutput(ended));
|
|
96743
|
+
} catch (error) {
|
|
96744
|
+
console.error("closeAbandonedToolExchange failed", error);
|
|
96745
|
+
}
|
|
96570
96746
|
this.agent.usage.endTurn();
|
|
96571
96747
|
this.agent.emitEvent(ended);
|
|
96572
96748
|
return ended;
|
|
@@ -96646,6 +96822,11 @@ var TurnFlow = class {
|
|
|
96646
96822
|
};
|
|
96647
96823
|
}
|
|
96648
96824
|
}
|
|
96825
|
+
try {
|
|
96826
|
+
this.agent.context.closeAbandonedToolExchange(abandonedToolResultOutput(ended));
|
|
96827
|
+
} catch (error) {
|
|
96828
|
+
console.error("closeAbandonedToolExchange failed", error);
|
|
96829
|
+
}
|
|
96649
96830
|
if (this.currentId === turnId) this.agent.usage.endTurn();
|
|
96650
96831
|
this.agent.emitEvent(ended);
|
|
96651
96832
|
if (standalone && this.currentId === turnId) this.activeTurn = null;
|
|
@@ -102568,7 +102749,7 @@ var SessionSubagentHost = class {
|
|
|
102568
102749
|
text: childPrompt
|
|
102569
102750
|
}], origin);
|
|
102570
102751
|
await runChildTurnToCompletion(child, options.signal);
|
|
102571
|
-
let result = lastAssistantText(child);
|
|
102752
|
+
let result = lastAssistantText$1(child);
|
|
102572
102753
|
let remainingContinuations = SUMMARY_CONTINUATION_ATTEMPTS;
|
|
102573
102754
|
while (remainingContinuations > 0 && result.length < SUMMARY_MIN_LENGTH) {
|
|
102574
102755
|
remainingContinuations -= 1;
|
|
@@ -102578,7 +102759,7 @@ var SessionSubagentHost = class {
|
|
|
102578
102759
|
text: summary_continuation_default
|
|
102579
102760
|
}], origin);
|
|
102580
102761
|
await runChildTurnToCompletion(child, options.signal);
|
|
102581
|
-
result = lastAssistantText(child);
|
|
102762
|
+
result = lastAssistantText$1(child);
|
|
102582
102763
|
}
|
|
102583
102764
|
const usage = child.usage.data().total;
|
|
102584
102765
|
let findingsBlock = "";
|
|
@@ -102694,7 +102875,7 @@ async function runChildTurnToCompletion(child, signal) {
|
|
|
102694
102875
|
function throwIfSubagentStoppedAtMaxTokens(stopReason) {
|
|
102695
102876
|
if (stopReason === "max_tokens") throw new Error(`${SUBAGENT_MAX_TOKENS_ERROR}.`);
|
|
102696
102877
|
}
|
|
102697
|
-
function lastAssistantText(agent) {
|
|
102878
|
+
function lastAssistantText$1(agent) {
|
|
102698
102879
|
for (const message of [...agent.context.history].toReversed()) {
|
|
102699
102880
|
if (message.role !== "assistant") continue;
|
|
102700
102881
|
const text = message.content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
@@ -120191,10 +120372,10 @@ var ScreamAuthFacade = class {
|
|
|
120191
120372
|
};
|
|
120192
120373
|
//#endregion
|
|
120193
120374
|
//#region ../../packages/node-sdk/src/rpc.ts
|
|
120194
|
-
const MAIN_AGENT_ID$
|
|
120375
|
+
const MAIN_AGENT_ID$3 = "main";
|
|
120195
120376
|
var SDKRpcClient = class {
|
|
120196
120377
|
core;
|
|
120197
|
-
interactiveAgentId = MAIN_AGENT_ID$
|
|
120378
|
+
interactiveAgentId = MAIN_AGENT_ID$3;
|
|
120198
120379
|
ready;
|
|
120199
120380
|
rpc;
|
|
120200
120381
|
eventListeners = /* @__PURE__ */ new Set();
|
|
@@ -120772,7 +120953,7 @@ function errorMessage$2(error) {
|
|
|
120772
120953
|
}
|
|
120773
120954
|
//#endregion
|
|
120774
120955
|
//#region ../../packages/node-sdk/src/session.ts
|
|
120775
|
-
const MAIN_AGENT_ID$
|
|
120956
|
+
const MAIN_AGENT_ID$2 = "main";
|
|
120776
120957
|
var Session = class {
|
|
120777
120958
|
id;
|
|
120778
120959
|
workDir;
|
|
@@ -121212,7 +121393,7 @@ var Session = class {
|
|
|
121212
121393
|
this.emit({
|
|
121213
121394
|
type: "session.meta.updated",
|
|
121214
121395
|
sessionId: this.id,
|
|
121215
|
-
agentId: MAIN_AGENT_ID$
|
|
121396
|
+
agentId: MAIN_AGENT_ID$2,
|
|
121216
121397
|
title: patch.title,
|
|
121217
121398
|
patch
|
|
121218
121399
|
});
|
|
@@ -121634,7 +121815,7 @@ function optionalBuildString(value) {
|
|
|
121634
121815
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
121635
121816
|
}
|
|
121636
121817
|
const SCREAM_BUILD_INFO = {
|
|
121637
|
-
version: optionalBuildString("0.12.
|
|
121818
|
+
version: optionalBuildString("0.12.8"),
|
|
121638
121819
|
channel: optionalBuildString(""),
|
|
121639
121820
|
commit: optionalBuildString(""),
|
|
121640
121821
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -122621,28 +122802,46 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
122621
122802
|
name: "auto",
|
|
122622
122803
|
aliases: [],
|
|
122623
122804
|
description: "registry.auto_desc",
|
|
122624
|
-
priority:
|
|
122805
|
+
priority: 220,
|
|
122625
122806
|
availability: "always"
|
|
122626
122807
|
},
|
|
122627
122808
|
{
|
|
122628
122809
|
name: "yes",
|
|
122629
122810
|
aliases: ["yolo"],
|
|
122630
122811
|
description: "registry.yolo_desc",
|
|
122631
|
-
priority:
|
|
122812
|
+
priority: 219,
|
|
122632
122813
|
availability: "always"
|
|
122633
122814
|
},
|
|
122634
122815
|
{
|
|
122635
122816
|
name: "ask",
|
|
122636
122817
|
aliases: ["ask"],
|
|
122637
122818
|
description: "registry.ask_desc",
|
|
122638
|
-
priority:
|
|
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,
|
|
122639
122838
|
availability: "always"
|
|
122640
122839
|
},
|
|
122641
122840
|
{
|
|
122642
122841
|
name: "rlm",
|
|
122643
122842
|
aliases: ["rlm"],
|
|
122644
122843
|
description: "registry.rlm_desc",
|
|
122645
|
-
priority:
|
|
122844
|
+
priority: 215,
|
|
122646
122845
|
availability: "always"
|
|
122647
122846
|
},
|
|
122648
122847
|
{
|
|
@@ -122650,39 +122849,21 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
122650
122849
|
aliases: [],
|
|
122651
122850
|
description: "registry.rlm_max_depth_desc",
|
|
122652
122851
|
argumentHint: "[N]",
|
|
122653
|
-
priority:
|
|
122654
|
-
availability: "always"
|
|
122655
|
-
},
|
|
122656
|
-
{
|
|
122657
|
-
name: "wolfpack",
|
|
122658
|
-
aliases: ["wp"],
|
|
122659
|
-
description: "registry.wolfpack_desc",
|
|
122660
|
-
priority: 122,
|
|
122852
|
+
priority: 214,
|
|
122661
122853
|
availability: "always"
|
|
122662
122854
|
},
|
|
122663
122855
|
{
|
|
122664
122856
|
name: "sessions",
|
|
122665
122857
|
aliases: ["resume"],
|
|
122666
122858
|
description: "registry.sessions_desc",
|
|
122667
|
-
priority:
|
|
122668
|
-
},
|
|
122669
|
-
{
|
|
122670
|
-
name: "goal",
|
|
122671
|
-
aliases: ["goaloff"],
|
|
122672
|
-
description: "registry.goal_desc",
|
|
122673
|
-
argumentHint: "[objective]",
|
|
122674
|
-
priority: 120,
|
|
122675
|
-
availability: (args) => {
|
|
122676
|
-
const trimmed = args.trim();
|
|
122677
|
-
return trimmed === "" || trimmed === "status" || trimmed === "pause" || trimmed === "off" ? "always" : "idle-only";
|
|
122678
|
-
}
|
|
122859
|
+
priority: 213
|
|
122679
122860
|
},
|
|
122680
122861
|
{
|
|
122681
122862
|
name: "memory",
|
|
122682
122863
|
aliases: ["memo", "mem"],
|
|
122683
122864
|
description: "registry.memory_desc",
|
|
122684
122865
|
argumentHint: "[query]",
|
|
122685
|
-
priority:
|
|
122866
|
+
priority: 212,
|
|
122686
122867
|
availability: "always"
|
|
122687
122868
|
},
|
|
122688
122869
|
{
|
|
@@ -122690,96 +122871,61 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
122690
122871
|
aliases: ["know"],
|
|
122691
122872
|
description: "registry.knowledge_desc",
|
|
122692
122873
|
argumentHint: "[query]",
|
|
122693
|
-
priority:
|
|
122874
|
+
priority: 211,
|
|
122694
122875
|
availability: "always"
|
|
122695
122876
|
},
|
|
122696
|
-
{
|
|
122697
|
-
name: "new",
|
|
122698
|
-
aliases: ["clear"],
|
|
122699
|
-
description: "registry.new_desc",
|
|
122700
|
-
priority: 120
|
|
122701
|
-
},
|
|
122702
122877
|
{
|
|
122703
122878
|
name: "model",
|
|
122704
122879
|
aliases: [],
|
|
122705
122880
|
description: "registry.model_desc",
|
|
122706
122881
|
argumentHint: "[alias]",
|
|
122707
|
-
priority:
|
|
122882
|
+
priority: 210
|
|
122883
|
+
},
|
|
122884
|
+
{
|
|
122885
|
+
name: "new",
|
|
122886
|
+
aliases: ["clear"],
|
|
122887
|
+
description: "registry.new_desc",
|
|
122888
|
+
priority: 209
|
|
122708
122889
|
},
|
|
122709
122890
|
{
|
|
122710
122891
|
name: "compact",
|
|
122711
122892
|
aliases: [],
|
|
122712
122893
|
description: "registry.compact_desc",
|
|
122713
|
-
priority:
|
|
122894
|
+
priority: 208
|
|
122714
122895
|
},
|
|
122715
122896
|
{
|
|
122716
|
-
name: "
|
|
122717
|
-
aliases: ["
|
|
122718
|
-
description: "registry.
|
|
122719
|
-
priority:
|
|
122720
|
-
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"
|
|
122721
122902
|
},
|
|
122722
122903
|
{
|
|
122723
122904
|
name: "plan",
|
|
122724
122905
|
aliases: [],
|
|
122725
122906
|
description: "registry.plan_desc",
|
|
122726
|
-
priority:
|
|
122727
|
-
availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
|
|
122728
|
-
},
|
|
122729
|
-
{
|
|
122730
|
-
name: "fusionplan",
|
|
122731
|
-
aliases: ["fp"],
|
|
122732
|
-
description: "registry.fusionplan_desc",
|
|
122733
|
-
priority: 118,
|
|
122907
|
+
priority: 206,
|
|
122734
122908
|
availability: (args) => args.trim().toLowerCase() === "clear" ? "idle-only" : "always"
|
|
122735
122909
|
},
|
|
122736
122910
|
{
|
|
122737
122911
|
name: "tasks",
|
|
122738
122912
|
aliases: ["task"],
|
|
122739
122913
|
description: "registry.tasks_desc",
|
|
122740
|
-
priority:
|
|
122741
|
-
availability: "always"
|
|
122742
|
-
},
|
|
122743
|
-
{
|
|
122744
|
-
name: "help",
|
|
122745
|
-
aliases: ["h", "?"],
|
|
122746
|
-
description: "registry.help_desc",
|
|
122747
|
-
priority: 116,
|
|
122748
|
-
availability: "always"
|
|
122749
|
-
},
|
|
122750
|
-
{
|
|
122751
|
-
name: "status",
|
|
122752
|
-
aliases: [],
|
|
122753
|
-
description: "registry.status_desc",
|
|
122754
|
-
priority: 115,
|
|
122755
|
-
availability: "always"
|
|
122756
|
-
},
|
|
122757
|
-
{
|
|
122758
|
-
name: "usage",
|
|
122759
|
-
aliases: [],
|
|
122760
|
-
description: "registry.usage_desc",
|
|
122761
|
-
priority: 114,
|
|
122914
|
+
priority: 205,
|
|
122762
122915
|
availability: "always"
|
|
122763
122916
|
},
|
|
122764
122917
|
{
|
|
122765
122918
|
name: "btw",
|
|
122766
122919
|
aliases: [],
|
|
122767
122920
|
description: "registry.btw_desc",
|
|
122768
|
-
priority:
|
|
122921
|
+
priority: 204,
|
|
122769
122922
|
availability: "always"
|
|
122770
122923
|
},
|
|
122771
122924
|
{
|
|
122772
122925
|
name: "like",
|
|
122773
122926
|
aliases: [],
|
|
122774
122927
|
description: "registry.like_desc",
|
|
122775
|
-
priority:
|
|
122776
|
-
availability: "always"
|
|
122777
|
-
},
|
|
122778
|
-
{
|
|
122779
|
-
name: "mcp",
|
|
122780
|
-
aliases: [],
|
|
122781
|
-
description: "registry.mcp_desc",
|
|
122782
|
-
priority: 112,
|
|
122928
|
+
priority: 203,
|
|
122783
122929
|
availability: "always"
|
|
122784
122930
|
},
|
|
122785
122931
|
{
|
|
@@ -122790,121 +122936,162 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
122790
122936
|
"plugins"
|
|
122791
122937
|
],
|
|
122792
122938
|
description: "registry.skill_desc",
|
|
122793
|
-
priority:
|
|
122939
|
+
priority: 202,
|
|
122794
122940
|
availability: "always"
|
|
122795
122941
|
},
|
|
122796
122942
|
{
|
|
122797
|
-
name: "
|
|
122943
|
+
name: "fork",
|
|
122798
122944
|
aliases: [],
|
|
122799
|
-
description: "registry.
|
|
122800
|
-
priority:
|
|
122945
|
+
description: "registry.fork_desc",
|
|
122946
|
+
priority: 201
|
|
122947
|
+
},
|
|
122948
|
+
{
|
|
122949
|
+
name: "title",
|
|
122950
|
+
aliases: ["rename"],
|
|
122951
|
+
description: "registry.title_desc",
|
|
122952
|
+
priority: 200,
|
|
122801
122953
|
availability: "always"
|
|
122802
122954
|
},
|
|
122803
122955
|
{
|
|
122804
|
-
name: "
|
|
122956
|
+
name: "config",
|
|
122805
122957
|
aliases: [],
|
|
122806
|
-
description: "registry.
|
|
122807
|
-
priority:
|
|
122958
|
+
description: "registry.config_desc",
|
|
122959
|
+
priority: 199
|
|
122960
|
+
},
|
|
122961
|
+
{
|
|
122962
|
+
name: "help",
|
|
122963
|
+
aliases: ["h", "?"],
|
|
122964
|
+
description: "registry.help_desc",
|
|
122965
|
+
priority: 198,
|
|
122808
122966
|
availability: "always"
|
|
122809
122967
|
},
|
|
122810
122968
|
{
|
|
122811
|
-
name: "
|
|
122812
|
-
aliases: [],
|
|
122813
|
-
description: "registry.
|
|
122814
|
-
priority:
|
|
122969
|
+
name: "make-skill",
|
|
122970
|
+
aliases: ["makeskill", "craftskill"],
|
|
122971
|
+
description: "registry.make_skill_desc",
|
|
122972
|
+
priority: 197,
|
|
122815
122973
|
availability: "idle-only"
|
|
122816
122974
|
},
|
|
122817
122975
|
{
|
|
122818
|
-
name: "
|
|
122976
|
+
name: "mcp",
|
|
122819
122977
|
aliases: [],
|
|
122820
|
-
description: "registry.
|
|
122821
|
-
priority:
|
|
122978
|
+
description: "registry.mcp_desc",
|
|
122979
|
+
priority: 196,
|
|
122980
|
+
availability: "always"
|
|
122822
122981
|
},
|
|
122823
122982
|
{
|
|
122824
|
-
name: "
|
|
122825
|
-
aliases: [
|
|
122826
|
-
description: "registry.
|
|
122827
|
-
priority:
|
|
122983
|
+
name: "status",
|
|
122984
|
+
aliases: [],
|
|
122985
|
+
description: "registry.status_desc",
|
|
122986
|
+
priority: 195,
|
|
122828
122987
|
availability: "always"
|
|
122829
122988
|
},
|
|
122830
122989
|
{
|
|
122831
|
-
name: "
|
|
122990
|
+
name: "usage",
|
|
122832
122991
|
aliases: [],
|
|
122833
|
-
description: "registry.
|
|
122834
|
-
priority:
|
|
122992
|
+
description: "registry.usage_desc",
|
|
122993
|
+
priority: 194,
|
|
122994
|
+
availability: "always"
|
|
122835
122995
|
},
|
|
122836
122996
|
{
|
|
122837
|
-
name: "
|
|
122997
|
+
name: "revoke",
|
|
122838
122998
|
aliases: [],
|
|
122839
|
-
description: "registry.
|
|
122840
|
-
priority:
|
|
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,
|
|
122841
123015
|
availability: "always"
|
|
122842
123016
|
},
|
|
122843
123017
|
{
|
|
122844
123018
|
name: "theme",
|
|
122845
123019
|
aliases: [],
|
|
122846
123020
|
description: "registry.theme_desc",
|
|
122847
|
-
priority:
|
|
123021
|
+
priority: 190,
|
|
122848
123022
|
availability: "always"
|
|
122849
123023
|
},
|
|
122850
123024
|
{
|
|
122851
123025
|
name: "language",
|
|
122852
123026
|
aliases: ["lang"],
|
|
122853
123027
|
description: "registry.language_desc",
|
|
122854
|
-
priority:
|
|
123028
|
+
priority: 189,
|
|
123029
|
+
availability: "always"
|
|
123030
|
+
},
|
|
123031
|
+
{
|
|
123032
|
+
name: "permission",
|
|
123033
|
+
aliases: [],
|
|
123034
|
+
description: "registry.permission_desc",
|
|
123035
|
+
priority: 188,
|
|
122855
123036
|
availability: "always"
|
|
122856
123037
|
},
|
|
122857
123038
|
{
|
|
122858
123039
|
name: "editor",
|
|
122859
123040
|
aliases: [],
|
|
122860
123041
|
description: "registry.editor_desc",
|
|
122861
|
-
priority:
|
|
123042
|
+
priority: 187,
|
|
122862
123043
|
availability: "always"
|
|
122863
123044
|
},
|
|
122864
123045
|
{
|
|
122865
123046
|
name: "settings",
|
|
122866
123047
|
aliases: [],
|
|
122867
123048
|
description: "registry.settings_desc",
|
|
122868
|
-
priority:
|
|
123049
|
+
priority: 186,
|
|
122869
123050
|
availability: "always"
|
|
122870
123051
|
},
|
|
122871
123052
|
{
|
|
122872
123053
|
name: "init",
|
|
122873
123054
|
aliases: [],
|
|
122874
123055
|
description: "registry.init_desc",
|
|
122875
|
-
priority:
|
|
123056
|
+
priority: 185
|
|
122876
123057
|
},
|
|
122877
123058
|
{
|
|
122878
123059
|
name: "export-md",
|
|
122879
123060
|
aliases: ["export"],
|
|
122880
123061
|
description: "registry.export_md_desc",
|
|
122881
|
-
priority:
|
|
123062
|
+
priority: 184
|
|
122882
123063
|
},
|
|
122883
123064
|
{
|
|
122884
123065
|
name: "export-debug-zip",
|
|
122885
123066
|
aliases: [],
|
|
122886
123067
|
description: "registry.export_debug_desc",
|
|
122887
|
-
priority:
|
|
123068
|
+
priority: 183
|
|
123069
|
+
},
|
|
123070
|
+
{
|
|
123071
|
+
name: "eval",
|
|
123072
|
+
aliases: [],
|
|
123073
|
+
description: "registry.eval_desc",
|
|
123074
|
+
priority: 182
|
|
122888
123075
|
},
|
|
122889
123076
|
{
|
|
122890
123077
|
name: "update",
|
|
122891
123078
|
aliases: [],
|
|
122892
123079
|
description: "registry.update_desc",
|
|
122893
|
-
priority:
|
|
123080
|
+
priority: 181,
|
|
122894
123081
|
availability: "idle-only"
|
|
122895
123082
|
},
|
|
122896
123083
|
{
|
|
122897
123084
|
name: "version",
|
|
122898
123085
|
aliases: [],
|
|
122899
123086
|
description: "registry.version_desc",
|
|
122900
|
-
priority:
|
|
123087
|
+
priority: 180,
|
|
122901
123088
|
availability: "always"
|
|
122902
123089
|
},
|
|
122903
123090
|
{
|
|
122904
123091
|
name: "logout",
|
|
122905
123092
|
aliases: ["disconnect"],
|
|
122906
123093
|
description: "registry.logout_desc",
|
|
122907
|
-
priority:
|
|
123094
|
+
priority: 179
|
|
122908
123095
|
},
|
|
122909
123096
|
{
|
|
122910
123097
|
name: "exit",
|
|
@@ -123022,7 +123209,7 @@ function getCtrlDHint() {
|
|
|
123022
123209
|
function getCtrlCHint() {
|
|
123023
123210
|
return t("constant.ctrl_c_hint");
|
|
123024
123211
|
}
|
|
123025
|
-
const MAIN_AGENT_ID = "main";
|
|
123212
|
+
const MAIN_AGENT_ID$1 = "main";
|
|
123026
123213
|
const EXIT_CONFIRM_WINDOW_MS = 1500;
|
|
123027
123214
|
function isManagedUsageProvider(providerKey) {
|
|
123028
123215
|
return providerKey === DEFAULT_OAUTH_PROVIDER_NAME;
|
|
@@ -125546,7 +125733,15 @@ const HEADING_HASH_PREFIX = /^((?:\u001B\[[0-9;]*m)*)#{1,6}[ \t]+/;
|
|
|
125546
125733
|
* takes one formatter function per token; tokens not listed here fall back to
|
|
125547
125734
|
* its DEFAULT_THEME.
|
|
125548
125735
|
*/
|
|
125549
|
-
|
|
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) {
|
|
125550
125745
|
const keyword = chalk.hex(colors.primary);
|
|
125551
125746
|
const str = chalk.hex(colors.success);
|
|
125552
125747
|
const comment = chalk.hex(colors.textDim);
|
|
@@ -125596,7 +125791,7 @@ function createMarkdownTheme(colors) {
|
|
|
125596
125791
|
const stripHash = (text) => text.replace(HEADING_HASH_PREFIX, "$1");
|
|
125597
125792
|
const muted = chalk.hex(colors.textMuted);
|
|
125598
125793
|
const border = chalk.hex(colors.border);
|
|
125599
|
-
const codeTheme =
|
|
125794
|
+
const codeTheme = createMarkdownCodeHighlightTheme(colors);
|
|
125600
125795
|
return {
|
|
125601
125796
|
heading: (text) => chalk.bold.hex(colors.text)(stripHash(text)),
|
|
125602
125797
|
link: (text) => chalk.hex(colors.mdLink)(text),
|
|
@@ -127448,7 +127643,7 @@ async function guidedGoalSetup(host) {
|
|
|
127448
127643
|
host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
|
|
127449
127644
|
return;
|
|
127450
127645
|
}
|
|
127451
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
127646
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-BIXonNNb.mjs");
|
|
127452
127647
|
const initialDesc = await promptText(host, TextInputDialogComponent, {
|
|
127453
127648
|
title: t("goal.setup_title_initial"),
|
|
127454
127649
|
subtitle: t("goal.setup_desc_hint"),
|
|
@@ -127469,7 +127664,7 @@ async function guidedGoalSetup(host) {
|
|
|
127469
127664
|
await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
|
|
127470
127665
|
}
|
|
127471
127666
|
async function showGoalConfigWizard(host, session, objective, replace) {
|
|
127472
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
127667
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-BIXonNNb.mjs");
|
|
127473
127668
|
const turnInput = await promptNumber(host, TextInputDialogComponent, {
|
|
127474
127669
|
title: t("goal.wizard_title", { objective }),
|
|
127475
127670
|
subtitle: t("goal.budget_turns_hint"),
|
|
@@ -128834,6 +129029,60 @@ var CachedContainer = class extends Container {
|
|
|
128834
129029
|
}
|
|
128835
129030
|
};
|
|
128836
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
|
|
128837
129086
|
//#region src/tui/components/media/code-highlight.ts
|
|
128838
129087
|
/**
|
|
128839
129088
|
* Shared syntax-highlighting helpers for code previews
|
|
@@ -128871,13 +129120,14 @@ function langFromPath(filePath) {
|
|
|
128871
129120
|
const lang = EXT_LANG_MAP[ext] ?? ext;
|
|
128872
129121
|
return supportsLanguage(lang) ? lang : void 0;
|
|
128873
129122
|
}
|
|
128874
|
-
function highlightLines(code, lang) {
|
|
129123
|
+
function highlightLines(code, lang, colors) {
|
|
128875
129124
|
const normalizedLang = lang?.trim().toLowerCase();
|
|
128876
129125
|
if (!normalizedLang || !supportsLanguage(normalizedLang)) return code.split("\n");
|
|
128877
129126
|
try {
|
|
128878
129127
|
return highlight(code, {
|
|
128879
129128
|
language: normalizedLang,
|
|
128880
|
-
ignoreIllegals: true
|
|
129129
|
+
ignoreIllegals: true,
|
|
129130
|
+
theme: createCodeHighlightTheme(colors)
|
|
128881
129131
|
}).split("\n");
|
|
128882
129132
|
} catch {
|
|
128883
129133
|
return code.split("\n");
|
|
@@ -128939,10 +129189,12 @@ function visualizeIndent(line) {
|
|
|
128939
129189
|
}
|
|
128940
129190
|
/**
|
|
128941
129191
|
* Render a diff line's code with leading-whitespace visualization (tabs as
|
|
128942
|
-
* `->`, spaces as `·`, dimmed) and optional syntax highlighting.
|
|
128943
|
-
* runs on the raw code
|
|
128944
|
-
*
|
|
128945
|
-
*
|
|
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.
|
|
128946
129198
|
*
|
|
128947
129199
|
* When highlighting is on and produced token colors, the diff line color is
|
|
128948
129200
|
* layered as the base foreground: syntax tokens override it, but every ANSI
|
|
@@ -128951,17 +129203,18 @@ function visualizeIndent(line) {
|
|
|
128951
129203
|
* When highlighting is off (streaming) or produced no token colors, the code
|
|
128952
129204
|
* part is colored with the diff line color instead.
|
|
128953
129205
|
*/
|
|
128954
|
-
function renderDiffCode(code, colorFn, highlight, lang) {
|
|
128955
|
-
const { text, indentEnd } = visualizeIndent(
|
|
129206
|
+
function renderDiffCode(code, colorFn, highlight, lang, colors) {
|
|
129207
|
+
const { text, indentEnd } = visualizeIndent(code);
|
|
128956
129208
|
const indent = text.slice(0, indentEnd);
|
|
128957
129209
|
const rest = text.slice(indentEnd);
|
|
128958
129210
|
const dimIndent = indent.length > 0 ? chalk.dim(indent) : indent;
|
|
128959
|
-
|
|
129211
|
+
const source = highlight ? highlightLines(rest, lang, colors)[0] ?? rest : rest;
|
|
129212
|
+
if (highlight && ANSI_RE.test(source)) {
|
|
128960
129213
|
const prefix = extractAnsiPrefix(colorFn);
|
|
128961
|
-
if (prefix.length > 0) return dimIndent + prefix +
|
|
128962
|
-
return dimIndent +
|
|
129214
|
+
if (prefix.length > 0) return dimIndent + prefix + source.replace(ANSI_RESET_RE, (m) => m + prefix) + "\x1B[39m";
|
|
129215
|
+
return dimIndent + source;
|
|
128963
129216
|
}
|
|
128964
|
-
return dimIndent + colorFn(
|
|
129217
|
+
return dimIndent + colorFn(source);
|
|
128965
129218
|
}
|
|
128966
129219
|
/**
|
|
128967
129220
|
* Compute word-level diff between two single lines and highlight the changed
|
|
@@ -129095,7 +129348,7 @@ function renderDiffLines(oldText, newText, path, colors, isIncomplete = false, o
|
|
|
129095
129348
|
const line = shown[i];
|
|
129096
129349
|
const marker = line.kind === "add" ? "+" : "-";
|
|
129097
129350
|
const color = line.kind === "add" ? s.add : s.del;
|
|
129098
|
-
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));
|
|
129099
129352
|
i += 1;
|
|
129100
129353
|
}
|
|
129101
129354
|
const hidden = changedLines.length - shown.length;
|
|
@@ -129146,11 +129399,11 @@ function buildClusters(diffLines, contextLines) {
|
|
|
129146
129399
|
removedCount: removed
|
|
129147
129400
|
};
|
|
129148
129401
|
}
|
|
129149
|
-
function formatDiffRow(line, s, doHighlight, lang) {
|
|
129402
|
+
function formatDiffRow(line, s, doHighlight, lang, colors) {
|
|
129150
129403
|
const gutter = s.gutter(String(line.lineNum).padStart(4) + " ");
|
|
129151
|
-
if (line.kind === "add") return gutter + s.add("+ ") + renderDiffCode(line.code, s.add, doHighlight, lang);
|
|
129152
|
-
if (line.kind === "delete") return gutter + s.del("- ") + renderDiffCode(line.code, s.del, doHighlight, lang);
|
|
129153
|
-
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);
|
|
129154
129407
|
}
|
|
129155
129408
|
/**
|
|
129156
129409
|
* Render a diff with surrounding context, eliding unchanged middle
|
|
@@ -129221,7 +129474,7 @@ function renderDiffLinesClustered(oldText, newText, path, colors, opts = {}) {
|
|
|
129221
129474
|
i += 2;
|
|
129222
129475
|
continue;
|
|
129223
129476
|
}
|
|
129224
|
-
output.push(formatDiffRow(line, s, doHighlight, lang));
|
|
129477
|
+
output.push(formatDiffRow(line, s, doHighlight, lang, colors));
|
|
129225
129478
|
body++;
|
|
129226
129479
|
if (line.kind !== "context") shownChanges++;
|
|
129227
129480
|
prevEnd = i;
|
|
@@ -131104,7 +131357,7 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
|
|
|
131104
131357
|
if (name === "Write") {
|
|
131105
131358
|
const content = str(this.toolCall.args["content"]);
|
|
131106
131359
|
if (content.length === 0) return;
|
|
131107
|
-
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);
|
|
131108
131361
|
const writeShouldCap = !this.expanded;
|
|
131109
131362
|
const shown = writeShouldCap ? allLines.slice(0, 10) : allLines;
|
|
131110
131363
|
const remaining = allLines.length - shown.length;
|
|
@@ -131209,7 +131462,7 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
|
|
|
131209
131462
|
const fragmentEnd = Math.min(streamText.length, fragmentStart + ToolCallComponent.WRITE_STREAM_TAIL_RAW_CHARS);
|
|
131210
131463
|
const fragment = unescapeJsonStringValue(streamText, fragmentStart, fragmentEnd);
|
|
131211
131464
|
if (fragment.length === 0) return;
|
|
131212
|
-
const lines = highlightLines(fragment, this.writeStreamLang);
|
|
131465
|
+
const lines = highlightLines(fragment, this.writeStreamLang, this.colors);
|
|
131213
131466
|
const displayLines = lines.slice(-10);
|
|
131214
131467
|
const firstFragmentLineNo = this.writeStreamNlCount + (tailStart > contentStart ? 2 : 1);
|
|
131215
131468
|
const skipped = lines.length - displayLines.length;
|
|
@@ -136032,6 +136285,429 @@ async function handleKnowledgeCommand(host, _args) {
|
|
|
136032
136285
|
showMenu();
|
|
136033
136286
|
}
|
|
136034
136287
|
//#endregion
|
|
136288
|
+
//#region ../../packages/evals/src/judge.ts
|
|
136289
|
+
/** Runs every rule against the input; a rule passes when its check is true. */
|
|
136290
|
+
function judgeOutput(input, rules) {
|
|
136291
|
+
const passed = [];
|
|
136292
|
+
const failed = [];
|
|
136293
|
+
for (const rule of rules) if (rule.check(input)) passed.push(rule.name);
|
|
136294
|
+
else failed.push(rule.name);
|
|
136295
|
+
return {
|
|
136296
|
+
passed,
|
|
136297
|
+
failed
|
|
136298
|
+
};
|
|
136299
|
+
}
|
|
136300
|
+
//#endregion
|
|
136301
|
+
//#region ../../packages/evals/src/harness.ts
|
|
136302
|
+
/**
|
|
136303
|
+
* Minimal end-to-end eval harness built on the public node-sdk
|
|
136304
|
+
* `ScreamHarness`. Each eval run gets an isolated temp **workspace** so runs
|
|
136305
|
+
* never touch real project files; the scream home defaults to the real one so
|
|
136306
|
+
* the provider/API-key configuration in `~/.scream-code/config.toml` is
|
|
136307
|
+
* honored (pass `screamHome` to override for fully isolated runs). Model
|
|
136308
|
+
* selection follows `SCREAM_EVAL_MODEL` (e.g. `provider/model`);
|
|
136309
|
+
* when unset the harness throws with a clear message.
|
|
136310
|
+
*/
|
|
136311
|
+
/** The main agent id; only its events settle the eval turn wait. */
|
|
136312
|
+
const MAIN_AGENT_ID = "main";
|
|
136313
|
+
/** The current scream default model, when resolvable from config. */
|
|
136314
|
+
const DEFAULT_EVAL_MODEL = process.env["SCREAM_EVAL_MODEL"];
|
|
136315
|
+
/**
|
|
136316
|
+
* Runs a single end-to-end prompt against a fresh isolated session and
|
|
136317
|
+
* returns the final assistant text plus token usage. The session, workspace
|
|
136318
|
+
* and scream home are torn down afterwards.
|
|
136319
|
+
*/
|
|
136320
|
+
async function runEvalPrompt(input, options = {}) {
|
|
136321
|
+
const workDir = options.workDir ?? await mkdtemp(join(tmpdir(), "scream-eval-ws-"));
|
|
136322
|
+
const screamHome = options.screamHome;
|
|
136323
|
+
const createdWorkDir = options.workDir === void 0;
|
|
136324
|
+
let harness;
|
|
136325
|
+
let session;
|
|
136326
|
+
try {
|
|
136327
|
+
for (const fixture of (typeof input === "string" ? [] : input.fixtures) ?? []) await writeFile(join(workDir, fixture.name), fixture.content, "utf-8");
|
|
136328
|
+
const prompt = typeof input === "string" ? input : input.prompt;
|
|
136329
|
+
const model = options.model ?? DEFAULT_EVAL_MODEL;
|
|
136330
|
+
if (model === void 0 || model.length === 0) throw new Error("No eval model configured. Set SCREAM_EVAL_MODEL, e.g. SCREAM_EVAL_MODEL=provider/model pnpm eval");
|
|
136331
|
+
const [provider, modelId, ...rest] = model.split("/");
|
|
136332
|
+
const usageProvider = rest.length > 0 ? model : provider ?? "unknown";
|
|
136333
|
+
const usageModel = modelId === void 0 || rest.length > 0 ? model : modelId;
|
|
136334
|
+
harness = new ScreamHarness({ homeDir: screamHome });
|
|
136335
|
+
session = await harness.createSession({
|
|
136336
|
+
workDir,
|
|
136337
|
+
model,
|
|
136338
|
+
thinking: options.thinking ?? "off",
|
|
136339
|
+
permission: "yolo"
|
|
136340
|
+
});
|
|
136341
|
+
const { timedOut } = await promptAndWaitForTurnEnd(session, prompt);
|
|
136342
|
+
let output = "";
|
|
136343
|
+
try {
|
|
136344
|
+
output = lastAssistantText((await session.getContext()).history);
|
|
136345
|
+
} catch {
|
|
136346
|
+
output = "";
|
|
136347
|
+
}
|
|
136348
|
+
const total = (await session.getUsage().catch(() => void 0))?.total;
|
|
136349
|
+
let verifiedFile = false;
|
|
136350
|
+
if (options.verifyFileAfterTurn !== void 0) try {
|
|
136351
|
+
verifiedFile = await readFile(join(workDir, options.verifyFileAfterTurn.path), "utf-8") === options.verifyFileAfterTurn.content;
|
|
136352
|
+
} catch {
|
|
136353
|
+
verifiedFile = false;
|
|
136354
|
+
}
|
|
136355
|
+
return {
|
|
136356
|
+
output,
|
|
136357
|
+
timedOut,
|
|
136358
|
+
verifiedFile,
|
|
136359
|
+
usage: {
|
|
136360
|
+
provider: usageProvider,
|
|
136361
|
+
model: usageModel,
|
|
136362
|
+
inputTokens: total?.inputOther ?? 0,
|
|
136363
|
+
outputTokens: total?.output ?? 0,
|
|
136364
|
+
totalTokens: (total?.inputOther ?? 0) + (total?.output ?? 0) + (total?.inputCacheRead ?? 0) + (total?.inputCacheCreation ?? 0)
|
|
136365
|
+
}
|
|
136366
|
+
};
|
|
136367
|
+
} finally {
|
|
136368
|
+
if (session !== void 0) await session.close({ extractMemories: false }).catch(() => {});
|
|
136369
|
+
if (createdWorkDir) await rm(workDir, {
|
|
136370
|
+
recursive: true,
|
|
136371
|
+
force: true
|
|
136372
|
+
}).catch(() => {});
|
|
136373
|
+
}
|
|
136374
|
+
}
|
|
136375
|
+
function lastAssistantText(history) {
|
|
136376
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
136377
|
+
const message = history[i];
|
|
136378
|
+
if (message.role !== "assistant") continue;
|
|
136379
|
+
const text = (message.content ?? []).filter((part) => part.type === "text" && part.text !== void 0).map((part) => part.text).join("");
|
|
136380
|
+
if (text.trim().length > 0) return text.trim();
|
|
136381
|
+
}
|
|
136382
|
+
return "";
|
|
136383
|
+
}
|
|
136384
|
+
/**
|
|
136385
|
+
* Sends a prompt and resolves when the main agent's turn ends. `Session.prompt`
|
|
136386
|
+
* only enqueues the RPC request; the assistant reply arrives asynchronously as
|
|
136387
|
+
* events, so we must wait for the `turn.ended` event to know the run finished.
|
|
136388
|
+
*
|
|
136389
|
+
* A hard timeout guards against a turn that never ends (e.g. a model looping
|
|
136390
|
+
* through tools instead of converging, or a network interruption). On timeout
|
|
136391
|
+
* we cancel the session so the caller's cleanup path still runs instead of
|
|
136392
|
+
* leaking the session and temp workspace. 90s is enough to complete any
|
|
136393
|
+
* legitimate single-turn tool round-trip; longer waits are almost always a
|
|
136394
|
+
* stuck model, so we fail fast instead of burning tokens.
|
|
136395
|
+
*/
|
|
136396
|
+
const TURN_WAIT_TIMEOUT_MS = 9e4;
|
|
136397
|
+
/**
|
|
136398
|
+
* Resolves with `timedOut: true` when the turn hit the timeout (model looping
|
|
136399
|
+
* or stuck) instead of throwing, so callers can still inspect side effects
|
|
136400
|
+
* (e.g. a file the Write tool already created). Real errors (turn failed /
|
|
136401
|
+
* error event) still reject.
|
|
136402
|
+
*/
|
|
136403
|
+
async function promptAndWaitForTurnEnd(session, prompt) {
|
|
136404
|
+
const { promise, resolve, reject } = promiseWithResolvers();
|
|
136405
|
+
let activeTurnId;
|
|
136406
|
+
let activeAgentId;
|
|
136407
|
+
let settled = false;
|
|
136408
|
+
let timeout;
|
|
136409
|
+
let graceTimer;
|
|
136410
|
+
/** Set once the timeout fires; guards against the cancel-triggered
|
|
136411
|
+
* `turn.ended('cancelled')` being misread as a real failure. */
|
|
136412
|
+
let timeoutFired = false;
|
|
136413
|
+
const finish = (result) => {
|
|
136414
|
+
if (settled) return;
|
|
136415
|
+
settled = true;
|
|
136416
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
136417
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
136418
|
+
if (result instanceof Error) reject(result);
|
|
136419
|
+
else resolve(result);
|
|
136420
|
+
};
|
|
136421
|
+
const unsubscribe = session.onEvent((event) => {
|
|
136422
|
+
if (event.agentId !== MAIN_AGENT_ID) return;
|
|
136423
|
+
if (event.type === "error") {
|
|
136424
|
+
finish(/* @__PURE__ */ new Error(`${event.code}: ${event.message}`));
|
|
136425
|
+
return;
|
|
136426
|
+
}
|
|
136427
|
+
if (event.type === "turn.started" && activeTurnId === void 0) {
|
|
136428
|
+
activeTurnId = event.turnId;
|
|
136429
|
+
activeAgentId = event.agentId;
|
|
136430
|
+
return;
|
|
136431
|
+
}
|
|
136432
|
+
if (activeTurnId === void 0 || activeAgentId === void 0 || !("turnId" in event) || event.turnId !== activeTurnId || event.agentId !== activeAgentId) return;
|
|
136433
|
+
if (event.type === "turn.ended") if (event.reason === "completed") finish({ timedOut: false });
|
|
136434
|
+
else if (timeoutFired && event.reason === "cancelled") return;
|
|
136435
|
+
else finish(/* @__PURE__ */ new Error(`Turn ended with reason: ${event.reason}`));
|
|
136436
|
+
});
|
|
136437
|
+
try {
|
|
136438
|
+
await session.prompt(prompt);
|
|
136439
|
+
timeout = setTimeout(() => {
|
|
136440
|
+
timeoutFired = true;
|
|
136441
|
+
session.cancel().catch(() => {});
|
|
136442
|
+
graceTimer = setTimeout(() => {
|
|
136443
|
+
finish({ timedOut: true });
|
|
136444
|
+
}, 500);
|
|
136445
|
+
}, TURN_WAIT_TIMEOUT_MS);
|
|
136446
|
+
return await promise;
|
|
136447
|
+
} finally {
|
|
136448
|
+
unsubscribe();
|
|
136449
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
136450
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
136451
|
+
}
|
|
136452
|
+
}
|
|
136453
|
+
function promiseWithResolvers() {
|
|
136454
|
+
let resolve;
|
|
136455
|
+
let reject;
|
|
136456
|
+
return {
|
|
136457
|
+
promise: new Promise((res, rej) => {
|
|
136458
|
+
resolve = res;
|
|
136459
|
+
reject = rej;
|
|
136460
|
+
}),
|
|
136461
|
+
resolve,
|
|
136462
|
+
reject
|
|
136463
|
+
};
|
|
136464
|
+
}
|
|
136465
|
+
const EVAL_CASES = [
|
|
136466
|
+
{
|
|
136467
|
+
id: "smoke",
|
|
136468
|
+
name: "Smoke — answers a trivial question",
|
|
136469
|
+
input: "What is the capital of France? Answer in one word.",
|
|
136470
|
+
rules: [{
|
|
136471
|
+
name: "answers the capital",
|
|
136472
|
+
check: (output) => {
|
|
136473
|
+
const lower = output.toLowerCase();
|
|
136474
|
+
return lower.includes("paris") || lower.includes("巴黎");
|
|
136475
|
+
}
|
|
136476
|
+
}],
|
|
136477
|
+
extra: (result) => {
|
|
136478
|
+
const failures = [];
|
|
136479
|
+
if (result.usage.totalTokens <= 0) failures.push("usage.totalTokens must be > 0");
|
|
136480
|
+
if (result.usage.model.length === 0) failures.push("usage.model must be non-empty");
|
|
136481
|
+
return failures;
|
|
136482
|
+
}
|
|
136483
|
+
},
|
|
136484
|
+
{
|
|
136485
|
+
id: "read-file",
|
|
136486
|
+
name: "Regression — reads a file via the Read tool",
|
|
136487
|
+
input: {
|
|
136488
|
+
prompt: "Read the file data.txt in the current directory using the Read tool, then tell me what it contains.",
|
|
136489
|
+
fixtures: [{
|
|
136490
|
+
name: "data.txt",
|
|
136491
|
+
content: "The quick brown fox jumps over the lazy dog."
|
|
136492
|
+
}]
|
|
136493
|
+
},
|
|
136494
|
+
rules: [{
|
|
136495
|
+
name: "reports file content",
|
|
136496
|
+
check: (output) => output.includes("quick brown fox")
|
|
136497
|
+
}]
|
|
136498
|
+
},
|
|
136499
|
+
{
|
|
136500
|
+
id: "write-file",
|
|
136501
|
+
name: "Regression — writes a file via the Write tool",
|
|
136502
|
+
input: { prompt: "Create a file named output.txt in the current directory using the Write tool. Write exactly the text: hello eval world. Make exactly one Write tool call and do not call any other tool afterwards. After the tool completes, immediately answer with the exact text you wrote and nothing else." },
|
|
136503
|
+
rules: [{
|
|
136504
|
+
name: "Write tool landed the file",
|
|
136505
|
+
check: (output) => output.includes("hello eval world")
|
|
136506
|
+
}],
|
|
136507
|
+
verifyFile: {
|
|
136508
|
+
path: "output.txt",
|
|
136509
|
+
content: "hello eval world"
|
|
136510
|
+
},
|
|
136511
|
+
extra: (result) => {
|
|
136512
|
+
const failures = [];
|
|
136513
|
+
if (!result.verifiedFile) failures.push("output.txt was not created with the exact content");
|
|
136514
|
+
return failures;
|
|
136515
|
+
}
|
|
136516
|
+
},
|
|
136517
|
+
{
|
|
136518
|
+
id: "isolated-workspace",
|
|
136519
|
+
name: "Regression — runs in an isolated temp workspace",
|
|
136520
|
+
input: {
|
|
136521
|
+
prompt: "Run \"pwd\" in a shell and answer with only the absolute path that the command printed.",
|
|
136522
|
+
fixtures: [{
|
|
136523
|
+
name: "marker.txt",
|
|
136524
|
+
content: "isolated"
|
|
136525
|
+
}]
|
|
136526
|
+
},
|
|
136527
|
+
rules: [{
|
|
136528
|
+
name: "prints an absolute path",
|
|
136529
|
+
check: (output) => output.match(/(?:\/[\w.\-/]+|[A-Za-z]:[\\/][\w.\\/-]*)/)?.[0] !== void 0
|
|
136530
|
+
}, {
|
|
136531
|
+
name: "path is not the repo",
|
|
136532
|
+
check: (output) => !output.match(/(?:\/[\w.\-/]+|[A-Za-z]:[\\/][\w.\\/-]*)/)?.[0]?.includes("scream-code")
|
|
136533
|
+
}]
|
|
136534
|
+
}
|
|
136535
|
+
];
|
|
136536
|
+
/** Runs every eval case sequentially and returns a structured report. */
|
|
136537
|
+
async function runAllEvals(options = {}) {
|
|
136538
|
+
const results = [];
|
|
136539
|
+
let passedCount = 0;
|
|
136540
|
+
let failedCount = 0;
|
|
136541
|
+
for (const caseDef of EVAL_CASES) {
|
|
136542
|
+
let result;
|
|
136543
|
+
try {
|
|
136544
|
+
result = await runEvalPrompt(caseDef.input, {
|
|
136545
|
+
model: options.model,
|
|
136546
|
+
verifyFileAfterTurn: caseDef.verifyFile
|
|
136547
|
+
});
|
|
136548
|
+
} catch (error) {
|
|
136549
|
+
results.push({
|
|
136550
|
+
id: caseDef.id,
|
|
136551
|
+
name: caseDef.name,
|
|
136552
|
+
passed: false,
|
|
136553
|
+
failedRules: [],
|
|
136554
|
+
passedRules: [],
|
|
136555
|
+
extraFailures: [],
|
|
136556
|
+
output: "",
|
|
136557
|
+
error: error instanceof Error ? error.message : String(error),
|
|
136558
|
+
timedOut: false,
|
|
136559
|
+
toolCheckFailed: false
|
|
136560
|
+
});
|
|
136561
|
+
failedCount += 1;
|
|
136562
|
+
options.onProgress?.({
|
|
136563
|
+
completed: results.length,
|
|
136564
|
+
total: EVAL_CASES.length,
|
|
136565
|
+
currentId: caseDef.id,
|
|
136566
|
+
currentName: caseDef.name,
|
|
136567
|
+
passed: passedCount,
|
|
136568
|
+
failed: failedCount
|
|
136569
|
+
});
|
|
136570
|
+
continue;
|
|
136571
|
+
}
|
|
136572
|
+
const judged = judgeOutput(result.output, caseDef.rules);
|
|
136573
|
+
const extraFailures = caseDef.extra?.(result) ?? [];
|
|
136574
|
+
const passed = judged.failed.length === 0 && extraFailures.length === 0;
|
|
136575
|
+
if (passed) passedCount += 1;
|
|
136576
|
+
else failedCount += 1;
|
|
136577
|
+
const toolCheckFailed = caseDef.verifyFile !== void 0 && !result.verifiedFile;
|
|
136578
|
+
results.push({
|
|
136579
|
+
id: caseDef.id,
|
|
136580
|
+
name: caseDef.name,
|
|
136581
|
+
passed,
|
|
136582
|
+
failedRules: judged.failed,
|
|
136583
|
+
passedRules: judged.passed,
|
|
136584
|
+
extraFailures,
|
|
136585
|
+
output: result.output,
|
|
136586
|
+
timedOut: result.timedOut,
|
|
136587
|
+
toolCheckFailed
|
|
136588
|
+
});
|
|
136589
|
+
options.onProgress?.({
|
|
136590
|
+
completed: results.length,
|
|
136591
|
+
total: EVAL_CASES.length,
|
|
136592
|
+
currentId: caseDef.id,
|
|
136593
|
+
currentName: caseDef.name,
|
|
136594
|
+
passed: passedCount,
|
|
136595
|
+
failed: failedCount
|
|
136596
|
+
});
|
|
136597
|
+
}
|
|
136598
|
+
return {
|
|
136599
|
+
results,
|
|
136600
|
+
passed: passedCount,
|
|
136601
|
+
failed: failedCount
|
|
136602
|
+
};
|
|
136603
|
+
}
|
|
136604
|
+
//#endregion
|
|
136605
|
+
//#region src/tui/commands/eval.ts
|
|
136606
|
+
const EVAL_PANEL_DISMISS_MS = 6e4;
|
|
136607
|
+
let activeEvalPanel;
|
|
136608
|
+
let activeEvalTimer;
|
|
136609
|
+
/** Guards against overlapping runs (each run calls a real model and costs
|
|
136610
|
+
* tokens; a second /eval while one is running is almost certainly a mistake). */
|
|
136611
|
+
let evalRunning = false;
|
|
136612
|
+
function dismissEvalPanel(state) {
|
|
136613
|
+
if (activeEvalTimer !== void 0) {
|
|
136614
|
+
clearTimeout(activeEvalTimer);
|
|
136615
|
+
activeEvalTimer = void 0;
|
|
136616
|
+
}
|
|
136617
|
+
if (activeEvalPanel !== void 0) {
|
|
136618
|
+
state.transcriptContainer.removeChild(activeEvalPanel);
|
|
136619
|
+
activeEvalPanel = void 0;
|
|
136620
|
+
state.ui.requestRender();
|
|
136621
|
+
}
|
|
136622
|
+
}
|
|
136623
|
+
function clearEvalPanelState(state) {
|
|
136624
|
+
dismissEvalPanel(state);
|
|
136625
|
+
}
|
|
136626
|
+
/** Model selector for eval runs; falls back to the active session model. */
|
|
136627
|
+
function resolveEvalModel(host) {
|
|
136628
|
+
return host.state.appState.model;
|
|
136629
|
+
}
|
|
136630
|
+
/**
|
|
136631
|
+
* Runs the end-to-end evals in the background (they call a real model and can
|
|
136632
|
+
* take minutes) and renders the per-case report when done. Returns immediately
|
|
136633
|
+
* so the TUI keeps responding.
|
|
136634
|
+
*/
|
|
136635
|
+
function runEvalCommand(host) {
|
|
136636
|
+
if (evalRunning) {
|
|
136637
|
+
host.showStatus(t("dispatch.eval_running"));
|
|
136638
|
+
return;
|
|
136639
|
+
}
|
|
136640
|
+
const model = resolveEvalModel(host);
|
|
136641
|
+
if (model === void 0 || model.length === 0) {
|
|
136642
|
+
host.showError(t("dispatch.eval_no_model"));
|
|
136643
|
+
return;
|
|
136644
|
+
}
|
|
136645
|
+
const sessionId = host.state.appState.sessionId;
|
|
136646
|
+
evalRunning = true;
|
|
136647
|
+
host.showStatus(t("dispatch.eval_started", { model }));
|
|
136648
|
+
(async () => {
|
|
136649
|
+
let summary;
|
|
136650
|
+
try {
|
|
136651
|
+
summary = await runAllEvals({
|
|
136652
|
+
model,
|
|
136653
|
+
onProgress: ({ completed, total, passed, failed, currentName }) => {
|
|
136654
|
+
host.showStatus(t("dispatch.eval_progress", {
|
|
136655
|
+
completed: String(completed),
|
|
136656
|
+
total: String(total),
|
|
136657
|
+
passed: String(passed),
|
|
136658
|
+
failed: String(failed),
|
|
136659
|
+
name: currentName
|
|
136660
|
+
}));
|
|
136661
|
+
}
|
|
136662
|
+
});
|
|
136663
|
+
} catch (error) {
|
|
136664
|
+
evalRunning = false;
|
|
136665
|
+
host.showError(t("dispatch.eval_failed", { error: error instanceof Error ? error.message : String(error) }));
|
|
136666
|
+
return;
|
|
136667
|
+
}
|
|
136668
|
+
evalRunning = false;
|
|
136669
|
+
if (sessionId !== void 0 && sessionId !== host.state.appState.sessionId) return;
|
|
136670
|
+
try {
|
|
136671
|
+
renderEvalSummary(host, summary);
|
|
136672
|
+
} catch (error) {
|
|
136673
|
+
host.showError(t("dispatch.eval_failed", { error: error instanceof Error ? error.message : String(error) }));
|
|
136674
|
+
}
|
|
136675
|
+
})();
|
|
136676
|
+
}
|
|
136677
|
+
function renderEvalSummary(host, summary) {
|
|
136678
|
+
const colors = host.state.theme.colors;
|
|
136679
|
+
const lines = [chalk.hex(colors.primary)(`${summary.passed} ${t("dispatch.eval_passed")} · ${summary.failed} ${t("dispatch.eval_failed_count")}`), ""];
|
|
136680
|
+
for (const result of summary.results) {
|
|
136681
|
+
const mark = result.passed ? "✓" : "×";
|
|
136682
|
+
const color = result.passed ? colors.success : colors.error;
|
|
136683
|
+
lines.push(`${chalk.hex(color)(mark)} ${result.name}`);
|
|
136684
|
+
if (result.passed) continue;
|
|
136685
|
+
lines.push(` ${chalk.hex(colors.error)(suggestionFor(result))}`);
|
|
136686
|
+
const details = [];
|
|
136687
|
+
for (const rule of result.failedRules) details.push(rule);
|
|
136688
|
+
for (const failure of result.extraFailures) details.push(failure);
|
|
136689
|
+
if (result.error !== void 0) details.push(result.error);
|
|
136690
|
+
if (result.output.length > 0) details.push(`output: ${result.output.slice(0, 160)}`);
|
|
136691
|
+
for (const detail of details) lines.push(` ${chalk.hex(colors.textDim)(`· ${detail}`)}`);
|
|
136692
|
+
}
|
|
136693
|
+
lines.push("");
|
|
136694
|
+
dismissEvalPanel(host.state);
|
|
136695
|
+
const panel = new UsagePanelComponent(lines, colors.primary, " Eval ");
|
|
136696
|
+
host.state.transcriptContainer.addChild(panel);
|
|
136697
|
+
activeEvalPanel = panel;
|
|
136698
|
+
activeEvalTimer = setTimeout(() => {
|
|
136699
|
+
dismissEvalPanel(host.state);
|
|
136700
|
+
}, EVAL_PANEL_DISMISS_MS);
|
|
136701
|
+
host.state.ui.requestRender();
|
|
136702
|
+
}
|
|
136703
|
+
/** Picks a user-facing self-check hint based on how the case failed. */
|
|
136704
|
+
function suggestionFor(result) {
|
|
136705
|
+
if (result.error !== void 0) return t("dispatch.eval_suggest_error");
|
|
136706
|
+
if (result.timedOut) return t("dispatch.eval_suggest_timeout");
|
|
136707
|
+
if (result.toolCheckFailed) return t("dispatch.eval_suggest_tool");
|
|
136708
|
+
return t("dispatch.eval_suggest_answer");
|
|
136709
|
+
}
|
|
136710
|
+
//#endregion
|
|
136035
136711
|
//#region src/tui/commands/dispatch.ts
|
|
136036
136712
|
function dispatchInput(host, text) {
|
|
136037
136713
|
if (parseSlashInput(text) !== null) {
|
|
@@ -136205,6 +136881,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
|
|
|
136205
136881
|
case "logout":
|
|
136206
136882
|
await handleLogoutCommand(host);
|
|
136207
136883
|
return;
|
|
136884
|
+
case "eval":
|
|
136885
|
+
runEvalCommand(host);
|
|
136886
|
+
return;
|
|
136208
136887
|
case "memory":
|
|
136209
136888
|
await handleMemoryCommand(host, args);
|
|
136210
136889
|
return;
|
|
@@ -137591,6 +138270,8 @@ var SessionEventHandler = class {
|
|
|
137591
138270
|
this.renderedSkillActivationIds.clear();
|
|
137592
138271
|
this.renderedMcpServerStatusKeys.clear();
|
|
137593
138272
|
this.stopAllMcpServerStatusSpinners();
|
|
138273
|
+
this.pendingSkillCandidates = [];
|
|
138274
|
+
this.promptedSkillCandidates.clear();
|
|
137594
138275
|
this.host.setAppState({ subagentUsage: {} });
|
|
137595
138276
|
}
|
|
137596
138277
|
startSubscription() {
|
|
@@ -137846,6 +138527,12 @@ var SessionEventHandler = class {
|
|
|
137846
138527
|
const todos = this.host.state.todoPanel.getTodos();
|
|
137847
138528
|
if (todos.length > 0 && todos.every((todo) => todo.status === "done")) this.host.streamingUI.setTodoList([]);
|
|
137848
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
|
+
}
|
|
137849
138536
|
this.host.streamingUI.finalizeTurn(sendQueued);
|
|
137850
138537
|
}
|
|
137851
138538
|
handleStepBegin(event) {
|
|
@@ -138220,6 +138907,10 @@ var SessionEventHandler = class {
|
|
|
138220
138907
|
}
|
|
138221
138908
|
/** Candidates already prompted this session, to avoid repeating the request. */
|
|
138222
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 = [];
|
|
138223
138914
|
/**
|
|
138224
138915
|
* Handles a `skill_candidate` event emitted after compaction detected a
|
|
138225
138916
|
* reusable process in the summary. Best-effort and non-fatal: if the session
|
|
@@ -138227,6 +138918,10 @@ var SessionEventHandler = class {
|
|
|
138227
138918
|
* no confirmation. When it works, the model runs an AskUserQuestion dialog
|
|
138228
138919
|
* (确定/忽略) so the user can confirm or dismiss with a single keystroke,
|
|
138229
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.
|
|
138230
138925
|
*/
|
|
138231
138926
|
handleSkillCandidate(event) {
|
|
138232
138927
|
try {
|
|
@@ -138236,10 +138931,25 @@ var SessionEventHandler = class {
|
|
|
138236
138931
|
this.promptedSkillCandidates.add(name);
|
|
138237
138932
|
const session = this.host.session;
|
|
138238
138933
|
if (session === void 0) return;
|
|
138239
|
-
|
|
138240
|
-
|
|
138934
|
+
if (this.host.streamingUI.hasActiveTurn()) {
|
|
138935
|
+
this.pendingSkillCandidates.push({
|
|
138936
|
+
name,
|
|
138937
|
+
purpose
|
|
138938
|
+
});
|
|
138939
|
+
return;
|
|
138940
|
+
}
|
|
138941
|
+
this.promptSkillCandidate(session, name, purpose);
|
|
138241
138942
|
} catch {}
|
|
138242
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
|
+
}
|
|
138243
138953
|
finishCompaction(sendQueued) {
|
|
138244
138954
|
if (!this.host.streamingUI.hasActiveTurn()) {
|
|
138245
138955
|
this.host.setAppState({
|
|
@@ -144537,7 +145247,7 @@ var SessionManager$1 = class {
|
|
|
144537
145247
|
}
|
|
144538
145248
|
resetSessionRuntime() {
|
|
144539
145249
|
this.host.state.queuedMessages = [];
|
|
144540
|
-
this.host.harness.interactiveAgentId = MAIN_AGENT_ID;
|
|
145250
|
+
this.host.harness.interactiveAgentId = MAIN_AGENT_ID$1;
|
|
144541
145251
|
this.host.streamingUI.discardPending();
|
|
144542
145252
|
this.host.streamingUI.resetToolCallState();
|
|
144543
145253
|
this.host.streamingUI.resetToolUi();
|
|
@@ -144590,7 +145300,7 @@ function renderDisplayBlock(block, s, colors) {
|
|
|
144590
145300
|
});
|
|
144591
145301
|
case "file_content": {
|
|
144592
145302
|
const lang = block.language ?? langFromPath(block.path);
|
|
144593
|
-
const allLines = highlightLines(replaceTabs(block.content), lang);
|
|
145303
|
+
const allLines = highlightLines(replaceTabs(block.content), lang, colors);
|
|
144594
145304
|
const shown = allLines.slice(0, CONTENT_SUMMARY_MAX_LINES);
|
|
144595
145305
|
const lines = [s.strong(block.path)];
|
|
144596
145306
|
for (const [i, line] of shown.entries()) lines.push(s.gutter(String(i + 1).padStart(4) + " ") + line);
|
|
@@ -145001,7 +145711,7 @@ function buildDiffBody(block, colors) {
|
|
|
145001
145711
|
}
|
|
145002
145712
|
function buildFileContentBody(block, colors) {
|
|
145003
145713
|
const lang = block.language ?? langFromPath(block.path);
|
|
145004
|
-
const highlighted = highlightLines(block.content, lang);
|
|
145714
|
+
const highlighted = highlightLines(block.content, lang, colors);
|
|
145005
145715
|
const gutter = chalk.hex(colors.diffGutter);
|
|
145006
145716
|
return {
|
|
145007
145717
|
lines: highlighted.map((line, i) => gutter(String(i + 1).padStart(4) + " ") + line),
|
|
@@ -147194,6 +147904,7 @@ var ScreamTUI = class {
|
|
|
147194
147904
|
this.sessionEventHandler.stopAllMcpServerStatusSpinners();
|
|
147195
147905
|
clearGoalState();
|
|
147196
147906
|
clearInfoPanelState(this.state);
|
|
147907
|
+
clearEvalPanelState(this.state);
|
|
147197
147908
|
this.state.terminal.write("\x1B[3J");
|
|
147198
147909
|
this.transcriptController.clearAndRedraw();
|
|
147199
147910
|
this.state.ui.requestRender(true);
|