scream-code 0.12.6 → 0.12.7
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);
|
|
@@ -76621,12 +76691,23 @@ function extractFileOpsFromMessage(message, ops) {
|
|
|
76621
76691
|
}
|
|
76622
76692
|
}
|
|
76623
76693
|
}
|
|
76694
|
+
/** Projects a `FileOperations` accumulator into the persistent, sorted
|
|
76695
|
+
* file lists stored on `CompactionResult`. Unlike `formatFileOperations`
|
|
76696
|
+
* this keeps the full list (no `FILE_LIMIT` elision) so later compactions
|
|
76697
|
+
* can merge the previous round's file context without losing entries. */
|
|
76698
|
+
function computeFileLists(ops) {
|
|
76699
|
+
const modified = new Set([...ops.edited, ...ops.written]);
|
|
76700
|
+
return {
|
|
76701
|
+
readFiles: [...ops.read].toSorted(),
|
|
76702
|
+
modifiedFiles: [...modified].toSorted()
|
|
76703
|
+
};
|
|
76704
|
+
}
|
|
76624
76705
|
const FILE_LIMIT = 20;
|
|
76625
76706
|
function formatFileOperations(ops) {
|
|
76626
76707
|
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])].
|
|
76708
|
+
const readOnly = [...ops.read].filter((f) => !modified.has(f)).toSorted();
|
|
76709
|
+
const modifiedFiles = [...modified].toSorted();
|
|
76710
|
+
const all = [...new Set([...readOnly, ...modifiedFiles])].toSorted();
|
|
76630
76711
|
if (all.length === 0) return "";
|
|
76631
76712
|
const mode = /* @__PURE__ */ new Map();
|
|
76632
76713
|
for (const f of readOnly) mode.set(f, "Read");
|
|
@@ -76769,6 +76850,10 @@ var FullCompaction = class {
|
|
|
76769
76850
|
* limit → overflow → compact cycle from consuming the entire
|
|
76770
76851
|
* maxCompactionPerTurn budget with marginal savings. */
|
|
76771
76852
|
reactiveAttempted = false;
|
|
76853
|
+
/** File lists from the most recent successful compaction. Merged into the
|
|
76854
|
+
* next compaction's file operations so file context accumulates across
|
|
76855
|
+
* repeated compactions instead of being reset each round. */
|
|
76856
|
+
lastCompactionFiles;
|
|
76772
76857
|
compacting = null;
|
|
76773
76858
|
_compactedHistory = [];
|
|
76774
76859
|
strategy;
|
|
@@ -77000,15 +77085,25 @@ var FullCompaction = class {
|
|
|
77000
77085
|
const recent = originalHistory.slice(compactedCount);
|
|
77001
77086
|
const messagesToCompactForOps = originalHistory.slice(0, compactedCount);
|
|
77002
77087
|
const fileOps = createFileOps();
|
|
77088
|
+
if (this.lastCompactionFiles !== void 0 && extractPreviousSummary(originalHistory) !== null) {
|
|
77089
|
+
for (const f of this.lastCompactionFiles.readFiles) fileOps.read.add(f);
|
|
77090
|
+
for (const f of this.lastCompactionFiles.modifiedFiles) fileOps.edited.add(f);
|
|
77091
|
+
} else this.lastCompactionFiles = void 0;
|
|
77003
77092
|
for (const msg of messagesToCompactForOps) extractFileOpsFromMessage(msg, fileOps);
|
|
77004
77093
|
const toolCallHistory = formatToolCallHistory(messagesToCompactForOps);
|
|
77005
77094
|
const processedSummary = this.postProcessSummary(summary, fileOps, toolCallHistory);
|
|
77006
77095
|
const tokensAfter = estimateTokens$1(processedSummary) + estimateTokensForMessages(recent);
|
|
77096
|
+
const fileLists = computeFileLists(fileOps);
|
|
77097
|
+
const MAX_PERSISTED_FILES = 100;
|
|
77098
|
+
const readFiles = fileLists.readFiles.slice(0, MAX_PERSISTED_FILES);
|
|
77099
|
+
const modifiedFiles = fileLists.modifiedFiles.slice(0, MAX_PERSISTED_FILES);
|
|
77007
77100
|
const result = {
|
|
77008
77101
|
summary: processedSummary,
|
|
77009
77102
|
compactedCount,
|
|
77010
77103
|
tokensBefore,
|
|
77011
77104
|
tokensAfter,
|
|
77105
|
+
...readFiles.length > 0 ? { readFiles } : {},
|
|
77106
|
+
...modifiedFiles.length > 0 ? { modifiedFiles } : {},
|
|
77012
77107
|
...isUpdate ? { isUpdate: true } : {}
|
|
77013
77108
|
};
|
|
77014
77109
|
this.markCompleted();
|
|
@@ -77017,6 +77112,7 @@ var FullCompaction = class {
|
|
|
77017
77112
|
result
|
|
77018
77113
|
});
|
|
77019
77114
|
this.agent.context.applyCompaction(result);
|
|
77115
|
+
this.lastCompactionFiles = fileLists;
|
|
77020
77116
|
this.lowWaterMark = Math.floor(this.effectiveTokenCount * 1.1);
|
|
77021
77117
|
await this.extractAndStoreMemos(processedSummary);
|
|
77022
77118
|
this.triggerPostCompactHook(data, result);
|
|
@@ -77145,7 +77241,9 @@ var FullCompaction = class {
|
|
|
77145
77241
|
*/
|
|
77146
77242
|
postProcessSummary(summary, fileOps, toolCallHistory) {
|
|
77147
77243
|
const todos = this.agent.tools.storeData()["todo"] ?? [];
|
|
77148
|
-
const
|
|
77244
|
+
const base = summary.trim().replaceAll(/<files>[\s\S]*?<\/files>\s*/g, "").trimEnd();
|
|
77245
|
+
const sections = [];
|
|
77246
|
+
if (base.length > 0) sections.push(base);
|
|
77149
77247
|
if (todos.length > 0) {
|
|
77150
77248
|
const lines = todos.map((t) => {
|
|
77151
77249
|
return `- [${t.status === "done" ? "x" : t.status === "in_progress" ? "-" : " "}] ${t.title}`;
|
|
@@ -78804,12 +78902,15 @@ var ContextMemory = class {
|
|
|
78804
78902
|
* message mutated (compaction summary, micro-compaction truncation, or a
|
|
78805
78903
|
* projection repair) and the cache broke from that index.
|
|
78806
78904
|
*
|
|
78807
|
-
*
|
|
78808
|
-
*
|
|
78905
|
+
* Unlike the read-only `messages` getter, this path closes any trailing
|
|
78906
|
+
* in-flight tool call by synthesizing an error result (synthesizeMissing):
|
|
78907
|
+
* these messages go straight to the provider, which rejects an assistant
|
|
78908
|
+
* tool_calls message with no matching tool result (e.g. after a network
|
|
78909
|
+
* drop mid-batch).
|
|
78809
78910
|
*/
|
|
78810
78911
|
messagesForLLM() {
|
|
78811
78912
|
this.agent.microCompaction.detect();
|
|
78812
|
-
const messages = project(this.agent.microCompaction.compact(this.history));
|
|
78913
|
+
const messages = project(this.agent.microCompaction.compact(this.history), { synthesizeMissing: true });
|
|
78813
78914
|
this.observePrefixStability(messages);
|
|
78814
78915
|
return messages;
|
|
78815
78916
|
}
|
|
@@ -78911,6 +79012,31 @@ var ContextMemory = class {
|
|
|
78911
79012
|
hasOpenToolExchange() {
|
|
78912
79013
|
return this.pendingToolResultIds.size > 0;
|
|
78913
79014
|
}
|
|
79015
|
+
/**
|
|
79016
|
+
* Defensive teardown for a live turn that ended — normally, cancelled, or
|
|
79017
|
+
* failed — while recorded tool calls were still awaiting results (e.g. the
|
|
79018
|
+
* batch's result dispatch died after a `tool.call` was already recorded,
|
|
79019
|
+
* like a network drop mid-execution). Synthesizes an error result for each
|
|
79020
|
+
* dangling call so the exchange closes: left open, the assistant tool_calls
|
|
79021
|
+
* message would have no matching tool message and the next request would be
|
|
79022
|
+
* rejected by the provider ("must be followed by tool messages responding
|
|
79023
|
+
* to each tool_call_id"). No-op when the exchange is already closed.
|
|
79024
|
+
*/
|
|
79025
|
+
closeAbandonedToolExchange(output) {
|
|
79026
|
+
if (this.pendingToolResultIds.size === 0) return 0;
|
|
79027
|
+
const interruptedToolCallIds = [...this.pendingToolResultIds];
|
|
79028
|
+
for (const toolCallId of interruptedToolCallIds) this.appendLoopEvent({
|
|
79029
|
+
type: "tool.result",
|
|
79030
|
+
parentUuid: toolCallId,
|
|
79031
|
+
toolCallId,
|
|
79032
|
+
result: {
|
|
79033
|
+
output,
|
|
79034
|
+
isError: true
|
|
79035
|
+
}
|
|
79036
|
+
});
|
|
79037
|
+
this.flushDeferredMessagesIfToolExchangeClosed();
|
|
79038
|
+
return interruptedToolCallIds.length;
|
|
79039
|
+
}
|
|
78914
79040
|
pushHistory(...messages) {
|
|
78915
79041
|
this._history.push(...messages);
|
|
78916
79042
|
for (const message of messages) {
|
|
@@ -96333,6 +96459,11 @@ var ToolCallDeduplicator = class {
|
|
|
96333
96459
|
};
|
|
96334
96460
|
//#endregion
|
|
96335
96461
|
//#region ../../packages/agent-core/src/agent/turn/index.ts
|
|
96462
|
+
/** Builds the error text synthesized for tool calls abandoned when a live
|
|
96463
|
+
* turn ends (cancelled, failed, or completed) before their results arrived. */
|
|
96464
|
+
function abandonedToolResultOutput(ended) {
|
|
96465
|
+
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.`;
|
|
96466
|
+
}
|
|
96336
96467
|
const GOAL_CONTINUATION_PROMPT = [
|
|
96337
96468
|
"Continue working toward the active goal.",
|
|
96338
96469
|
"Keep the self-audit brief. Do not explore unrelated interpretations once the goal can be",
|
|
@@ -96567,6 +96698,11 @@ var TurnFlow = class {
|
|
|
96567
96698
|
turnId,
|
|
96568
96699
|
reason: "completed"
|
|
96569
96700
|
};
|
|
96701
|
+
try {
|
|
96702
|
+
this.agent.context.closeAbandonedToolExchange(abandonedToolResultOutput(ended));
|
|
96703
|
+
} catch (error) {
|
|
96704
|
+
console.error("closeAbandonedToolExchange failed", error);
|
|
96705
|
+
}
|
|
96570
96706
|
this.agent.usage.endTurn();
|
|
96571
96707
|
this.agent.emitEvent(ended);
|
|
96572
96708
|
return ended;
|
|
@@ -96646,6 +96782,11 @@ var TurnFlow = class {
|
|
|
96646
96782
|
};
|
|
96647
96783
|
}
|
|
96648
96784
|
}
|
|
96785
|
+
try {
|
|
96786
|
+
this.agent.context.closeAbandonedToolExchange(abandonedToolResultOutput(ended));
|
|
96787
|
+
} catch (error) {
|
|
96788
|
+
console.error("closeAbandonedToolExchange failed", error);
|
|
96789
|
+
}
|
|
96649
96790
|
if (this.currentId === turnId) this.agent.usage.endTurn();
|
|
96650
96791
|
this.agent.emitEvent(ended);
|
|
96651
96792
|
if (standalone && this.currentId === turnId) this.activeTurn = null;
|
|
@@ -102568,7 +102709,7 @@ var SessionSubagentHost = class {
|
|
|
102568
102709
|
text: childPrompt
|
|
102569
102710
|
}], origin);
|
|
102570
102711
|
await runChildTurnToCompletion(child, options.signal);
|
|
102571
|
-
let result = lastAssistantText(child);
|
|
102712
|
+
let result = lastAssistantText$1(child);
|
|
102572
102713
|
let remainingContinuations = SUMMARY_CONTINUATION_ATTEMPTS;
|
|
102573
102714
|
while (remainingContinuations > 0 && result.length < SUMMARY_MIN_LENGTH) {
|
|
102574
102715
|
remainingContinuations -= 1;
|
|
@@ -102578,7 +102719,7 @@ var SessionSubagentHost = class {
|
|
|
102578
102719
|
text: summary_continuation_default
|
|
102579
102720
|
}], origin);
|
|
102580
102721
|
await runChildTurnToCompletion(child, options.signal);
|
|
102581
|
-
result = lastAssistantText(child);
|
|
102722
|
+
result = lastAssistantText$1(child);
|
|
102582
102723
|
}
|
|
102583
102724
|
const usage = child.usage.data().total;
|
|
102584
102725
|
let findingsBlock = "";
|
|
@@ -102694,7 +102835,7 @@ async function runChildTurnToCompletion(child, signal) {
|
|
|
102694
102835
|
function throwIfSubagentStoppedAtMaxTokens(stopReason) {
|
|
102695
102836
|
if (stopReason === "max_tokens") throw new Error(`${SUBAGENT_MAX_TOKENS_ERROR}.`);
|
|
102696
102837
|
}
|
|
102697
|
-
function lastAssistantText(agent) {
|
|
102838
|
+
function lastAssistantText$1(agent) {
|
|
102698
102839
|
for (const message of [...agent.context.history].toReversed()) {
|
|
102699
102840
|
if (message.role !== "assistant") continue;
|
|
102700
102841
|
const text = message.content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
@@ -120191,10 +120332,10 @@ var ScreamAuthFacade = class {
|
|
|
120191
120332
|
};
|
|
120192
120333
|
//#endregion
|
|
120193
120334
|
//#region ../../packages/node-sdk/src/rpc.ts
|
|
120194
|
-
const MAIN_AGENT_ID$
|
|
120335
|
+
const MAIN_AGENT_ID$3 = "main";
|
|
120195
120336
|
var SDKRpcClient = class {
|
|
120196
120337
|
core;
|
|
120197
|
-
interactiveAgentId = MAIN_AGENT_ID$
|
|
120338
|
+
interactiveAgentId = MAIN_AGENT_ID$3;
|
|
120198
120339
|
ready;
|
|
120199
120340
|
rpc;
|
|
120200
120341
|
eventListeners = /* @__PURE__ */ new Set();
|
|
@@ -120772,7 +120913,7 @@ function errorMessage$2(error) {
|
|
|
120772
120913
|
}
|
|
120773
120914
|
//#endregion
|
|
120774
120915
|
//#region ../../packages/node-sdk/src/session.ts
|
|
120775
|
-
const MAIN_AGENT_ID$
|
|
120916
|
+
const MAIN_AGENT_ID$2 = "main";
|
|
120776
120917
|
var Session = class {
|
|
120777
120918
|
id;
|
|
120778
120919
|
workDir;
|
|
@@ -121212,7 +121353,7 @@ var Session = class {
|
|
|
121212
121353
|
this.emit({
|
|
121213
121354
|
type: "session.meta.updated",
|
|
121214
121355
|
sessionId: this.id,
|
|
121215
|
-
agentId: MAIN_AGENT_ID$
|
|
121356
|
+
agentId: MAIN_AGENT_ID$2,
|
|
121216
121357
|
title: patch.title,
|
|
121217
121358
|
patch
|
|
121218
121359
|
});
|
|
@@ -121634,7 +121775,7 @@ function optionalBuildString(value) {
|
|
|
121634
121775
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
121635
121776
|
}
|
|
121636
121777
|
const SCREAM_BUILD_INFO = {
|
|
121637
|
-
version: optionalBuildString("0.12.
|
|
121778
|
+
version: optionalBuildString("0.12.7"),
|
|
121638
121779
|
channel: optionalBuildString(""),
|
|
121639
121780
|
commit: optionalBuildString(""),
|
|
121640
121781
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -122906,6 +123047,12 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
122906
123047
|
description: "registry.logout_desc",
|
|
122907
123048
|
priority: 93
|
|
122908
123049
|
},
|
|
123050
|
+
{
|
|
123051
|
+
name: "eval",
|
|
123052
|
+
aliases: [],
|
|
123053
|
+
description: "registry.eval_desc",
|
|
123054
|
+
priority: 92
|
|
123055
|
+
},
|
|
122909
123056
|
{
|
|
122910
123057
|
name: "exit",
|
|
122911
123058
|
aliases: ["quit", "q"],
|
|
@@ -123022,7 +123169,7 @@ function getCtrlDHint() {
|
|
|
123022
123169
|
function getCtrlCHint() {
|
|
123023
123170
|
return t("constant.ctrl_c_hint");
|
|
123024
123171
|
}
|
|
123025
|
-
const MAIN_AGENT_ID = "main";
|
|
123172
|
+
const MAIN_AGENT_ID$1 = "main";
|
|
123026
123173
|
const EXIT_CONFIRM_WINDOW_MS = 1500;
|
|
123027
123174
|
function isManagedUsageProvider(providerKey) {
|
|
123028
123175
|
return providerKey === DEFAULT_OAUTH_PROVIDER_NAME;
|
|
@@ -127448,7 +127595,7 @@ async function guidedGoalSetup(host) {
|
|
|
127448
127595
|
host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
|
|
127449
127596
|
return;
|
|
127450
127597
|
}
|
|
127451
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
127598
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-BIXonNNb.mjs");
|
|
127452
127599
|
const initialDesc = await promptText(host, TextInputDialogComponent, {
|
|
127453
127600
|
title: t("goal.setup_title_initial"),
|
|
127454
127601
|
subtitle: t("goal.setup_desc_hint"),
|
|
@@ -127469,7 +127616,7 @@ async function guidedGoalSetup(host) {
|
|
|
127469
127616
|
await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
|
|
127470
127617
|
}
|
|
127471
127618
|
async function showGoalConfigWizard(host, session, objective, replace) {
|
|
127472
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
127619
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-BIXonNNb.mjs");
|
|
127473
127620
|
const turnInput = await promptNumber(host, TextInputDialogComponent, {
|
|
127474
127621
|
title: t("goal.wizard_title", { objective }),
|
|
127475
127622
|
subtitle: t("goal.budget_turns_hint"),
|
|
@@ -136032,6 +136179,429 @@ async function handleKnowledgeCommand(host, _args) {
|
|
|
136032
136179
|
showMenu();
|
|
136033
136180
|
}
|
|
136034
136181
|
//#endregion
|
|
136182
|
+
//#region ../../packages/evals/src/judge.ts
|
|
136183
|
+
/** Runs every rule against the input; a rule passes when its check is true. */
|
|
136184
|
+
function judgeOutput(input, rules) {
|
|
136185
|
+
const passed = [];
|
|
136186
|
+
const failed = [];
|
|
136187
|
+
for (const rule of rules) if (rule.check(input)) passed.push(rule.name);
|
|
136188
|
+
else failed.push(rule.name);
|
|
136189
|
+
return {
|
|
136190
|
+
passed,
|
|
136191
|
+
failed
|
|
136192
|
+
};
|
|
136193
|
+
}
|
|
136194
|
+
//#endregion
|
|
136195
|
+
//#region ../../packages/evals/src/harness.ts
|
|
136196
|
+
/**
|
|
136197
|
+
* Minimal end-to-end eval harness built on the public node-sdk
|
|
136198
|
+
* `ScreamHarness`. Each eval run gets an isolated temp **workspace** so runs
|
|
136199
|
+
* never touch real project files; the scream home defaults to the real one so
|
|
136200
|
+
* the provider/API-key configuration in `~/.scream-code/config.toml` is
|
|
136201
|
+
* honored (pass `screamHome` to override for fully isolated runs). Model
|
|
136202
|
+
* selection follows `SCREAM_EVAL_MODEL` (e.g. `provider/model`);
|
|
136203
|
+
* when unset the harness throws with a clear message.
|
|
136204
|
+
*/
|
|
136205
|
+
/** The main agent id; only its events settle the eval turn wait. */
|
|
136206
|
+
const MAIN_AGENT_ID = "main";
|
|
136207
|
+
/** The current scream default model, when resolvable from config. */
|
|
136208
|
+
const DEFAULT_EVAL_MODEL = process.env["SCREAM_EVAL_MODEL"];
|
|
136209
|
+
/**
|
|
136210
|
+
* Runs a single end-to-end prompt against a fresh isolated session and
|
|
136211
|
+
* returns the final assistant text plus token usage. The session, workspace
|
|
136212
|
+
* and scream home are torn down afterwards.
|
|
136213
|
+
*/
|
|
136214
|
+
async function runEvalPrompt(input, options = {}) {
|
|
136215
|
+
const workDir = options.workDir ?? await mkdtemp(join(tmpdir(), "scream-eval-ws-"));
|
|
136216
|
+
const screamHome = options.screamHome;
|
|
136217
|
+
const createdWorkDir = options.workDir === void 0;
|
|
136218
|
+
let harness;
|
|
136219
|
+
let session;
|
|
136220
|
+
try {
|
|
136221
|
+
for (const fixture of (typeof input === "string" ? [] : input.fixtures) ?? []) await writeFile(join(workDir, fixture.name), fixture.content, "utf-8");
|
|
136222
|
+
const prompt = typeof input === "string" ? input : input.prompt;
|
|
136223
|
+
const model = options.model ?? DEFAULT_EVAL_MODEL;
|
|
136224
|
+
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");
|
|
136225
|
+
const [provider, modelId, ...rest] = model.split("/");
|
|
136226
|
+
const usageProvider = rest.length > 0 ? model : provider ?? "unknown";
|
|
136227
|
+
const usageModel = modelId === void 0 || rest.length > 0 ? model : modelId;
|
|
136228
|
+
harness = new ScreamHarness({ homeDir: screamHome });
|
|
136229
|
+
session = await harness.createSession({
|
|
136230
|
+
workDir,
|
|
136231
|
+
model,
|
|
136232
|
+
thinking: options.thinking ?? "off",
|
|
136233
|
+
permission: "yolo"
|
|
136234
|
+
});
|
|
136235
|
+
const { timedOut } = await promptAndWaitForTurnEnd(session, prompt);
|
|
136236
|
+
let output = "";
|
|
136237
|
+
try {
|
|
136238
|
+
output = lastAssistantText((await session.getContext()).history);
|
|
136239
|
+
} catch {
|
|
136240
|
+
output = "";
|
|
136241
|
+
}
|
|
136242
|
+
const total = (await session.getUsage().catch(() => void 0))?.total;
|
|
136243
|
+
let verifiedFile = false;
|
|
136244
|
+
if (options.verifyFileAfterTurn !== void 0) try {
|
|
136245
|
+
verifiedFile = await readFile(join(workDir, options.verifyFileAfterTurn.path), "utf-8") === options.verifyFileAfterTurn.content;
|
|
136246
|
+
} catch {
|
|
136247
|
+
verifiedFile = false;
|
|
136248
|
+
}
|
|
136249
|
+
return {
|
|
136250
|
+
output,
|
|
136251
|
+
timedOut,
|
|
136252
|
+
verifiedFile,
|
|
136253
|
+
usage: {
|
|
136254
|
+
provider: usageProvider,
|
|
136255
|
+
model: usageModel,
|
|
136256
|
+
inputTokens: total?.inputOther ?? 0,
|
|
136257
|
+
outputTokens: total?.output ?? 0,
|
|
136258
|
+
totalTokens: (total?.inputOther ?? 0) + (total?.output ?? 0) + (total?.inputCacheRead ?? 0) + (total?.inputCacheCreation ?? 0)
|
|
136259
|
+
}
|
|
136260
|
+
};
|
|
136261
|
+
} finally {
|
|
136262
|
+
if (session !== void 0) await session.close({ extractMemories: false }).catch(() => {});
|
|
136263
|
+
if (createdWorkDir) await rm(workDir, {
|
|
136264
|
+
recursive: true,
|
|
136265
|
+
force: true
|
|
136266
|
+
}).catch(() => {});
|
|
136267
|
+
}
|
|
136268
|
+
}
|
|
136269
|
+
function lastAssistantText(history) {
|
|
136270
|
+
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
136271
|
+
const message = history[i];
|
|
136272
|
+
if (message.role !== "assistant") continue;
|
|
136273
|
+
const text = (message.content ?? []).filter((part) => part.type === "text" && part.text !== void 0).map((part) => part.text).join("");
|
|
136274
|
+
if (text.trim().length > 0) return text.trim();
|
|
136275
|
+
}
|
|
136276
|
+
return "";
|
|
136277
|
+
}
|
|
136278
|
+
/**
|
|
136279
|
+
* Sends a prompt and resolves when the main agent's turn ends. `Session.prompt`
|
|
136280
|
+
* only enqueues the RPC request; the assistant reply arrives asynchronously as
|
|
136281
|
+
* events, so we must wait for the `turn.ended` event to know the run finished.
|
|
136282
|
+
*
|
|
136283
|
+
* A hard timeout guards against a turn that never ends (e.g. a model looping
|
|
136284
|
+
* through tools instead of converging, or a network interruption). On timeout
|
|
136285
|
+
* we cancel the session so the caller's cleanup path still runs instead of
|
|
136286
|
+
* leaking the session and temp workspace. 90s is enough to complete any
|
|
136287
|
+
* legitimate single-turn tool round-trip; longer waits are almost always a
|
|
136288
|
+
* stuck model, so we fail fast instead of burning tokens.
|
|
136289
|
+
*/
|
|
136290
|
+
const TURN_WAIT_TIMEOUT_MS = 9e4;
|
|
136291
|
+
/**
|
|
136292
|
+
* Resolves with `timedOut: true` when the turn hit the timeout (model looping
|
|
136293
|
+
* or stuck) instead of throwing, so callers can still inspect side effects
|
|
136294
|
+
* (e.g. a file the Write tool already created). Real errors (turn failed /
|
|
136295
|
+
* error event) still reject.
|
|
136296
|
+
*/
|
|
136297
|
+
async function promptAndWaitForTurnEnd(session, prompt) {
|
|
136298
|
+
const { promise, resolve, reject } = promiseWithResolvers();
|
|
136299
|
+
let activeTurnId;
|
|
136300
|
+
let activeAgentId;
|
|
136301
|
+
let settled = false;
|
|
136302
|
+
let timeout;
|
|
136303
|
+
let graceTimer;
|
|
136304
|
+
/** Set once the timeout fires; guards against the cancel-triggered
|
|
136305
|
+
* `turn.ended('cancelled')` being misread as a real failure. */
|
|
136306
|
+
let timeoutFired = false;
|
|
136307
|
+
const finish = (result) => {
|
|
136308
|
+
if (settled) return;
|
|
136309
|
+
settled = true;
|
|
136310
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
136311
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
136312
|
+
if (result instanceof Error) reject(result);
|
|
136313
|
+
else resolve(result);
|
|
136314
|
+
};
|
|
136315
|
+
const unsubscribe = session.onEvent((event) => {
|
|
136316
|
+
if (event.agentId !== MAIN_AGENT_ID) return;
|
|
136317
|
+
if (event.type === "error") {
|
|
136318
|
+
finish(/* @__PURE__ */ new Error(`${event.code}: ${event.message}`));
|
|
136319
|
+
return;
|
|
136320
|
+
}
|
|
136321
|
+
if (event.type === "turn.started" && activeTurnId === void 0) {
|
|
136322
|
+
activeTurnId = event.turnId;
|
|
136323
|
+
activeAgentId = event.agentId;
|
|
136324
|
+
return;
|
|
136325
|
+
}
|
|
136326
|
+
if (activeTurnId === void 0 || activeAgentId === void 0 || !("turnId" in event) || event.turnId !== activeTurnId || event.agentId !== activeAgentId) return;
|
|
136327
|
+
if (event.type === "turn.ended") if (event.reason === "completed") finish({ timedOut: false });
|
|
136328
|
+
else if (timeoutFired && event.reason === "cancelled") return;
|
|
136329
|
+
else finish(/* @__PURE__ */ new Error(`Turn ended with reason: ${event.reason}`));
|
|
136330
|
+
});
|
|
136331
|
+
try {
|
|
136332
|
+
await session.prompt(prompt);
|
|
136333
|
+
timeout = setTimeout(() => {
|
|
136334
|
+
timeoutFired = true;
|
|
136335
|
+
session.cancel().catch(() => {});
|
|
136336
|
+
graceTimer = setTimeout(() => {
|
|
136337
|
+
finish({ timedOut: true });
|
|
136338
|
+
}, 500);
|
|
136339
|
+
}, TURN_WAIT_TIMEOUT_MS);
|
|
136340
|
+
return await promise;
|
|
136341
|
+
} finally {
|
|
136342
|
+
unsubscribe();
|
|
136343
|
+
if (timeout !== void 0) clearTimeout(timeout);
|
|
136344
|
+
if (graceTimer !== void 0) clearTimeout(graceTimer);
|
|
136345
|
+
}
|
|
136346
|
+
}
|
|
136347
|
+
function promiseWithResolvers() {
|
|
136348
|
+
let resolve;
|
|
136349
|
+
let reject;
|
|
136350
|
+
return {
|
|
136351
|
+
promise: new Promise((res, rej) => {
|
|
136352
|
+
resolve = res;
|
|
136353
|
+
reject = rej;
|
|
136354
|
+
}),
|
|
136355
|
+
resolve,
|
|
136356
|
+
reject
|
|
136357
|
+
};
|
|
136358
|
+
}
|
|
136359
|
+
const EVAL_CASES = [
|
|
136360
|
+
{
|
|
136361
|
+
id: "smoke",
|
|
136362
|
+
name: "Smoke — answers a trivial question",
|
|
136363
|
+
input: "What is the capital of France? Answer in one word.",
|
|
136364
|
+
rules: [{
|
|
136365
|
+
name: "answers the capital",
|
|
136366
|
+
check: (output) => {
|
|
136367
|
+
const lower = output.toLowerCase();
|
|
136368
|
+
return lower.includes("paris") || lower.includes("巴黎");
|
|
136369
|
+
}
|
|
136370
|
+
}],
|
|
136371
|
+
extra: (result) => {
|
|
136372
|
+
const failures = [];
|
|
136373
|
+
if (result.usage.totalTokens <= 0) failures.push("usage.totalTokens must be > 0");
|
|
136374
|
+
if (result.usage.model.length === 0) failures.push("usage.model must be non-empty");
|
|
136375
|
+
return failures;
|
|
136376
|
+
}
|
|
136377
|
+
},
|
|
136378
|
+
{
|
|
136379
|
+
id: "read-file",
|
|
136380
|
+
name: "Regression — reads a file via the Read tool",
|
|
136381
|
+
input: {
|
|
136382
|
+
prompt: "Read the file data.txt in the current directory using the Read tool, then tell me what it contains.",
|
|
136383
|
+
fixtures: [{
|
|
136384
|
+
name: "data.txt",
|
|
136385
|
+
content: "The quick brown fox jumps over the lazy dog."
|
|
136386
|
+
}]
|
|
136387
|
+
},
|
|
136388
|
+
rules: [{
|
|
136389
|
+
name: "reports file content",
|
|
136390
|
+
check: (output) => output.includes("quick brown fox")
|
|
136391
|
+
}]
|
|
136392
|
+
},
|
|
136393
|
+
{
|
|
136394
|
+
id: "write-file",
|
|
136395
|
+
name: "Regression — writes a file via the Write tool",
|
|
136396
|
+
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." },
|
|
136397
|
+
rules: [{
|
|
136398
|
+
name: "Write tool landed the file",
|
|
136399
|
+
check: (output) => output.includes("hello eval world")
|
|
136400
|
+
}],
|
|
136401
|
+
verifyFile: {
|
|
136402
|
+
path: "output.txt",
|
|
136403
|
+
content: "hello eval world"
|
|
136404
|
+
},
|
|
136405
|
+
extra: (result) => {
|
|
136406
|
+
const failures = [];
|
|
136407
|
+
if (!result.verifiedFile) failures.push("output.txt was not created with the exact content");
|
|
136408
|
+
return failures;
|
|
136409
|
+
}
|
|
136410
|
+
},
|
|
136411
|
+
{
|
|
136412
|
+
id: "isolated-workspace",
|
|
136413
|
+
name: "Regression — runs in an isolated temp workspace",
|
|
136414
|
+
input: {
|
|
136415
|
+
prompt: "Run \"pwd\" in a shell and answer with only the absolute path that the command printed.",
|
|
136416
|
+
fixtures: [{
|
|
136417
|
+
name: "marker.txt",
|
|
136418
|
+
content: "isolated"
|
|
136419
|
+
}]
|
|
136420
|
+
},
|
|
136421
|
+
rules: [{
|
|
136422
|
+
name: "prints an absolute path",
|
|
136423
|
+
check: (output) => output.match(/(?:\/[\w.\-/]+|[A-Za-z]:[\\/][\w.\\/-]*)/)?.[0] !== void 0
|
|
136424
|
+
}, {
|
|
136425
|
+
name: "path is not the repo",
|
|
136426
|
+
check: (output) => !output.match(/(?:\/[\w.\-/]+|[A-Za-z]:[\\/][\w.\\/-]*)/)?.[0]?.includes("scream-code")
|
|
136427
|
+
}]
|
|
136428
|
+
}
|
|
136429
|
+
];
|
|
136430
|
+
/** Runs every eval case sequentially and returns a structured report. */
|
|
136431
|
+
async function runAllEvals(options = {}) {
|
|
136432
|
+
const results = [];
|
|
136433
|
+
let passedCount = 0;
|
|
136434
|
+
let failedCount = 0;
|
|
136435
|
+
for (const caseDef of EVAL_CASES) {
|
|
136436
|
+
let result;
|
|
136437
|
+
try {
|
|
136438
|
+
result = await runEvalPrompt(caseDef.input, {
|
|
136439
|
+
model: options.model,
|
|
136440
|
+
verifyFileAfterTurn: caseDef.verifyFile
|
|
136441
|
+
});
|
|
136442
|
+
} catch (error) {
|
|
136443
|
+
results.push({
|
|
136444
|
+
id: caseDef.id,
|
|
136445
|
+
name: caseDef.name,
|
|
136446
|
+
passed: false,
|
|
136447
|
+
failedRules: [],
|
|
136448
|
+
passedRules: [],
|
|
136449
|
+
extraFailures: [],
|
|
136450
|
+
output: "",
|
|
136451
|
+
error: error instanceof Error ? error.message : String(error),
|
|
136452
|
+
timedOut: false,
|
|
136453
|
+
toolCheckFailed: false
|
|
136454
|
+
});
|
|
136455
|
+
failedCount += 1;
|
|
136456
|
+
options.onProgress?.({
|
|
136457
|
+
completed: results.length,
|
|
136458
|
+
total: EVAL_CASES.length,
|
|
136459
|
+
currentId: caseDef.id,
|
|
136460
|
+
currentName: caseDef.name,
|
|
136461
|
+
passed: passedCount,
|
|
136462
|
+
failed: failedCount
|
|
136463
|
+
});
|
|
136464
|
+
continue;
|
|
136465
|
+
}
|
|
136466
|
+
const judged = judgeOutput(result.output, caseDef.rules);
|
|
136467
|
+
const extraFailures = caseDef.extra?.(result) ?? [];
|
|
136468
|
+
const passed = judged.failed.length === 0 && extraFailures.length === 0;
|
|
136469
|
+
if (passed) passedCount += 1;
|
|
136470
|
+
else failedCount += 1;
|
|
136471
|
+
const toolCheckFailed = caseDef.verifyFile !== void 0 && !result.verifiedFile;
|
|
136472
|
+
results.push({
|
|
136473
|
+
id: caseDef.id,
|
|
136474
|
+
name: caseDef.name,
|
|
136475
|
+
passed,
|
|
136476
|
+
failedRules: judged.failed,
|
|
136477
|
+
passedRules: judged.passed,
|
|
136478
|
+
extraFailures,
|
|
136479
|
+
output: result.output,
|
|
136480
|
+
timedOut: result.timedOut,
|
|
136481
|
+
toolCheckFailed
|
|
136482
|
+
});
|
|
136483
|
+
options.onProgress?.({
|
|
136484
|
+
completed: results.length,
|
|
136485
|
+
total: EVAL_CASES.length,
|
|
136486
|
+
currentId: caseDef.id,
|
|
136487
|
+
currentName: caseDef.name,
|
|
136488
|
+
passed: passedCount,
|
|
136489
|
+
failed: failedCount
|
|
136490
|
+
});
|
|
136491
|
+
}
|
|
136492
|
+
return {
|
|
136493
|
+
results,
|
|
136494
|
+
passed: passedCount,
|
|
136495
|
+
failed: failedCount
|
|
136496
|
+
};
|
|
136497
|
+
}
|
|
136498
|
+
//#endregion
|
|
136499
|
+
//#region src/tui/commands/eval.ts
|
|
136500
|
+
const EVAL_PANEL_DISMISS_MS = 6e4;
|
|
136501
|
+
let activeEvalPanel;
|
|
136502
|
+
let activeEvalTimer;
|
|
136503
|
+
/** Guards against overlapping runs (each run calls a real model and costs
|
|
136504
|
+
* tokens; a second /eval while one is running is almost certainly a mistake). */
|
|
136505
|
+
let evalRunning = false;
|
|
136506
|
+
function dismissEvalPanel(state) {
|
|
136507
|
+
if (activeEvalTimer !== void 0) {
|
|
136508
|
+
clearTimeout(activeEvalTimer);
|
|
136509
|
+
activeEvalTimer = void 0;
|
|
136510
|
+
}
|
|
136511
|
+
if (activeEvalPanel !== void 0) {
|
|
136512
|
+
state.transcriptContainer.removeChild(activeEvalPanel);
|
|
136513
|
+
activeEvalPanel = void 0;
|
|
136514
|
+
state.ui.requestRender();
|
|
136515
|
+
}
|
|
136516
|
+
}
|
|
136517
|
+
function clearEvalPanelState(state) {
|
|
136518
|
+
dismissEvalPanel(state);
|
|
136519
|
+
}
|
|
136520
|
+
/** Model selector for eval runs; falls back to the active session model. */
|
|
136521
|
+
function resolveEvalModel(host) {
|
|
136522
|
+
return host.state.appState.model;
|
|
136523
|
+
}
|
|
136524
|
+
/**
|
|
136525
|
+
* Runs the end-to-end evals in the background (they call a real model and can
|
|
136526
|
+
* take minutes) and renders the per-case report when done. Returns immediately
|
|
136527
|
+
* so the TUI keeps responding.
|
|
136528
|
+
*/
|
|
136529
|
+
function runEvalCommand(host) {
|
|
136530
|
+
if (evalRunning) {
|
|
136531
|
+
host.showStatus(t("dispatch.eval_running"));
|
|
136532
|
+
return;
|
|
136533
|
+
}
|
|
136534
|
+
const model = resolveEvalModel(host);
|
|
136535
|
+
if (model === void 0 || model.length === 0) {
|
|
136536
|
+
host.showError(t("dispatch.eval_no_model"));
|
|
136537
|
+
return;
|
|
136538
|
+
}
|
|
136539
|
+
const sessionId = host.state.appState.sessionId;
|
|
136540
|
+
evalRunning = true;
|
|
136541
|
+
host.showStatus(t("dispatch.eval_started", { model }));
|
|
136542
|
+
(async () => {
|
|
136543
|
+
let summary;
|
|
136544
|
+
try {
|
|
136545
|
+
summary = await runAllEvals({
|
|
136546
|
+
model,
|
|
136547
|
+
onProgress: ({ completed, total, passed, failed, currentName }) => {
|
|
136548
|
+
host.showStatus(t("dispatch.eval_progress", {
|
|
136549
|
+
completed: String(completed),
|
|
136550
|
+
total: String(total),
|
|
136551
|
+
passed: String(passed),
|
|
136552
|
+
failed: String(failed),
|
|
136553
|
+
name: currentName
|
|
136554
|
+
}));
|
|
136555
|
+
}
|
|
136556
|
+
});
|
|
136557
|
+
} catch (error) {
|
|
136558
|
+
evalRunning = false;
|
|
136559
|
+
host.showError(t("dispatch.eval_failed", { error: error instanceof Error ? error.message : String(error) }));
|
|
136560
|
+
return;
|
|
136561
|
+
}
|
|
136562
|
+
evalRunning = false;
|
|
136563
|
+
if (sessionId !== void 0 && sessionId !== host.state.appState.sessionId) return;
|
|
136564
|
+
try {
|
|
136565
|
+
renderEvalSummary(host, summary);
|
|
136566
|
+
} catch (error) {
|
|
136567
|
+
host.showError(t("dispatch.eval_failed", { error: error instanceof Error ? error.message : String(error) }));
|
|
136568
|
+
}
|
|
136569
|
+
})();
|
|
136570
|
+
}
|
|
136571
|
+
function renderEvalSummary(host, summary) {
|
|
136572
|
+
const colors = host.state.theme.colors;
|
|
136573
|
+
const lines = [chalk.hex(colors.primary)(`${summary.passed} ${t("dispatch.eval_passed")} · ${summary.failed} ${t("dispatch.eval_failed_count")}`), ""];
|
|
136574
|
+
for (const result of summary.results) {
|
|
136575
|
+
const mark = result.passed ? "✓" : "×";
|
|
136576
|
+
const color = result.passed ? colors.success : colors.error;
|
|
136577
|
+
lines.push(`${chalk.hex(color)(mark)} ${result.name}`);
|
|
136578
|
+
if (result.passed) continue;
|
|
136579
|
+
lines.push(` ${chalk.hex(colors.error)(suggestionFor(result))}`);
|
|
136580
|
+
const details = [];
|
|
136581
|
+
for (const rule of result.failedRules) details.push(rule);
|
|
136582
|
+
for (const failure of result.extraFailures) details.push(failure);
|
|
136583
|
+
if (result.error !== void 0) details.push(result.error);
|
|
136584
|
+
if (result.output.length > 0) details.push(`output: ${result.output.slice(0, 160)}`);
|
|
136585
|
+
for (const detail of details) lines.push(` ${chalk.hex(colors.textDim)(`· ${detail}`)}`);
|
|
136586
|
+
}
|
|
136587
|
+
lines.push("");
|
|
136588
|
+
dismissEvalPanel(host.state);
|
|
136589
|
+
const panel = new UsagePanelComponent(lines, colors.primary, " Eval ");
|
|
136590
|
+
host.state.transcriptContainer.addChild(panel);
|
|
136591
|
+
activeEvalPanel = panel;
|
|
136592
|
+
activeEvalTimer = setTimeout(() => {
|
|
136593
|
+
dismissEvalPanel(host.state);
|
|
136594
|
+
}, EVAL_PANEL_DISMISS_MS);
|
|
136595
|
+
host.state.ui.requestRender();
|
|
136596
|
+
}
|
|
136597
|
+
/** Picks a user-facing self-check hint based on how the case failed. */
|
|
136598
|
+
function suggestionFor(result) {
|
|
136599
|
+
if (result.error !== void 0) return t("dispatch.eval_suggest_error");
|
|
136600
|
+
if (result.timedOut) return t("dispatch.eval_suggest_timeout");
|
|
136601
|
+
if (result.toolCheckFailed) return t("dispatch.eval_suggest_tool");
|
|
136602
|
+
return t("dispatch.eval_suggest_answer");
|
|
136603
|
+
}
|
|
136604
|
+
//#endregion
|
|
136035
136605
|
//#region src/tui/commands/dispatch.ts
|
|
136036
136606
|
function dispatchInput(host, text) {
|
|
136037
136607
|
if (parseSlashInput(text) !== null) {
|
|
@@ -136205,6 +136775,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
|
|
|
136205
136775
|
case "logout":
|
|
136206
136776
|
await handleLogoutCommand(host);
|
|
136207
136777
|
return;
|
|
136778
|
+
case "eval":
|
|
136779
|
+
runEvalCommand(host);
|
|
136780
|
+
return;
|
|
136208
136781
|
case "memory":
|
|
136209
136782
|
await handleMemoryCommand(host, args);
|
|
136210
136783
|
return;
|
|
@@ -144537,7 +145110,7 @@ var SessionManager$1 = class {
|
|
|
144537
145110
|
}
|
|
144538
145111
|
resetSessionRuntime() {
|
|
144539
145112
|
this.host.state.queuedMessages = [];
|
|
144540
|
-
this.host.harness.interactiveAgentId = MAIN_AGENT_ID;
|
|
145113
|
+
this.host.harness.interactiveAgentId = MAIN_AGENT_ID$1;
|
|
144541
145114
|
this.host.streamingUI.discardPending();
|
|
144542
145115
|
this.host.streamingUI.resetToolCallState();
|
|
144543
145116
|
this.host.streamingUI.resetToolUi();
|
|
@@ -147194,6 +147767,7 @@ var ScreamTUI = class {
|
|
|
147194
147767
|
this.sessionEventHandler.stopAllMcpServerStatusSpinners();
|
|
147195
147768
|
clearGoalState();
|
|
147196
147769
|
clearInfoPanelState(this.state);
|
|
147770
|
+
clearEvalPanelState(this.state);
|
|
147197
147771
|
this.state.terminal.write("\x1B[3J");
|
|
147198
147772
|
this.transcriptController.clearAndRedraw();
|
|
147199
147773
|
this.state.ui.requestRender(true);
|
package/dist/main.mjs
CHANGED
|
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
|
|
|
6
6
|
import "./suppress-sqlite-warning-C2VB0doZ.mjs";
|
|
7
7
|
//#region src/main.ts
|
|
8
8
|
try {
|
|
9
|
-
(await import("./app-
|
|
9
|
+
(await import("./app-DoxADJlo.mjs")).main();
|
|
10
10
|
} catch (error) {
|
|
11
11
|
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
12
12
|
process.exit(1);
|
|
@@ -1058,6 +1058,17 @@ const dictionaries = {
|
|
|
1058
1058
|
"dispatch.tasks_browser_failed": "打开任务浏览器失败:{error}",
|
|
1059
1059
|
"dispatch.usage_failed": "显示使用情况失败:{error}",
|
|
1060
1060
|
"dispatch.status_failed": "显示状态报告失败:{error}",
|
|
1061
|
+
"dispatch.eval_no_model": "没有可用的模型。请先通过 /connect 或 /config 配置模型。",
|
|
1062
|
+
"dispatch.eval_started": "正在运行端到端评测(模型:{model})…完成后将显示结果",
|
|
1063
|
+
"dispatch.eval_running": "评测已在运行中,请等待完成",
|
|
1064
|
+
"dispatch.eval_progress": "评测 {completed}/{total}:{name}(已通过 {passed},未通过 {failed})…",
|
|
1065
|
+
"dispatch.eval_passed": "通过",
|
|
1066
|
+
"dispatch.eval_failed_count": "未通过",
|
|
1067
|
+
"dispatch.eval_failed": "评测失败:{error}",
|
|
1068
|
+
"dispatch.eval_suggest_error": "可能是网络连接、API 配置或账户余额的问题,请检查后重试",
|
|
1069
|
+
"dispatch.eval_suggest_timeout": "任务执行超时,可能是网络较慢或模型响应时间较长,可更换模型后重试",
|
|
1070
|
+
"dispatch.eval_suggest_tool": "文件读写工具执行未成功,请检查工作目录权限;若重试后仍失败,请反馈",
|
|
1071
|
+
"dispatch.eval_suggest_answer": "模型回答可能不完整或不正确,可尝试更换更强的模型后重试",
|
|
1061
1072
|
"constant.llm_not_set": "LLM 未设置,运行 /config 自定义模型配置",
|
|
1062
1073
|
"constant.no_active_session": "没有活动会话。运行 /config 自定义模型配置。",
|
|
1063
1074
|
"constant.ctrl_d_hint": "再次按 Ctrl+D 退出",
|
|
@@ -1121,6 +1132,7 @@ const dictionaries = {
|
|
|
1121
1132
|
"registry.update_desc": "手动更新 Scream Code 到最新版本",
|
|
1122
1133
|
"registry.version_desc": "显示版本信息",
|
|
1123
1134
|
"registry.logout_desc": "删除已配置的模型",
|
|
1135
|
+
"registry.eval_desc": "运行端到端测试(Agent健康度检查)",
|
|
1124
1136
|
"registry.exit_desc": "退出应用",
|
|
1125
1137
|
"kw.hint_drag": "拖拽平移 · 滚轮缩放 · 单击展开/收起 · 双击详情",
|
|
1126
1138
|
"kw.detail_name": "名称",
|
|
@@ -2121,6 +2133,17 @@ const dictionaries = {
|
|
|
2121
2133
|
"dispatch.tasks_browser_failed": "Failed to open tasks browser: {error}",
|
|
2122
2134
|
"dispatch.usage_failed": "Failed to show usage: {error}",
|
|
2123
2135
|
"dispatch.status_failed": "Failed to show status report: {error}",
|
|
2136
|
+
"dispatch.eval_no_model": "No model available. Configure one via /connect or /config first.",
|
|
2137
|
+
"dispatch.eval_started": "Running end-to-end evals (model: {model})… results will appear when done",
|
|
2138
|
+
"dispatch.eval_running": "An eval run is already in progress, please wait",
|
|
2139
|
+
"dispatch.eval_progress": "Eval {completed}/{total}: {name} ({passed} passed, {failed} failed)…",
|
|
2140
|
+
"dispatch.eval_passed": "passed",
|
|
2141
|
+
"dispatch.eval_failed_count": "failed",
|
|
2142
|
+
"dispatch.eval_failed": "Eval run failed: {error}",
|
|
2143
|
+
"dispatch.eval_suggest_error": "This may be a network, API config, or account balance issue — please check and retry",
|
|
2144
|
+
"dispatch.eval_suggest_timeout": "The task timed out; this is often a slow network or slow model response. Try a different model and retry",
|
|
2145
|
+
"dispatch.eval_suggest_tool": "A file read/write tool did not complete. Please check directory permissions; if it still fails after retrying, please report it",
|
|
2146
|
+
"dispatch.eval_suggest_answer": "The model answer may be incomplete or incorrect. Try a stronger model and retry",
|
|
2124
2147
|
"constant.llm_not_set": "LLM not set, run /config to configure model",
|
|
2125
2148
|
"constant.no_active_session": "No active session. Run /config to configure model.",
|
|
2126
2149
|
"constant.ctrl_d_hint": "Press Ctrl+D again to exit",
|
|
@@ -2184,6 +2207,7 @@ const dictionaries = {
|
|
|
2184
2207
|
"registry.update_desc": "Manually update Scream Code to latest version",
|
|
2185
2208
|
"registry.version_desc": "Show version info",
|
|
2186
2209
|
"registry.logout_desc": "Remove configured models",
|
|
2210
|
+
"registry.eval_desc": "Run end-to-end tests (Agent health check)",
|
|
2187
2211
|
"registry.exit_desc": "Exit application",
|
|
2188
2212
|
"kw.hint_drag": "Drag to pan · Scroll to zoom · Click to expand/collapse · Double-click for details",
|
|
2189
2213
|
"kw.detail_name": "Name",
|
|
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
|
|
|
3
3
|
import { dirname as __cjsShimDirname } from 'node:path';
|
|
4
4
|
const __filename = __cjsShimFileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = __cjsShimDirname(__filename);
|
|
6
|
-
import { t as TextInputDialogComponent } from "./text-input-dialog-
|
|
6
|
+
import { t as TextInputDialogComponent } from "./text-input-dialog-9ywV88Lh.mjs";
|
|
7
7
|
export { TextInputDialogComponent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scream-code",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.7",
|
|
4
4
|
"description": "A terminal-native AI agent for builders",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ScreamCli",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
75
75
|
"@scream-code/agent-core": "workspace:^",
|
|
76
76
|
"@scream-code/config": "workspace:^",
|
|
77
|
+
"@scream-code/evals": "workspace:^",
|
|
77
78
|
"@scream-code/knowledge": "workspace:*",
|
|
78
79
|
"@scream-code/memory": "workspace:*",
|
|
79
80
|
"@scream-code/scream-code-sdk": "workspace:^",
|