scream-code 0.10.6 → 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-DsLBIrSM.mjs → app-D_usIcl7.mjs} +899 -107
- package/dist/main.mjs +1 -1
- package/dist/{text-input-dialog-zZlNFEk3.mjs → text-input-dialog-B_rgO4qn.mjs} +1 -1
- package/dist/{text-input-dialog-pClOx34t.mjs → text-input-dialog-CiWeHRNJ.mjs} +27 -3
- 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
|
/**
|
|
@@ -55788,15 +55788,35 @@ function formatBudget(value, unit) {
|
|
|
55788
55788
|
*/
|
|
55789
55789
|
/** Maximum objective length in characters. */
|
|
55790
55790
|
const MAX_GOAL_OBJECTIVE_LENGTH = 4e3;
|
|
55791
|
+
/** Consecutive markBlocked calls with the same reason required before blocking. */
|
|
55792
|
+
const BLOCKED_STREAK_THRESHOLD = 3;
|
|
55791
55793
|
/** Maximum number of working notes kept per goal. */
|
|
55792
|
-
const MAX_GOAL_NOTES =
|
|
55794
|
+
const MAX_GOAL_NOTES = 60;
|
|
55793
55795
|
/** Maximum characters per note. */
|
|
55794
|
-
const MAX_NOTE_LENGTH =
|
|
55796
|
+
const MAX_NOTE_LENGTH = 400;
|
|
55795
55797
|
const GOAL_CANCELLED_REMINDER = [
|
|
55796
55798
|
"The user cancelled the current goal.",
|
|
55797
55799
|
"Ignore earlier active-goal reminders for that goal.",
|
|
55798
55800
|
"Handle the next user request normally unless the user starts or resumes a goal."
|
|
55799
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
|
+
}
|
|
55800
55820
|
const GOAL_COMPLETION_REMINDER_NAME = "goal_completion_summary";
|
|
55801
55821
|
const GOAL_BLOCKED_REMINDER_NAME = "goal_blocked_reason";
|
|
55802
55822
|
var GoalMode = class {
|
|
@@ -55835,7 +55855,8 @@ var GoalMode = class {
|
|
|
55835
55855
|
tokensUsed: 0,
|
|
55836
55856
|
wallClockMs: 0,
|
|
55837
55857
|
budgetLimits: {},
|
|
55838
|
-
notes: []
|
|
55858
|
+
notes: [],
|
|
55859
|
+
blockedStreak: 0
|
|
55839
55860
|
};
|
|
55840
55861
|
this.state = state;
|
|
55841
55862
|
}
|
|
@@ -55855,6 +55876,7 @@ var GoalMode = class {
|
|
|
55855
55876
|
state.wallClockResumedAt = void 0;
|
|
55856
55877
|
}
|
|
55857
55878
|
if (record.budgetLimits !== void 0) state.budgetLimits = record.budgetLimits;
|
|
55879
|
+
if (record.objective !== void 0) state.objective = record.objective;
|
|
55858
55880
|
}
|
|
55859
55881
|
restoreClear(_record) {
|
|
55860
55882
|
this.state = void 0;
|
|
@@ -55887,7 +55909,8 @@ var GoalMode = class {
|
|
|
55887
55909
|
wallClockMs: 0,
|
|
55888
55910
|
wallClockResumedAt: Date.now(),
|
|
55889
55911
|
budgetLimits: {},
|
|
55890
|
-
notes: []
|
|
55912
|
+
notes: [],
|
|
55913
|
+
blockedStreak: 0
|
|
55891
55914
|
};
|
|
55892
55915
|
this.persistState(state);
|
|
55893
55916
|
this.agent.records.logRecord({
|
|
@@ -55932,6 +55955,8 @@ var GoalMode = class {
|
|
|
55932
55955
|
if (state.status === "active") return this.toSnapshot(state);
|
|
55933
55956
|
if (state.status !== "paused" && state.status !== "blocked") throw new ScreamError(ErrorCodes.GOAL_NOT_RESUMABLE, `Cannot resume a goal in status "${state.status}"`);
|
|
55934
55957
|
state.terminalReason = void 0;
|
|
55958
|
+
state.blockedStreak = 0;
|
|
55959
|
+
state.lastBlockedReason = void 0;
|
|
55935
55960
|
this.applyStatus(state, "active");
|
|
55936
55961
|
this.persistState(state, { change: {
|
|
55937
55962
|
kind: "lifecycle",
|
|
@@ -55952,6 +55977,23 @@ var GoalMode = class {
|
|
|
55952
55977
|
this.appendGoalUpdate({ budgetLimits: state.budgetLimits });
|
|
55953
55978
|
return this.toSnapshot(state);
|
|
55954
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
|
+
}
|
|
55955
55997
|
async cancelGoal(actor = "user") {
|
|
55956
55998
|
const state = this.requireState();
|
|
55957
55999
|
const snapshot = this.toSnapshot(state);
|
|
@@ -55965,8 +56007,22 @@ var GoalMode = class {
|
|
|
55965
56007
|
async markBlocked(input = {}, actor = "runtime") {
|
|
55966
56008
|
const state = this.state;
|
|
55967
56009
|
if (state === void 0 || state.status !== "active") return null;
|
|
56010
|
+
if (actor === "model") {
|
|
56011
|
+
const reason = input.reason ?? "";
|
|
56012
|
+
if (reason === (state.lastBlockedReason ?? "")) state.blockedStreak += 1;
|
|
56013
|
+
else {
|
|
56014
|
+
state.blockedStreak = 1;
|
|
56015
|
+
state.lastBlockedReason = reason;
|
|
56016
|
+
}
|
|
56017
|
+
if (state.blockedStreak < BLOCKED_STREAK_THRESHOLD) {
|
|
56018
|
+
this.persistState(state, { silent: true });
|
|
56019
|
+
return null;
|
|
56020
|
+
}
|
|
56021
|
+
}
|
|
55968
56022
|
this.applyStatus(state, "blocked");
|
|
55969
56023
|
state.terminalReason = input.reason;
|
|
56024
|
+
state.blockedStreak = 0;
|
|
56025
|
+
state.lastBlockedReason = void 0;
|
|
55970
56026
|
this.persistState(state, { change: {
|
|
55971
56027
|
kind: "lifecycle",
|
|
55972
56028
|
status: "blocked",
|
|
@@ -56021,7 +56077,7 @@ var GoalMode = class {
|
|
|
56021
56077
|
content: trimmed,
|
|
56022
56078
|
time: Date.now()
|
|
56023
56079
|
});
|
|
56024
|
-
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);
|
|
56025
56081
|
this.persistState(state, { silent: true });
|
|
56026
56082
|
return this.toSnapshot(state);
|
|
56027
56083
|
}
|
|
@@ -56168,13 +56224,18 @@ function formatTokens$2(tokens) {
|
|
|
56168
56224
|
}
|
|
56169
56225
|
//#endregion
|
|
56170
56226
|
//#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.ts
|
|
56171
|
-
const UpdateGoalToolInputSchema = z.object({
|
|
56172
|
-
|
|
56173
|
-
|
|
56174
|
-
|
|
56175
|
-
|
|
56176
|
-
|
|
56227
|
+
const UpdateGoalToolInputSchema = z.object({
|
|
56228
|
+
status: z.enum([
|
|
56229
|
+
"active",
|
|
56230
|
+
"complete",
|
|
56231
|
+
"paused",
|
|
56232
|
+
"blocked"
|
|
56233
|
+
]).describe("The lifecycle status to set for the current goal."),
|
|
56234
|
+
reason: z.string().optional().describe("Optional reason for the status change, especially for blocked.")
|
|
56235
|
+
}).strict();
|
|
56177
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;
|
|
56178
56239
|
function extractRecentOutput(history) {
|
|
56179
56240
|
const parts = [];
|
|
56180
56241
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
@@ -56187,6 +56248,51 @@ function extractRecentOutput(history) {
|
|
|
56187
56248
|
const joined = parts.join("\n\n");
|
|
56188
56249
|
return joined.length > MAX_GRADER_OUTPUT_CHARS ? `${joined.slice(0, MAX_GRADER_OUTPUT_CHARS)}…` : joined;
|
|
56189
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
|
+
}
|
|
56190
56296
|
var UpdateGoalTool = class {
|
|
56191
56297
|
agent;
|
|
56192
56298
|
grader;
|
|
@@ -56209,15 +56315,18 @@ var UpdateGoalTool = class {
|
|
|
56209
56315
|
}
|
|
56210
56316
|
if (args.status === "complete") return this.handleComplete(goal);
|
|
56211
56317
|
if (args.status === "blocked") {
|
|
56212
|
-
const blocked = await goal.markBlocked({}, "model");
|
|
56213
|
-
if (blocked !== null)
|
|
56214
|
-
|
|
56215
|
-
|
|
56216
|
-
|
|
56217
|
-
|
|
56218
|
-
|
|
56219
|
-
|
|
56220
|
-
|
|
56318
|
+
const blocked = await goal.markBlocked(args.reason !== void 0 ? { reason: args.reason } : {}, "model");
|
|
56319
|
+
if (blocked !== null) {
|
|
56320
|
+
this.agent.context.appendSystemReminder(buildGoalBlockedReasonPrompt(blocked), {
|
|
56321
|
+
kind: "system_trigger",
|
|
56322
|
+
name: GOAL_BLOCKED_REMINDER_NAME
|
|
56323
|
+
});
|
|
56324
|
+
return {
|
|
56325
|
+
output: "Goal marked blocked.",
|
|
56326
|
+
stopTurn: true
|
|
56327
|
+
};
|
|
56328
|
+
}
|
|
56329
|
+
return { output: "Goal remains active. Report the same blocker in subsequent turns to confirm it cannot be resolved. Continue working on the goal in the meantime." };
|
|
56221
56330
|
}
|
|
56222
56331
|
await goal.pauseGoal({}, "model");
|
|
56223
56332
|
return {
|
|
@@ -56230,7 +56339,7 @@ var UpdateGoalTool = class {
|
|
|
56230
56339
|
async handleComplete(goal) {
|
|
56231
56340
|
const goalState = goal.getGoal().goal;
|
|
56232
56341
|
if (!goalState) return { output: "No active goal." };
|
|
56233
|
-
const
|
|
56342
|
+
const outputWithContext = await appendGitDiffStat(appendGoalNotes(extractRecentOutput(this.agent.context.history), goalState.notes), this.agent.config?.cwd ?? "");
|
|
56234
56343
|
try {
|
|
56235
56344
|
await goal.pauseGoal({ reason: "verifying" }, "system");
|
|
56236
56345
|
} catch (error) {
|
|
@@ -56238,7 +56347,7 @@ var UpdateGoalTool = class {
|
|
|
56238
56347
|
}
|
|
56239
56348
|
let rawGrade;
|
|
56240
56349
|
try {
|
|
56241
|
-
rawGrade = await this.grader(goalState.objective, goalState.completionCriterion,
|
|
56350
|
+
rawGrade = await this.grader(goalState.objective, goalState.completionCriterion, outputWithContext);
|
|
56242
56351
|
} catch (error) {
|
|
56243
56352
|
const resumeError = await resumeAfterGrading(goal);
|
|
56244
56353
|
if (resumeError !== void 0) return resumeError;
|
|
@@ -56314,7 +56423,7 @@ function errorMessage$4(error) {
|
|
|
56314
56423
|
}
|
|
56315
56424
|
//#endregion
|
|
56316
56425
|
//#region ../../packages/agent-core/src/tools/builtin/goal/write-goal-note.ts
|
|
56317
|
-
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();
|
|
56318
56427
|
var WriteGoalNoteTool = class {
|
|
56319
56428
|
agent;
|
|
56320
56429
|
name = "WriteGoalNote";
|
|
@@ -57551,6 +57660,16 @@ function parseMemoryMemos(text) {
|
|
|
57551
57660
|
}
|
|
57552
57661
|
return memos;
|
|
57553
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
|
+
}
|
|
57554
57673
|
/** System prompt for exit-time extraction — instructs the LLM how to extract. */
|
|
57555
57674
|
const EXIT_EXTRACTION_SYSTEM_PROMPT = "你是一个任务经验提取助手。任务是从对话记录中识别已完成的任务闭环,提炼出任务经验记录。用对话的主要语言输出(中文对话用中文,英文对话用英文)。只输出指定的 JSON 格式,不要调用任何工具。";
|
|
57556
57675
|
/** Build the user prompt for exit-time extraction, including a conversation sample. */
|
|
@@ -58303,17 +58422,20 @@ var MemoryWriteTool = class {
|
|
|
58303
58422
|
};
|
|
58304
58423
|
const sessionId = this.agent.homedir ? basename$1(dirname$2(dirname$2(this.agent.homedir))) : "unknown";
|
|
58305
58424
|
const sourceSessionTitle = await this.agent.getSessionTitle();
|
|
58306
|
-
const
|
|
58307
|
-
const
|
|
58308
|
-
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}`));
|
|
58309
58431
|
const memo = createMemoryMemo({
|
|
58310
58432
|
sourceSessionId: sessionId,
|
|
58311
58433
|
sourceSessionTitle,
|
|
58312
|
-
userNeed
|
|
58313
|
-
approach
|
|
58314
|
-
outcome
|
|
58315
|
-
whatFailed: whatFailed
|
|
58316
|
-
whatWorked: whatWorked
|
|
58434
|
+
userNeed,
|
|
58435
|
+
approach,
|
|
58436
|
+
outcome,
|
|
58437
|
+
whatFailed: whatFailed.length === 0 ? "none" : whatFailed,
|
|
58438
|
+
whatWorked: whatWorked.length === 0 ? "none" : whatWorked,
|
|
58317
58439
|
tags,
|
|
58318
58440
|
extractionSource: "manual",
|
|
58319
58441
|
projectDir: this.agent.config.cwd
|
|
@@ -62680,20 +62802,20 @@ function isRecord$6(value) {
|
|
|
62680
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";
|
|
62681
62803
|
//#endregion
|
|
62682
62804
|
//#region ../../packages/agent-core/src/skill/builtin/dream.ts
|
|
62683
|
-
const PSEUDO_PATH$
|
|
62684
|
-
const parsed$
|
|
62805
|
+
const PSEUDO_PATH$2 = "builtin://dream";
|
|
62806
|
+
const parsed$2 = parseSkillText({
|
|
62685
62807
|
skillMdPath: "/builtin/skills/dream.md",
|
|
62686
62808
|
skillDirName: "dream",
|
|
62687
62809
|
source: "builtin",
|
|
62688
62810
|
text: dream_default
|
|
62689
62811
|
});
|
|
62690
62812
|
const DREAM_SKILL = {
|
|
62691
|
-
...parsed$
|
|
62692
|
-
path: PSEUDO_PATH$
|
|
62693
|
-
dir: PSEUDO_PATH$
|
|
62813
|
+
...parsed$2,
|
|
62814
|
+
path: PSEUDO_PATH$2,
|
|
62815
|
+
dir: PSEUDO_PATH$2,
|
|
62694
62816
|
metadata: {
|
|
62695
|
-
...parsed$
|
|
62696
|
-
type: parsed$
|
|
62817
|
+
...parsed$2.metadata,
|
|
62818
|
+
type: parsed$2.metadata.type ?? "inline",
|
|
62697
62819
|
disableModelInvocation: true
|
|
62698
62820
|
}
|
|
62699
62821
|
};
|
|
@@ -62702,14 +62824,36 @@ const DREAM_SKILL = {
|
|
|
62702
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";
|
|
62703
62825
|
//#endregion
|
|
62704
62826
|
//#region ../../packages/agent-core/src/skill/builtin/make-skill.ts
|
|
62705
|
-
const PSEUDO_PATH = "builtin://make-skill";
|
|
62706
|
-
const parsed = parseSkillText({
|
|
62827
|
+
const PSEUDO_PATH$1 = "builtin://make-skill";
|
|
62828
|
+
const parsed$1 = parseSkillText({
|
|
62707
62829
|
skillMdPath: "/builtin/skills/make-skill.md",
|
|
62708
62830
|
skillDirName: "make-skill",
|
|
62709
62831
|
source: "builtin",
|
|
62710
62832
|
text: make_skill_default
|
|
62711
62833
|
});
|
|
62712
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 = {
|
|
62713
62857
|
...parsed,
|
|
62714
62858
|
path: PSEUDO_PATH,
|
|
62715
62859
|
dir: PSEUDO_PATH,
|
|
@@ -62724,6 +62868,7 @@ const MAKE_SKILL_SKILL = {
|
|
|
62724
62868
|
function registerBuiltinSkills(registry) {
|
|
62725
62869
|
registry.registerBuiltinSkill(DREAM_SKILL);
|
|
62726
62870
|
registry.registerBuiltinSkill(MAKE_SKILL_SKILL);
|
|
62871
|
+
registry.registerBuiltinSkill(TOOL_PROMPT_OPTIMIZATION_SKILL);
|
|
62727
62872
|
}
|
|
62728
62873
|
//#endregion
|
|
62729
62874
|
//#region ../../packages/agent-core/src/skill/scanner.ts
|
|
@@ -75281,17 +75426,25 @@ var ToolResultBuilder = class {
|
|
|
75281
75426
|
maxChars;
|
|
75282
75427
|
maxTailChars;
|
|
75283
75428
|
maxLineLength;
|
|
75429
|
+
artifactSink;
|
|
75430
|
+
maxFullOutputChars;
|
|
75284
75431
|
buffer = [];
|
|
75285
75432
|
nCharsValue = 0;
|
|
75286
75433
|
truncationHappened = false;
|
|
75287
75434
|
headTruncated = false;
|
|
75288
75435
|
tailBuf = [];
|
|
75289
75436
|
tailCharsValue = 0;
|
|
75437
|
+
fullOutput;
|
|
75438
|
+
fullOutputChars = 0;
|
|
75439
|
+
totalLinesWritten = 0;
|
|
75290
75440
|
constructor(options = {}) {
|
|
75291
75441
|
this.maxChars = options.maxChars ?? DEFAULT_MAX_CHARS;
|
|
75292
75442
|
this.maxTailChars = options.maxTailChars ?? DEFAULT_TAIL_CHARS;
|
|
75293
75443
|
this.maxLineLength = options.maxLineLength === void 0 ? DEFAULT_MAX_LINE_LENGTH : options.maxLineLength;
|
|
75294
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 = [];
|
|
75295
75448
|
}
|
|
75296
75449
|
get nChars() {
|
|
75297
75450
|
return this.nCharsValue + this.tailCharsValue;
|
|
@@ -75301,12 +75454,19 @@ var ToolResultBuilder = class {
|
|
|
75301
75454
|
if (!this.headTruncated || this.tailCharsValue === 0) return head;
|
|
75302
75455
|
this.trimTail();
|
|
75303
75456
|
const tail = this.tailBuf.join("");
|
|
75304
|
-
|
|
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}`;
|
|
75305
75460
|
}
|
|
75306
75461
|
write(text) {
|
|
75307
75462
|
if (text.length === 0) return 0;
|
|
75308
75463
|
const lines = text.match(/[^\r\n]*(?:\r\n|[\n\r])|[^\r\n]+/g) ?? [];
|
|
75309
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
|
+
}
|
|
75310
75470
|
let charsWritten = 0;
|
|
75311
75471
|
for (const originalLine of lines) if (this.nCharsValue < this.maxChars) {
|
|
75312
75472
|
const remainingChars = this.maxChars - this.nCharsValue;
|
|
@@ -75354,11 +75514,36 @@ var ToolResultBuilder = class {
|
|
|
75354
75514
|
this.tailBuf.push(trimmed);
|
|
75355
75515
|
this.tailCharsValue = trimmed.length;
|
|
75356
75516
|
}
|
|
75357
|
-
|
|
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 = {}) {
|
|
75358
75541
|
let finalMessage = message;
|
|
75359
75542
|
if (finalMessage.length > 0 && !finalMessage.endsWith(".")) finalMessage += ".";
|
|
75360
75543
|
if (this.truncationHappened) finalMessage = finalMessage.length === 0 ? TRUNCATION_MESSAGE : `${finalMessage} ${TRUNCATION_MESSAGE}`;
|
|
75361
|
-
const
|
|
75544
|
+
const baseOutput = this.toString();
|
|
75545
|
+
const artifactRef = await this.maybeWriteArtifact();
|
|
75546
|
+
const output = artifactRef === void 0 ? baseOutput : this.appendArtifactRef(baseOutput, artifactRef);
|
|
75362
75547
|
return {
|
|
75363
75548
|
isError: false,
|
|
75364
75549
|
output: finalMessage.length > 0 && (this.truncationHappened || output.length === 0) ? output.length === 0 ? finalMessage : output.endsWith("\n") ? `${output}${finalMessage}` : `${output}\n${finalMessage}` : output,
|
|
@@ -75367,9 +75552,11 @@ var ToolResultBuilder = class {
|
|
|
75367
75552
|
brief: options.brief
|
|
75368
75553
|
};
|
|
75369
75554
|
}
|
|
75370
|
-
error(message, options = {}) {
|
|
75555
|
+
async error(message, options = {}) {
|
|
75371
75556
|
const finalMessage = this.truncationHappened ? message.length === 0 ? TRUNCATION_MESSAGE : `${message} ${TRUNCATION_MESSAGE}` : message;
|
|
75372
|
-
const
|
|
75557
|
+
const baseOutput = this.toString();
|
|
75558
|
+
const artifactRef = await this.maybeWriteArtifact();
|
|
75559
|
+
const output = artifactRef === void 0 ? baseOutput : this.appendArtifactRef(baseOutput, artifactRef);
|
|
75373
75560
|
return {
|
|
75374
75561
|
isError: true,
|
|
75375
75562
|
output: finalMessage.length === 0 ? output : output.length === 0 ? finalMessage : output.endsWith("\n") ? `${output}${finalMessage}` : `${output}\n${finalMessage}`,
|
|
@@ -75379,6 +75566,10 @@ var ToolResultBuilder = class {
|
|
|
75379
75566
|
};
|
|
75380
75567
|
}
|
|
75381
75568
|
};
|
|
75569
|
+
function countLines(text) {
|
|
75570
|
+
if (text.length === 0) return 0;
|
|
75571
|
+
return text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
|
|
75572
|
+
}
|
|
75382
75573
|
//#endregion
|
|
75383
75574
|
//#region ../../packages/agent-core/src/tools/builtin/file/grep.md
|
|
75384
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";
|
|
@@ -75568,7 +75759,7 @@ var GrepTool = class {
|
|
|
75568
75759
|
const combined = visibleBody === "" && messages.length === 0 ? emptyResultMessage : messages.length > 0 ? visibleBody === "" ? messages.join("\n") : `${visibleBody}\n${messages.join("\n")}` : visibleBody;
|
|
75569
75760
|
const builder = new ToolResultBuilder();
|
|
75570
75761
|
builder.write(combined);
|
|
75571
|
-
const result = builder.ok(sideChannelMessages.join("\n"));
|
|
75762
|
+
const result = await builder.ok(sideChannelMessages.join("\n"));
|
|
75572
75763
|
const display = buildSearchResultsDisplay(args, limited, mode, contentIncludesLineNumbers);
|
|
75573
75764
|
if (display === void 0) return result;
|
|
75574
75765
|
if (result.isError === true) return result;
|
|
@@ -77788,6 +77979,30 @@ function truncateUtf8$1(input, maxBytes) {
|
|
|
77788
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";
|
|
77789
77980
|
//#endregion
|
|
77790
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
|
+
*/
|
|
77791
78006
|
const MS_PER_SECOND = 1e3;
|
|
77792
78007
|
const DEFAULT_TIMEOUT_S = 60;
|
|
77793
78008
|
const MAX_TIMEOUT_S = 300;
|
|
@@ -78038,7 +78253,11 @@ var BashTool = class {
|
|
|
78038
78253
|
killProc();
|
|
78039
78254
|
}, timeoutMs);
|
|
78040
78255
|
try {
|
|
78041
|
-
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
|
+
} });
|
|
78042
78261
|
const [, exitCode] = await Promise.all([Promise.all([readStreamIntoBuilder(proc.stdout, builder), readStreamIntoBuilder(proc.stderr, builder)]), proc.wait()]);
|
|
78043
78262
|
if (timedOut) {
|
|
78044
78263
|
const timeoutLabel = timeoutMs % 1e3 === 0 ? `${String(timeoutMs / 1e3)}s` : `${String(timeoutMs)}ms`;
|
|
@@ -79629,7 +79848,8 @@ const DEFAULT_CONFIG = {
|
|
|
79629
79848
|
minContentTokens: 100,
|
|
79630
79849
|
minContextUsageRatio: .5,
|
|
79631
79850
|
truncatedMarker: "[Old tool result content cleared]",
|
|
79632
|
-
uselessMarker: "[Uneventful result elided]"
|
|
79851
|
+
uselessMarker: "[Uneventful result elided]",
|
|
79852
|
+
noMatchesMarker: "[no matches]"
|
|
79633
79853
|
};
|
|
79634
79854
|
/**
|
|
79635
79855
|
* Compute the cutoff index: everything at index < cutoff is eligible for
|
|
@@ -79650,30 +79870,107 @@ function computeCutoff(messages, config) {
|
|
|
79650
79870
|
}
|
|
79651
79871
|
return cutoff;
|
|
79652
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"]);
|
|
79653
79879
|
/**
|
|
79654
|
-
*
|
|
79655
|
-
*
|
|
79656
|
-
*
|
|
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).
|
|
79657
79943
|
*
|
|
79658
|
-
* Only considers tool results before the cutoff line
|
|
79659
|
-
* 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).
|
|
79660
79950
|
*/
|
|
79661
79951
|
function findSupersededPaths(messages, cutoff) {
|
|
79662
79952
|
const superseded = /* @__PURE__ */ new Map();
|
|
79663
|
-
const
|
|
79953
|
+
const latestReadByPath = /* @__PURE__ */ new Map();
|
|
79664
79954
|
for (let i = 0; i < messages.length; i++) {
|
|
79665
79955
|
const msg = messages[i];
|
|
79666
79956
|
if (msg === void 0) continue;
|
|
79667
|
-
if (msg.role
|
|
79668
|
-
|
|
79669
|
-
|
|
79670
|
-
|
|
79671
|
-
|
|
79672
|
-
|
|
79673
|
-
|
|
79674
|
-
|
|
79675
|
-
|
|
79676
|
-
|
|
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
|
+
});
|
|
79677
79974
|
}
|
|
79678
79975
|
}
|
|
79679
79976
|
}
|
|
@@ -79727,20 +80024,26 @@ var MicroCompaction = class {
|
|
|
79727
80024
|
}
|
|
79728
80025
|
/**
|
|
79729
80026
|
* Apply micro-compaction to a message list: replace old tool results
|
|
79730
|
-
* before the cutoff line with truncated markers. Read results
|
|
79731
|
-
* that were re-read later get a supersede marker
|
|
79732
|
-
* the old content is stale.
|
|
79733
|
-
* elided
|
|
79734
|
-
*
|
|
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.
|
|
79735
80033
|
*/
|
|
79736
80034
|
compact(messages) {
|
|
79737
80035
|
const config = this.config;
|
|
79738
80036
|
const superseded = findSupersededPaths(messages, this.cutoff);
|
|
80037
|
+
const toolNames = buildToolCallNameMap(messages);
|
|
79739
80038
|
const result = [];
|
|
79740
80039
|
let i = 0;
|
|
79741
80040
|
for (const msg of messages) {
|
|
79742
|
-
const
|
|
79743
|
-
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;
|
|
79744
80047
|
if (isUseless) result.push({
|
|
79745
80048
|
...msg,
|
|
79746
80049
|
content: [{
|
|
@@ -79748,8 +80051,16 @@ var MicroCompaction = class {
|
|
|
79748
80051
|
text: config.uselessMarker
|
|
79749
80052
|
}]
|
|
79750
80053
|
});
|
|
80054
|
+
else if (isZeroMatch) result.push({
|
|
80055
|
+
...msg,
|
|
80056
|
+
content: [{
|
|
80057
|
+
type: "text",
|
|
80058
|
+
text: config.noMatchesMarker
|
|
80059
|
+
}]
|
|
80060
|
+
});
|
|
79751
80061
|
else if (isOversizedTruncatable) {
|
|
79752
|
-
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;
|
|
79753
80064
|
result.push({
|
|
79754
80065
|
...msg,
|
|
79755
80066
|
content: [{
|
|
@@ -79774,20 +80085,28 @@ var MicroCompaction = class {
|
|
|
79774
80085
|
measureEffect(messages, cutoff) {
|
|
79775
80086
|
let markerTokenCount;
|
|
79776
80087
|
let uselessMarkerTokenCount;
|
|
80088
|
+
let noMatchesMarkerTokenCount;
|
|
79777
80089
|
let truncatedToolResultCount = 0;
|
|
79778
80090
|
let beforeTokens = 0;
|
|
79779
80091
|
let afterTokens = 0;
|
|
80092
|
+
const toolNames = buildToolCallNameMap(messages);
|
|
79780
80093
|
for (let i = 0; i < messages.length && i < cutoff; i++) {
|
|
79781
80094
|
const message = messages[i];
|
|
79782
80095
|
if (message?.role !== "tool" || message.toolCallId === void 0) continue;
|
|
79783
80096
|
const contentTokens = estimateTokensForMessages([message]);
|
|
79784
80097
|
const isUseless = message.useless === true;
|
|
79785
|
-
|
|
80098
|
+
const isZeroMatch = isZeroMatchSearchResult(toolNames.get(message.toolCallId), message.content);
|
|
80099
|
+
if (!isUseless && !isZeroMatch && contentTokens < this.config.minContentTokens) continue;
|
|
79786
80100
|
if (isUseless) {
|
|
79787
80101
|
uselessMarkerTokenCount ??= estimateTokens$1(this.config.uselessMarker);
|
|
79788
80102
|
truncatedToolResultCount += 1;
|
|
79789
80103
|
beforeTokens += contentTokens;
|
|
79790
80104
|
afterTokens += uselessMarkerTokenCount;
|
|
80105
|
+
} else if (isZeroMatch) {
|
|
80106
|
+
noMatchesMarkerTokenCount ??= estimateTokens$1(this.config.noMatchesMarker);
|
|
80107
|
+
truncatedToolResultCount += 1;
|
|
80108
|
+
beforeTokens += contentTokens;
|
|
80109
|
+
afterTokens += noMatchesMarkerTokenCount;
|
|
79791
80110
|
} else {
|
|
79792
80111
|
markerTokenCount ??= estimateTokens$1(this.config.truncatedMarker);
|
|
79793
80112
|
truncatedToolResultCount += 1;
|
|
@@ -80796,6 +81115,71 @@ var ConfigState = class {
|
|
|
80796
81115
|
}
|
|
80797
81116
|
};
|
|
80798
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
|
|
80799
81183
|
//#region ../../packages/agent-core/src/agent/context/types.ts
|
|
80800
81184
|
const USER_PROMPT_ORIGIN = { kind: "user" };
|
|
80801
81185
|
//#endregion
|
|
@@ -80818,6 +81202,18 @@ var ContextMemory = class {
|
|
|
80818
81202
|
openSteps = /* @__PURE__ */ new Map();
|
|
80819
81203
|
pendingToolResultIds = /* @__PURE__ */ new Set();
|
|
80820
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 = [];
|
|
80821
81217
|
constructor(agent) {
|
|
80822
81218
|
this.agent = agent;
|
|
80823
81219
|
}
|
|
@@ -80867,6 +81263,7 @@ var ContextMemory = class {
|
|
|
80867
81263
|
this.openSteps.clear();
|
|
80868
81264
|
this.pendingToolResultIds.clear();
|
|
80869
81265
|
this.deferredMessages = [];
|
|
81266
|
+
this.lastSentFingerprints = [];
|
|
80870
81267
|
this.agent.injection.onContextClear();
|
|
80871
81268
|
this.agent.emitStatusUpdated();
|
|
80872
81269
|
}
|
|
@@ -80911,6 +81308,19 @@ var ContextMemory = class {
|
|
|
80911
81308
|
}
|
|
80912
81309
|
if (!this.agent.records.restoring && (stoppedAtBoundary || removedUserCount < count)) {}
|
|
80913
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
|
+
*/
|
|
80914
81324
|
applyCompaction(summary) {
|
|
80915
81325
|
this.agent.records.logRecord({
|
|
80916
81326
|
type: "context.apply_compaction",
|
|
@@ -80954,6 +81364,48 @@ var ContextMemory = class {
|
|
|
80954
81364
|
this.agent.microCompaction.detect();
|
|
80955
81365
|
return project(this.agent.microCompaction.compact(this.history));
|
|
80956
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
|
+
}
|
|
80957
81409
|
appendLoopEvent(event) {
|
|
80958
81410
|
this.agent.records.logRecord({
|
|
80959
81411
|
type: "context.append_loop_event",
|
|
@@ -81590,6 +82042,8 @@ function buildGoalReminder(goal) {
|
|
|
81590
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.");
|
|
81591
82043
|
lines.push("");
|
|
81592
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.");
|
|
81593
82047
|
return lines.join("\n");
|
|
81594
82048
|
}
|
|
81595
82049
|
function maxBudgetFraction(goal) {
|
|
@@ -97303,6 +97757,13 @@ function buildGraderPrompt(objective, criteria, output) {
|
|
|
97303
97757
|
"## Agent Output",
|
|
97304
97758
|
output || "(no output captured)",
|
|
97305
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
|
+
"",
|
|
97306
97767
|
"Evaluate each dimension independently against the acceptance criteria, then decide overall PASS/FAIL.",
|
|
97307
97768
|
"When FAIL, list every specific issue with an actionable fix direction so the agent knows exactly what to address next.",
|
|
97308
97769
|
"Respond with JSON:",
|
|
@@ -97983,14 +98444,21 @@ const GOAL_CONTINUATION_PROMPT = [
|
|
|
97983
98444
|
"reassess. Call UpdateGoal with `complete` only when all required work is done, any stated",
|
|
97984
98445
|
"validation has passed, and there is no useful next action. Do not mark complete after only",
|
|
97985
98446
|
"producing a plan, summary, first pass, or partial result. If an external condition or required",
|
|
97986
|
-
"user input prevents progress,
|
|
97987
|
-
"
|
|
97988
|
-
"
|
|
98447
|
+
"user input prevents progress, call UpdateGoal with `blocked` and include a `reason`. The goal",
|
|
98448
|
+
"will only be marked blocked after you report the same blocker for at least 3 consecutive",
|
|
98449
|
+
"turns, so first try alternative approaches. Otherwise keep going — use the existing",
|
|
98450
|
+
"conversation context and your tools, and do not ask the user for input unless a real blocker",
|
|
98451
|
+
"prevents progress."
|
|
97989
98452
|
].join(" ");
|
|
97990
98453
|
const GOAL_CONTINUATION_ORIGIN = {
|
|
97991
98454
|
kind: "system_trigger",
|
|
97992
98455
|
name: "goal_continuation"
|
|
97993
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
|
+
};
|
|
97994
98462
|
var TurnFlow = class {
|
|
97995
98463
|
agent;
|
|
97996
98464
|
steerBuffer = [];
|
|
@@ -98155,6 +98623,14 @@ var TurnFlow = class {
|
|
|
98155
98623
|
}
|
|
98156
98624
|
}
|
|
98157
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
|
+
}
|
|
98158
98634
|
const end = await this.runOneTurn(turnId, turnInput, turnOrigin, signal, false);
|
|
98159
98635
|
if (end.event.reason === "cancelled") {
|
|
98160
98636
|
await this.agent.goal.pauseOnInterrupt({ reason: "Paused after interruption" });
|
|
@@ -98346,7 +98822,7 @@ var TurnFlow = class {
|
|
|
98346
98822
|
turnId: String(turnId),
|
|
98347
98823
|
signal,
|
|
98348
98824
|
llm: this.agent.llm,
|
|
98349
|
-
buildMessages: () => this.agent.context.
|
|
98825
|
+
buildMessages: () => this.agent.context.messagesForLLM(),
|
|
98350
98826
|
dispatchEvent: this.buildDispatchEvent(turnId),
|
|
98351
98827
|
tools: this.agent.tools.loopTools,
|
|
98352
98828
|
log: this.agent.log,
|
|
@@ -98875,6 +99351,7 @@ var LtodLLM = class {
|
|
|
98875
99351
|
this.obfuscator = config.obfuscator;
|
|
98876
99352
|
}
|
|
98877
99353
|
async chat(params) {
|
|
99354
|
+
const chatStartedAt = Date.now();
|
|
98878
99355
|
let requestStartedAt = Date.now();
|
|
98879
99356
|
let firstChunkAt;
|
|
98880
99357
|
let streamEndedAt;
|
|
@@ -98894,13 +99371,22 @@ var LtodLLM = class {
|
|
|
98894
99371
|
capability: this.capability
|
|
98895
99372
|
});
|
|
98896
99373
|
const outboundMessages = this.obfuscator && this.obfuscator.hasSecrets() ? obfuscateMessages(this.obfuscator, params.messages) : params.messages;
|
|
98897
|
-
const
|
|
99374
|
+
const runGenerate = (messages) => this.generate(effectiveProvider, this.systemPrompt, [...params.tools], messages, callbacks, generateOptions(params, {
|
|
98898
99375
|
onRequestStart: markRequestStart,
|
|
98899
99376
|
onStreamEnd: () => {
|
|
98900
99377
|
markStreamEnd();
|
|
98901
99378
|
flushPending();
|
|
98902
99379
|
}
|
|
98903
99380
|
}));
|
|
99381
|
+
let result;
|
|
99382
|
+
try {
|
|
99383
|
+
result = await runGenerate(outboundMessages);
|
|
99384
|
+
} catch (error) {
|
|
99385
|
+
if (!(error instanceof APIContextOverflowError)) throw error;
|
|
99386
|
+
const strippedMessages = stripMediaFromMessages(outboundMessages);
|
|
99387
|
+
if (strippedMessages === null) throw error;
|
|
99388
|
+
result = await runGenerate(strippedMessages);
|
|
99389
|
+
}
|
|
98904
99390
|
if (params.onTextPart !== void 0 || params.onThinkPart !== void 0) {
|
|
98905
99391
|
for (const part of result.message.content) if (part.type === "text" && params.onTextPart !== void 0) await params.onTextPart(part);
|
|
98906
99392
|
else if (part.type === "think" && params.onThinkPart !== void 0) await params.onThinkPart(part);
|
|
@@ -98910,18 +99396,21 @@ var LtodLLM = class {
|
|
|
98910
99396
|
providerFinishReason: result.finishReason ?? void 0,
|
|
98911
99397
|
rawFinishReason: result.rawFinishReason ?? void 0,
|
|
98912
99398
|
usage: result.usage ?? emptyUsage(),
|
|
98913
|
-
streamTiming: firstChunkAt === void 0 ? void 0 : buildStreamTiming(requestStartedAt, firstChunkAt, streamEndedAt)
|
|
99399
|
+
streamTiming: firstChunkAt === void 0 ? void 0 : buildStreamTiming(chatStartedAt, requestStartedAt, firstChunkAt, streamEndedAt)
|
|
98914
99400
|
};
|
|
98915
99401
|
}
|
|
98916
99402
|
isRetryableError(error) {
|
|
98917
99403
|
return isRetryableGenerateError(error);
|
|
98918
99404
|
}
|
|
98919
99405
|
};
|
|
98920
|
-
function buildStreamTiming(requestStartedAt, firstChunkAt, streamEndedAt) {
|
|
99406
|
+
function buildStreamTiming(chatStartedAt, requestStartedAt, firstChunkAt, streamEndedAt) {
|
|
98921
99407
|
const outputEndedAt = streamEndedAt ?? Date.now();
|
|
98922
99408
|
return {
|
|
98923
99409
|
firstTokenLatencyMs: Math.max(0, firstChunkAt - requestStartedAt),
|
|
98924
|
-
streamDurationMs: Math.max(0, outputEndedAt - firstChunkAt)
|
|
99410
|
+
streamDurationMs: Math.max(0, outputEndedAt - firstChunkAt),
|
|
99411
|
+
requestBuildMs: Math.max(0, requestStartedAt - chatStartedAt),
|
|
99412
|
+
serverFirstTokenMs: Math.max(0, firstChunkAt - requestStartedAt),
|
|
99413
|
+
serverDecodeMs: Math.max(0, outputEndedAt - firstChunkAt)
|
|
98925
99414
|
};
|
|
98926
99415
|
}
|
|
98927
99416
|
function generateOptions(params, hooks) {
|
|
@@ -99056,6 +99545,27 @@ function buildLtodCallbacks(params, markStreamOutput, obfuscator) {
|
|
|
99056
99545
|
}
|
|
99057
99546
|
};
|
|
99058
99547
|
}
|
|
99548
|
+
/**
|
|
99549
|
+
* Remove image parts from messages for context-overflow retry.
|
|
99550
|
+
* Returns null when no media was found (no point retrying without media).
|
|
99551
|
+
*/
|
|
99552
|
+
function stripMediaFromMessages(messages) {
|
|
99553
|
+
let hasMedia = false;
|
|
99554
|
+
const stripped = messages.map((msg) => {
|
|
99555
|
+
const newContent = msg.content.filter((part) => {
|
|
99556
|
+
if (part.type !== "text" && part.type !== "think") {
|
|
99557
|
+
hasMedia = true;
|
|
99558
|
+
return false;
|
|
99559
|
+
}
|
|
99560
|
+
return true;
|
|
99561
|
+
});
|
|
99562
|
+
return {
|
|
99563
|
+
...msg,
|
|
99564
|
+
content: newContent
|
|
99565
|
+
};
|
|
99566
|
+
});
|
|
99567
|
+
return hasMedia ? stripped : null;
|
|
99568
|
+
}
|
|
99059
99569
|
//#endregion
|
|
99060
99570
|
//#region ../../packages/agent-core/src/agent/usage/index.ts
|
|
99061
99571
|
function copyUsage(usage) {
|
|
@@ -99447,6 +99957,9 @@ var Agent = class {
|
|
|
99447
99957
|
if (status === "paused") return this.goal.pauseGoal({}, "user");
|
|
99448
99958
|
return this.goal.resumeGoal({}, "user");
|
|
99449
99959
|
},
|
|
99960
|
+
updateGoalObjective: async (payload) => {
|
|
99961
|
+
return this.goal.updateObjective({ objective: payload.objective }, "user");
|
|
99962
|
+
},
|
|
99450
99963
|
cancelGoal: async () => {
|
|
99451
99964
|
return this.goal.cancelGoal("user");
|
|
99452
99965
|
},
|
|
@@ -119503,6 +120016,9 @@ var SessionAPIImpl = class {
|
|
|
119503
120016
|
updateGoalStatus({ agentId, ...payload }) {
|
|
119504
120017
|
return this.getAgent(agentId).updateGoalStatus(payload);
|
|
119505
120018
|
}
|
|
120019
|
+
updateGoalObjective({ agentId, ...payload }) {
|
|
120020
|
+
return this.getAgent(agentId).updateGoalObjective(payload);
|
|
120021
|
+
}
|
|
119506
120022
|
cancelGoal({ agentId, ...payload }) {
|
|
119507
120023
|
return this.getAgent(agentId).cancelGoal(payload);
|
|
119508
120024
|
}
|
|
@@ -121245,6 +121761,9 @@ var ScreamCore = class {
|
|
|
121245
121761
|
updateGoalStatus({ sessionId, ...payload }) {
|
|
121246
121762
|
return this.sessionApi(sessionId).updateGoalStatus(payload);
|
|
121247
121763
|
}
|
|
121764
|
+
updateGoalObjective({ sessionId, ...payload }) {
|
|
121765
|
+
return this.sessionApi(sessionId).updateGoalObjective(payload);
|
|
121766
|
+
}
|
|
121248
121767
|
cancelGoal({ sessionId, ...payload }) {
|
|
121249
121768
|
return this.sessionApi(sessionId).cancelGoal(payload);
|
|
121250
121769
|
}
|
|
@@ -121698,6 +122217,13 @@ var SDKRpcClient = class {
|
|
|
121698
122217
|
status: input.status
|
|
121699
122218
|
});
|
|
121700
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
|
+
}
|
|
121701
122227
|
async cancelGoal(input) {
|
|
121702
122228
|
return (await this.getRpc()).cancelGoal({
|
|
121703
122229
|
sessionId: input.sessionId,
|
|
@@ -122458,6 +122984,13 @@ var Session = class {
|
|
|
122458
122984
|
status
|
|
122459
122985
|
});
|
|
122460
122986
|
}
|
|
122987
|
+
async updateGoalObjective(objective) {
|
|
122988
|
+
this.ensureOpen();
|
|
122989
|
+
return this.rpc.updateGoalObjective({
|
|
122990
|
+
sessionId: this.id,
|
|
122991
|
+
objective
|
|
122992
|
+
});
|
|
122993
|
+
}
|
|
122461
122994
|
async cancelGoal() {
|
|
122462
122995
|
this.ensureOpen();
|
|
122463
122996
|
return this.rpc.cancelGoal({ sessionId: this.id });
|
|
@@ -122917,7 +123450,7 @@ function optionalBuildString(value) {
|
|
|
122917
123450
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
122918
123451
|
}
|
|
122919
123452
|
const SCREAM_BUILD_INFO = {
|
|
122920
|
-
version: optionalBuildString("0.10.
|
|
123453
|
+
version: optionalBuildString("0.10.8"),
|
|
122921
123454
|
channel: optionalBuildString(""),
|
|
122922
123455
|
commit: optionalBuildString(""),
|
|
122923
123456
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -123910,6 +124443,7 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
123910
124443
|
name: "goal",
|
|
123911
124444
|
aliases: ["goaloff"],
|
|
123912
124445
|
description: "registry.goal_desc",
|
|
124446
|
+
argumentHint: "[objective]",
|
|
123913
124447
|
priority: 122,
|
|
123914
124448
|
availability: (args) => {
|
|
123915
124449
|
const trimmed = args.trim();
|
|
@@ -123920,6 +124454,7 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
123920
124454
|
name: "memory",
|
|
123921
124455
|
aliases: ["memo", "mem"],
|
|
123922
124456
|
description: "registry.memory_desc",
|
|
124457
|
+
argumentHint: "[query]",
|
|
123923
124458
|
priority: 120,
|
|
123924
124459
|
availability: "always"
|
|
123925
124460
|
},
|
|
@@ -123927,6 +124462,7 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
123927
124462
|
name: "knowledge",
|
|
123928
124463
|
aliases: ["know"],
|
|
123929
124464
|
description: "registry.knowledge_desc",
|
|
124465
|
+
argumentHint: "[query]",
|
|
123930
124466
|
priority: 119,
|
|
123931
124467
|
availability: "always"
|
|
123932
124468
|
},
|
|
@@ -123940,6 +124476,7 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
123940
124476
|
name: "model",
|
|
123941
124477
|
aliases: [],
|
|
123942
124478
|
description: "registry.model_desc",
|
|
124479
|
+
argumentHint: "[alias]",
|
|
123943
124480
|
priority: 120
|
|
123944
124481
|
},
|
|
123945
124482
|
{
|
|
@@ -126123,6 +126660,24 @@ function formatContextStatus(usage, tokens, maxTokens) {
|
|
|
126123
126660
|
});
|
|
126124
126661
|
return t("footer.context_short", { pct });
|
|
126125
126662
|
}
|
|
126663
|
+
/** Format goal wall-clock duration compactly: `3m`, `1m30s`, `45s`. */
|
|
126664
|
+
function formatGoalDuration(ms) {
|
|
126665
|
+
const totalSeconds = Math.floor(ms / 1e3);
|
|
126666
|
+
if (totalSeconds < 60) return `${totalSeconds}s`;
|
|
126667
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
126668
|
+
const seconds = totalSeconds % 60;
|
|
126669
|
+
return seconds > 0 ? `${minutes}m${seconds}s` : `${minutes}m`;
|
|
126670
|
+
}
|
|
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`;
|
|
126680
|
+
}
|
|
126126
126681
|
const CONTEXT_WARNING_PERCENT_THRESHOLD = 60;
|
|
126127
126682
|
const CONTEXT_ERROR_PERCENT_THRESHOLD = 90;
|
|
126128
126683
|
function pickContextColor(usage, colors) {
|
|
@@ -126148,7 +126703,7 @@ const SPINNER_FRAMES$1 = [
|
|
|
126148
126703
|
"◎",
|
|
126149
126704
|
"◉"
|
|
126150
126705
|
];
|
|
126151
|
-
const SPINNER_TICK_MS =
|
|
126706
|
+
const SPINNER_TICK_MS = 60;
|
|
126152
126707
|
function hexToRgb$2(hex) {
|
|
126153
126708
|
const v = parseInt(hex.slice(1), 16);
|
|
126154
126709
|
return [
|
|
@@ -126172,7 +126727,7 @@ function lerpGradient(t) {
|
|
|
126172
126727
|
}
|
|
126173
126728
|
function buildStatusLine(streamingPhase, streamingStartTime, reconnectAttempt) {
|
|
126174
126729
|
if (streamingPhase === "idle") return t("status.idle");
|
|
126175
|
-
if (reconnectAttempt > 0) return chalk.hex("#E85454").bold("◎") + " " + chalk.hex("#E85454")(`${t("status.reconnecting")} ${String(reconnectAttempt)}`);
|
|
126730
|
+
if (reconnectAttempt > 0 && streamingPhase === "waiting") return chalk.hex("#E85454").bold("◎") + " " + chalk.hex("#E85454")(`${t("status.reconnecting")} ${String(reconnectAttempt)}`);
|
|
126176
126731
|
let label;
|
|
126177
126732
|
if (streamingPhase === "tool") label = t("status.tool");
|
|
126178
126733
|
else if (streamingPhase === "waiting") label = t("status.waiting");
|
|
@@ -126217,15 +126772,17 @@ var FooterComponent = class {
|
|
|
126217
126772
|
this.onGitStatusChange = onGitStatusChange;
|
|
126218
126773
|
this.gitCacheWorkDir = state.workDir;
|
|
126219
126774
|
this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
|
|
126775
|
+
this.#restartStatusTimer(state.streamingPhase, state.goalActive);
|
|
126220
126776
|
}
|
|
126221
126777
|
setState(state) {
|
|
126222
126778
|
const previousPhase = this.state?.streamingPhase;
|
|
126779
|
+
const previousGoalActive = this.state?.goalActive;
|
|
126223
126780
|
if (state.workDir !== this.gitCacheWorkDir) {
|
|
126224
126781
|
this.gitCacheWorkDir = state.workDir;
|
|
126225
126782
|
this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
|
|
126226
126783
|
}
|
|
126227
126784
|
this.state = state;
|
|
126228
|
-
if (state.streamingPhase !== previousPhase) this.#restartStatusTimer(state.streamingPhase);
|
|
126785
|
+
if (state.streamingPhase !== previousPhase || state.goalActive !== previousGoalActive) this.#restartStatusTimer(state.streamingPhase, state.goalActive);
|
|
126229
126786
|
}
|
|
126230
126787
|
setColors(colors) {
|
|
126231
126788
|
this.colors = colors;
|
|
@@ -126253,10 +126810,10 @@ var FooterComponent = class {
|
|
|
126253
126810
|
dispose() {
|
|
126254
126811
|
this.#stopStatusTimer();
|
|
126255
126812
|
}
|
|
126256
|
-
#restartStatusTimer(phase) {
|
|
126813
|
+
#restartStatusTimer(phase, goalActive) {
|
|
126257
126814
|
this.#stopStatusTimer();
|
|
126258
|
-
if (phase === "idle") return;
|
|
126259
|
-
const intervalMs =
|
|
126815
|
+
if (phase === "idle" && !goalActive) return;
|
|
126816
|
+
const intervalMs = 1e3 / 60;
|
|
126260
126817
|
this.statusTimer = setInterval(() => {
|
|
126261
126818
|
this.ui.requestRender();
|
|
126262
126819
|
}, intervalMs);
|
|
@@ -126275,7 +126832,10 @@ var FooterComponent = class {
|
|
|
126275
126832
|
left.push(chalk.hex(isFusion ? colors.fusionPlanMode : colors.planMode).bold(isFusion ? t("badge.fusion") : t("badge.plan")));
|
|
126276
126833
|
}
|
|
126277
126834
|
if (state.wolfpackMode) left.push(chalk.hex(colors.wolfpackMode).bold(t("badge.wolfpack")));
|
|
126278
|
-
if (state.goalActive
|
|
126835
|
+
if (state.goalActive && state.goal) {
|
|
126836
|
+
const goalLabel = formatGoalBadge(state.goal);
|
|
126837
|
+
left.push(chalk.hex(colors.primary).bold(goalLabel));
|
|
126838
|
+
}
|
|
126279
126839
|
const model = shortenModel(modelDisplayName(state));
|
|
126280
126840
|
if (model) if (state.streamingPhase === "thinking") left.push(shimmerText(model, colors));
|
|
126281
126841
|
else left.push(chalk.hex(colors.textDim)(model));
|
|
@@ -126649,10 +127209,63 @@ function createThemeStyles(colors) {
|
|
|
126649
127209
|
//#endregion
|
|
126650
127210
|
//#region src/tui/theme/pi-tui-theme.ts
|
|
126651
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
|
+
}
|
|
126652
127264
|
function createMarkdownTheme(colors) {
|
|
126653
127265
|
const stripHash = (text) => text.replace(HEADING_HASH_PREFIX, "$1");
|
|
126654
127266
|
const muted = chalk.hex(colors.textMuted);
|
|
126655
127267
|
const border = chalk.hex(colors.border);
|
|
127268
|
+
const codeTheme = createCodeHighlightTheme(colors);
|
|
126656
127269
|
return {
|
|
126657
127270
|
heading: (text) => chalk.bold.hex(colors.text)(stripHash(text)),
|
|
126658
127271
|
link: (text) => chalk.hex(colors.mdLink)(text),
|
|
@@ -126674,7 +127287,8 @@ function createMarkdownTheme(colors) {
|
|
|
126674
127287
|
try {
|
|
126675
127288
|
return highlight(code, {
|
|
126676
127289
|
language,
|
|
126677
|
-
ignoreIllegals: true
|
|
127290
|
+
ignoreIllegals: true,
|
|
127291
|
+
theme: codeTheme
|
|
126678
127292
|
}).split("\n");
|
|
126679
127293
|
} catch {
|
|
126680
127294
|
return code.split("\n");
|
|
@@ -128103,6 +128717,10 @@ function buildEmptyGoalLines(colors) {
|
|
|
128103
128717
|
value(t("goalpanel.no_goal")),
|
|
128104
128718
|
"",
|
|
128105
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"))}`,
|
|
128106
128724
|
`${muted("/goal pause")} ${muted(t("goalpanel.pause_goal"))}`,
|
|
128107
128725
|
`${muted("/goal resume")} ${muted(t("goalpanel.resume_goal"))}`,
|
|
128108
128726
|
`${muted("/goaloff")} ${muted(t("goalpanel.cancel_goal"))}`
|
|
@@ -128165,6 +128783,19 @@ function parseGoalCommand(rawArgs) {
|
|
|
128165
128783
|
const tokens = args.split(/\s+/);
|
|
128166
128784
|
const first = tokens[0];
|
|
128167
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
|
+
}
|
|
128168
128799
|
let index = 0;
|
|
128169
128800
|
let replace = false;
|
|
128170
128801
|
if (tokens[index] === "replace") {
|
|
@@ -128194,6 +128825,9 @@ async function handleGoalCommand(host, args) {
|
|
|
128194
128825
|
case "status":
|
|
128195
128826
|
await showGoalStatus(host);
|
|
128196
128827
|
return;
|
|
128828
|
+
case "setup":
|
|
128829
|
+
await guidedGoalSetup(host);
|
|
128830
|
+
return;
|
|
128197
128831
|
case "pause":
|
|
128198
128832
|
await pauseGoal(host);
|
|
128199
128833
|
return;
|
|
@@ -128203,6 +128837,9 @@ async function handleGoalCommand(host, args) {
|
|
|
128203
128837
|
case "off":
|
|
128204
128838
|
await handleGoalOffCommand(host);
|
|
128205
128839
|
return;
|
|
128840
|
+
case "update":
|
|
128841
|
+
await updateGoalObjective(host, parsed);
|
|
128842
|
+
return;
|
|
128206
128843
|
case "create":
|
|
128207
128844
|
await createGoal(host, parsed);
|
|
128208
128845
|
return;
|
|
@@ -128220,8 +128857,51 @@ async function createGoal(host, parsed) {
|
|
|
128220
128857
|
}
|
|
128221
128858
|
await showGoalConfigWizard(host, session, parsed.objective, parsed.replace);
|
|
128222
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
|
+
}
|
|
128223
128903
|
async function showGoalConfigWizard(host, session, objective, replace) {
|
|
128224
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
128904
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-B_rgO4qn.mjs");
|
|
128225
128905
|
const turnInput = await promptNumber(host, TextInputDialogComponent, {
|
|
128226
128906
|
title: t("goal.wizard_title", { objective }),
|
|
128227
128907
|
subtitle: t("goal.budget_turns_hint"),
|
|
@@ -128291,6 +128971,27 @@ function promptNumber(host, TextInputDialogComponent, opts) {
|
|
|
128291
128971
|
host.mountEditorReplacement(dialog);
|
|
128292
128972
|
});
|
|
128293
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
|
+
}
|
|
128294
128995
|
async function pauseGoal(host) {
|
|
128295
128996
|
const session = host.session;
|
|
128296
128997
|
if (session === void 0) {
|
|
@@ -128331,6 +129032,24 @@ async function resumeGoal(host) {
|
|
|
128331
129032
|
host.showError(t("goal.resume_failed", { msg: message }));
|
|
128332
129033
|
}
|
|
128333
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
|
+
}
|
|
128334
129053
|
async function handleGoalOffCommand(host) {
|
|
128335
129054
|
const session = host.session;
|
|
128336
129055
|
if (session === void 0) {
|
|
@@ -129360,6 +130079,12 @@ function easeSpeedRatio(ratio) {
|
|
|
129360
130079
|
}
|
|
129361
130080
|
//#endregion
|
|
129362
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
|
+
}
|
|
129363
130088
|
var ThinkingComponent = class {
|
|
129364
130089
|
text;
|
|
129365
130090
|
color;
|
|
@@ -129398,7 +130123,7 @@ var ThinkingComponent = class {
|
|
|
129398
130123
|
this.textComponent.setText(this.styled(trimmed));
|
|
129399
130124
|
}
|
|
129400
130125
|
styled(text) {
|
|
129401
|
-
return chalk.hex(this.color).italic(text);
|
|
130126
|
+
return chalk.hex(this.color).italic(filterThinkingNoise(text));
|
|
129402
130127
|
}
|
|
129403
130128
|
finalize() {
|
|
129404
130129
|
if (this.mode === "finalized") return;
|
|
@@ -129575,6 +130300,47 @@ function makeDiffStyles(colors) {
|
|
|
129575
130300
|
meta: (s) => chalk.hex(colors.diffMeta)(s)
|
|
129576
130301
|
};
|
|
129577
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
|
+
}
|
|
129578
130344
|
/**
|
|
129579
130345
|
* Compute word-level diff between two single lines and highlight the changed
|
|
129580
130346
|
* words with `chalk.inverse()`. Only the first removed/added part has its
|
|
@@ -129681,6 +130447,8 @@ function computeDiffLines(oldText, newText, oldStart = 1, newStart = 1, isIncomp
|
|
|
129681
130447
|
}
|
|
129682
130448
|
function renderDiffLines(oldText, newText, path, colors, isIncomplete = false, oldStart, newStart, maxLines) {
|
|
129683
130449
|
const s = makeDiffStyles(colors);
|
|
130450
|
+
const lang = langFromPath(path);
|
|
130451
|
+
const doHighlight = !isIncomplete && lang !== void 0;
|
|
129684
130452
|
const changedLines = computeDiffLines(oldText, newText, oldStart ?? 1, newStart ?? 1, isIncomplete).filter((l) => l.kind !== "context");
|
|
129685
130453
|
const added = changedLines.filter((l) => l.kind === "add").length;
|
|
129686
130454
|
const removed = changedLines.filter((l) => l.kind === "delete").length;
|
|
@@ -129705,7 +130473,7 @@ function renderDiffLines(oldText, newText, path, colors, isIncomplete = false, o
|
|
|
129705
130473
|
const line = shown[i];
|
|
129706
130474
|
const marker = line.kind === "add" ? "+" : "-";
|
|
129707
130475
|
const color = line.kind === "add" ? s.add : s.del;
|
|
129708
|
-
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));
|
|
129709
130477
|
i += 1;
|
|
129710
130478
|
}
|
|
129711
130479
|
const hidden = changedLines.length - shown.length;
|
|
@@ -129756,11 +130524,11 @@ function buildClusters(diffLines, contextLines) {
|
|
|
129756
130524
|
removedCount: removed
|
|
129757
130525
|
};
|
|
129758
130526
|
}
|
|
129759
|
-
function formatDiffRow(line, s) {
|
|
130527
|
+
function formatDiffRow(line, s, doHighlight, lang) {
|
|
129760
130528
|
const gutter = s.gutter(String(line.lineNum).padStart(4) + " ");
|
|
129761
|
-
if (line.kind === "add") return gutter + s.add("+ " + line.code);
|
|
129762
|
-
if (line.kind === "delete") return gutter + s.del("- " + line.code);
|
|
129763
|
-
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);
|
|
129764
130532
|
}
|
|
129765
130533
|
/**
|
|
129766
130534
|
* Render a diff with surrounding context, eliding unchanged middle
|
|
@@ -129773,6 +130541,8 @@ function formatDiffRow(line, s) {
|
|
|
129773
130541
|
*/
|
|
129774
130542
|
function renderDiffLinesClustered(oldText, newText, path, colors, opts = {}) {
|
|
129775
130543
|
const s = makeDiffStyles(colors);
|
|
130544
|
+
const lang = langFromPath(path);
|
|
130545
|
+
const doHighlight = !(opts.isIncomplete ?? false) && lang !== void 0;
|
|
129776
130546
|
const contextLines = opts.contextLines ?? 3;
|
|
129777
130547
|
const maxLines = opts.maxLines;
|
|
129778
130548
|
const diffLines = computeDiffLines(oldText, newText, 1, 1, opts.isIncomplete ?? false);
|
|
@@ -129829,7 +130599,7 @@ function renderDiffLinesClustered(oldText, newText, path, colors, opts = {}) {
|
|
|
129829
130599
|
i += 2;
|
|
129830
130600
|
continue;
|
|
129831
130601
|
}
|
|
129832
|
-
output.push(formatDiffRow(line, s));
|
|
130602
|
+
output.push(formatDiffRow(line, s, doHighlight, lang));
|
|
129833
130603
|
body++;
|
|
129834
130604
|
if (line.kind !== "context") shownChanges++;
|
|
129835
130605
|
prevEnd = i;
|
|
@@ -138266,7 +139036,10 @@ var SessionEventHandler = class {
|
|
|
138266
139036
|
pendingApproval: null,
|
|
138267
139037
|
pendingQuestion: null
|
|
138268
139038
|
});
|
|
138269
|
-
this.host.setAppState({
|
|
139039
|
+
this.host.setAppState({
|
|
139040
|
+
streamingPhase: "waiting",
|
|
139041
|
+
reconnectAttempt: 0
|
|
139042
|
+
});
|
|
138270
139043
|
}
|
|
138271
139044
|
handleStepCompleted(event) {
|
|
138272
139045
|
this.host.streamingUI.flushNow();
|
|
@@ -138564,7 +139337,12 @@ var SessionEventHandler = class {
|
|
|
138564
139337
|
goalActive: false
|
|
138565
139338
|
});
|
|
138566
139339
|
else this.host.setAppState({
|
|
138567
|
-
goal:
|
|
139340
|
+
goal: {
|
|
139341
|
+
objective: snapshot.objective,
|
|
139342
|
+
turnsUsed: snapshot.turnsUsed ?? 0,
|
|
139343
|
+
wallClockMs: snapshot.wallClockMs ?? 0,
|
|
139344
|
+
wallClockBaseAt: Date.now()
|
|
139345
|
+
},
|
|
138568
139346
|
goalActive: snapshot.status === "active"
|
|
138569
139347
|
});
|
|
138570
139348
|
}
|
|
@@ -142383,9 +143161,10 @@ var FileMentionProvider = class {
|
|
|
142383
143161
|
const name = ac.value ?? ac.name ?? "";
|
|
142384
143162
|
const desc = ac.description ?? "";
|
|
142385
143163
|
const resolvedDesc = desc ? t(desc) : "";
|
|
143164
|
+
const aliases = cmd.aliases;
|
|
142386
143165
|
return {
|
|
142387
143166
|
value: name,
|
|
142388
|
-
label: `/${name}${resolvedDesc ? ` — ${resolvedDesc}` : ""}`
|
|
143167
|
+
label: `/${name}${aliases && aliases.length > 0 ? ` (${aliases.join(", ")})` : ""}${resolvedDesc ? ` — ${resolvedDesc}` : ""}`
|
|
142389
143168
|
};
|
|
142390
143169
|
});
|
|
142391
143170
|
this.inner = new CombinedAutocompleteProvider(slashCommands, workDir, fdPath);
|
|
@@ -142740,10 +143519,18 @@ var InputController = class InputController {
|
|
|
142740
143519
|
this.host = host;
|
|
142741
143520
|
}
|
|
142742
143521
|
setupAutocomplete() {
|
|
142743
|
-
const
|
|
143522
|
+
const visible = this.host.getSlashCommands().filter((cmd) => !cmd.name.startsWith("skill:"));
|
|
143523
|
+
const slashCommands = visible.map((cmd) => cmd);
|
|
142744
143524
|
const { state } = this.host;
|
|
142745
143525
|
const provider = new FileMentionProvider(slashCommands, state.appState.workDir, state.fdPath, state.gitLsFilesCache);
|
|
142746
143526
|
state.editor.setAutocompleteProvider(provider);
|
|
143527
|
+
const argumentHints = /* @__PURE__ */ new Map();
|
|
143528
|
+
for (const cmd of visible) {
|
|
143529
|
+
if (cmd.argumentHint === void 0) continue;
|
|
143530
|
+
argumentHints.set(cmd.name, cmd.argumentHint);
|
|
143531
|
+
for (const alias of cmd.aliases) argumentHints.set(alias, cmd.argumentHint);
|
|
143532
|
+
}
|
|
143533
|
+
state.editor.setArgumentHints(argumentHints);
|
|
142747
143534
|
state.editor.onFirstInput = () => {
|
|
142748
143535
|
this.host.stopWelcomeBreathing();
|
|
142749
143536
|
this.#permanentlyStopBreathing();
|
|
@@ -144628,7 +145415,12 @@ var SessionManager = class {
|
|
|
144628
145415
|
maxContextTokens: status.maxContextTokens,
|
|
144629
145416
|
contextUsage: status.contextUsage,
|
|
144630
145417
|
sessionTitle: session.summary?.title ?? null,
|
|
144631
|
-
goal: goal
|
|
145418
|
+
goal: goal ? {
|
|
145419
|
+
objective: goal.objective,
|
|
145420
|
+
turnsUsed: goal.turnsUsed ?? 0,
|
|
145421
|
+
wallClockMs: goal.wallClockMs ?? 0,
|
|
145422
|
+
wallClockBaseAt: Date.now()
|
|
145423
|
+
} : null,
|
|
144632
145424
|
goalActive: goal?.status === "active",
|
|
144633
145425
|
goalContinuationCount: 0
|
|
144634
145426
|
});
|