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-BI6Q02oM.mjs";
10
+ import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-9ywV88Lh.mjs";
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
- function project(history) {
76348
- const usable = history.filter((message) => {
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
- function mergeAdjacentUserMessages(history) {
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)).sort();
76628
- const modifiedFiles = [...modified].sort();
76629
- const all = [...new Set([...readOnly, ...modifiedFiles])].sort();
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 sections = [summary.trim()];
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
- * Behavior is otherwise identical to the getter - this is observation
78808
- * only, it does not alter the messages returned.
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$2 = "main";
120335
+ const MAIN_AGENT_ID$3 = "main";
120195
120336
  var SDKRpcClient = class {
120196
120337
  core;
120197
- interactiveAgentId = MAIN_AGENT_ID$2;
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$1 = "main";
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$1,
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.6"),
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-BViNsv5f.mjs");
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-BViNsv5f.mjs");
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-NZLMIJND.mjs")).main();
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-BI6Q02oM.mjs";
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.6",
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:^",