scream-code 0.10.7 → 0.10.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{app-BZwFbrNj.mjs → app-D_usIcl7.mjs} +771 -81
- package/dist/main.mjs +1 -1
- package/dist/{text-input-dialog-DqBy9bEe.mjs → text-input-dialog-B_rgO4qn.mjs} +1 -1
- package/dist/{text-input-dialog-BWp39mq8.mjs → text-input-dialog-CiWeHRNJ.mjs} +25 -1
- package/package.json +1 -1
- package/dist/{src-BMbOMRuY.mjs → src-C8lOjRbK.mjs} +1 -1
|
@@ -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-CiWeHRNJ.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";
|
|
@@ -28,6 +28,8 @@ import Vt, { appendFileSync, chmodSync, closeSync, constants, createReadStream,
|
|
|
28
28
|
import * as path$8 from "node:path";
|
|
29
29
|
import path, { basename, dirname as dirname$1, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
30
30
|
import { z } from "zod";
|
|
31
|
+
import { exec, execFile, execSync, spawn, spawnSync } from "node:child_process";
|
|
32
|
+
import { promisify } from "node:util";
|
|
31
33
|
import { DatabaseSync } from "node:sqlite";
|
|
32
34
|
import * as nodeOs from "node:os";
|
|
33
35
|
import { homedir, tmpdir } from "node:os";
|
|
@@ -35,7 +37,6 @@ import { EventEmitter as EventEmitter$1 } from "node:events";
|
|
|
35
37
|
import { StringDecoder } from "node:string_decoder";
|
|
36
38
|
import Pi from "assert";
|
|
37
39
|
import ro from "node:assert";
|
|
38
|
-
import { exec, execFile, execSync, spawn, spawnSync } from "node:child_process";
|
|
39
40
|
import * as posixPath from "node:path/posix";
|
|
40
41
|
import * as win32Path from "node:path/win32";
|
|
41
42
|
import { createServer } from "node:http";
|
|
@@ -54,7 +55,6 @@ import chalk, { chalkStderr } from "chalk";
|
|
|
54
55
|
import { CombinedAutocompleteProvider, Container, Editor, Image, Input, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, getImageDimensions, isKeyRelease, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@liutod-scream/pi-tui";
|
|
55
56
|
import { highlight, supportsLanguage } from "cli-highlight";
|
|
56
57
|
import { diffWords } from "diff";
|
|
57
|
-
import { promisify } from "node:util";
|
|
58
58
|
import { gt, valid } from "semver";
|
|
59
59
|
import { createInterface as createInterface$1 } from "node:readline";
|
|
60
60
|
//#region ../../packages/agent-core/src/errors/codes.ts
|
|
@@ -10336,7 +10336,7 @@ var require_gaxios = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
10336
10336
|
}
|
|
10337
10337
|
static async #getFetch() {
|
|
10338
10338
|
const hasWindow = typeof window !== "undefined" && !!window;
|
|
10339
|
-
this.#fetch ||= hasWindow ? window.fetch : (await import("./src-
|
|
10339
|
+
this.#fetch ||= hasWindow ? window.fetch : (await import("./src-C8lOjRbK.mjs")).default;
|
|
10340
10340
|
return this.#fetch;
|
|
10341
10341
|
}
|
|
10342
10342
|
/**
|
|
@@ -55791,14 +55791,32 @@ const MAX_GOAL_OBJECTIVE_LENGTH = 4e3;
|
|
|
55791
55791
|
/** Consecutive markBlocked calls with the same reason required before blocking. */
|
|
55792
55792
|
const BLOCKED_STREAK_THRESHOLD = 3;
|
|
55793
55793
|
/** Maximum number of working notes kept per goal. */
|
|
55794
|
-
const MAX_GOAL_NOTES =
|
|
55794
|
+
const MAX_GOAL_NOTES = 60;
|
|
55795
55795
|
/** Maximum characters per note. */
|
|
55796
|
-
const MAX_NOTE_LENGTH =
|
|
55796
|
+
const MAX_NOTE_LENGTH = 400;
|
|
55797
55797
|
const GOAL_CANCELLED_REMINDER = [
|
|
55798
55798
|
"The user cancelled the current goal.",
|
|
55799
55799
|
"Ignore earlier active-goal reminders for that goal.",
|
|
55800
55800
|
"Handle the next user request normally unless the user starts or resumes a goal."
|
|
55801
55801
|
].join(" ");
|
|
55802
|
+
/**
|
|
55803
|
+
* Returns true when any configured budget has less than `threshold` of its
|
|
55804
|
+
* allowance remaining (e.g. 0.2 = under 20% left). Budgets that are not
|
|
55805
|
+
* configured (null) are ignored. Used to steer the model toward convergence
|
|
55806
|
+
* before a hard over-budget block fires.
|
|
55807
|
+
*/
|
|
55808
|
+
function isBudgetNearExhaustion(budget, threshold) {
|
|
55809
|
+
if (budget.turnBudget !== null && budget.remainingTurns !== null && budget.turnBudget > 0) {
|
|
55810
|
+
if (budget.remainingTurns / budget.turnBudget < threshold) return true;
|
|
55811
|
+
}
|
|
55812
|
+
if (budget.tokenBudget !== null && budget.remainingTokens !== null && budget.tokenBudget > 0) {
|
|
55813
|
+
if (budget.remainingTokens / budget.tokenBudget < threshold) return true;
|
|
55814
|
+
}
|
|
55815
|
+
if (budget.wallClockBudgetMs !== null && budget.remainingWallClockMs !== null && budget.wallClockBudgetMs > 0) {
|
|
55816
|
+
if (budget.remainingWallClockMs / budget.wallClockBudgetMs < threshold) return true;
|
|
55817
|
+
}
|
|
55818
|
+
return false;
|
|
55819
|
+
}
|
|
55802
55820
|
const GOAL_COMPLETION_REMINDER_NAME = "goal_completion_summary";
|
|
55803
55821
|
const GOAL_BLOCKED_REMINDER_NAME = "goal_blocked_reason";
|
|
55804
55822
|
var GoalMode = class {
|
|
@@ -55858,6 +55876,7 @@ var GoalMode = class {
|
|
|
55858
55876
|
state.wallClockResumedAt = void 0;
|
|
55859
55877
|
}
|
|
55860
55878
|
if (record.budgetLimits !== void 0) state.budgetLimits = record.budgetLimits;
|
|
55879
|
+
if (record.objective !== void 0) state.objective = record.objective;
|
|
55861
55880
|
}
|
|
55862
55881
|
restoreClear(_record) {
|
|
55863
55882
|
this.state = void 0;
|
|
@@ -55958,6 +55977,23 @@ var GoalMode = class {
|
|
|
55958
55977
|
this.appendGoalUpdate({ budgetLimits: state.budgetLimits });
|
|
55959
55978
|
return this.toSnapshot(state);
|
|
55960
55979
|
}
|
|
55980
|
+
async updateObjective(input, _actor = "user") {
|
|
55981
|
+
const state = this.requireState();
|
|
55982
|
+
const objective = input.objective.trim();
|
|
55983
|
+
if (objective.length === 0) throw new ScreamError(ErrorCodes.GOAL_OBJECTIVE_EMPTY, "Goal objective cannot be empty");
|
|
55984
|
+
if (state.status === "complete") throw new ScreamError(ErrorCodes.GOAL_STATUS_INVALID, "Cannot update a completed goal.");
|
|
55985
|
+
if (state.status === "blocked") throw new ScreamError(ErrorCodes.GOAL_STATUS_INVALID, "Cannot update a blocked goal. Resume it first.");
|
|
55986
|
+
state.objective = objective;
|
|
55987
|
+
const noteContent = `Goal objective updated by user: ${objective}`.slice(0, MAX_NOTE_LENGTH);
|
|
55988
|
+
state.notes.push({
|
|
55989
|
+
content: noteContent,
|
|
55990
|
+
time: Date.now()
|
|
55991
|
+
});
|
|
55992
|
+
if (state.notes.length > MAX_GOAL_NOTES) state.notes = state.notes.slice(-60);
|
|
55993
|
+
this.persistState(state);
|
|
55994
|
+
this.appendGoalUpdate({ objective });
|
|
55995
|
+
return this.toSnapshot(state);
|
|
55996
|
+
}
|
|
55961
55997
|
async cancelGoal(actor = "user") {
|
|
55962
55998
|
const state = this.requireState();
|
|
55963
55999
|
const snapshot = this.toSnapshot(state);
|
|
@@ -56041,7 +56077,7 @@ var GoalMode = class {
|
|
|
56041
56077
|
content: trimmed,
|
|
56042
56078
|
time: Date.now()
|
|
56043
56079
|
});
|
|
56044
|
-
if (state.notes.length > MAX_GOAL_NOTES) state.notes = state.notes.slice(-
|
|
56080
|
+
if (state.notes.length > MAX_GOAL_NOTES) state.notes = state.notes.slice(-60);
|
|
56045
56081
|
this.persistState(state, { silent: true });
|
|
56046
56082
|
return this.toSnapshot(state);
|
|
56047
56083
|
}
|
|
@@ -56198,6 +56234,8 @@ const UpdateGoalToolInputSchema = z.object({
|
|
|
56198
56234
|
reason: z.string().optional().describe("Optional reason for the status change, especially for blocked.")
|
|
56199
56235
|
}).strict();
|
|
56200
56236
|
const MAX_GRADER_OUTPUT_CHARS = 4e3;
|
|
56237
|
+
/** Maximum characters of `git diff --stat HEAD` to append to the grader input. */
|
|
56238
|
+
const MAX_DIFF_STAT_CHARS = 2e3;
|
|
56201
56239
|
function extractRecentOutput(history) {
|
|
56202
56240
|
const parts = [];
|
|
56203
56241
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
@@ -56210,6 +56248,51 @@ function extractRecentOutput(history) {
|
|
|
56210
56248
|
const joined = parts.join("\n\n");
|
|
56211
56249
|
return joined.length > MAX_GRADER_OUTPUT_CHARS ? `${joined.slice(0, MAX_GRADER_OUTPUT_CHARS)}…` : joined;
|
|
56212
56250
|
}
|
|
56251
|
+
/**
|
|
56252
|
+
* Append cross-turn working notes to the output seen by the grader. Notes are
|
|
56253
|
+
* written by the agent across continuation turns, so they provide focused
|
|
56254
|
+
* context (key findings, constraints, partial results) without exposing the
|
|
56255
|
+
* full text of previously rejected outputs.
|
|
56256
|
+
*/
|
|
56257
|
+
function appendGoalNotes(output, notes) {
|
|
56258
|
+
if (notes.length === 0) return output;
|
|
56259
|
+
return `${output}\n\n## Cross-turn working notes\n${notes.map((note) => `• ${note.content}`).join("\n")}`;
|
|
56260
|
+
}
|
|
56261
|
+
const execFileAsync = promisify(execFile);
|
|
56262
|
+
/**
|
|
56263
|
+
* Fetch a concise git diff stat against HEAD for the current working directory.
|
|
56264
|
+
* Using HEAD includes both staged and unstaged changes, so the reviewer sees
|
|
56265
|
+
* every file touched during the turn even if the agent ran `git add`. Returns
|
|
56266
|
+
* `null` when there are no changes. Throws when git is unavailable or the
|
|
56267
|
+
* directory is not a git repository.
|
|
56268
|
+
*/
|
|
56269
|
+
/** Exported for testing only. */
|
|
56270
|
+
async function fetchGitDiffStat(cwd) {
|
|
56271
|
+
const { stdout } = await execFileAsync("git", [
|
|
56272
|
+
"diff",
|
|
56273
|
+
"--stat",
|
|
56274
|
+
"HEAD"
|
|
56275
|
+
], { cwd });
|
|
56276
|
+
const stat = stdout.trim();
|
|
56277
|
+
return stat.length > 0 ? stat : null;
|
|
56278
|
+
}
|
|
56279
|
+
/**
|
|
56280
|
+
* Append a git diff stat to the grader input so the reviewer can correlate the
|
|
56281
|
+
* agent's claims with the actual files changed during the turn. Very large
|
|
56282
|
+
* stats are truncated to avoid overflowing the reviewer's context window. When
|
|
56283
|
+
* git is unavailable, a note is appended so the reviewer knows why no diff is
|
|
56284
|
+
* present.
|
|
56285
|
+
*/
|
|
56286
|
+
async function appendGitDiffStat(output, cwd) {
|
|
56287
|
+
let stat;
|
|
56288
|
+
try {
|
|
56289
|
+
stat = await fetchGitDiffStat(cwd);
|
|
56290
|
+
} catch {
|
|
56291
|
+
return `${output}\n\n## Changes this turn\n(Git diff unavailable — workspace may not be a git repository or git is not installed.)`;
|
|
56292
|
+
}
|
|
56293
|
+
if (stat === null) return output;
|
|
56294
|
+
return `${output}\n\n## Changes this turn\n${stat.length > MAX_DIFF_STAT_CHARS ? `${stat.slice(0, MAX_DIFF_STAT_CHARS)}…\n(diff stat truncated)` : stat}`;
|
|
56295
|
+
}
|
|
56213
56296
|
var UpdateGoalTool = class {
|
|
56214
56297
|
agent;
|
|
56215
56298
|
grader;
|
|
@@ -56256,7 +56339,7 @@ var UpdateGoalTool = class {
|
|
|
56256
56339
|
async handleComplete(goal) {
|
|
56257
56340
|
const goalState = goal.getGoal().goal;
|
|
56258
56341
|
if (!goalState) return { output: "No active goal." };
|
|
56259
|
-
const
|
|
56342
|
+
const outputWithContext = await appendGitDiffStat(appendGoalNotes(extractRecentOutput(this.agent.context.history), goalState.notes), this.agent.config?.cwd ?? "");
|
|
56260
56343
|
try {
|
|
56261
56344
|
await goal.pauseGoal({ reason: "verifying" }, "system");
|
|
56262
56345
|
} catch (error) {
|
|
@@ -56264,7 +56347,7 @@ var UpdateGoalTool = class {
|
|
|
56264
56347
|
}
|
|
56265
56348
|
let rawGrade;
|
|
56266
56349
|
try {
|
|
56267
|
-
rawGrade = await this.grader(goalState.objective, goalState.completionCriterion,
|
|
56350
|
+
rawGrade = await this.grader(goalState.objective, goalState.completionCriterion, outputWithContext);
|
|
56268
56351
|
} catch (error) {
|
|
56269
56352
|
const resumeError = await resumeAfterGrading(goal);
|
|
56270
56353
|
if (resumeError !== void 0) return resumeError;
|
|
@@ -56340,7 +56423,7 @@ function errorMessage$4(error) {
|
|
|
56340
56423
|
}
|
|
56341
56424
|
//#endregion
|
|
56342
56425
|
//#region ../../packages/agent-core/src/tools/builtin/goal/write-goal-note.ts
|
|
56343
|
-
const WriteGoalNoteInputSchema = z.object({ content: z.string().min(1).max(
|
|
56426
|
+
const WriteGoalNoteInputSchema = z.object({ content: z.string().min(1).max(400).describe("A concise note about what you learned, verified, or decided. Notes are injected into future continuation turns so you can build on prior work.") }).strict();
|
|
56344
56427
|
var WriteGoalNoteTool = class {
|
|
56345
56428
|
agent;
|
|
56346
56429
|
name = "WriteGoalNote";
|
|
@@ -57577,6 +57660,16 @@ function parseMemoryMemos(text) {
|
|
|
57577
57660
|
}
|
|
57578
57661
|
return memos;
|
|
57579
57662
|
}
|
|
57663
|
+
/**
|
|
57664
|
+
* Strip injected memory-memo content from text before writing it back to the
|
|
57665
|
+
* store, preventing feedback loops where recalled/compacted content gets
|
|
57666
|
+
* re-stored. Removes fenced ```memory-memo blocks (the format emitted by
|
|
57667
|
+
* compaction and exit-time extraction) and any <memories>...</memories>
|
|
57668
|
+
* wrapper an injector might use.
|
|
57669
|
+
*/
|
|
57670
|
+
function stripMemoryTags(text) {
|
|
57671
|
+
return text.replaceAll(/```memory-memo[\s\S]*?```/gi, "").replaceAll(/<memories>[\s\S]*?<\/memories>/gi, "").trim();
|
|
57672
|
+
}
|
|
57580
57673
|
/** System prompt for exit-time extraction — instructs the LLM how to extract. */
|
|
57581
57674
|
const EXIT_EXTRACTION_SYSTEM_PROMPT = "你是一个任务经验提取助手。任务是从对话记录中识别已完成的任务闭环,提炼出任务经验记录。用对话的主要语言输出(中文对话用中文,英文对话用英文)。只输出指定的 JSON 格式,不要调用任何工具。";
|
|
57582
57675
|
/** Build the user prompt for exit-time extraction, including a conversation sample. */
|
|
@@ -58329,17 +58422,20 @@ var MemoryWriteTool = class {
|
|
|
58329
58422
|
};
|
|
58330
58423
|
const sessionId = this.agent.homedir ? basename$1(dirname$2(dirname$2(this.agent.homedir))) : "unknown";
|
|
58331
58424
|
const sourceSessionTitle = await this.agent.getSessionTitle();
|
|
58332
|
-
const
|
|
58333
|
-
const
|
|
58334
|
-
const
|
|
58425
|
+
const userNeed = stripMemoryTags(args.userNeed);
|
|
58426
|
+
const approach = stripMemoryTags(args.approach);
|
|
58427
|
+
const outcome = stripMemoryTags(args.outcome);
|
|
58428
|
+
const whatFailed = stripMemoryTags(args.whatFailed ?? "");
|
|
58429
|
+
const whatWorked = stripMemoryTags(args.whatWorked ?? "");
|
|
58430
|
+
const tags = normalizeTags(args.tags !== void 0 && args.tags.length > 0 ? args.tags : generateTags(`${userNeed} ${approach}`));
|
|
58335
58431
|
const memo = createMemoryMemo({
|
|
58336
58432
|
sourceSessionId: sessionId,
|
|
58337
58433
|
sourceSessionTitle,
|
|
58338
|
-
userNeed
|
|
58339
|
-
approach
|
|
58340
|
-
outcome
|
|
58341
|
-
whatFailed: whatFailed
|
|
58342
|
-
whatWorked: whatWorked
|
|
58434
|
+
userNeed,
|
|
58435
|
+
approach,
|
|
58436
|
+
outcome,
|
|
58437
|
+
whatFailed: whatFailed.length === 0 ? "none" : whatFailed,
|
|
58438
|
+
whatWorked: whatWorked.length === 0 ? "none" : whatWorked,
|
|
58343
58439
|
tags,
|
|
58344
58440
|
extractionSource: "manual",
|
|
58345
58441
|
projectDir: this.agent.config.cwd
|
|
@@ -62706,20 +62802,20 @@ function isRecord$6(value) {
|
|
|
62706
62802
|
var dream_default = "---\nname: dream\ndescription: 整理记忆库 — 合并重复、解决矛盾、清理过时条目\n---\n\n# Dream: 记忆合并整理\n\n用户调用了 `/dream`。你要对全局记忆库进行一次完整的整理和清理。\n\n记忆库是**全局**的,所有会话的记忆都保存在同一个 SQLite 数据库里(`<screamHomeDir>/memory/memos.sqlite`),不是按会话分散存放。\n\n## 前置检查\n\n1. 调用 `MemoryConsolidatePlan` 工具获取整理计划。\n - 如果返回\"记忆库为空\",告知用户\"记忆库为空,无需整理\"并停止。\n - 否则你会得到一个 JSON 计划,包含:duplicateGroups(重复组)、resolved(已完成条目)、stale(过时条目)、summary(统计)。\n\n2. 不要直接修改记忆文件。所有删除和写入都通过 `MemoryConsolidateApply` 工具完成。\n\n## 整理计划展示\n\n把计划转换成用户可读的格式:\n\n```\n## Dream 整理计划\n\n### 概况\n- 当前共 X 条记忆\n- 重复组:N 组\n- 建议删除:M 条(已完成 + 过时)\n- 整理后预计:Y 条\n\n### 重复合并(N 组)\n**组 1: 修复登录 token 刷新**\n- memo-abc123 (2026-05-01, 完成) — \"登录页 token 过期需要手动刷新\"\n- memo-def456 (2026-05-10, 完成) — \"登录 token 过期问题修复\"\n→ 合并为: \"修复登录页 token 过期问题。方案: 在 axios 拦截器中添加自动 refresh 逻辑...\"\n 结果: 完成\n\n### 建议删除(M 条)\n- memo-xyz789: \"添加暗色模式\" (完成, 2026-04-01) — 已完成超过 2 个月\n- memo-old001: \"尝试某方案\" (放弃, 2026-03-01) — 过时\n\n### 总结\n- 合并: N 组 → 减少 X 条\n- 删除: M 条\n- 整理后: 共 Y 条记忆\n```\n\n用 AskUserQuestion 让用户选择:\n- \"执行整理\" — 按上述计划执行\n- \"仅显示计划\" — 不做修改,直接结束\n- \"取消\"\n\n## 执行整理\n\n如果用户选择\"执行整理\":\n\n1. 直接把 `MemoryConsolidatePlan` 返回的完整 JSON 计划作为参数,调用 `MemoryConsolidateApply`。\n2. 工具会自动完成以下操作:\n - 删除重复组中的原记忆\n - 为每组重复记忆追加一条合并后的新记忆(字段格式由工具保证正确)\n - 删除已完成和过时的记忆\n - 更新 `dream-lock.json`,重置建议计数器\n3. 向用户报告工具返回的结果:\"已删除 X 条,创建 Y 条合并记忆。记忆库整理完成。\"\n\n## 重要规则\n\n- **仅合并高度重复的记忆**。只有当两条记忆描述的是同一件事、同一个问题、同一个修复方案时才可合并。如果只是主题相关但细节不同(如两个不同的 bug、两个不同的优化方向),**绝对不能合并**。宁可多保留十条,不可误删一条。\n- 如果 `MemoryConsolidatePlan` 标记的某组重复实际上并不是同一件事,在展示计划时把它从\"重复合并\"里去掉,只保留你确信重复的组,再把精简后的计划传给 `MemoryConsolidateApply`。\n- 不确定时保留原文,不要猜测删除。\n- 操作前必须得到用户确认,不能擅自执行。\n- 不要手动写 Bash 去修改 `entries.jsonl`,统一通过 `MemoryConsolidateApply` 工具执行。\n";
|
|
62707
62803
|
//#endregion
|
|
62708
62804
|
//#region ../../packages/agent-core/src/skill/builtin/dream.ts
|
|
62709
|
-
const PSEUDO_PATH$
|
|
62710
|
-
const parsed$
|
|
62805
|
+
const PSEUDO_PATH$2 = "builtin://dream";
|
|
62806
|
+
const parsed$2 = parseSkillText({
|
|
62711
62807
|
skillMdPath: "/builtin/skills/dream.md",
|
|
62712
62808
|
skillDirName: "dream",
|
|
62713
62809
|
source: "builtin",
|
|
62714
62810
|
text: dream_default
|
|
62715
62811
|
});
|
|
62716
62812
|
const DREAM_SKILL = {
|
|
62717
|
-
...parsed$
|
|
62718
|
-
path: PSEUDO_PATH$
|
|
62719
|
-
dir: PSEUDO_PATH$
|
|
62813
|
+
...parsed$2,
|
|
62814
|
+
path: PSEUDO_PATH$2,
|
|
62815
|
+
dir: PSEUDO_PATH$2,
|
|
62720
62816
|
metadata: {
|
|
62721
|
-
...parsed$
|
|
62722
|
-
type: parsed$
|
|
62817
|
+
...parsed$2.metadata,
|
|
62818
|
+
type: parsed$2.metadata.type ?? "inline",
|
|
62723
62819
|
disableModelInvocation: true
|
|
62724
62820
|
}
|
|
62725
62821
|
};
|
|
@@ -62728,14 +62824,36 @@ const DREAM_SKILL = {
|
|
|
62728
62824
|
var make_skill_default = "---\nname: make-skill\ndescription: 从当前会话上下文沉淀工作流为可复用 Skill\n---\n\n# Make Skill: 从上下文提炼 Skill\n\n用户调用了 `/make-skill`。你的任务是通过对话引导用户,把当前会话中解决问题的方式沉淀为一个可复用的 Scream Code Skill,并安装到插件中心。\n\n## 激活参数\n\n本次激活的参数:\n\n```json\n$ARGUMENTS\n```\n\n- `initialRequest`:用户输入 `/make-skill` 时附带的一句话描述,可能为空。\n\n## 工作方式\n\n这不是一次性任务。你需要通过**多轮对话**澄清以下信息,每一轮只问一个问题,等用户回答后再进入下一阶段:\n\n1. Skill 类型(`workflow` / `code-pattern` / `troubleshooting` / `tool-chain` / `custom`)\n2. Skill 名称(kebab-case)\n3. 这个 Skill 主要解决什么问题\n4. 希望重点关注哪些内容\n5. 生成草案并确认安装\n\n## 阶段判断\n\n根据当前对话历史判断你处于哪个阶段:\n\n- **阶段 0**:本 Skill 刚激活,还没有问过任何问题。先分析会话上下文和 `initialRequest`,然后用 `AskUserQuestion` 询问 Skill 类型。\n- **阶段 1**:已经确定了 Skill 类型,但还没有确定名称。根据类型和上下文建议一个 kebab-case 名称,用 `AskUserQuestion` 询问用户是否接受或修改。\n- **阶段 2**:已经确定了名称,但还没有明确解决的问题。根据上下文总结一句话描述,用 `AskUserQuestion` 询问用户是否接受或修改。\n- **阶段 3**:已经确定了问题,但还没有明确关注重点。给出 2-4 个关注重点建议,用 `AskUserQuestion` 让用户选择或输入。\n- **阶段 4**:类型、名称、问题、重点都已确定。调用 `MakeSkillPlanTool` 生成草案,展示给用户,并用 `AskUserQuestion` 询问是否确认安装。\n- **阶段 5**:用户已确认安装。调用 `MakeSkillApplyTool` 写入插件中心。\n\n如何判断“已确定”:历史消息中已经有你提出的 `AskUserQuestion` 以及用户给出的明确回答(或选择了你建议的选项)。\n\n## 每轮提问规范\n\n除非处于阶段 4/5,否则**每轮必须也只允许使用一次 `AskUserQuestion`**。不要直接用普通文本提问,这样无法给用户结构化选项。\n\n### 阶段 0:询问 Skill 类型\n\n先分析当前会话上下文,判断最可能想沉淀什么。给出 2-4 个建议选项,不要列出全部 5 种。把最相关的放在最前面,并标记 `(Recommended)`。\n\n示例问题:\n\n```json\n{\n \"questions\": [\n {\n \"question\": \"根据刚才的会话,你想把什么沉淀成 Skill?\",\n \"header\": \"类型\",\n \"options\": [\n { \"label\": \"Code pattern (Recommended)\", \"description\": \"把 React 表单验证的代码模式提炼为可复用模板\" },\n { \"label\": \"Workflow\", \"description\": \"把解决表单验证问题的步骤沉淀为流程\" },\n { \"label\": \"Troubleshooting\", \"description\": \"把常见验证错误排查过程沉淀为诊断指南\" }\n ],\n \"multi_select\": false\n }\n ]\n}\n```\n\n注意:系统会自动添加 \"Other\" 选项,**不要自己添加**。如果用户想选未列出的类型(如 `tool-chain` 或 `custom`),他们会通过 Other 输入。\n\n### 阶段 1:询问 Skill 名称\n\n根据已确定的类型和上下文,建议一个 kebab-case 名称。用 `AskUserQuestion` 让用户接受、修改或自己输入。\n\n示例:\n\n```json\n{\n \"questions\": [\n {\n \"question\": \"建议把这个 Skill 命名为 react-form-validate,是否接受?\",\n \"header\": \"名称\",\n \"options\": [\n { \"label\": \"使用 react-form-validate (Recommended)\", \"description\": \"简洁直观,符合 kebab-case\" },\n { \"label\": \"换一个名称\", \"description\": \"我给出其他建议\" },\n { \"label\": \"我自己输入\", \"description\": \"手动指定名称\" }\n ],\n \"multi_select\": false\n }\n ]\n}\n```\n\n如果用户选择“换一个名称”或“我自己输入”,你需要在下一轮继续用 `AskUserQuestion` 给出新建议或请求输入。\n\n### 阶段 2:询问解决的问题\n\n根据上下文总结一句话描述,让用户接受、修改或自己输入。\n\n示例:\n\n```json\n{\n \"questions\": [\n {\n \"question\": \"这个 Skill 主要用于:在 React 中用 zod + react-hook-form 实现表单验证。是否准确?\",\n \"header\": \"用途\",\n \"options\": [\n { \"label\": \"准确 (Recommended)\", \"description\": \"保持这个描述\" },\n { \"label\": \"不够准确\", \"description\": \"我帮你调整\" },\n { \"label\": \"我自己描述\", \"description\": \"手动输入用途\" }\n ],\n \"multi_select\": false\n }\n ]\n}\n```\n\n### 阶段 3:询问关注重点\n\n给出 2-4 个基于上下文的关注重点建议。\n\n示例:\n\n```json\n{\n \"questions\": [\n {\n \"question\": \"生成时希望重点关注哪些方面?\",\n \"header\": \"重点\",\n \"options\": [\n { \"label\": \"验证 schema 定义 (Recommended)\", \"description\": \"重点提取 zod schema 的编写模式\" },\n { \"label\": \"错误处理与提示\", \"description\": \"重点提取错误展示和反馈逻辑\" },\n { \"label\": \"组件绑定方式\", \"description\": \"重点提取 react-hook-form 的绑定代码\" }\n ],\n \"multi_select\": true\n }\n ]\n}\n```\n\n### 阶段 4:展示草案并确认\n\n调用 `MakeSkillPlanTool`,参数:\n\n- `type`:阶段 0 确定的类型\n- `nameHint`:阶段 1 确定的名称\n- `purpose`:阶段 2 确定的问题描述\n- `focus`:阶段 3 确定的关注重点(多选用逗号连接成字符串)\n\n工具返回 JSON 后,用中文清晰展示:\n\n- Skill 名称和描述\n- 文件清单(至少包含 `SKILL.md`)\n- 适用场景\n- 安装位置:`~/.scream-code/plugins/managed/<name>/`,可通过 `/plugin` 管理\n\n然后用 `AskUserQuestion` 询问:\n\n```json\n{\n \"questions\": [\n {\n \"question\": \"是否安装这个 Skill?\",\n \"header\": \"确认\",\n \"options\": [\n { \"label\": \"确认安装 (Recommended)\", \"description\": \"写入插件中心并在新会话中可用\" },\n { \"label\": \"取消\", \"description\": \"不保存任何内容\" }\n ],\n \"multi_select\": false\n }\n ]\n}\n```\n\n### 阶段 5:执行安装\n\n如果用户选择“确认安装”,调用 `MakeSkillApplyTool`,传入工具返回的完整草案 JSON(`name`、`description`、`content`、`files`)。\n\n把结果告知用户,例如:\n\n- 成功:`Skill 已安装到 ~/.scream-code/plugins/managed/<name>/。新会话中可通过 /<name> 调用。`\n- 失败:说明错误原因,不要重试。\n\n## 重要规则\n\n- 除了阶段 4 的展示文本外,**所有澄清问题都必须通过 `AskUserQuestion` 工具提出**,不要直接用文本回复提问。\n- 每轮只能问一个问题。等用户回答后再推进到下一阶段。\n- 不要在没有调用 `MakeSkillApplyTool` 的情况下直接写文件。\n- 如果用户选择“取消”或关闭问题,礼貌地告知已取消,不做任何修改。\n- 如果 `MakeSkillApplyTool` 返回错误(例如同名 Skill 已存在),向用户说明错误并停止,不要重试。\n- 新安装的 Skill 只在**新会话**中可用;当前会话不会立即加载它。\n- 安装后的 Skill 会出现在 `/plugin` 插件中心里,用户可以统一启用、禁用或卸载。\n";
|
|
62729
62825
|
//#endregion
|
|
62730
62826
|
//#region ../../packages/agent-core/src/skill/builtin/make-skill.ts
|
|
62731
|
-
const PSEUDO_PATH = "builtin://make-skill";
|
|
62732
|
-
const parsed = parseSkillText({
|
|
62827
|
+
const PSEUDO_PATH$1 = "builtin://make-skill";
|
|
62828
|
+
const parsed$1 = parseSkillText({
|
|
62733
62829
|
skillMdPath: "/builtin/skills/make-skill.md",
|
|
62734
62830
|
skillDirName: "make-skill",
|
|
62735
62831
|
source: "builtin",
|
|
62736
62832
|
text: make_skill_default
|
|
62737
62833
|
});
|
|
62738
62834
|
const MAKE_SKILL_SKILL = {
|
|
62835
|
+
...parsed$1,
|
|
62836
|
+
path: PSEUDO_PATH$1,
|
|
62837
|
+
dir: PSEUDO_PATH$1,
|
|
62838
|
+
metadata: {
|
|
62839
|
+
...parsed$1.metadata,
|
|
62840
|
+
type: parsed$1.metadata.type ?? "inline",
|
|
62841
|
+
disableModelInvocation: true
|
|
62842
|
+
}
|
|
62843
|
+
};
|
|
62844
|
+
//#endregion
|
|
62845
|
+
//#region ../../packages/agent-core/src/skill/builtin/tool-prompt-optimization/SKILL.md
|
|
62846
|
+
var SKILL_default = "---\nname: tool-prompt-optimization\ndescription: Audit and trim tool prompt text that duplicates information already inferable from the tool's JSON schema, reducing system prompt token cost.\n---\n\n# Tool Prompt Optimization\n\nA meta-skill for cutting system-prompt token waste: tool `description` text often\nrestates field names, types, and constraints that the Zod/JSON schema already\nencodes. Use a probe to measure the overlap, then trim what the model can infer\nfrom the schema alone.\n\n## When to use\n\n- System prompt token cost is high and tool descriptions are a meaningful share.\n- A tool's `description` repeats field names or types already in its schema.\n- Auditing tool definitions for redundancy before a release.\n\n## How to audit\n\n1. For each tool, lay the `description` text next to the Zod/JSON schema fields.\n2. Flag sentences that merely restate field names, types, or constraints already\n encoded in the schema (e.g. \"command is a string\" when the schema already\n declares `z.string()`).\n3. **Probe**: ask the model \"Given only the schema (no description), which\n behaviors and constraints can you infer?\" The intersection is pure redundancy.\n4. Trim description text the model already infers from the schema alone.\n5. **Keep** what the schema cannot express:\n - edge cases, gotchas, and ordering requirements\n - cross-tool interactions and precedence rules\n - safety-critical warnings and permission notes\n - examples that disambiguate ambiguous schema fields\n\n## Rules\n\n- Never remove safety-critical warnings or permission notes.\n- Never remove examples that clarify ambiguous schema fields.\n- Before deleting a sentence, run `git blame` to check whether it was added to\n fix a specific bug; if so, keep it (or confirm the bug is gone).\n- Measure the before/after token count to confirm savings actually materialized.\n- Prefer precise constraints over vague prose; if a constraint is enforceable in\n the schema (e.g. `.min(1)`), move it there instead of describing it in text.\n";
|
|
62847
|
+
//#endregion
|
|
62848
|
+
//#region ../../packages/agent-core/src/skill/builtin/tool-prompt-optimization.ts
|
|
62849
|
+
const PSEUDO_PATH = "builtin://tool-prompt-optimization";
|
|
62850
|
+
const parsed = parseSkillText({
|
|
62851
|
+
skillMdPath: "/builtin/skills/tool-prompt-optimization/SKILL.md",
|
|
62852
|
+
skillDirName: "tool-prompt-optimization",
|
|
62853
|
+
source: "builtin",
|
|
62854
|
+
text: SKILL_default
|
|
62855
|
+
});
|
|
62856
|
+
const TOOL_PROMPT_OPTIMIZATION_SKILL = {
|
|
62739
62857
|
...parsed,
|
|
62740
62858
|
path: PSEUDO_PATH,
|
|
62741
62859
|
dir: PSEUDO_PATH,
|
|
@@ -62750,6 +62868,7 @@ const MAKE_SKILL_SKILL = {
|
|
|
62750
62868
|
function registerBuiltinSkills(registry) {
|
|
62751
62869
|
registry.registerBuiltinSkill(DREAM_SKILL);
|
|
62752
62870
|
registry.registerBuiltinSkill(MAKE_SKILL_SKILL);
|
|
62871
|
+
registry.registerBuiltinSkill(TOOL_PROMPT_OPTIMIZATION_SKILL);
|
|
62753
62872
|
}
|
|
62754
62873
|
//#endregion
|
|
62755
62874
|
//#region ../../packages/agent-core/src/skill/scanner.ts
|
|
@@ -75307,17 +75426,25 @@ var ToolResultBuilder = class {
|
|
|
75307
75426
|
maxChars;
|
|
75308
75427
|
maxTailChars;
|
|
75309
75428
|
maxLineLength;
|
|
75429
|
+
artifactSink;
|
|
75430
|
+
maxFullOutputChars;
|
|
75310
75431
|
buffer = [];
|
|
75311
75432
|
nCharsValue = 0;
|
|
75312
75433
|
truncationHappened = false;
|
|
75313
75434
|
headTruncated = false;
|
|
75314
75435
|
tailBuf = [];
|
|
75315
75436
|
tailCharsValue = 0;
|
|
75437
|
+
fullOutput;
|
|
75438
|
+
fullOutputChars = 0;
|
|
75439
|
+
totalLinesWritten = 0;
|
|
75316
75440
|
constructor(options = {}) {
|
|
75317
75441
|
this.maxChars = options.maxChars ?? DEFAULT_MAX_CHARS;
|
|
75318
75442
|
this.maxTailChars = options.maxTailChars ?? DEFAULT_TAIL_CHARS;
|
|
75319
75443
|
this.maxLineLength = options.maxLineLength === void 0 ? DEFAULT_MAX_LINE_LENGTH : options.maxLineLength;
|
|
75320
75444
|
if (this.maxLineLength !== null && this.maxLineLength <= 14) throw new Error("maxLineLength must be greater than the truncation marker length.");
|
|
75445
|
+
this.artifactSink = options.artifactSink;
|
|
75446
|
+
this.maxFullOutputChars = options.maxFullOutputChars ?? 1e6;
|
|
75447
|
+
if (this.artifactSink !== void 0) this.fullOutput = [];
|
|
75321
75448
|
}
|
|
75322
75449
|
get nChars() {
|
|
75323
75450
|
return this.nCharsValue + this.tailCharsValue;
|
|
@@ -75327,12 +75454,19 @@ var ToolResultBuilder = class {
|
|
|
75327
75454
|
if (!this.headTruncated || this.tailCharsValue === 0) return head;
|
|
75328
75455
|
this.trimTail();
|
|
75329
75456
|
const tail = this.tailBuf.join("");
|
|
75330
|
-
|
|
75457
|
+
const separator = head.endsWith("\n") ? "" : "\n";
|
|
75458
|
+
const elided = this.computeElidedLines(head, tail);
|
|
75459
|
+
return `${head}${separator}${elided > 0 ? `[…${String(elided)} lines elided…]\n${TRUNCATION_MARKER}` : TRUNCATION_MARKER}\n${tail}`;
|
|
75331
75460
|
}
|
|
75332
75461
|
write(text) {
|
|
75333
75462
|
if (text.length === 0) return 0;
|
|
75334
75463
|
const lines = text.match(/[^\r\n]*(?:\r\n|[\n\r])|[^\r\n]+/g) ?? [];
|
|
75335
75464
|
if (lines.length === 0) return 0;
|
|
75465
|
+
this.totalLinesWritten += lines.length;
|
|
75466
|
+
if (this.fullOutput !== void 0 && this.fullOutputChars < this.maxFullOutputChars) {
|
|
75467
|
+
this.fullOutput.push(text);
|
|
75468
|
+
this.fullOutputChars += text.length;
|
|
75469
|
+
}
|
|
75336
75470
|
let charsWritten = 0;
|
|
75337
75471
|
for (const originalLine of lines) if (this.nCharsValue < this.maxChars) {
|
|
75338
75472
|
const remainingChars = this.maxChars - this.nCharsValue;
|
|
@@ -75380,11 +75514,36 @@ var ToolResultBuilder = class {
|
|
|
75380
75514
|
this.tailBuf.push(trimmed);
|
|
75381
75515
|
this.tailCharsValue = trimmed.length;
|
|
75382
75516
|
}
|
|
75383
|
-
|
|
75517
|
+
computeElidedLines(head, tail) {
|
|
75518
|
+
if (this.totalLinesWritten === 0) return 0;
|
|
75519
|
+
if (this.fullOutput !== void 0) {
|
|
75520
|
+
const total = countLines(this.fullOutput.join(""));
|
|
75521
|
+
return Math.max(0, total - countLines(head) - countLines(tail));
|
|
75522
|
+
}
|
|
75523
|
+
return Math.max(0, this.totalLinesWritten - countLines(head) - countLines(tail));
|
|
75524
|
+
}
|
|
75525
|
+
async maybeWriteArtifact() {
|
|
75526
|
+
if (this.artifactSink === void 0 || this.fullOutput === void 0) return void 0;
|
|
75527
|
+
if (!this.truncationHappened) return void 0;
|
|
75528
|
+
const full = this.fullOutput.join("");
|
|
75529
|
+
if (full.length === 0) return void 0;
|
|
75530
|
+
try {
|
|
75531
|
+
return await this.artifactSink(full);
|
|
75532
|
+
} catch {
|
|
75533
|
+
return;
|
|
75534
|
+
}
|
|
75535
|
+
}
|
|
75536
|
+
appendArtifactRef(output, ref) {
|
|
75537
|
+
const line = `[full output saved: ${ref}]`;
|
|
75538
|
+
return output.length === 0 ? line : output.endsWith("\n") ? `${output}${line}` : `${output}\n${line}`;
|
|
75539
|
+
}
|
|
75540
|
+
async ok(message = "", options = {}) {
|
|
75384
75541
|
let finalMessage = message;
|
|
75385
75542
|
if (finalMessage.length > 0 && !finalMessage.endsWith(".")) finalMessage += ".";
|
|
75386
75543
|
if (this.truncationHappened) finalMessage = finalMessage.length === 0 ? TRUNCATION_MESSAGE : `${finalMessage} ${TRUNCATION_MESSAGE}`;
|
|
75387
|
-
const
|
|
75544
|
+
const baseOutput = this.toString();
|
|
75545
|
+
const artifactRef = await this.maybeWriteArtifact();
|
|
75546
|
+
const output = artifactRef === void 0 ? baseOutput : this.appendArtifactRef(baseOutput, artifactRef);
|
|
75388
75547
|
return {
|
|
75389
75548
|
isError: false,
|
|
75390
75549
|
output: finalMessage.length > 0 && (this.truncationHappened || output.length === 0) ? output.length === 0 ? finalMessage : output.endsWith("\n") ? `${output}${finalMessage}` : `${output}\n${finalMessage}` : output,
|
|
@@ -75393,9 +75552,11 @@ var ToolResultBuilder = class {
|
|
|
75393
75552
|
brief: options.brief
|
|
75394
75553
|
};
|
|
75395
75554
|
}
|
|
75396
|
-
error(message, options = {}) {
|
|
75555
|
+
async error(message, options = {}) {
|
|
75397
75556
|
const finalMessage = this.truncationHappened ? message.length === 0 ? TRUNCATION_MESSAGE : `${message} ${TRUNCATION_MESSAGE}` : message;
|
|
75398
|
-
const
|
|
75557
|
+
const baseOutput = this.toString();
|
|
75558
|
+
const artifactRef = await this.maybeWriteArtifact();
|
|
75559
|
+
const output = artifactRef === void 0 ? baseOutput : this.appendArtifactRef(baseOutput, artifactRef);
|
|
75399
75560
|
return {
|
|
75400
75561
|
isError: true,
|
|
75401
75562
|
output: finalMessage.length === 0 ? output : output.length === 0 ? finalMessage : output.endsWith("\n") ? `${output}${finalMessage}` : `${output}\n${finalMessage}`,
|
|
@@ -75405,6 +75566,10 @@ var ToolResultBuilder = class {
|
|
|
75405
75566
|
};
|
|
75406
75567
|
}
|
|
75407
75568
|
};
|
|
75569
|
+
function countLines(text) {
|
|
75570
|
+
if (text.length === 0) return 0;
|
|
75571
|
+
return text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
|
|
75572
|
+
}
|
|
75408
75573
|
//#endregion
|
|
75409
75574
|
//#region ../../packages/agent-core/src/tools/builtin/file/grep.md
|
|
75410
75575
|
var grep_default = "Search file contents using regular expressions (powered by ripgrep).\n\nUse Grep when the task is to find unknown content or unknown file locations. Do not use shell `grep` or `rg` directly; this tool applies workspace path policy, output limits, and sensitive-file filtering.\nALWAYS use Grep tool instead of running `grep` or `rg` from a shell — direct shell calls bypass workspace policy, output limits, and sensitive-file filtering.\nIf you already know a concrete file path and need to inspect its contents, use Read directly instead.\n\nWrite patterns in ripgrep regex syntax, which differs from POSIX `grep` syntax. For example, braces are special, so escape them as `\\{` to match a literal `{`.\n\nHidden files (dotfiles such as `.gitlab-ci.yml` or `.eslintrc.json`) are searched by default. To also search files excluded by `.gitignore` (such as `node_modules` or build outputs), set `include_ignored` to `true`. Sensitive files (such as `.env`) are always skipped for safety, even when `include_ignored` is `true`.\n";
|
|
@@ -75594,7 +75759,7 @@ var GrepTool = class {
|
|
|
75594
75759
|
const combined = visibleBody === "" && messages.length === 0 ? emptyResultMessage : messages.length > 0 ? visibleBody === "" ? messages.join("\n") : `${visibleBody}\n${messages.join("\n")}` : visibleBody;
|
|
75595
75760
|
const builder = new ToolResultBuilder();
|
|
75596
75761
|
builder.write(combined);
|
|
75597
|
-
const result = builder.ok(sideChannelMessages.join("\n"));
|
|
75762
|
+
const result = await builder.ok(sideChannelMessages.join("\n"));
|
|
75598
75763
|
const display = buildSearchResultsDisplay(args, limited, mode, contentIncludesLineNumbers);
|
|
75599
75764
|
if (display === void 0) return result;
|
|
75600
75765
|
if (result.isError === true) return result;
|
|
@@ -77814,6 +77979,30 @@ function truncateUtf8$1(input, maxBytes) {
|
|
|
77814
77979
|
var bash_default = "Execute a `{{ SHELL_NAME }}` command. Use this for shell semantics — pipes, env, processes, git, package managers, build/test runners, anything genuinely interactive or multi-step.\n\n**Translate these to a dedicated tool instead:**\n- `cat` / `head` / `tail` (known path) → `Read`\n- `sed` / `awk` (in-place edit) → `Edit`\n- `echo > file` / `cat <<EOF` → `Write`\n- `find` / recursive `ls` to locate files by name pattern → `Glob` (plain `ls <known-directory>` is fine for listing a directory)\n- `grep` / `rg` (search file contents) → `Grep`\n- `echo` / `printf` (talk to the user) → just output text directly\n\nThe dedicated tools render in the per-tool permission UI and keep raw stdout out of the conversation; that is why they are worth reaching for whenever one fits.\n\n**Output:**\nThe stdout and stderr will be combined and returned as a string. The output may be truncated if it is too long. If the command failed, the output will end with a `Command failed with exit code: N` line stating the non-zero exit code.\n\nIf `run_in_background=true`, the command will be started as a background task and this tool will return a task ID instead of waiting for command completion. When doing that, you must provide a short `description`. Background commands default to a {{ DEFAULT_BACKGROUND_TIMEOUT_S }}s timeout and `timeout` is capped at {{ MAX_BACKGROUND_TIMEOUT_S }}s; set `disable_timeout=true` only when the task should run without a timeout. You will be automatically notified when the task completes. Use `TaskOutput` for a non-blocking status/output snapshot, and only set `block=true` when you explicitly want to wait for completion. Use `TaskStop` only if the task must be cancelled. If a human user wants to inspect background tasks themselves, point them to the `/tasks` command, which opens an interactive panel; it has no subcommands.\n\n**Guidelines for safety and security:**\n- Each shell tool call will be executed in a fresh shell environment. The shell variables, current working directory changes, and the shell history is not preserved between calls.\n- The tool call will return after the command is finished. You shall not use this tool to execute an interactive command or a command that may run forever. For possibly long-running foreground commands, set the `timeout` argument in seconds. Foreground commands default to {{ DEFAULT_TIMEOUT_S }}s and allow up to {{ MAX_TIMEOUT_S }}s.\n- Avoid using `..` to access files or directories outside of the working directory.\n- Avoid modifying files outside of the working directory unless explicitly instructed to do so.\n- Never run commands that require superuser privileges unless explicitly instructed to do so.\n\n**Guidelines for efficiency:**\n- For multiple related commands, use `&&` to chain them in a single call, e.g. `cd /path && ls -la`\n- Use `;` to run commands sequentially regardless of success/failure\n- Use `||` for conditional execution (run second command only if first fails)\n- Use pipe operations (`|`) and redirections (`>`, `>>`) to chain input and output between commands\n- Always quote file paths containing spaces with double quotes (e.g., cd \"/path with spaces/\")\n- Compose multi-step logic in a single call with `if` / `case` / `for` / `while` control flows.\n- Prefer `run_in_background=true` for long-running builds, tests, watchers, or servers when you need the conversation to continue before the command finishes.\n\n**Commands available:**\nThe following common command categories are usually available. Availability still depends on the host, so when in doubt run `which <command>` first to confirm a command exists before relying on it.\n- Navigation and inspection: `ls`, `pwd`, `cd`, `stat`, `file`, `du`, `df`, `tree`\n- File and directory management: `cp`, `mv`, `rm`, `mkdir`, `touch`, `ln`, `chmod`, `chown`\n- Text and data processing: `wc`, `sort`, `uniq`, `cut`, `tr`, `diff`, `xargs`\n- Archives and compression: `tar`, `gzip`, `gunzip`, `zip`, `unzip`\n- Networking and transfer: `curl`, `wget`, `ping`, `ssh`, `scp`\n- Version control: `git`\n- Process and system: `ps`, `kill`, `top`, `env`, `date`, `uname`, `whoami`\n- Language and package toolchains: `node`, `npm`, `pnpm`, `yarn`, `python`, `pip` (use whichever the project actually relies on)\n";
|
|
77815
77980
|
//#endregion
|
|
77816
77981
|
//#region ../../packages/agent-core/src/tools/builtin/shell/bash.ts
|
|
77982
|
+
/**
|
|
77983
|
+
* BashTool — execute shell commands.
|
|
77984
|
+
*
|
|
77985
|
+
* Invokes bash (POSIX) according to an injected `Environment`. On Windows
|
|
77986
|
+
* the shell is Git Bash; the path is resolved by `detectEnvironment`.
|
|
77987
|
+
*
|
|
77988
|
+
* Dependencies injected via constructor:
|
|
77989
|
+
* - `Jian` — shell execution abstraction (exec / execWithEnv)
|
|
77990
|
+
* - `cwd` — default working directory for commands
|
|
77991
|
+
* - `Environment` — cross-platform probe (shellName / shellPath)
|
|
77992
|
+
* - `BackgroundProcessManager?` — optional: required iff run_in_background=true
|
|
77993
|
+
*
|
|
77994
|
+
* Execution goes through Jian, never directly via node:child_process.
|
|
77995
|
+
*
|
|
77996
|
+
* Hardening:
|
|
77997
|
+
* - `args.timeout` (seconds) and the ambient `signal` both drive
|
|
77998
|
+
* `Promise.race`; fire-a-kill on either edge.
|
|
77999
|
+
* - stdin is closed immediately so interactive commands (`cat`, `read`,
|
|
78000
|
+
* `python -c 'input()'`) receive EOF instead of hanging.
|
|
78001
|
+
* - Two-phase kill: SIGTERM → 5s grace → SIGKILL (Jian honours this
|
|
78002
|
+
* contract cross-platform).
|
|
78003
|
+
* - stdout/stderr stream into ToolResultBuilder; excess is replaced with a
|
|
78004
|
+
* truncation marker so a runaway command cannot OOM the host.
|
|
78005
|
+
*/
|
|
77817
78006
|
const MS_PER_SECOND = 1e3;
|
|
77818
78007
|
const DEFAULT_TIMEOUT_S = 60;
|
|
77819
78008
|
const MAX_TIMEOUT_S = 300;
|
|
@@ -78064,7 +78253,11 @@ var BashTool = class {
|
|
|
78064
78253
|
killProc();
|
|
78065
78254
|
}, timeoutMs);
|
|
78066
78255
|
try {
|
|
78067
|
-
const builder = new ToolResultBuilder()
|
|
78256
|
+
const builder = new ToolResultBuilder({ artifactSink: async (fullOutput) => {
|
|
78257
|
+
const artifactPath = join(tmpdir(), `scream-bash-output-${randomUUID()}.log`);
|
|
78258
|
+
await writeFile(artifactPath, fullOutput, "utf8");
|
|
78259
|
+
return artifactPath;
|
|
78260
|
+
} });
|
|
78068
78261
|
const [, exitCode] = await Promise.all([Promise.all([readStreamIntoBuilder(proc.stdout, builder), readStreamIntoBuilder(proc.stderr, builder)]), proc.wait()]);
|
|
78069
78262
|
if (timedOut) {
|
|
78070
78263
|
const timeoutLabel = timeoutMs % 1e3 === 0 ? `${String(timeoutMs / 1e3)}s` : `${String(timeoutMs)}ms`;
|
|
@@ -79655,7 +79848,8 @@ const DEFAULT_CONFIG = {
|
|
|
79655
79848
|
minContentTokens: 100,
|
|
79656
79849
|
minContextUsageRatio: .5,
|
|
79657
79850
|
truncatedMarker: "[Old tool result content cleared]",
|
|
79658
|
-
uselessMarker: "[Uneventful result elided]"
|
|
79851
|
+
uselessMarker: "[Uneventful result elided]",
|
|
79852
|
+
noMatchesMarker: "[no matches]"
|
|
79659
79853
|
};
|
|
79660
79854
|
/**
|
|
79661
79855
|
* Compute the cutoff index: everything at index < cutoff is eligible for
|
|
@@ -79676,30 +79870,107 @@ function computeCutoff(messages, config) {
|
|
|
79676
79870
|
}
|
|
79677
79871
|
return cutoff;
|
|
79678
79872
|
}
|
|
79873
|
+
/** Tool names whose results are file reads eligible for supersede pruning. */
|
|
79874
|
+
const READ_TOOL_NAMES = new Set(["Read", "ReadGroup"]);
|
|
79875
|
+
/** Tool names whose empty results can be elided as a no-match marker. */
|
|
79876
|
+
const SEARCH_TOOL_NAMES = new Set(["Grep", "Glob"]);
|
|
79877
|
+
/** Exact tool-result texts that indicate a zero-match search result. */
|
|
79878
|
+
const ZERO_MATCH_TEXTS = new Set(["No matches found", "No non-sensitive matches found"]);
|
|
79679
79879
|
/**
|
|
79680
|
-
*
|
|
79681
|
-
*
|
|
79682
|
-
*
|
|
79880
|
+
* Parse a tool call's arguments JSON. Returns undefined for null or malformed
|
|
79881
|
+
* JSON - persisted history can carry truncated arguments that must not crash
|
|
79882
|
+
* compaction. `ToolCall.arguments` is a JSON string (or null), never a parsed
|
|
79883
|
+
* object, so every consumer must go through this helper.
|
|
79884
|
+
*/
|
|
79885
|
+
function parseToolCallArguments$1(argumentsJson) {
|
|
79886
|
+
if (argumentsJson === null || argumentsJson === void 0) return void 0;
|
|
79887
|
+
try {
|
|
79888
|
+
const parsed = JSON.parse(argumentsJson);
|
|
79889
|
+
return typeof parsed === "object" && parsed !== null ? parsed : void 0;
|
|
79890
|
+
} catch {
|
|
79891
|
+
return;
|
|
79892
|
+
}
|
|
79893
|
+
}
|
|
79894
|
+
/**
|
|
79895
|
+
* Extract the file paths targeted by a read tool call. `Read` carries a
|
|
79896
|
+
* single `path`; `ReadGroup` carries a `paths` array. Returns an empty array
|
|
79897
|
+
* for non-read tools or calls whose arguments don't yield usable paths.
|
|
79898
|
+
*/
|
|
79899
|
+
function extractReadFilePaths(name, args) {
|
|
79900
|
+
if (args === void 0) return [];
|
|
79901
|
+
if (name === "Read") {
|
|
79902
|
+
const path = typeof args["path"] === "string" ? args["path"] : void 0;
|
|
79903
|
+
return path !== void 0 && path.length > 0 ? [path] : [];
|
|
79904
|
+
}
|
|
79905
|
+
if (name === "ReadGroup") {
|
|
79906
|
+
const paths = args["paths"];
|
|
79907
|
+
if (!Array.isArray(paths)) return [];
|
|
79908
|
+
return paths.filter((p) => typeof p === "string" && p.length > 0);
|
|
79909
|
+
}
|
|
79910
|
+
return [];
|
|
79911
|
+
}
|
|
79912
|
+
/** Concatenate the `text` parts of a message's content into a single string. */
|
|
79913
|
+
function extractTextContent(content) {
|
|
79914
|
+
let text = "";
|
|
79915
|
+
for (const part of content) if (typeof part === "object" && part !== null && part.type === "text") text += part.text;
|
|
79916
|
+
return text;
|
|
79917
|
+
}
|
|
79918
|
+
/** Build a toolCallId -> tool-name map by scanning assistant messages. */
|
|
79919
|
+
function buildToolCallNameMap(messages) {
|
|
79920
|
+
const names = /* @__PURE__ */ new Map();
|
|
79921
|
+
for (const msg of messages) {
|
|
79922
|
+
if (msg.role !== "assistant") continue;
|
|
79923
|
+
for (const tc of msg.toolCalls) names.set(tc.id, tc.name);
|
|
79924
|
+
}
|
|
79925
|
+
return names;
|
|
79926
|
+
}
|
|
79927
|
+
/**
|
|
79928
|
+
* Whether a tool result is a zero-match Grep/Glob result eligible for elision.
|
|
79929
|
+
* Only exact-match the canonical empty-result texts so results carrying extra
|
|
79930
|
+
* information (sensitive-file filter notices, pagination notices, errors) are
|
|
79931
|
+
* preserved verbatim.
|
|
79932
|
+
*/
|
|
79933
|
+
function isZeroMatchSearchResult(toolName, content) {
|
|
79934
|
+
if (toolName === void 0 || !SEARCH_TOOL_NAMES.has(toolName)) return false;
|
|
79935
|
+
const text = extractTextContent(content).trim();
|
|
79936
|
+
return ZERO_MATCH_TEXTS.has(text);
|
|
79937
|
+
}
|
|
79938
|
+
/**
|
|
79939
|
+
* Walk the message list and find Read/ReadGroup tool calls whose file paths
|
|
79940
|
+
* were superseded by a later read of the same path. Returns a map from the
|
|
79941
|
+
* superseded tool call's ID to the list of file paths covered by the newer
|
|
79942
|
+
* read (a ReadGroup can cover several).
|
|
79683
79943
|
*
|
|
79684
|
-
* Only considers tool results before the cutoff line
|
|
79685
|
-
* protected and their results are kept verbatim.
|
|
79944
|
+
* Only considers tool results before the cutoff line - newer reads are
|
|
79945
|
+
* protected and their results are kept verbatim. The comparison uses the raw
|
|
79946
|
+
* path strings from the tool arguments; path canonicalization would need
|
|
79947
|
+
* workspace context the compaction layer doesn't have, so the same file read
|
|
79948
|
+
* via two different spellings is treated as two different files (safe: it
|
|
79949
|
+
* just misses a supersede opportunity rather than dropping a distinct result).
|
|
79686
79950
|
*/
|
|
79687
79951
|
function findSupersededPaths(messages, cutoff) {
|
|
79688
79952
|
const superseded = /* @__PURE__ */ new Map();
|
|
79689
|
-
const
|
|
79953
|
+
const latestReadByPath = /* @__PURE__ */ new Map();
|
|
79690
79954
|
for (let i = 0; i < messages.length; i++) {
|
|
79691
79955
|
const msg = messages[i];
|
|
79692
79956
|
if (msg === void 0) continue;
|
|
79693
|
-
if (msg.role
|
|
79694
|
-
|
|
79695
|
-
|
|
79696
|
-
|
|
79697
|
-
|
|
79698
|
-
|
|
79699
|
-
|
|
79700
|
-
|
|
79701
|
-
|
|
79702
|
-
|
|
79957
|
+
if (msg.role !== "assistant" || msg.toolCalls.length === 0) continue;
|
|
79958
|
+
for (const tc of msg.toolCalls) {
|
|
79959
|
+
if (!READ_TOOL_NAMES.has(tc.name)) continue;
|
|
79960
|
+
const args = parseToolCallArguments$1(tc.arguments);
|
|
79961
|
+
const paths = extractReadFilePaths(tc.name, args);
|
|
79962
|
+
if (paths.length === 0) continue;
|
|
79963
|
+
for (const filePath of paths) {
|
|
79964
|
+
const prev = latestReadByPath.get(filePath);
|
|
79965
|
+
if (prev !== void 0 && prev.index < cutoff) {
|
|
79966
|
+
const existing = superseded.get(prev.toolCallId);
|
|
79967
|
+
if (existing === void 0) superseded.set(prev.toolCallId, [filePath]);
|
|
79968
|
+
else if (!existing.includes(filePath)) superseded.set(prev.toolCallId, [...existing, filePath]);
|
|
79969
|
+
}
|
|
79970
|
+
latestReadByPath.set(filePath, {
|
|
79971
|
+
toolCallId: tc.id,
|
|
79972
|
+
index: i
|
|
79973
|
+
});
|
|
79703
79974
|
}
|
|
79704
79975
|
}
|
|
79705
79976
|
}
|
|
@@ -79753,20 +80024,26 @@ var MicroCompaction = class {
|
|
|
79753
80024
|
}
|
|
79754
80025
|
/**
|
|
79755
80026
|
* Apply micro-compaction to a message list: replace old tool results
|
|
79756
|
-
* before the cutoff line with truncated markers. Read results
|
|
79757
|
-
* that were re-read later get a supersede marker
|
|
79758
|
-
* the old content is stale.
|
|
79759
|
-
* elided
|
|
79760
|
-
*
|
|
80027
|
+
* before the cutoff line with truncated markers. Read/ReadGroup results
|
|
80028
|
+
* for files that were re-read later get a supersede marker (listing the
|
|
80029
|
+
* covered paths) so the model knows the old content is stale. Zero-match
|
|
80030
|
+
* Grep/Glob results are elided to a short `[no matches]` notice regardless
|
|
80031
|
+
* of size. Tool results explicitly marked useless are elided with a short
|
|
80032
|
+
* notice regardless of size, since they carry no actionable information.
|
|
79761
80033
|
*/
|
|
79762
80034
|
compact(messages) {
|
|
79763
80035
|
const config = this.config;
|
|
79764
80036
|
const superseded = findSupersededPaths(messages, this.cutoff);
|
|
80037
|
+
const toolNames = buildToolCallNameMap(messages);
|
|
79765
80038
|
const result = [];
|
|
79766
80039
|
let i = 0;
|
|
79767
80040
|
for (const msg of messages) {
|
|
79768
|
-
const
|
|
79769
|
-
const
|
|
80041
|
+
const isOld = i < this.cutoff;
|
|
80042
|
+
const toolCallId = msg.toolCallId;
|
|
80043
|
+
const isTool = msg.role === "tool" && toolCallId !== void 0;
|
|
80044
|
+
const isUseless = isOld && isTool && msg.useless === true;
|
|
80045
|
+
const isZeroMatch = isOld && isTool && toolCallId !== void 0 && isZeroMatchSearchResult(toolNames.get(toolCallId), msg.content);
|
|
80046
|
+
const isOversizedTruncatable = isOld && isTool && estimateTokensForMessages([msg]) >= config.minContentTokens;
|
|
79770
80047
|
if (isUseless) result.push({
|
|
79771
80048
|
...msg,
|
|
79772
80049
|
content: [{
|
|
@@ -79774,8 +80051,16 @@ var MicroCompaction = class {
|
|
|
79774
80051
|
text: config.uselessMarker
|
|
79775
80052
|
}]
|
|
79776
80053
|
});
|
|
80054
|
+
else if (isZeroMatch) result.push({
|
|
80055
|
+
...msg,
|
|
80056
|
+
content: [{
|
|
80057
|
+
type: "text",
|
|
80058
|
+
text: config.noMatchesMarker
|
|
80059
|
+
}]
|
|
80060
|
+
});
|
|
79777
80061
|
else if (isOversizedTruncatable) {
|
|
79778
|
-
const
|
|
80062
|
+
const paths = toolCallId !== void 0 ? superseded.get(toolCallId) : void 0;
|
|
80063
|
+
const marker = paths !== void 0 && paths.length > 0 ? `[Superseded by a newer read of ${paths.join(", ")}]` : config.truncatedMarker;
|
|
79779
80064
|
result.push({
|
|
79780
80065
|
...msg,
|
|
79781
80066
|
content: [{
|
|
@@ -79800,20 +80085,28 @@ var MicroCompaction = class {
|
|
|
79800
80085
|
measureEffect(messages, cutoff) {
|
|
79801
80086
|
let markerTokenCount;
|
|
79802
80087
|
let uselessMarkerTokenCount;
|
|
80088
|
+
let noMatchesMarkerTokenCount;
|
|
79803
80089
|
let truncatedToolResultCount = 0;
|
|
79804
80090
|
let beforeTokens = 0;
|
|
79805
80091
|
let afterTokens = 0;
|
|
80092
|
+
const toolNames = buildToolCallNameMap(messages);
|
|
79806
80093
|
for (let i = 0; i < messages.length && i < cutoff; i++) {
|
|
79807
80094
|
const message = messages[i];
|
|
79808
80095
|
if (message?.role !== "tool" || message.toolCallId === void 0) continue;
|
|
79809
80096
|
const contentTokens = estimateTokensForMessages([message]);
|
|
79810
80097
|
const isUseless = message.useless === true;
|
|
79811
|
-
|
|
80098
|
+
const isZeroMatch = isZeroMatchSearchResult(toolNames.get(message.toolCallId), message.content);
|
|
80099
|
+
if (!isUseless && !isZeroMatch && contentTokens < this.config.minContentTokens) continue;
|
|
79812
80100
|
if (isUseless) {
|
|
79813
80101
|
uselessMarkerTokenCount ??= estimateTokens$1(this.config.uselessMarker);
|
|
79814
80102
|
truncatedToolResultCount += 1;
|
|
79815
80103
|
beforeTokens += contentTokens;
|
|
79816
80104
|
afterTokens += uselessMarkerTokenCount;
|
|
80105
|
+
} else if (isZeroMatch) {
|
|
80106
|
+
noMatchesMarkerTokenCount ??= estimateTokens$1(this.config.noMatchesMarker);
|
|
80107
|
+
truncatedToolResultCount += 1;
|
|
80108
|
+
beforeTokens += contentTokens;
|
|
80109
|
+
afterTokens += noMatchesMarkerTokenCount;
|
|
79817
80110
|
} else {
|
|
79818
80111
|
markerTokenCount ??= estimateTokens$1(this.config.truncatedMarker);
|
|
79819
80112
|
truncatedToolResultCount += 1;
|
|
@@ -80822,6 +81115,71 @@ var ConfigState = class {
|
|
|
80822
81115
|
}
|
|
80823
81116
|
};
|
|
80824
81117
|
//#endregion
|
|
81118
|
+
//#region ../../packages/agent-core/src/agent/context/prefix-fingerprint.ts
|
|
81119
|
+
/**
|
|
81120
|
+
* Deterministic non-crypto string hash (djb2). Fast and sufficient for change
|
|
81121
|
+
* detection; not used for any security purpose. Returns a compact base36
|
|
81122
|
+
* string so an array of fingerprints stays small.
|
|
81123
|
+
*/
|
|
81124
|
+
function hashString(input) {
|
|
81125
|
+
let hash = 5381;
|
|
81126
|
+
for (let i = 0; i < input.length; i++) hash = (hash << 5) + hash + (input.codePointAt(i) ?? 0) | 0;
|
|
81127
|
+
return (hash >>> 0).toString(36);
|
|
81128
|
+
}
|
|
81129
|
+
/**
|
|
81130
|
+
* Stable JSON serialization that sorts object keys so key insertion order
|
|
81131
|
+
* can't affect the fingerprint. Only used for media parts whose payload is
|
|
81132
|
+
* already a stable base64/url string; text/think parts have a dedicated
|
|
81133
|
+
* fast path.
|
|
81134
|
+
*/
|
|
81135
|
+
function stableJson(value) {
|
|
81136
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
81137
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
81138
|
+
const obj = value;
|
|
81139
|
+
return `{${Object.keys(obj).toSorted().map((k) => `${JSON.stringify(k)}:${stableJson(obj[k])}`).join(",")}}`;
|
|
81140
|
+
}
|
|
81141
|
+
function serializeContentPart(part) {
|
|
81142
|
+
switch (part.type) {
|
|
81143
|
+
case "text": return `text:${part.text}`;
|
|
81144
|
+
case "think": return `think:${part.think}${part.encrypted !== void 0 ? `\u0003${part.encrypted}` : ""}`;
|
|
81145
|
+
default: return `${part.type}:${stableJson(part)}`;
|
|
81146
|
+
}
|
|
81147
|
+
}
|
|
81148
|
+
function serializeToolCall(tc) {
|
|
81149
|
+
return `function|${tc.id}|${tc.name}|${tc.arguments ?? ""}`;
|
|
81150
|
+
}
|
|
81151
|
+
/**
|
|
81152
|
+
* Serialize a message to a deterministic string covering all
|
|
81153
|
+
* provider-visible bytes. Two messages with the same serialization produce
|
|
81154
|
+
* identical provider bytes (modulo serialization the provider adapter
|
|
81155
|
+
* normalizes). Separator bytes (\x00-\x03) are used so concatenated fields
|
|
81156
|
+
* can't alias.
|
|
81157
|
+
*/
|
|
81158
|
+
function serializeMessage$1(message) {
|
|
81159
|
+
const content = message.content.map(serializeContentPart).join("");
|
|
81160
|
+
const toolCalls = message.toolCalls.map(serializeToolCall).join("");
|
|
81161
|
+
return `${message.role}\u0000${message.name ?? ""}\u0000${message.toolCallId ?? ""}\u0000${content}\u0000${toolCalls}`;
|
|
81162
|
+
}
|
|
81163
|
+
/** Per-message fingerprint. Equal fingerprints => equal provider bytes. */
|
|
81164
|
+
function messageFingerprint(message) {
|
|
81165
|
+
return hashString(serializeMessage$1(message));
|
|
81166
|
+
}
|
|
81167
|
+
/**
|
|
81168
|
+
* Longest common prefix length by provider-visible bytes. This is the number
|
|
81169
|
+
* of leading messages a provider prompt cache could reuse from the previous
|
|
81170
|
+
* call. When this is less than the previous call's message count, an early
|
|
81171
|
+
* message mutated and the cache broke from that index.
|
|
81172
|
+
*
|
|
81173
|
+
* `prev` is the array of per-message fingerprints captured last call;
|
|
81174
|
+
* `current` is the live messages this call.
|
|
81175
|
+
*/
|
|
81176
|
+
function stablePrefixLength(prev, current) {
|
|
81177
|
+
const n = Math.min(prev.length, current.length);
|
|
81178
|
+
let i = 0;
|
|
81179
|
+
for (; i < n; i++) if (prev[i] !== messageFingerprint(current[i])) break;
|
|
81180
|
+
return i;
|
|
81181
|
+
}
|
|
81182
|
+
//#endregion
|
|
80825
81183
|
//#region ../../packages/agent-core/src/agent/context/types.ts
|
|
80826
81184
|
const USER_PROMPT_ORIGIN = { kind: "user" };
|
|
80827
81185
|
//#endregion
|
|
@@ -80844,6 +81202,18 @@ var ContextMemory = class {
|
|
|
80844
81202
|
openSteps = /* @__PURE__ */ new Map();
|
|
80845
81203
|
pendingToolResultIds = /* @__PURE__ */ new Set();
|
|
80846
81204
|
deferredMessages = [];
|
|
81205
|
+
/**
|
|
81206
|
+
* Per-message fingerprints captured from the last message list handed to
|
|
81207
|
+
* the LLM via {@link messagesForLLM}. Used to measure prefix stability
|
|
81208
|
+
* across calls: a provider prompt cache only hits when the leading
|
|
81209
|
+
* messages are byte-identical to the previous request, so the length of
|
|
81210
|
+
* the matching prefix here approximates the cacheable prefix length.
|
|
81211
|
+
*
|
|
81212
|
+
* Reset on {@link clear}; a compaction naturally produces a 0-length
|
|
81213
|
+
* stable prefix (the summary replaces the head), which is the correct
|
|
81214
|
+
* cache-break signal rather than a reset.
|
|
81215
|
+
*/
|
|
81216
|
+
lastSentFingerprints = [];
|
|
80847
81217
|
constructor(agent) {
|
|
80848
81218
|
this.agent = agent;
|
|
80849
81219
|
}
|
|
@@ -80893,6 +81263,7 @@ var ContextMemory = class {
|
|
|
80893
81263
|
this.openSteps.clear();
|
|
80894
81264
|
this.pendingToolResultIds.clear();
|
|
80895
81265
|
this.deferredMessages = [];
|
|
81266
|
+
this.lastSentFingerprints = [];
|
|
80896
81267
|
this.agent.injection.onContextClear();
|
|
80897
81268
|
this.agent.emitStatusUpdated();
|
|
80898
81269
|
}
|
|
@@ -80937,6 +81308,19 @@ var ContextMemory = class {
|
|
|
80937
81308
|
}
|
|
80938
81309
|
if (!this.agent.records.restoring && (stoppedAtBoundary || removedUserCount < count)) {}
|
|
80939
81310
|
}
|
|
81311
|
+
/**
|
|
81312
|
+
* Apply a full compaction summary.
|
|
81313
|
+
*
|
|
81314
|
+
* Prefix-stability note: this is a **replaceHead** operation, not a
|
|
81315
|
+
* replaceTail. The first `compactedCount` messages are collapsed into a
|
|
81316
|
+
* single summary message; the trailing recent messages are preserved
|
|
81317
|
+
* verbatim. This necessarily breaks the provider prompt cache for the
|
|
81318
|
+
* whole prefix (the summary is new content), which is inherent to
|
|
81319
|
+
* summarization and cannot be avoided. After compaction the new prefix
|
|
81320
|
+
* `[summary, ...tail]` is stable again until the next compaction or
|
|
81321
|
+
* micro-compaction cutoff advance, so subsequent append-only steps resume
|
|
81322
|
+
* hitting the cache.
|
|
81323
|
+
*/
|
|
80940
81324
|
applyCompaction(summary) {
|
|
80941
81325
|
this.agent.records.logRecord({
|
|
80942
81326
|
type: "context.apply_compaction",
|
|
@@ -80980,6 +81364,48 @@ var ContextMemory = class {
|
|
|
80980
81364
|
this.agent.microCompaction.detect();
|
|
80981
81365
|
return project(this.agent.microCompaction.compact(this.history));
|
|
80982
81366
|
}
|
|
81367
|
+
/**
|
|
81368
|
+
* Build the message list for an LLM call, with prefix-stability
|
|
81369
|
+
* observation.
|
|
81370
|
+
*
|
|
81371
|
+
* This is the LLM-bound counterpart of the {@link messages} getter: it
|
|
81372
|
+
* runs the same detect + compact + project pipeline, then fingerprints
|
|
81373
|
+
* the result and logs how much of the prefix survived since the last
|
|
81374
|
+
* call. A stable prefix length equal to the previous message count means
|
|
81375
|
+
* the provider prompt cache should hit; a smaller value means an early
|
|
81376
|
+
* message mutated (compaction summary, micro-compaction truncation, or a
|
|
81377
|
+
* projection repair) and the cache broke from that index.
|
|
81378
|
+
*
|
|
81379
|
+
* Behavior is otherwise identical to the getter - this is observation
|
|
81380
|
+
* only, it does not alter the messages returned.
|
|
81381
|
+
*/
|
|
81382
|
+
messagesForLLM() {
|
|
81383
|
+
this.agent.microCompaction.detect();
|
|
81384
|
+
const messages = project(this.agent.microCompaction.compact(this.history));
|
|
81385
|
+
this.observePrefixStability(messages);
|
|
81386
|
+
return messages;
|
|
81387
|
+
}
|
|
81388
|
+
/**
|
|
81389
|
+
* Compare the projected messages against the last LLM-bound batch and
|
|
81390
|
+
* log the stable-prefix length. Pure observation: no state that affects
|
|
81391
|
+
* message content is mutated, only the fingerprint baseline used by the
|
|
81392
|
+
* next call's comparison.
|
|
81393
|
+
*/
|
|
81394
|
+
observePrefixStability(messages) {
|
|
81395
|
+
const prev = this.lastSentFingerprints;
|
|
81396
|
+
const stable = stablePrefixLength(prev, messages);
|
|
81397
|
+
this.lastSentFingerprints = messages.map(messageFingerprint);
|
|
81398
|
+
if (prev.length === 0) return;
|
|
81399
|
+
const appended = messages.length - prev.length;
|
|
81400
|
+
if (stable >= prev.length) return;
|
|
81401
|
+
this.agent.log.debug("prefix-stability: provider prompt cache prefix broke", {
|
|
81402
|
+
stablePrefixLength: stable,
|
|
81403
|
+
prevMessageCount: prev.length,
|
|
81404
|
+
currentMessageCount: messages.length,
|
|
81405
|
+
appendedSinceLast: appended,
|
|
81406
|
+
breakIndex: stable
|
|
81407
|
+
});
|
|
81408
|
+
}
|
|
80983
81409
|
appendLoopEvent(event) {
|
|
80984
81410
|
this.agent.records.logRecord({
|
|
80985
81411
|
type: "context.append_loop_event",
|
|
@@ -81616,6 +82042,8 @@ function buildGoalReminder(goal) {
|
|
|
81616
82042
|
lines.push("Goal mode is iterative. Keep the self-audit brief each turn. Do not explore unrelated interpretations once the goal can be decided. If the objective is simple, already answered, impossible, unsafe, or contradictory, do not run another goal turn. Explain briefly if useful, then call UpdateGoal with `complete` or `blocked` in the same turn. Otherwise, self-audit against the objective and any completion criteria above, then do one coherent slice of work toward the objective. Use multiple turns when the task naturally has multiple phases. Call UpdateGoal with `complete` only when all required work is done, any stated validation has passed, and there is no useful next action. Do not mark complete after only producing a plan, summary, first pass, or partial result. If an external condition or required user input prevents progress, or the objective cannot be completed as stated, call UpdateGoal with `blocked`. Otherwise keep working — after your turn ends you will be prompted to continue. Call UpdateGoal as soon as the goal is genuinely done or cannot proceed; don't keep going once there is nothing left to do.");
|
|
81617
82043
|
lines.push("");
|
|
81618
82044
|
lines.push("When you call UpdateGoal with `complete`, an independent reviewer will verify that the completion criteria are met. In your final response before calling UpdateGoal, provide a structured summary: what was done, which files changed, the verification command and result, and any remaining work or blockers. Do not rely on the UpdateGoal argument alone; the reviewer and the user must see this summary in your natural-language reply.");
|
|
82045
|
+
lines.push("");
|
|
82046
|
+
lines.push("Important: before calling UpdateGoal with `complete`, always call WriteGoalNote first to summarize the key findings, constraints, or partial results from this turn. The reviewer will see these notes together with your final output, so include anything that helps them evaluate cross-turn context. Keep the note concise and actionable.");
|
|
81619
82047
|
return lines.join("\n");
|
|
81620
82048
|
}
|
|
81621
82049
|
function maxBudgetFraction(goal) {
|
|
@@ -97329,6 +97757,13 @@ function buildGraderPrompt(objective, criteria, output) {
|
|
|
97329
97757
|
"## Agent Output",
|
|
97330
97758
|
output || "(no output captured)",
|
|
97331
97759
|
"",
|
|
97760
|
+
"The Agent Output section contains the following optional parts, in order:",
|
|
97761
|
+
"1. The agent's natural-language summary of what was done this turn.",
|
|
97762
|
+
"2. \"## Cross-turn working notes\" — short notes the agent recorded using the WriteGoalNote tool across continuation turns. These notes capture key findings, constraints, decisions, and partial results. Treat them as supplementary context when evaluating whether the acceptance criteria are met; they are not the deliverable itself.",
|
|
97763
|
+
"3. \"## Changes this turn\" — a git diff stat (or a note if git is unavailable) showing which files were modified. Use it to verify that the claimed work has an actual code footprint.",
|
|
97764
|
+
"",
|
|
97765
|
+
"If the Agent Output does not contain a \"## Cross-turn working notes\" section, add a non-blocking issue: \"No cross-turn working notes were provided. Use WriteGoalNote to record key findings, constraints, and partial results across turns.\" This issue alone must not cause a FAIL.",
|
|
97766
|
+
"",
|
|
97332
97767
|
"Evaluate each dimension independently against the acceptance criteria, then decide overall PASS/FAIL.",
|
|
97333
97768
|
"When FAIL, list every specific issue with an actionable fix direction so the agent knows exactly what to address next.",
|
|
97334
97769
|
"Respond with JSON:",
|
|
@@ -98019,6 +98454,11 @@ const GOAL_CONTINUATION_ORIGIN = {
|
|
|
98019
98454
|
kind: "system_trigger",
|
|
98020
98455
|
name: "goal_continuation"
|
|
98021
98456
|
};
|
|
98457
|
+
const GOAL_BUDGET_STEER_PROMPT = "Budget nearly exhausted. Wrap up immediately: verify your work, run tests, and call UpdateGoal with status \"complete\" or \"blocked\". Do not start any new work.";
|
|
98458
|
+
const GOAL_BUDGET_STEER_ORIGIN = {
|
|
98459
|
+
kind: "system_trigger",
|
|
98460
|
+
name: "goal_budget_steer"
|
|
98461
|
+
};
|
|
98022
98462
|
var TurnFlow = class {
|
|
98023
98463
|
agent;
|
|
98024
98464
|
steerBuffer = [];
|
|
@@ -98183,6 +98623,14 @@ var TurnFlow = class {
|
|
|
98183
98623
|
}
|
|
98184
98624
|
}
|
|
98185
98625
|
await this.agent.goal.incrementTurn();
|
|
98626
|
+
const budgetSnapshot = this.agent.goal.getGoal().goal;
|
|
98627
|
+
if (budgetSnapshot !== null && budgetSnapshot.status === "active" && (budgetSnapshot.budget.overBudget || isBudgetNearExhaustion(budgetSnapshot.budget, .2))) {
|
|
98628
|
+
turnInput = [{
|
|
98629
|
+
type: "text",
|
|
98630
|
+
text: GOAL_BUDGET_STEER_PROMPT
|
|
98631
|
+
}];
|
|
98632
|
+
turnOrigin = GOAL_BUDGET_STEER_ORIGIN;
|
|
98633
|
+
}
|
|
98186
98634
|
const end = await this.runOneTurn(turnId, turnInput, turnOrigin, signal, false);
|
|
98187
98635
|
if (end.event.reason === "cancelled") {
|
|
98188
98636
|
await this.agent.goal.pauseOnInterrupt({ reason: "Paused after interruption" });
|
|
@@ -98374,7 +98822,7 @@ var TurnFlow = class {
|
|
|
98374
98822
|
turnId: String(turnId),
|
|
98375
98823
|
signal,
|
|
98376
98824
|
llm: this.agent.llm,
|
|
98377
|
-
buildMessages: () => this.agent.context.
|
|
98825
|
+
buildMessages: () => this.agent.context.messagesForLLM(),
|
|
98378
98826
|
dispatchEvent: this.buildDispatchEvent(turnId),
|
|
98379
98827
|
tools: this.agent.tools.loopTools,
|
|
98380
98828
|
log: this.agent.log,
|
|
@@ -99509,6 +99957,9 @@ var Agent = class {
|
|
|
99509
99957
|
if (status === "paused") return this.goal.pauseGoal({}, "user");
|
|
99510
99958
|
return this.goal.resumeGoal({}, "user");
|
|
99511
99959
|
},
|
|
99960
|
+
updateGoalObjective: async (payload) => {
|
|
99961
|
+
return this.goal.updateObjective({ objective: payload.objective }, "user");
|
|
99962
|
+
},
|
|
99512
99963
|
cancelGoal: async () => {
|
|
99513
99964
|
return this.goal.cancelGoal("user");
|
|
99514
99965
|
},
|
|
@@ -119565,6 +120016,9 @@ var SessionAPIImpl = class {
|
|
|
119565
120016
|
updateGoalStatus({ agentId, ...payload }) {
|
|
119566
120017
|
return this.getAgent(agentId).updateGoalStatus(payload);
|
|
119567
120018
|
}
|
|
120019
|
+
updateGoalObjective({ agentId, ...payload }) {
|
|
120020
|
+
return this.getAgent(agentId).updateGoalObjective(payload);
|
|
120021
|
+
}
|
|
119568
120022
|
cancelGoal({ agentId, ...payload }) {
|
|
119569
120023
|
return this.getAgent(agentId).cancelGoal(payload);
|
|
119570
120024
|
}
|
|
@@ -121307,6 +121761,9 @@ var ScreamCore = class {
|
|
|
121307
121761
|
updateGoalStatus({ sessionId, ...payload }) {
|
|
121308
121762
|
return this.sessionApi(sessionId).updateGoalStatus(payload);
|
|
121309
121763
|
}
|
|
121764
|
+
updateGoalObjective({ sessionId, ...payload }) {
|
|
121765
|
+
return this.sessionApi(sessionId).updateGoalObjective(payload);
|
|
121766
|
+
}
|
|
121310
121767
|
cancelGoal({ sessionId, ...payload }) {
|
|
121311
121768
|
return this.sessionApi(sessionId).cancelGoal(payload);
|
|
121312
121769
|
}
|
|
@@ -121760,6 +122217,13 @@ var SDKRpcClient = class {
|
|
|
121760
122217
|
status: input.status
|
|
121761
122218
|
});
|
|
121762
122219
|
}
|
|
122220
|
+
async updateGoalObjective(input) {
|
|
122221
|
+
return (await this.getRpc()).updateGoalObjective({
|
|
122222
|
+
sessionId: input.sessionId,
|
|
122223
|
+
agentId: this.interactiveAgentId,
|
|
122224
|
+
objective: input.objective
|
|
122225
|
+
});
|
|
122226
|
+
}
|
|
121763
122227
|
async cancelGoal(input) {
|
|
121764
122228
|
return (await this.getRpc()).cancelGoal({
|
|
121765
122229
|
sessionId: input.sessionId,
|
|
@@ -122520,6 +122984,13 @@ var Session = class {
|
|
|
122520
122984
|
status
|
|
122521
122985
|
});
|
|
122522
122986
|
}
|
|
122987
|
+
async updateGoalObjective(objective) {
|
|
122988
|
+
this.ensureOpen();
|
|
122989
|
+
return this.rpc.updateGoalObjective({
|
|
122990
|
+
sessionId: this.id,
|
|
122991
|
+
objective
|
|
122992
|
+
});
|
|
122993
|
+
}
|
|
122523
122994
|
async cancelGoal() {
|
|
122524
122995
|
this.ensureOpen();
|
|
122525
122996
|
return this.rpc.cancelGoal({ sessionId: this.id });
|
|
@@ -122979,7 +123450,7 @@ function optionalBuildString(value) {
|
|
|
122979
123450
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
122980
123451
|
}
|
|
122981
123452
|
const SCREAM_BUILD_INFO = {
|
|
122982
|
-
version: optionalBuildString("0.10.
|
|
123453
|
+
version: optionalBuildString("0.10.8"),
|
|
122983
123454
|
channel: optionalBuildString(""),
|
|
122984
123455
|
commit: optionalBuildString(""),
|
|
122985
123456
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -126197,9 +126668,15 @@ function formatGoalDuration(ms) {
|
|
|
126197
126668
|
const seconds = totalSeconds % 60;
|
|
126198
126669
|
return seconds > 0 ? `${minutes}m${seconds}s` : `${minutes}m`;
|
|
126199
126670
|
}
|
|
126200
|
-
/** Build the footer goal badge: `GOAL 3m · 7 turns`.
|
|
126201
|
-
|
|
126202
|
-
|
|
126671
|
+
/** Build the footer goal badge: `GOAL 3m · 7 turns`.
|
|
126672
|
+
*
|
|
126673
|
+
* The TUI only receives `goal.updated` events on state changes, so we keep a
|
|
126674
|
+
* local base timestamp and add the elapsed time since the last snapshot to
|
|
126675
|
+
* produce a live wall-clock reading between sparse events.
|
|
126676
|
+
*/
|
|
126677
|
+
function formatGoalBadge(goal) {
|
|
126678
|
+
const elapsedSinceSnapshot = Date.now() - goal.wallClockBaseAt;
|
|
126679
|
+
return `GOAL ${formatGoalDuration(goal.wallClockMs + Math.max(0, elapsedSinceSnapshot))} · ${goal.turnsUsed} turns`;
|
|
126203
126680
|
}
|
|
126204
126681
|
const CONTEXT_WARNING_PERCENT_THRESHOLD = 60;
|
|
126205
126682
|
const CONTEXT_ERROR_PERCENT_THRESHOLD = 90;
|
|
@@ -126295,15 +126772,17 @@ var FooterComponent = class {
|
|
|
126295
126772
|
this.onGitStatusChange = onGitStatusChange;
|
|
126296
126773
|
this.gitCacheWorkDir = state.workDir;
|
|
126297
126774
|
this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
|
|
126775
|
+
this.#restartStatusTimer(state.streamingPhase, state.goalActive);
|
|
126298
126776
|
}
|
|
126299
126777
|
setState(state) {
|
|
126300
126778
|
const previousPhase = this.state?.streamingPhase;
|
|
126779
|
+
const previousGoalActive = this.state?.goalActive;
|
|
126301
126780
|
if (state.workDir !== this.gitCacheWorkDir) {
|
|
126302
126781
|
this.gitCacheWorkDir = state.workDir;
|
|
126303
126782
|
this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
|
|
126304
126783
|
}
|
|
126305
126784
|
this.state = state;
|
|
126306
|
-
if (state.streamingPhase !== previousPhase) this.#restartStatusTimer(state.streamingPhase);
|
|
126785
|
+
if (state.streamingPhase !== previousPhase || state.goalActive !== previousGoalActive) this.#restartStatusTimer(state.streamingPhase, state.goalActive);
|
|
126307
126786
|
}
|
|
126308
126787
|
setColors(colors) {
|
|
126309
126788
|
this.colors = colors;
|
|
@@ -126331,9 +126810,9 @@ var FooterComponent = class {
|
|
|
126331
126810
|
dispose() {
|
|
126332
126811
|
this.#stopStatusTimer();
|
|
126333
126812
|
}
|
|
126334
|
-
#restartStatusTimer(phase) {
|
|
126813
|
+
#restartStatusTimer(phase, goalActive) {
|
|
126335
126814
|
this.#stopStatusTimer();
|
|
126336
|
-
if (phase === "idle") return;
|
|
126815
|
+
if (phase === "idle" && !goalActive) return;
|
|
126337
126816
|
const intervalMs = 1e3 / 60;
|
|
126338
126817
|
this.statusTimer = setInterval(() => {
|
|
126339
126818
|
this.ui.requestRender();
|
|
@@ -126354,8 +126833,7 @@ var FooterComponent = class {
|
|
|
126354
126833
|
}
|
|
126355
126834
|
if (state.wolfpackMode) left.push(chalk.hex(colors.wolfpackMode).bold(t("badge.wolfpack")));
|
|
126356
126835
|
if (state.goalActive && state.goal) {
|
|
126357
|
-
const
|
|
126358
|
-
const goalLabel = formatGoalBadge(g.wallClockMs, g.turnsUsed);
|
|
126836
|
+
const goalLabel = formatGoalBadge(state.goal);
|
|
126359
126837
|
left.push(chalk.hex(colors.primary).bold(goalLabel));
|
|
126360
126838
|
}
|
|
126361
126839
|
const model = shortenModel(modelDisplayName(state));
|
|
@@ -126731,10 +127209,63 @@ function createThemeStyles(colors) {
|
|
|
126731
127209
|
//#endregion
|
|
126732
127210
|
//#region src/tui/theme/pi-tui-theme.ts
|
|
126733
127211
|
const HEADING_HASH_PREFIX = /^((?:\u001B\[[0-9;]*m)*)#{1,6}[ \t]+/;
|
|
127212
|
+
/**
|
|
127213
|
+
* Map cli-highlight syntax tokens onto the ColorPalette so code blocks follow
|
|
127214
|
+
* the active theme instead of cli-highlight's built-in colors. cli-highlight
|
|
127215
|
+
* takes one formatter function per token; tokens not listed here fall back to
|
|
127216
|
+
* its DEFAULT_THEME.
|
|
127217
|
+
*/
|
|
127218
|
+
function createCodeHighlightTheme(colors) {
|
|
127219
|
+
const keyword = chalk.hex(colors.primary);
|
|
127220
|
+
const str = chalk.hex(colors.success);
|
|
127221
|
+
const comment = chalk.hex(colors.textDim);
|
|
127222
|
+
const num = chalk.hex(colors.warning);
|
|
127223
|
+
const fn = chalk.hex(colors.primary);
|
|
127224
|
+
const cls = chalk.hex(colors.accent);
|
|
127225
|
+
const text = chalk.hex(colors.text);
|
|
127226
|
+
return {
|
|
127227
|
+
keyword,
|
|
127228
|
+
built_in: fn,
|
|
127229
|
+
type: cls,
|
|
127230
|
+
literal: num,
|
|
127231
|
+
number: num,
|
|
127232
|
+
regexp: str,
|
|
127233
|
+
string: str,
|
|
127234
|
+
subst: str,
|
|
127235
|
+
symbol: num,
|
|
127236
|
+
class: cls,
|
|
127237
|
+
function: fn,
|
|
127238
|
+
title: fn,
|
|
127239
|
+
params: text,
|
|
127240
|
+
comment,
|
|
127241
|
+
doctag: comment,
|
|
127242
|
+
meta: chalk.hex(colors.textMuted),
|
|
127243
|
+
"meta-keyword": keyword,
|
|
127244
|
+
"meta-string": str,
|
|
127245
|
+
section: keyword,
|
|
127246
|
+
tag: cls,
|
|
127247
|
+
name: fn,
|
|
127248
|
+
"builtin-name": fn,
|
|
127249
|
+
attr: num,
|
|
127250
|
+
attribute: num,
|
|
127251
|
+
variable: text,
|
|
127252
|
+
bullet: num,
|
|
127253
|
+
code: str,
|
|
127254
|
+
emphasis: (s) => chalk.italic(s),
|
|
127255
|
+
strong: (s) => chalk.bold(s),
|
|
127256
|
+
formula: text,
|
|
127257
|
+
link: chalk.hex(colors.mdLink),
|
|
127258
|
+
quote: chalk.hex(colors.mdQuote),
|
|
127259
|
+
addition: chalk.hex(colors.diffAdded),
|
|
127260
|
+
deletion: chalk.hex(colors.diffRemoved),
|
|
127261
|
+
default: text
|
|
127262
|
+
};
|
|
127263
|
+
}
|
|
126734
127264
|
function createMarkdownTheme(colors) {
|
|
126735
127265
|
const stripHash = (text) => text.replace(HEADING_HASH_PREFIX, "$1");
|
|
126736
127266
|
const muted = chalk.hex(colors.textMuted);
|
|
126737
127267
|
const border = chalk.hex(colors.border);
|
|
127268
|
+
const codeTheme = createCodeHighlightTheme(colors);
|
|
126738
127269
|
return {
|
|
126739
127270
|
heading: (text) => chalk.bold.hex(colors.text)(stripHash(text)),
|
|
126740
127271
|
link: (text) => chalk.hex(colors.mdLink)(text),
|
|
@@ -126756,7 +127287,8 @@ function createMarkdownTheme(colors) {
|
|
|
126756
127287
|
try {
|
|
126757
127288
|
return highlight(code, {
|
|
126758
127289
|
language,
|
|
126759
|
-
ignoreIllegals: true
|
|
127290
|
+
ignoreIllegals: true,
|
|
127291
|
+
theme: codeTheme
|
|
126760
127292
|
}).split("\n");
|
|
126761
127293
|
} catch {
|
|
126762
127294
|
return code.split("\n");
|
|
@@ -128185,6 +128717,10 @@ function buildEmptyGoalLines(colors) {
|
|
|
128185
128717
|
value(t("goalpanel.no_goal")),
|
|
128186
128718
|
"",
|
|
128187
128719
|
`${muted("/goal")} ${value(t("goalpanel.goal_placeholder"))} ${muted(t("goalpanel.create_goal"))}`,
|
|
128720
|
+
`${muted("/goal setup")} ${muted(t("goalpanel.setup_goal"))}`,
|
|
128721
|
+
`${muted("/goal update")} ${value("<new objective>")} ${muted(t("goalpanel.update_goal"))}`,
|
|
128722
|
+
`${muted("/goal status")} ${muted(t("goalpanel.status_goal"))}`,
|
|
128723
|
+
`${muted("/goal replace")} ${value("<new objective>")} ${muted(t("goalpanel.replace_goal"))}`,
|
|
128188
128724
|
`${muted("/goal pause")} ${muted(t("goalpanel.pause_goal"))}`,
|
|
128189
128725
|
`${muted("/goal resume")} ${muted(t("goalpanel.resume_goal"))}`,
|
|
128190
128726
|
`${muted("/goaloff")} ${muted(t("goalpanel.cancel_goal"))}`
|
|
@@ -128247,6 +128783,19 @@ function parseGoalCommand(rawArgs) {
|
|
|
128247
128783
|
const tokens = args.split(/\s+/);
|
|
128248
128784
|
const first = tokens[0];
|
|
128249
128785
|
if (first !== void 0 && CONTROL_SUBCOMMANDS.has(first) && tokens.length === 1) return { kind: first };
|
|
128786
|
+
if (first === "setup") return { kind: "setup" };
|
|
128787
|
+
if (first === "update") {
|
|
128788
|
+
const objective = tokens.slice(1).join(" ").trim();
|
|
128789
|
+
if (objective.length === 0) return {
|
|
128790
|
+
kind: "error",
|
|
128791
|
+
severity: "hint",
|
|
128792
|
+
message: t("goal.need_desc")
|
|
128793
|
+
};
|
|
128794
|
+
return {
|
|
128795
|
+
kind: "update",
|
|
128796
|
+
objective
|
|
128797
|
+
};
|
|
128798
|
+
}
|
|
128250
128799
|
let index = 0;
|
|
128251
128800
|
let replace = false;
|
|
128252
128801
|
if (tokens[index] === "replace") {
|
|
@@ -128276,6 +128825,9 @@ async function handleGoalCommand(host, args) {
|
|
|
128276
128825
|
case "status":
|
|
128277
128826
|
await showGoalStatus(host);
|
|
128278
128827
|
return;
|
|
128828
|
+
case "setup":
|
|
128829
|
+
await guidedGoalSetup(host);
|
|
128830
|
+
return;
|
|
128279
128831
|
case "pause":
|
|
128280
128832
|
await pauseGoal(host);
|
|
128281
128833
|
return;
|
|
@@ -128285,6 +128837,9 @@ async function handleGoalCommand(host, args) {
|
|
|
128285
128837
|
case "off":
|
|
128286
128838
|
await handleGoalOffCommand(host);
|
|
128287
128839
|
return;
|
|
128840
|
+
case "update":
|
|
128841
|
+
await updateGoalObjective(host, parsed);
|
|
128842
|
+
return;
|
|
128288
128843
|
case "create":
|
|
128289
128844
|
await createGoal(host, parsed);
|
|
128290
128845
|
return;
|
|
@@ -128302,8 +128857,51 @@ async function createGoal(host, parsed) {
|
|
|
128302
128857
|
}
|
|
128303
128858
|
await showGoalConfigWizard(host, session, parsed.objective, parsed.replace);
|
|
128304
128859
|
}
|
|
128860
|
+
const GOAL_REFINER_SYSTEM_PROMPT = "You are a goal refiner. Given a brief task description, produce a single clear, actionable objective sentence (max 200 chars). Do not add explanations, quotes, or prefixes.";
|
|
128861
|
+
/**
|
|
128862
|
+
* Guided goal creation: collect a brief task description, refine it via the
|
|
128863
|
+
* LLM into a single objective sentence, let the user confirm/edit, then enter
|
|
128864
|
+
* the standard configuration wizard. Falls back to the raw description if the
|
|
128865
|
+
* LLM call fails.
|
|
128866
|
+
*/
|
|
128867
|
+
async function guidedGoalSetup(host) {
|
|
128868
|
+
const session = host.session;
|
|
128869
|
+
if (session === void 0) {
|
|
128870
|
+
host.showError(t("error.no_session"));
|
|
128871
|
+
return;
|
|
128872
|
+
}
|
|
128873
|
+
if (detectGoalConflict(host.state.appState, "enable_goal") === "goal_active") {
|
|
128874
|
+
host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
|
|
128875
|
+
return;
|
|
128876
|
+
}
|
|
128877
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-B_rgO4qn.mjs");
|
|
128878
|
+
const initialDesc = await promptText(host, TextInputDialogComponent, {
|
|
128879
|
+
title: t("goal.setup_title_initial"),
|
|
128880
|
+
subtitle: t("goal.setup_desc_hint"),
|
|
128881
|
+
placeholder: t("goal.setup_desc_placeholder"),
|
|
128882
|
+
allowEmpty: false
|
|
128883
|
+
});
|
|
128884
|
+
if (initialDesc === void 0) return;
|
|
128885
|
+
host.showStatus(t("goal.setup_refining"));
|
|
128886
|
+
let objective;
|
|
128887
|
+
try {
|
|
128888
|
+
objective = (await session.generateText(GOAL_REFINER_SYSTEM_PROMPT, initialDesc)).trim();
|
|
128889
|
+
if (objective.length === 0) objective = initialDesc;
|
|
128890
|
+
} catch {
|
|
128891
|
+
objective = initialDesc;
|
|
128892
|
+
}
|
|
128893
|
+
const confirmed = await promptText(host, TextInputDialogComponent, {
|
|
128894
|
+
title: t("goal.setup_title_confirm"),
|
|
128895
|
+
subtitle: t("goal.setup_confirm_hint"),
|
|
128896
|
+
placeholder: objective,
|
|
128897
|
+
initialValue: objective,
|
|
128898
|
+
allowEmpty: true
|
|
128899
|
+
});
|
|
128900
|
+
if (confirmed === void 0) return;
|
|
128901
|
+
await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
|
|
128902
|
+
}
|
|
128305
128903
|
async function showGoalConfigWizard(host, session, objective, replace) {
|
|
128306
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
128904
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-B_rgO4qn.mjs");
|
|
128307
128905
|
const turnInput = await promptNumber(host, TextInputDialogComponent, {
|
|
128308
128906
|
title: t("goal.wizard_title", { objective }),
|
|
128309
128907
|
subtitle: t("goal.budget_turns_hint"),
|
|
@@ -128373,6 +128971,27 @@ function promptNumber(host, TextInputDialogComponent, opts) {
|
|
|
128373
128971
|
host.mountEditorReplacement(dialog);
|
|
128374
128972
|
});
|
|
128375
128973
|
}
|
|
128974
|
+
/** Prompt user for free-form text. Returns undefined on cancel. */
|
|
128975
|
+
function promptText(host, TextInputDialogComponent, opts) {
|
|
128976
|
+
return new Promise((resolve) => {
|
|
128977
|
+
const dialog = new TextInputDialogComponent((result) => {
|
|
128978
|
+
host.restoreEditor();
|
|
128979
|
+
if (result.kind !== "ok") {
|
|
128980
|
+
resolve(void 0);
|
|
128981
|
+
return;
|
|
128982
|
+
}
|
|
128983
|
+
resolve(result.value.trim());
|
|
128984
|
+
}, {
|
|
128985
|
+
title: opts.title,
|
|
128986
|
+
subtitle: opts.subtitle,
|
|
128987
|
+
placeholder: opts.placeholder,
|
|
128988
|
+
initialValue: opts.initialValue,
|
|
128989
|
+
allowEmpty: opts.allowEmpty,
|
|
128990
|
+
colors: host.state.theme.colors
|
|
128991
|
+
});
|
|
128992
|
+
host.mountEditorReplacement(dialog);
|
|
128993
|
+
});
|
|
128994
|
+
}
|
|
128376
128995
|
async function pauseGoal(host) {
|
|
128377
128996
|
const session = host.session;
|
|
128378
128997
|
if (session === void 0) {
|
|
@@ -128413,6 +129032,24 @@ async function resumeGoal(host) {
|
|
|
128413
129032
|
host.showError(t("goal.resume_failed", { msg: message }));
|
|
128414
129033
|
}
|
|
128415
129034
|
}
|
|
129035
|
+
async function updateGoalObjective(host, parsed) {
|
|
129036
|
+
const session = host.session;
|
|
129037
|
+
if (session === void 0) {
|
|
129038
|
+
host.showError(t("error.no_session"));
|
|
129039
|
+
return;
|
|
129040
|
+
}
|
|
129041
|
+
try {
|
|
129042
|
+
if ((await session.getGoal()).goal === null) {
|
|
129043
|
+
host.showStatus(t("goal.no_active"));
|
|
129044
|
+
return;
|
|
129045
|
+
}
|
|
129046
|
+
await session.updateGoalObjective(parsed.objective);
|
|
129047
|
+
host.showStatus(t("goal.updated", { objective: parsed.objective }));
|
|
129048
|
+
} catch (error) {
|
|
129049
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
129050
|
+
host.showError(t("goal.update_failed", { msg: message }));
|
|
129051
|
+
}
|
|
129052
|
+
}
|
|
128416
129053
|
async function handleGoalOffCommand(host) {
|
|
128417
129054
|
const session = host.session;
|
|
128418
129055
|
if (session === void 0) {
|
|
@@ -129442,6 +130079,12 @@ function easeSpeedRatio(ratio) {
|
|
|
129442
130079
|
}
|
|
129443
130080
|
//#endregion
|
|
129444
130081
|
//#region src/tui/components/messages/thinking.ts
|
|
130082
|
+
/** gpt-5 reasoning summaries contain empty HTML comment padding sentinels
|
|
130083
|
+
* like `<!-- -->`. Strip them to keep the thinking display clean. */
|
|
130084
|
+
const EMPTY_COMMENT_RE = /<!--\s*-->/g;
|
|
130085
|
+
function filterThinkingNoise(text) {
|
|
130086
|
+
return text.replace(EMPTY_COMMENT_RE, "");
|
|
130087
|
+
}
|
|
129445
130088
|
var ThinkingComponent = class {
|
|
129446
130089
|
text;
|
|
129447
130090
|
color;
|
|
@@ -129480,7 +130123,7 @@ var ThinkingComponent = class {
|
|
|
129480
130123
|
this.textComponent.setText(this.styled(trimmed));
|
|
129481
130124
|
}
|
|
129482
130125
|
styled(text) {
|
|
129483
|
-
return chalk.hex(this.color).italic(text);
|
|
130126
|
+
return chalk.hex(this.color).italic(filterThinkingNoise(text));
|
|
129484
130127
|
}
|
|
129485
130128
|
finalize() {
|
|
129486
130129
|
if (this.mode === "finalized") return;
|
|
@@ -129657,6 +130300,47 @@ function makeDiffStyles(colors) {
|
|
|
129657
130300
|
meta: (s) => chalk.hex(colors.diffMeta)(s)
|
|
129658
130301
|
};
|
|
129659
130302
|
}
|
|
130303
|
+
const ANSI_RE = /\u001B\[[0-9;]*m/;
|
|
130304
|
+
/** Visualize leading whitespace: tabs as `->`, leading spaces as `·`. */
|
|
130305
|
+
function visualizeIndent(line) {
|
|
130306
|
+
let i = 0;
|
|
130307
|
+
let visual = "";
|
|
130308
|
+
while (i < line.length) {
|
|
130309
|
+
const ch = line[i];
|
|
130310
|
+
if (ch === " ") {
|
|
130311
|
+
visual += "->";
|
|
130312
|
+
i++;
|
|
130313
|
+
continue;
|
|
130314
|
+
}
|
|
130315
|
+
if (ch === " ") {
|
|
130316
|
+
visual += "·";
|
|
130317
|
+
i++;
|
|
130318
|
+
continue;
|
|
130319
|
+
}
|
|
130320
|
+
break;
|
|
130321
|
+
}
|
|
130322
|
+
return {
|
|
130323
|
+
text: visual + line.slice(i),
|
|
130324
|
+
indentEnd: visual.length
|
|
130325
|
+
};
|
|
130326
|
+
}
|
|
130327
|
+
/**
|
|
130328
|
+
* Render a diff line's code with leading-whitespace visualization (tabs as
|
|
130329
|
+
* `->`, spaces as `·`, dimmed) and optional syntax highlighting. Highlighting
|
|
130330
|
+
* runs on the raw code first; indent visualization then runs on the
|
|
130331
|
+
* highlighted string - leading whitespace carries no token color, so it is
|
|
130332
|
+
* still plain and can be replaced and dimmed without disturbing the tokens.
|
|
130333
|
+
* When highlighting is off (streaming) or produced no token colors, the code
|
|
130334
|
+
* part is colored with the diff line color instead.
|
|
130335
|
+
*/
|
|
130336
|
+
function renderDiffCode(code, colorFn, highlight, lang) {
|
|
130337
|
+
const { text, indentEnd } = visualizeIndent(highlight ? highlightLines(code, lang)[0] ?? code : code);
|
|
130338
|
+
const indent = text.slice(0, indentEnd);
|
|
130339
|
+
const rest = text.slice(indentEnd);
|
|
130340
|
+
const dimIndent = indent.length > 0 ? chalk.dim(indent) : indent;
|
|
130341
|
+
if (highlight && ANSI_RE.test(rest)) return dimIndent + rest;
|
|
130342
|
+
return dimIndent + colorFn(rest);
|
|
130343
|
+
}
|
|
129660
130344
|
/**
|
|
129661
130345
|
* Compute word-level diff between two single lines and highlight the changed
|
|
129662
130346
|
* words with `chalk.inverse()`. Only the first removed/added part has its
|
|
@@ -129763,6 +130447,8 @@ function computeDiffLines(oldText, newText, oldStart = 1, newStart = 1, isIncomp
|
|
|
129763
130447
|
}
|
|
129764
130448
|
function renderDiffLines(oldText, newText, path, colors, isIncomplete = false, oldStart, newStart, maxLines) {
|
|
129765
130449
|
const s = makeDiffStyles(colors);
|
|
130450
|
+
const lang = langFromPath(path);
|
|
130451
|
+
const doHighlight = !isIncomplete && lang !== void 0;
|
|
129766
130452
|
const changedLines = computeDiffLines(oldText, newText, oldStart ?? 1, newStart ?? 1, isIncomplete).filter((l) => l.kind !== "context");
|
|
129767
130453
|
const added = changedLines.filter((l) => l.kind === "add").length;
|
|
129768
130454
|
const removed = changedLines.filter((l) => l.kind === "delete").length;
|
|
@@ -129787,7 +130473,7 @@ function renderDiffLines(oldText, newText, path, colors, isIncomplete = false, o
|
|
|
129787
130473
|
const line = shown[i];
|
|
129788
130474
|
const marker = line.kind === "add" ? "+" : "-";
|
|
129789
130475
|
const color = line.kind === "add" ? s.add : s.del;
|
|
129790
|
-
output.push(s.gutter(String(line.lineNum).padStart(4) + " ") + color(marker
|
|
130476
|
+
output.push(s.gutter(String(line.lineNum).padStart(4) + " ") + color(`${marker} `) + renderDiffCode(line.code, color, doHighlight, lang));
|
|
129791
130477
|
i += 1;
|
|
129792
130478
|
}
|
|
129793
130479
|
const hidden = changedLines.length - shown.length;
|
|
@@ -129838,11 +130524,11 @@ function buildClusters(diffLines, contextLines) {
|
|
|
129838
130524
|
removedCount: removed
|
|
129839
130525
|
};
|
|
129840
130526
|
}
|
|
129841
|
-
function formatDiffRow(line, s) {
|
|
130527
|
+
function formatDiffRow(line, s, doHighlight, lang) {
|
|
129842
130528
|
const gutter = s.gutter(String(line.lineNum).padStart(4) + " ");
|
|
129843
|
-
if (line.kind === "add") return gutter + s.add("+ " + line.code);
|
|
129844
|
-
if (line.kind === "delete") return gutter + s.del("- " + line.code);
|
|
129845
|
-
return gutter + " " + line.code;
|
|
130529
|
+
if (line.kind === "add") return gutter + s.add("+ ") + renderDiffCode(line.code, s.add, doHighlight, lang);
|
|
130530
|
+
if (line.kind === "delete") return gutter + s.del("- ") + renderDiffCode(line.code, s.del, doHighlight, lang);
|
|
130531
|
+
return gutter + " " + renderDiffCode(line.code, (x) => x, doHighlight, lang);
|
|
129846
130532
|
}
|
|
129847
130533
|
/**
|
|
129848
130534
|
* Render a diff with surrounding context, eliding unchanged middle
|
|
@@ -129855,6 +130541,8 @@ function formatDiffRow(line, s) {
|
|
|
129855
130541
|
*/
|
|
129856
130542
|
function renderDiffLinesClustered(oldText, newText, path, colors, opts = {}) {
|
|
129857
130543
|
const s = makeDiffStyles(colors);
|
|
130544
|
+
const lang = langFromPath(path);
|
|
130545
|
+
const doHighlight = !(opts.isIncomplete ?? false) && lang !== void 0;
|
|
129858
130546
|
const contextLines = opts.contextLines ?? 3;
|
|
129859
130547
|
const maxLines = opts.maxLines;
|
|
129860
130548
|
const diffLines = computeDiffLines(oldText, newText, 1, 1, opts.isIncomplete ?? false);
|
|
@@ -129911,7 +130599,7 @@ function renderDiffLinesClustered(oldText, newText, path, colors, opts = {}) {
|
|
|
129911
130599
|
i += 2;
|
|
129912
130600
|
continue;
|
|
129913
130601
|
}
|
|
129914
|
-
output.push(formatDiffRow(line, s));
|
|
130602
|
+
output.push(formatDiffRow(line, s, doHighlight, lang));
|
|
129915
130603
|
body++;
|
|
129916
130604
|
if (line.kind !== "context") shownChanges++;
|
|
129917
130605
|
prevEnd = i;
|
|
@@ -138652,7 +139340,8 @@ var SessionEventHandler = class {
|
|
|
138652
139340
|
goal: {
|
|
138653
139341
|
objective: snapshot.objective,
|
|
138654
139342
|
turnsUsed: snapshot.turnsUsed ?? 0,
|
|
138655
|
-
wallClockMs: snapshot.wallClockMs ?? 0
|
|
139343
|
+
wallClockMs: snapshot.wallClockMs ?? 0,
|
|
139344
|
+
wallClockBaseAt: Date.now()
|
|
138656
139345
|
},
|
|
138657
139346
|
goalActive: snapshot.status === "active"
|
|
138658
139347
|
});
|
|
@@ -144729,7 +145418,8 @@ var SessionManager = class {
|
|
|
144729
145418
|
goal: goal ? {
|
|
144730
145419
|
objective: goal.objective,
|
|
144731
145420
|
turnsUsed: goal.turnsUsed ?? 0,
|
|
144732
|
-
wallClockMs: goal.wallClockMs ?? 0
|
|
145421
|
+
wallClockMs: goal.wallClockMs ?? 0,
|
|
145422
|
+
wallClockBaseAt: Date.now()
|
|
144733
145423
|
} : null,
|
|
144734
145424
|
goalActive: goal?.status === "active",
|
|
144735
145425
|
goalContinuationCount: 0
|