scream-code 0.10.8 → 0.10.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,7 +7,7 @@ import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } fr
|
|
|
7
7
|
import "./suppress-sqlite-warning-C2VB0doZ.mjs";
|
|
8
8
|
import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-BH9W5k24.mjs";
|
|
9
9
|
import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
|
|
10
|
-
import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-
|
|
10
|
+
import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-DRUaPOEQ.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";
|
|
@@ -52,7 +52,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
52
52
|
import { Command, Option } from "commander";
|
|
53
53
|
import { createInterface } from "node:readline/promises";
|
|
54
54
|
import chalk, { chalkStderr } from "chalk";
|
|
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
|
+
import { CombinedAutocompleteProvider, Container, Editor, Image, Input, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, getImageDimensions, isKeyRelease, matchesKey, setTightMode, sliceByColumn, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@liutod-scream/pi-tui";
|
|
56
56
|
import { highlight, supportsLanguage } from "cli-highlight";
|
|
57
57
|
import { diffWords } from "diff";
|
|
58
58
|
import { gt, valid } from "semver";
|
|
@@ -49870,6 +49870,7 @@ async function generate(provider, systemPrompt, tools, history, callbacks, optio
|
|
|
49870
49870
|
const hasText = message.content.some((p) => p.type === "text" && p.text.trim().length > 0);
|
|
49871
49871
|
const hasToolCalls = message.toolCalls.length > 0;
|
|
49872
49872
|
if (hasThink && !hasText && !hasToolCalls) throw new APIEmptyResponseError(`The API returned a response containing only thinking content without any text or tool calls. This usually indicates the stream was interrupted or the output token budget was exhausted during reasoning. Provider: ${provider.name}, model: ${provider.modelName}. If this persists, reduce reasoning effort or switch to a model with a larger output budget.`);
|
|
49873
|
+
if (!hasText && !hasToolCalls && message.content.some((p) => p.type === "text")) throw new APIEmptyResponseError(`The API returned a response containing only empty or whitespace text. This usually indicates a truncated or malformed stream. Provider: ${provider.name}, model: ${provider.modelName}.`);
|
|
49873
49874
|
if (callbacks?.onToolCall !== void 0) for (const toolCall of message.toolCalls) {
|
|
49874
49875
|
await throwIfAborted$1(options?.signal, stream);
|
|
49875
49876
|
await callbacks.onToolCall(toolCall);
|
|
@@ -50801,6 +50802,14 @@ function resolveGlobalLogPath(homeDir) {
|
|
|
50801
50802
|
//#endregion
|
|
50802
50803
|
//#region ../../packages/agent-core/src/utils/tokens.ts
|
|
50803
50804
|
/**
|
|
50805
|
+
* WeakMap cache for per-message token estimates. Messages are immutable once
|
|
50806
|
+
* settled (streaming `partial` messages never enter compaction paths), so the
|
|
50807
|
+
* cache is safe. Spread copies created by micro-compaction ({ ...msg, content })
|
|
50808
|
+
* are new objects → natural cache miss → re-estimated. Original objects retain
|
|
50809
|
+
* their cached count, avoiding O(n²) re-scans during compaction detection.
|
|
50810
|
+
*/
|
|
50811
|
+
const messageTokenCache = /* @__PURE__ */ new WeakMap();
|
|
50812
|
+
/**
|
|
50804
50813
|
* Estimate token count from text using a character-based heuristic.
|
|
50805
50814
|
* - ASCII (~4 chars per token)
|
|
50806
50815
|
* - CJK and other non-ASCII (~1 char per token)
|
|
@@ -50830,12 +50839,15 @@ function estimateTokensForTools(tools) {
|
|
|
50830
50839
|
return total;
|
|
50831
50840
|
}
|
|
50832
50841
|
function estimateTokensForMessage(message) {
|
|
50842
|
+
const cached = messageTokenCache.get(message);
|
|
50843
|
+
if (cached !== void 0) return cached;
|
|
50833
50844
|
let total = estimateTokens$1(message.role);
|
|
50834
50845
|
for (const part of message.content) total += estimateTokensForContentPart(part);
|
|
50835
50846
|
if (message.toolCalls !== void 0) for (const call of message.toolCalls) {
|
|
50836
50847
|
total += estimateTokens$1(call.name);
|
|
50837
50848
|
total += estimateTokens$1(JSON.stringify(call.arguments));
|
|
50838
50849
|
}
|
|
50850
|
+
messageTokenCache.set(message, total);
|
|
50839
50851
|
return total;
|
|
50840
50852
|
}
|
|
50841
50853
|
function estimateTokensForContentPart(part) {
|
|
@@ -54165,7 +54177,7 @@ function matchRuleSubjects(ruleArgs, subjects, matchesPositivePattern) {
|
|
|
54165
54177
|
}
|
|
54166
54178
|
//#endregion
|
|
54167
54179
|
//#region ../../packages/agent-core/src/tools/background/task-list.md
|
|
54168
|
-
var task_list_default = "List background tasks and their current status.\n\nUse this tool to discover which background tasks exist and where each one\nstands. It is the entry point for inspecting background work: it returns a\ntask ID, status, command, description, and PID for every task it reports,\nplus the exit code and stop reason for tasks that have already finished.\n\nGuidelines:\n\n- After a context compaction, or whenever you are unsure which background\n tasks are running or what their task IDs are, call this tool to\n re-enumerate them instead of guessing a task ID.\n-
|
|
54180
|
+
var task_list_default = "List background tasks and their current status.\n\nUse this tool to discover which background tasks exist and where each one\nstands. It is the entry point for inspecting background work: it returns a\ntask ID, status, command, description, and PID for every task it reports,\nplus the exit code and stop reason for tasks that have already finished.\n\nGuidelines:\n\n- After a context compaction, or whenever you are unsure which background\n tasks are running or what their task IDs are, call this tool to\n re-enumerate them instead of guessing a task ID.\n- Pass `active_only=false` only when you specifically need to see tasks that\n have already finished. With `active_only=false` the result may also include\n `lost` tasks — tasks left over from a previous process that can no longer be\n inspected or controlled; treat them as already terminated.\n- This tool only lists tasks; it does not return their output. Use it first\n to locate the task ID you need, then call `TaskOutput` with that ID to read\n the task's output and details.\n- This tool is read-only and does not change any state, so it is always safe\n to call, including in plan mode.\n";
|
|
54169
54181
|
//#endregion
|
|
54170
54182
|
//#region ../../packages/agent-core/src/tools/background/task-list.ts
|
|
54171
54183
|
/**
|
|
@@ -54739,7 +54751,7 @@ function oneShotJitteredNextCronRunMs(task, idealMs, config = DEFAULT_CRON_JITTE
|
|
|
54739
54751
|
}
|
|
54740
54752
|
//#endregion
|
|
54741
54753
|
//#region ../../packages/agent-core/src/tools/cron/cron-create.md
|
|
54742
|
-
var cron_create_default = "Schedule a prompt to be enqueued at a future time. Use for both recurring schedules and one-shot reminders.\n\
|
|
54754
|
+
var cron_create_default = "Schedule a prompt to be enqueued at a future time. Use for both recurring schedules and one-shot reminders.\n\n`0 9 * * *` means 9am local — no timezone conversion needed.\n\n## One-shot tasks (recurring: false)\n\nFor \"remind me at X\" or \"at <time>, do Y\" requests — fire once then auto-delete.\nPin minute/hour/day-of-month/month to specific values:\n \"remind me at 2:30pm today to check the deploy\" → cron: \"30 14 <today_dom> <today_month> *\", recurring: false\n \"tomorrow morning, run the smoke test\" → cron: \"57 8 <tomorrow_dom> <tomorrow_month> *\", recurring: false\n\n## Recurring jobs\n\nFor \"every N minutes\" / \"every hour\" / \"weekdays at 9am\" requests:\n \"*/5 * * * *\" (every 5 min), \"0 * * * *\" (hourly), \"0 9 * * 1-5\" (weekdays at 9am local)\n\n## Avoid the :00 and :30 minute marks when the task allows it\n\nEvery user who asks for \"9am\" gets `0 9`, and every user who asks for \"hourly\" gets `0 *` — which means requests from across the planet land on the API at the same instant. When the user's request is approximate, pick a minute that is NOT 0 or 30:\n \"every morning around 9\" → \"57 8 * * *\" or \"3 9 * * *\" (not \"0 9 * * *\")\n \"hourly\" → \"7 * * * *\" (not \"0 * * * *\")\n \"in an hour or so, remind me to...\" → pick whatever minute you land on, don't round\n\nOnly use minute 0 or 30 when the user names that exact time and clearly means it (\"at 9:00 sharp\", \"at half past\", coordinating with a meeting). When in doubt, nudge a few minutes early or late — the user will not notice, and the fleet will.\n\n## Coalesce semantics\n\nIf the scheduler slept past multiple ideal fire times (laptop closed, long-running turn, etc.), only **one** fire is delivered when it wakes up. The origin carries `coalescedCount` showing how many ideal fires were collapsed into this single delivery. You should treat `coalescedCount > 1` as \"I missed some checks; only the latest state matters\" rather than running the prompt that many times.\n\n## Cron-fire envelope\n\nWhen a cron task fires, the prompt you scheduled is re-injected wrapped in an XML envelope that exposes the fire context:\n\n```\n<cron-fire jobId=\"...\" cron=\"...\" recurring=\"true|false\" coalescedCount=\"N\" stale=\"true|false\">\n<prompt>\nyour original prompt text, verbatim\n</prompt>\n</cron-fire>\n```\n\nThe envelope is parseable. Use `coalescedCount > 1` to know multiple ideal fires were collapsed into a single delivery (treat as \"only the latest state matters\"), and `stale=\"true\"` as a cue that the task is past its 7-day threshold.\n\n## 7-day stale behavior\n\nRecurring tasks that have been alive for more than 7 days fire one\nfinal time with `stale: true` on the envelope, and the system then\nauto-deletes the task. The flag is the model's notice that this is\nthe last delivery. If the schedule is still wanted, call `CronCreate`\nagain with the same `cron` and `prompt` — that resets `createdAt` and\nstarts a fresh 7-day window. One-shot tasks are never marked stale.\n\nBench / acceptance runs can set `SCREAM_CRON_NO_STALE=1` to disable the\njudgment entirely.\n\n## Jitter behavior\n\nAnti-herd jitter is applied deterministically per task id:\n - Recurring: ideal fire time is shifted **forward** by an offset ≤ min(10% of the cron period, 15 minutes). A `*/5 * * * *` task can drift up to 30s; a `0 9 * * *` task can drift up to 15 minutes.\n - One-shot: only when the ideal fire lands on `:00` or `:30` of the hour, the fire is pulled **earlier** by ≤ 90 seconds. Other minutes pass through unchanged.\n\nBench / acceptance tests can set `SCREAM_CRON_NO_JITTER=1` to disable jitter entirely.\n\n## One-shot vs recurring — when to pick which\n\nUse one-shot for \"remind me at X\" style requests, single deadlines, and any task that should not repeat. Use recurring for periodic polling (CI status, build watchers, scheduled reports), workday rituals, and anything the user explicitly described as recurring.\n\n## Session lifetime\n\nCron tasks live in the current scream CLI session. When you exit, they\nare persisted under the session homedir; the next `scream resume` of the\nsame session reloads them and the scheduler resumes from each task's\n`createdAt`. Fire times that fell during the offline window are\ncollapsed into a single delivery via `coalescedCount` (and recurring\ntasks past their 7-day window arrive with `stale: true` as their final\ndelivery).\n\nTasks do **not** carry over into a brand-new session — they are scoped\nto the resumed session id, not to the working directory.\n\n## Returned fields\n\n`id` (8-hex), `humanSchedule` (English summary), `recurring`,\n`nextFireAt` (ISO timestamp or null). `id` is needed by `CronDelete`.\n";
|
|
54743
54755
|
/**
|
|
54744
54756
|
* Hard ceiling on `prompt` byte length (UTF-8). The zod `.max(...)`
|
|
54745
54757
|
* upstream is in code units, which underflows multi-byte input
|
|
@@ -54860,7 +54872,7 @@ function formatOutput(o) {
|
|
|
54860
54872
|
}
|
|
54861
54873
|
//#endregion
|
|
54862
54874
|
//#region ../../packages/agent-core/src/tools/cron/cron-delete.md
|
|
54863
|
-
var cron_delete_default = "Cancel a scheduled cron job by id.\n\nUse this tool to remove a cron task previously scheduled with\n`CronCreate`.
|
|
54875
|
+
var cron_delete_default = "Cancel a scheduled cron job by id.\n\nUse this tool to remove a cron task previously scheduled with\n`CronCreate`. Quote the id verbatim, no prefix.\n\nBehaviour by task kind:\n\n- **Recurring task** (`recurring: true`): stops all future fires\n immediately. The scheduler picks up the deletion on its next tick.\n- **One-shot task** (`recurring: false`): cancels the pending fire if\n it has not happened yet. One-shots that have already fired\n auto-delete themselves, so calling `CronDelete` on a fired one-shot\n returns \"no cron job with id ...\".\n\nNot-found is reported as an error (not a silent no-op) so you can\ncorrect yourself — typically by calling `CronList` to see which ids\nare actually live, rather than re-trying with the same stale id.\n\nRefresh pattern (use when you want a stale recurring schedule to\ncontinue):\n\nStale recurring tasks are auto-deleted by the system after their final\nfire — there is nothing for `CronDelete` to remove at that point. To\nkeep the schedule running, just call `CronCreate` with the same `cron`\nand `prompt`. Use `CronList`'s `prompt` field to recall the original\ntext after a context compaction.\n\n`CronDelete` remains the right call when you want to cancel a task\nthat is still live (recurring not yet stale, or a one-shot still\npending).\n\nGuidelines:\n\n- Cron deletion is irreversible — there is no undo. If you delete the\n wrong task, you must re-create it with `CronCreate`.\n- If the model is unsure which id is current (e.g. after a context\n compaction), call `CronList` first rather than guessing.\n";
|
|
54864
54876
|
//#endregion
|
|
54865
54877
|
//#region ../../packages/agent-core/src/tools/cron/cron-delete.ts
|
|
54866
54878
|
/**
|
|
@@ -55471,7 +55483,7 @@ function filterSubagentsBySpawns$1(subagents, allowedSpawns) {
|
|
|
55471
55483
|
}
|
|
55472
55484
|
//#endregion
|
|
55473
55485
|
//#region ../../packages/agent-core/src/tools/builtin/collaboration/ask-user.md
|
|
55474
|
-
var ask_user_default = "Use this tool when you need to ask the user questions with structured options during execution. This allows you to:\n1. Collect user preferences or requirements before proceeding\n2. Resolve ambiguous or underspecified instructions\n3. Let the user decide between implementation approaches as you work\n4. Present concrete options when multiple valid directions exist\n\n**When NOT to use:**\n- When you can infer the answer from context
|
|
55486
|
+
var ask_user_default = "Use this tool when you need to ask the user questions with structured options during execution. This allows you to:\n1. Collect user preferences or requirements before proceeding\n2. Resolve ambiguous or underspecified instructions\n3. Let the user decide between implementation approaches as you work\n4. Present concrete options when multiple valid directions exist\n\n**When NOT to use:**\n- When you can infer the answer from context - be decisive and proceed\n- Trivial decisions that don't materially affect the outcome\n\nOverusing this tool interrupts the user's flow. Only use it when the user's input genuinely changes your next action.\n\n**Usage notes:**\n- Group related questions to minimize interruptions.\n- If you recommend a specific option, list it first.\n";
|
|
55475
55487
|
//#endregion
|
|
55476
55488
|
//#region ../../packages/agent-core/src/tools/builtin/collaboration/ask-user.ts
|
|
55477
55489
|
/**
|
|
@@ -55719,7 +55731,7 @@ const SetGoalBudgetToolInputSchema = z.object({
|
|
|
55719
55731
|
var SetGoalBudgetTool = class {
|
|
55720
55732
|
agent;
|
|
55721
55733
|
name = "SetGoalBudget";
|
|
55722
|
-
description = "Set a hard budget limit for the current goal.
|
|
55734
|
+
description = "Set a hard budget limit for the current goal. The goal will be blocked when the budget is reached.";
|
|
55723
55735
|
parameters = toInputJsonSchema(SetGoalBudgetToolInputSchema);
|
|
55724
55736
|
constructor(agent) {
|
|
55725
55737
|
this.agent = agent;
|
|
@@ -56223,6 +56235,85 @@ function formatTokens$2(tokens) {
|
|
|
56223
56235
|
return `${(tokens / 1e6).toFixed(1)}M`;
|
|
56224
56236
|
}
|
|
56225
56237
|
//#endregion
|
|
56238
|
+
//#region ../../packages/agent-core/src/tools/builtin/goal/emission-guard.ts
|
|
56239
|
+
/**
|
|
56240
|
+
* Grader emission guard - deduplicates and filters grader (referee) feedback
|
|
56241
|
+
* before it is injected into the agent context.
|
|
56242
|
+
*
|
|
56243
|
+
* Without this guard, a grader that repeatedly returns the same FAIL reason
|
|
56244
|
+
* (e.g. "redo", "not right") causes the identical feedback to be appended to
|
|
56245
|
+
* context on every submission. This wastes context budget and can trap the
|
|
56246
|
+
* agent in a loop where it keeps retrying without new information.
|
|
56247
|
+
*
|
|
56248
|
+
* Inspired by oh-my-pi's `advisor/emission-guard.ts`, but adapted for
|
|
56249
|
+
* scream-code's grader model: the grader is called once per `complete`
|
|
56250
|
+
* submission (not continuously), so per-round limiting is unnecessary. The
|
|
56251
|
+
* guard focuses on normalization, content-free filtering, and exact-text
|
|
56252
|
+
* deduplication keyed by goal objective.
|
|
56253
|
+
*/
|
|
56254
|
+
/** Short phrases that carry no actionable feedback. */
|
|
56255
|
+
const CONTENT_FREE_PHRASES = new Set([
|
|
56256
|
+
"stop",
|
|
56257
|
+
"done",
|
|
56258
|
+
"lgtm",
|
|
56259
|
+
"no issue",
|
|
56260
|
+
"redo",
|
|
56261
|
+
"try again",
|
|
56262
|
+
"not good enough",
|
|
56263
|
+
"fix it",
|
|
56264
|
+
"wrong",
|
|
56265
|
+
"incorrect",
|
|
56266
|
+
"bad",
|
|
56267
|
+
"重做",
|
|
56268
|
+
"不对",
|
|
56269
|
+
"不行",
|
|
56270
|
+
"继续",
|
|
56271
|
+
"再做",
|
|
56272
|
+
"错误",
|
|
56273
|
+
"不行",
|
|
56274
|
+
"不好",
|
|
56275
|
+
"改"
|
|
56276
|
+
]);
|
|
56277
|
+
/** Maximum number of historically seen feedback texts retained per goal. */
|
|
56278
|
+
const MAX_SEEN_PER_GOAL = 16;
|
|
56279
|
+
/** Phrases at or below this length are checked against the content-free set. */
|
|
56280
|
+
function isContentFree(text) {
|
|
56281
|
+
const lower = text.toLowerCase().trim();
|
|
56282
|
+
return CONTENT_FREE_PHRASES.has(lower);
|
|
56283
|
+
}
|
|
56284
|
+
/**
|
|
56285
|
+
* Filters grader feedback before injection into the agent context.
|
|
56286
|
+
*
|
|
56287
|
+
* Returns the denoised feedback text if it should be injected, or `null` if
|
|
56288
|
+
* the feedback is empty, content-free, or an exact duplicate of a previously
|
|
56289
|
+
* seen feedback for the same goal. The caller should skip injection when
|
|
56290
|
+
* `null` is returned.
|
|
56291
|
+
*
|
|
56292
|
+
* @param reason The raw grader FAIL reason.
|
|
56293
|
+
* @param goalKey A stable identifier for the current goal (e.g. the objective
|
|
56294
|
+
* text). Different goals get independent deduplication buckets.
|
|
56295
|
+
*/
|
|
56296
|
+
var GraderEmissionGuard = class {
|
|
56297
|
+
seenByGoal = /* @__PURE__ */ new Map();
|
|
56298
|
+
filter(reason, goalKey) {
|
|
56299
|
+
const normalized = reason.normalize("NFKC").trim();
|
|
56300
|
+
if (normalized.length === 0) return null;
|
|
56301
|
+
if (isContentFree(normalized)) return null;
|
|
56302
|
+
const key = normalized.toLowerCase();
|
|
56303
|
+
const bucket = this.seenByGoal.get(goalKey);
|
|
56304
|
+
if (bucket !== void 0 && bucket.includes(key)) return null;
|
|
56305
|
+
const next = bucket ?? [];
|
|
56306
|
+
next.push(key);
|
|
56307
|
+
if (next.length > MAX_SEEN_PER_GOAL) next.shift();
|
|
56308
|
+
this.seenByGoal.set(goalKey, next);
|
|
56309
|
+
return normalized;
|
|
56310
|
+
}
|
|
56311
|
+
/** Clear the deduplication bucket for a goal (e.g. on goal change). */
|
|
56312
|
+
resetGoal(goalKey) {
|
|
56313
|
+
this.seenByGoal.delete(goalKey);
|
|
56314
|
+
}
|
|
56315
|
+
};
|
|
56316
|
+
//#endregion
|
|
56226
56317
|
//#region ../../packages/agent-core/src/tools/builtin/goal/update-goal.ts
|
|
56227
56318
|
const UpdateGoalToolInputSchema = z.object({
|
|
56228
56319
|
status: z.enum([
|
|
@@ -56236,6 +56327,11 @@ const UpdateGoalToolInputSchema = z.object({
|
|
|
56236
56327
|
const MAX_GRADER_OUTPUT_CHARS = 4e3;
|
|
56237
56328
|
/** Maximum characters of `git diff --stat HEAD` to append to the grader input. */
|
|
56238
56329
|
const MAX_DIFF_STAT_CHARS = 2e3;
|
|
56330
|
+
/**
|
|
56331
|
+
* Module-level emission guard. Deduplicates grader FAIL feedback per goal so
|
|
56332
|
+
* the referee cannot trap the agent in a loop by repeating the same reason.
|
|
56333
|
+
*/
|
|
56334
|
+
const graderEmissionGuard = new GraderEmissionGuard();
|
|
56239
56335
|
function extractRecentOutput(history) {
|
|
56240
56336
|
const parts = [];
|
|
56241
56337
|
for (let i = history.length - 1; i >= 0; i--) {
|
|
@@ -56372,6 +56468,7 @@ var UpdateGoalTool = class {
|
|
|
56372
56468
|
if (grade.pass) {
|
|
56373
56469
|
try {
|
|
56374
56470
|
const completed = await goal.markComplete({}, "model");
|
|
56471
|
+
graderEmissionGuard.resetGoal(goalState.objective);
|
|
56375
56472
|
if (completed === null) return toolError("Failed to mark verified goal complete", goal);
|
|
56376
56473
|
this.agent.context.appendSystemReminder(buildGoalCompletionSummaryPrompt(completed), {
|
|
56377
56474
|
kind: "system_trigger",
|
|
@@ -56385,8 +56482,12 @@ var UpdateGoalTool = class {
|
|
|
56385
56482
|
stopTurn: true
|
|
56386
56483
|
};
|
|
56387
56484
|
}
|
|
56388
|
-
|
|
56389
|
-
|
|
56485
|
+
const denoised = graderEmissionGuard.filter(grade.reason, goalState.objective);
|
|
56486
|
+
if (denoised !== null) {
|
|
56487
|
+
this.appendGradingFeedback(denoised);
|
|
56488
|
+
return { output: `Verification failed: ${denoised}. Continue working.` };
|
|
56489
|
+
}
|
|
56490
|
+
return { output: "Previous verification feedback still applies. Address the earlier feedback and retry." };
|
|
56390
56491
|
}
|
|
56391
56492
|
appendGradingFeedback(reason) {
|
|
56392
56493
|
this.agent.context.appendSystemReminder(buildGradingFeedbackPrompt(reason), {
|
|
@@ -56427,7 +56528,7 @@ const WriteGoalNoteInputSchema = z.object({ content: z.string().min(1).max(400).
|
|
|
56427
56528
|
var WriteGoalNoteTool = class {
|
|
56428
56529
|
agent;
|
|
56429
56530
|
name = "WriteGoalNote";
|
|
56430
|
-
description = "Record a working note during goal execution.
|
|
56531
|
+
description = "Record a working note during goal execution. Use this for dead ends you hit, decisions you made, or anything future-you should not re-derive.";
|
|
56431
56532
|
parameters = toInputJsonSchema(WriteGoalNoteInputSchema);
|
|
56432
56533
|
constructor(agent) {
|
|
56433
56534
|
this.agent = agent;
|
|
@@ -58303,7 +58404,7 @@ const MemoryLookupInputSchema = z.object({
|
|
|
58303
58404
|
var MemoryLookupTool = class {
|
|
58304
58405
|
agent;
|
|
58305
58406
|
name = "MemoryLookup";
|
|
58306
|
-
description = "Search the memory memo store for historical experiences from past user tasks. Call this when the current task may benefit from prior work, when you encounter a repeating error or pattern, or when you are unsure of the best approach. Returns memos ranked by relevance, including the approach taken, the outcome, what failed, what worked, project, and tags.
|
|
58407
|
+
description = "Search the memory memo store for historical experiences from past user tasks. Call this when the current task may benefit from prior work, when you encounter a repeating error or pattern, or when you are unsure of the best approach. Returns memos ranked by relevance, including the approach taken, the outcome, what failed, what worked, project, and tags.";
|
|
58307
58408
|
parameters = toInputJsonSchema(MemoryLookupInputSchema);
|
|
58308
58409
|
constructor(agent) {
|
|
58309
58410
|
this.agent = agent;
|
|
@@ -58405,7 +58506,7 @@ const MemoryWriteInputSchema = z.object({
|
|
|
58405
58506
|
var MemoryWriteTool = class {
|
|
58406
58507
|
agent;
|
|
58407
58508
|
name = "MemoryWrite";
|
|
58408
|
-
description = "Write a new memory memo to the global memory memo store. Call this when the user explicitly asks to save an experience, lesson, or summary to memory, for example \"保存到记忆\", \"保存到备忘录\", \"总结并保存\", \"永久记忆\", \"记录我的记忆\", \"记住这个\", \"添加到记忆\", or \"存入记忆库\".
|
|
58509
|
+
description = "Write a new memory memo to the global memory memo store. Call this when the user explicitly asks to save an experience, lesson, or summary to memory, for example \"保存到记忆\", \"保存到备忘录\", \"总结并保存\", \"永久记忆\", \"记录我的记忆\", \"记住这个\", \"添加到记忆\", or \"存入记忆库\".";
|
|
58409
58510
|
parameters = toInputJsonSchema(MemoryWriteInputSchema);
|
|
58410
58511
|
constructor(agent) {
|
|
58411
58512
|
this.agent = agent;
|
|
@@ -58971,8 +59072,7 @@ var LspTool = class {
|
|
|
58971
59072
|
lspRegistry;
|
|
58972
59073
|
name = "LSP";
|
|
58973
59074
|
description = [
|
|
58974
|
-
"Query a language server for code intelligence.",
|
|
58975
|
-
"Use 'references' to find all usages of a symbol, 'definition' to jump to where a symbol is defined, 'diagnostics' to get type errors and warnings for a file, and 'rename' to rename a symbol across all its references.",
|
|
59075
|
+
"Query a language server for code intelligence: find usages, jump to definitions, get diagnostics, or rename a symbol across all references.",
|
|
58976
59076
|
"The language server is started automatically for supported file types (TypeScript/JavaScript, Python, Rust, Go).",
|
|
58977
59077
|
"Rename requires the typescript-language-server (or equivalent) binary on PATH for the file type."
|
|
58978
59078
|
].join(" ");
|
|
@@ -62802,20 +62902,20 @@ function isRecord$6(value) {
|
|
|
62802
62902
|
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";
|
|
62803
62903
|
//#endregion
|
|
62804
62904
|
//#region ../../packages/agent-core/src/skill/builtin/dream.ts
|
|
62805
|
-
const PSEUDO_PATH$
|
|
62806
|
-
const parsed$
|
|
62905
|
+
const PSEUDO_PATH$1 = "builtin://dream";
|
|
62906
|
+
const parsed$1 = parseSkillText({
|
|
62807
62907
|
skillMdPath: "/builtin/skills/dream.md",
|
|
62808
62908
|
skillDirName: "dream",
|
|
62809
62909
|
source: "builtin",
|
|
62810
62910
|
text: dream_default
|
|
62811
62911
|
});
|
|
62812
62912
|
const DREAM_SKILL = {
|
|
62813
|
-
...parsed$
|
|
62814
|
-
path: PSEUDO_PATH$
|
|
62815
|
-
dir: PSEUDO_PATH$
|
|
62913
|
+
...parsed$1,
|
|
62914
|
+
path: PSEUDO_PATH$1,
|
|
62915
|
+
dir: PSEUDO_PATH$1,
|
|
62816
62916
|
metadata: {
|
|
62817
|
-
...parsed$
|
|
62818
|
-
type: parsed$
|
|
62917
|
+
...parsed$1.metadata,
|
|
62918
|
+
type: parsed$1.metadata.type ?? "inline",
|
|
62819
62919
|
disableModelInvocation: true
|
|
62820
62920
|
}
|
|
62821
62921
|
};
|
|
@@ -62824,36 +62924,14 @@ const DREAM_SKILL = {
|
|
|
62824
62924
|
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";
|
|
62825
62925
|
//#endregion
|
|
62826
62926
|
//#region ../../packages/agent-core/src/skill/builtin/make-skill.ts
|
|
62827
|
-
const PSEUDO_PATH
|
|
62828
|
-
const parsed
|
|
62927
|
+
const PSEUDO_PATH = "builtin://make-skill";
|
|
62928
|
+
const parsed = parseSkillText({
|
|
62829
62929
|
skillMdPath: "/builtin/skills/make-skill.md",
|
|
62830
62930
|
skillDirName: "make-skill",
|
|
62831
62931
|
source: "builtin",
|
|
62832
62932
|
text: make_skill_default
|
|
62833
62933
|
});
|
|
62834
62934
|
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 = {
|
|
62857
62935
|
...parsed,
|
|
62858
62936
|
path: PSEUDO_PATH,
|
|
62859
62937
|
dir: PSEUDO_PATH,
|
|
@@ -62868,7 +62946,6 @@ const TOOL_PROMPT_OPTIMIZATION_SKILL = {
|
|
|
62868
62946
|
function registerBuiltinSkills(registry) {
|
|
62869
62947
|
registry.registerBuiltinSkill(DREAM_SKILL);
|
|
62870
62948
|
registry.registerBuiltinSkill(MAKE_SKILL_SKILL);
|
|
62871
|
-
registry.registerBuiltinSkill(TOOL_PROMPT_OPTIMIZATION_SKILL);
|
|
62872
62949
|
}
|
|
62873
62950
|
//#endregion
|
|
62874
62951
|
//#region ../../packages/agent-core/src/skill/scanner.ts
|
|
@@ -71274,7 +71351,7 @@ function validateSkillPlan(plan, nameHint) {
|
|
|
71274
71351
|
}
|
|
71275
71352
|
//#endregion
|
|
71276
71353
|
//#region ../../packages/agent-core/src/tools/builtin/collaboration/wolfpack.md
|
|
71277
|
-
var wolfpack_default = "Use WolfPack to spawn multiple subagents in parallel for batch operations.\nThis is ideal when processing many independent items (files, checks, searches)\nthat all use the same subagent type and follow a similar pattern.\n\
|
|
71354
|
+
var wolfpack_default = "Use WolfPack to spawn multiple subagents in parallel for batch operations.\nThis is ideal when processing many independent items (files, checks, searches)\nthat all use the same subagent type and follow a similar pattern.\n\nItems must be independent - no subagent depends on another's output.\nIf items depend on each other, use separate Agent calls instead.\n\nChoosing subagent_type for the batch:\n- Batch code review, audit, or bug-finding across files -> reviewer\n- Batch writing, reports, or long-form content -> writer\n- Batch read-only exploration (find files, grep, understand modules) -> explore\n- Batch verification (run build/test/lint per item) -> verify\n- Batch deep debugging or architecture decisions -> oracle\n- Batch planning or design work -> plan\n- General engineering tasks with no specialised match -> coder (default)\n\nExample: review source files for OWASP vulnerabilities by setting items to the file\npaths, subagent_type to \"reviewer\", and prompt_template to the review instruction.\nAll items are processed in parallel.\n";
|
|
71278
71355
|
//#endregion
|
|
71279
71356
|
//#region ../../packages/agent-core/src/tools/builtin/collaboration/wolfpack.ts
|
|
71280
71357
|
/**
|
|
@@ -71472,6 +71549,68 @@ function withTimeout$1(promise, timeoutMs, parentSignal) {
|
|
|
71472
71549
|
});
|
|
71473
71550
|
}
|
|
71474
71551
|
//#endregion
|
|
71552
|
+
//#region ../../packages/agent-core/src/tools/support/scan-cache.ts
|
|
71553
|
+
var FsScanCache = class {
|
|
71554
|
+
cache = /* @__PURE__ */ new Map();
|
|
71555
|
+
ttlMs;
|
|
71556
|
+
maxEntries;
|
|
71557
|
+
constructor({ ttlMs = 1e3, maxEntries = 16 } = {}) {
|
|
71558
|
+
this.ttlMs = ttlMs;
|
|
71559
|
+
this.maxEntries = maxEntries;
|
|
71560
|
+
}
|
|
71561
|
+
key(root, pattern, includeDirs) {
|
|
71562
|
+
return `${root}\0${pattern}\0${String(includeDirs)}`;
|
|
71563
|
+
}
|
|
71564
|
+
/**
|
|
71565
|
+
* Returns the cached output if present and not expired. Expired entries are
|
|
71566
|
+
* deleted on access.
|
|
71567
|
+
*/
|
|
71568
|
+
get(root, pattern, includeDirs) {
|
|
71569
|
+
const k = this.key(root, pattern, includeDirs);
|
|
71570
|
+
const entry = this.cache.get(k);
|
|
71571
|
+
if (entry === void 0) return void 0;
|
|
71572
|
+
if (Date.now() - entry.createdAt > this.ttlMs) {
|
|
71573
|
+
this.cache.delete(k);
|
|
71574
|
+
return;
|
|
71575
|
+
}
|
|
71576
|
+
return entry.output;
|
|
71577
|
+
}
|
|
71578
|
+
/** Cache a scan output. Evicts the oldest entry when at capacity. */
|
|
71579
|
+
set(root, pattern, includeDirs, output) {
|
|
71580
|
+
if (this.cache.size >= this.maxEntries) {
|
|
71581
|
+
let oldestKey;
|
|
71582
|
+
let oldestTime = Infinity;
|
|
71583
|
+
for (const [k, v] of this.cache) if (v.createdAt < oldestTime) {
|
|
71584
|
+
oldestTime = v.createdAt;
|
|
71585
|
+
oldestKey = k;
|
|
71586
|
+
}
|
|
71587
|
+
if (oldestKey !== void 0) this.cache.delete(oldestKey);
|
|
71588
|
+
}
|
|
71589
|
+
this.cache.set(this.key(root, pattern, includeDirs), {
|
|
71590
|
+
output,
|
|
71591
|
+
createdAt: Date.now()
|
|
71592
|
+
});
|
|
71593
|
+
}
|
|
71594
|
+
/** Invalidate all cached entries rooted under the given directory path. */
|
|
71595
|
+
invalidateByRoot(root) {
|
|
71596
|
+
const prefix = `${root}\0`;
|
|
71597
|
+
for (const k of this.cache.keys()) if (k.startsWith(prefix)) this.cache.delete(k);
|
|
71598
|
+
}
|
|
71599
|
+
/** Clear the entire cache (useful for tests). */
|
|
71600
|
+
clear() {
|
|
71601
|
+
this.cache.clear();
|
|
71602
|
+
}
|
|
71603
|
+
/** Number of cached entries (for tests / introspection). */
|
|
71604
|
+
size() {
|
|
71605
|
+
return this.cache.size;
|
|
71606
|
+
}
|
|
71607
|
+
};
|
|
71608
|
+
/**
|
|
71609
|
+
* Module-level singleton shared by Glob (producer) and Edit/Write (invalidator).
|
|
71610
|
+
* Importing this instance ensures all tools reference the same cache.
|
|
71611
|
+
*/
|
|
71612
|
+
const scanCache = new FsScanCache();
|
|
71613
|
+
//#endregion
|
|
71475
71614
|
//#region ../../packages/agent-core/src/tools/builtin/file/conflict-detect.ts
|
|
71476
71615
|
/**
|
|
71477
71616
|
* Detect unresolved git merge conflict markers in read output.
|
|
@@ -71718,7 +71857,7 @@ function hashEditPayload(input) {
|
|
|
71718
71857
|
}
|
|
71719
71858
|
//#endregion
|
|
71720
71859
|
//#region ../../packages/agent-core/src/tools/builtin/file/edit.md
|
|
71721
|
-
var edit_default = "Perform exact string replacements against the text view returned by Read.\n\n-
|
|
71860
|
+
var edit_default = "Perform exact string replacements against the text view returned by Read.\n\n- By default, old_string must occur exactly once. If it matches multiple locations, add surrounding context.\n- Prefer Edit for targeted changes to existing files; use Write only for new files or complete overwrites.\n- To modify a file, always use Edit; do not run a Shell `sed` command for edits.\n- When making several independent changes, issue multiple Edit calls in parallel within a single response; edits to the same file are serialized automatically by a write lock.\n- When several parallel Edit calls target the same file, a write lock serializes them; they apply in the order the calls appear in your response. An edit fails with `old_string not found` if its old_string was taken from text an earlier edit already replaced - base every old_string on the latest Read view and order dependent edits accordingly.\n- Edit refuses if `old_string` lands inside an unresolved merge conflict block, or if `new_string` would introduce `<<<<<<<`/`=======`/`>>>>>>>` markers. To clean up a conflict, include the markers in `old_string` so Edit replaces them.\n";
|
|
71722
71861
|
//#endregion
|
|
71723
71862
|
//#region ../../packages/agent-core/src/tools/builtin/file/edit.ts
|
|
71724
71863
|
const EditInputSchema = z.object({
|
|
@@ -71767,10 +71906,11 @@ var EditTool = class {
|
|
|
71767
71906
|
pathClass: this.jian.pathClass(),
|
|
71768
71907
|
homeDir: this.jian.gethome()
|
|
71769
71908
|
}),
|
|
71770
|
-
execute: () => this.execution(args, path)
|
|
71909
|
+
execute: ({ signal }) => this.execution(args, path, signal)
|
|
71771
71910
|
};
|
|
71772
71911
|
}
|
|
71773
|
-
async execution(args, safePath) {
|
|
71912
|
+
async execution(args, safePath, signal) {
|
|
71913
|
+
signal?.throwIfAborted();
|
|
71774
71914
|
const result = await this.executionCore(args, safePath);
|
|
71775
71915
|
const inputHash = hashEditPayload({
|
|
71776
71916
|
path: args.path,
|
|
@@ -71867,6 +72007,7 @@ var EditTool = class {
|
|
|
71867
72007
|
}
|
|
71868
72008
|
const newContent = replaceOnceLiteral(content, args.old_string, args.new_string);
|
|
71869
72009
|
await this.jian.writeText(safePath, materializeModelText(newContent, modelView.lineEndingStyle));
|
|
72010
|
+
scanCache.clear();
|
|
71870
72011
|
const { notice, hasErrors } = await this.appendDiagnostics(safePath);
|
|
71871
72012
|
const output = `Replaced 1 occurrence in ${args.path}${notice}`;
|
|
71872
72013
|
return hasErrors ? {
|
|
@@ -71885,6 +72026,7 @@ var EditTool = class {
|
|
|
71885
72026
|
}
|
|
71886
72027
|
const newContent = parts.join(args.new_string);
|
|
71887
72028
|
await this.jian.writeText(safePath, materializeModelText(newContent, modelView.lineEndingStyle));
|
|
72029
|
+
scanCache.clear();
|
|
71888
72030
|
const { notice, hasErrors } = await this.appendDiagnostics(safePath);
|
|
71889
72031
|
const output = `Replaced ${String(replacementCount)} occurrences in ${args.path}${notice}`;
|
|
71890
72032
|
return hasErrors ? {
|
|
@@ -71981,7 +72123,7 @@ async function listDirectory(jian, workDir = jian.getcwd()) {
|
|
|
71981
72123
|
}
|
|
71982
72124
|
//#endregion
|
|
71983
72125
|
//#region ../../packages/agent-core/src/tools/builtin/file/glob.md
|
|
71984
|
-
var glob_default = "Find files (and optionally directories) by glob pattern, sorted by modification time (most recent first).\n\nGood patterns:\n- `*.ts` — files in the current directory matching an extension\n- `src/**/*.ts` — recursive with a subdirectory anchor and extension\n- `test_*.py` — files whose name starts with a literal prefix\n\nRejected patterns (no literal anchor — nothing bounds the result set):\n- `**`, `**/*`, `*/*` — pure wildcards. Add an extension or subdirectory to give the walk a concrete target.\n- Anything that starts with `**/` (e.g. `**/*.md`, `**/main/*.py`). The leading `**/` has no literal anchor in front of it. Anchor it with a top-level subdirectory like `src/**/*.md`.\n- `*.{ts,tsx}` — brace expansion is not supported. Issue two calls: `*.ts` and `*.tsx`.\n\nLarge-directory warning — avoid recursing into dependency/build output even with an anchor:\n- `node_modules/**/*.js`, `.venv/**/*.py`, `__pycache__/**`, `target/**` all match technically but\n typically produce thousands of results that truncate at the match cap and waste the caller context.\n Prefer specific subpaths like `node_modules/react/src/**/*.js`.";
|
|
72126
|
+
var glob_default = "Find files (and optionally directories) by glob pattern, sorted by modification time (most recent first).\n\nGood patterns:\n- `*.ts` — files in the current directory matching an extension\n- `src/**/*.ts` — recursive with a subdirectory anchor and extension\n- `test_*.py` — files whose name starts with a literal prefix\n\nRejected patterns (no literal anchor — nothing bounds the result set):\n- `**`, `**/*`, `*/*` — pure wildcards. Add an extension or subdirectory to give the walk a concrete target.\n- Anything that starts with `**/` (e.g. `**/*.md`, `**/main/*.py`). The leading `**/` has no literal anchor in front of it. Anchor it with a top-level subdirectory like `src/**/*.md`.\n- `*.{ts,tsx}` — brace expansion is not supported. Issue two calls: `*.ts` and `*.tsx`.\n\nLarge-directory warning — avoid recursing into dependency/build output even with an anchor:\n- `node_modules/**/*.js`, `.venv/**/*.py`, `__pycache__/**`, `target/**` all match technically but\n typically produce thousands of results that truncate at the {{ MAX_MATCHES }}-match cap and waste the caller context.\n Prefer specific subpaths like `node_modules/react/src/**/*.js`.";
|
|
71985
72127
|
//#endregion
|
|
71986
72128
|
//#region ../../packages/agent-core/src/tools/builtin/file/glob.ts
|
|
71987
72129
|
const GlobInputSchema = z.object({
|
|
@@ -71990,6 +72132,7 @@ const GlobInputSchema = z.object({
|
|
|
71990
72132
|
include_dirs: z.boolean().default(true).optional().describe("Whether to include directories in results. Defaults to true. Set false to return only files.")
|
|
71991
72133
|
});
|
|
71992
72134
|
const MAX_MATCHES = 1e3;
|
|
72135
|
+
const GLOB_DESCRIPTION = renderPrompt(glob_default, { MAX_MATCHES });
|
|
71993
72136
|
/**
|
|
71994
72137
|
* Path-shape hint appended to the tool description only on a Windows
|
|
71995
72138
|
* (`win32` path class) backend. The `path` argument accepts both native
|
|
@@ -72019,7 +72162,7 @@ var GlobTool = class {
|
|
|
72019
72162
|
constructor(jian, workspace) {
|
|
72020
72163
|
this.jian = jian;
|
|
72021
72164
|
this.workspace = workspace;
|
|
72022
|
-
this.description = this.jian.pathClass() === "win32" ?
|
|
72165
|
+
this.description = this.jian.pathClass() === "win32" ? GLOB_DESCRIPTION + WINDOWS_PATH_HINT : GLOB_DESCRIPTION;
|
|
72023
72166
|
}
|
|
72024
72167
|
async resolveExecution(args) {
|
|
72025
72168
|
let path;
|
|
@@ -72043,10 +72186,11 @@ var GlobTool = class {
|
|
|
72043
72186
|
},
|
|
72044
72187
|
approvalRule: literalRulePattern(this.name, args.pattern),
|
|
72045
72188
|
matchesRule: (ruleArgs) => matchesGlobRuleSubject(ruleArgs, args.pattern),
|
|
72046
|
-
execute: () => this.execution(args, searchRoots)
|
|
72189
|
+
execute: ({ signal }) => this.execution(args, searchRoots, signal)
|
|
72047
72190
|
};
|
|
72048
72191
|
}
|
|
72049
|
-
async execution(args, searchRoots) {
|
|
72192
|
+
async execution(args, searchRoots, signal) {
|
|
72193
|
+
signal?.throwIfAborted();
|
|
72050
72194
|
if (startsWithDoubleStarPrefix(args.pattern)) {
|
|
72051
72195
|
let tree;
|
|
72052
72196
|
try {
|
|
@@ -72095,6 +72239,8 @@ var GlobTool = class {
|
|
|
72095
72239
|
}
|
|
72096
72240
|
}
|
|
72097
72241
|
try {
|
|
72242
|
+
const cachedOutput = scanCache.get(searchRoots[0], args.pattern, includeDirs);
|
|
72243
|
+
if (cachedOutput !== void 0) return { output: cachedOutput };
|
|
72098
72244
|
const seen = /* @__PURE__ */ new Set();
|
|
72099
72245
|
const entries = [];
|
|
72100
72246
|
const YIELD_SAFETY_CAP = MAX_MATCHES * 2;
|
|
@@ -72102,6 +72248,7 @@ var GlobTool = class {
|
|
|
72102
72248
|
let truncated = false;
|
|
72103
72249
|
outer: for (const root of searchRoots) for await (const filePath of this.jian.glob(root, args.pattern, { allowedRoots: [root] })) {
|
|
72104
72250
|
yielded++;
|
|
72251
|
+
if (signal && yielded % 128 === 0) signal.throwIfAborted();
|
|
72105
72252
|
if (yielded >= YIELD_SAFETY_CAP) {
|
|
72106
72253
|
truncated = true;
|
|
72107
72254
|
break outer;
|
|
@@ -72130,16 +72277,22 @@ var GlobTool = class {
|
|
|
72130
72277
|
const pathClass = this.jian.pathClass();
|
|
72131
72278
|
const relBase = searchRoots[0] ?? this.workspace.workspaceDir;
|
|
72132
72279
|
const displayLines = paths.map((p) => relativizeIfUnder$1(p, relBase, pathClass));
|
|
72133
|
-
|
|
72134
|
-
|
|
72135
|
-
|
|
72136
|
-
lines
|
|
72137
|
-
|
|
72280
|
+
let output;
|
|
72281
|
+
if (entries.length === 0 && !truncated) output = "No matches found";
|
|
72282
|
+
else {
|
|
72283
|
+
const lines = [];
|
|
72284
|
+
if (truncated) {
|
|
72285
|
+
lines.push(`[Truncated at ${String(MAX_MATCHES)} matches — use a more specific pattern]`);
|
|
72286
|
+
lines.push(`Only the first ${String(MAX_MATCHES)} matches are returned.`);
|
|
72287
|
+
}
|
|
72288
|
+
lines.push(...displayLines);
|
|
72289
|
+
if (!truncated && entries.length === 1e3) lines.push(`Found ${String(entries.length)} matches`);
|
|
72290
|
+
output = lines.join("\n");
|
|
72138
72291
|
}
|
|
72139
|
-
|
|
72140
|
-
|
|
72141
|
-
return { output: lines.join("\n") };
|
|
72292
|
+
scanCache.set(searchRoots[0], args.pattern, includeDirs, output);
|
|
72293
|
+
return { output };
|
|
72142
72294
|
} catch (error) {
|
|
72295
|
+
if (signal?.aborted) throw error;
|
|
72143
72296
|
if (error !== null && typeof error === "object" && "code" in error) {
|
|
72144
72297
|
const code = error.code;
|
|
72145
72298
|
const path = searchRoots[0] ?? this.workspace.workspaceDir;
|
|
@@ -75416,6 +75569,85 @@ function rgUnavailableMessage(cause) {
|
|
|
75416
75569
|
Error: ${cause instanceof Error ? cause.message : typeof cause === "string" ? cause : "unknown error"}\n\nFix options:\n macOS: brew install ripgrep\n Ubuntu: sudo apt-get install ripgrep\n Other: https://github.com/BurntSushi/ripgrep#installation\n\nAlternatively, drop a static rg binary at ${join$1(getShareDir(), "bin", rgBinaryName())}`;
|
|
75417
75570
|
}
|
|
75418
75571
|
//#endregion
|
|
75572
|
+
//#region ../../packages/agent-core/src/tools/support/rg-pattern-sanitize.ts
|
|
75573
|
+
/**
|
|
75574
|
+
* Ripgrep pattern sanitization and error detection.
|
|
75575
|
+
*
|
|
75576
|
+
* Users frequently search for code fragments that contain regex metacharacters
|
|
75577
|
+
* (e.g. `fetchFoo(`, `${platform}`, `a{3`). ripgrep uses the Rust `regex`
|
|
75578
|
+
* crate, which rejects unescaped `{`/`}` that do not form a valid repetition
|
|
75579
|
+
* quantifier (`{N}`, `{N,}`, `{N,M}`). This module pre-cleans such braces so
|
|
75580
|
+
* the common case "search for a literal-ish fragment" succeeds without a
|
|
75581
|
+
* syntax error.
|
|
75582
|
+
*
|
|
75583
|
+
* When sanitization is insufficient (e.g. unbalanced parentheses), the caller
|
|
75584
|
+
* can detect the regex error from ripgrep stderr and retry with
|
|
75585
|
+
* `--fixed-strings`, which treats the pattern as a literal string.
|
|
75586
|
+
*/
|
|
75587
|
+
/** Matches the inner content of a valid repetition quantifier: `N`, `N,`, `N,M`. */
|
|
75588
|
+
const QUANTIFIER_INNER_RE = /^\d+(?:,\d*)?$/;
|
|
75589
|
+
/**
|
|
75590
|
+
* Escape `{` and `}` that cannot form a valid repetition quantifier so they
|
|
75591
|
+
* are treated as literals by the regex engine.
|
|
75592
|
+
*
|
|
75593
|
+
* Already-escaped braces (`\{`, `\}`) are preserved. Valid quantifiers
|
|
75594
|
+
* (`{3}`, `{2,}`, `{1,5}`) are preserved. Everything else is escaped.
|
|
75595
|
+
*
|
|
75596
|
+
* Escaping braces inside character classes (e.g. `[{]`) is also safe:
|
|
75597
|
+
* `\{` inside `[...]` matches a literal `{` just like an unescaped `{` would.
|
|
75598
|
+
*/
|
|
75599
|
+
function sanitizeRgPattern(pattern) {
|
|
75600
|
+
let result = "";
|
|
75601
|
+
let i = 0;
|
|
75602
|
+
while (i < pattern.length) {
|
|
75603
|
+
const ch = pattern[i];
|
|
75604
|
+
if (ch === "\\" && i + 1 < pattern.length) {
|
|
75605
|
+
result += pattern.slice(i, i + 2);
|
|
75606
|
+
i += 2;
|
|
75607
|
+
continue;
|
|
75608
|
+
}
|
|
75609
|
+
if (ch === "{") {
|
|
75610
|
+
const closeIdx = pattern.indexOf("}", i + 1);
|
|
75611
|
+
if (closeIdx !== -1) {
|
|
75612
|
+
const inner = pattern.slice(i + 1, closeIdx);
|
|
75613
|
+
if (QUANTIFIER_INNER_RE.test(inner)) {
|
|
75614
|
+
result += pattern.slice(i, closeIdx + 1);
|
|
75615
|
+
i = closeIdx + 1;
|
|
75616
|
+
continue;
|
|
75617
|
+
}
|
|
75618
|
+
}
|
|
75619
|
+
result += "\\{";
|
|
75620
|
+
i++;
|
|
75621
|
+
continue;
|
|
75622
|
+
}
|
|
75623
|
+
if (ch === "}") {
|
|
75624
|
+
result += "\\}";
|
|
75625
|
+
i++;
|
|
75626
|
+
continue;
|
|
75627
|
+
}
|
|
75628
|
+
result += ch;
|
|
75629
|
+
i++;
|
|
75630
|
+
}
|
|
75631
|
+
return result;
|
|
75632
|
+
}
|
|
75633
|
+
/** Substrings that indicate ripgrep failed to parse the regex pattern. */
|
|
75634
|
+
const REGEX_ERROR_MARKERS = [
|
|
75635
|
+
"regex parse error",
|
|
75636
|
+
"unrecognized escape",
|
|
75637
|
+
"unclosed group",
|
|
75638
|
+
"unbalanced",
|
|
75639
|
+
"unexpected repetition"
|
|
75640
|
+
];
|
|
75641
|
+
/**
|
|
75642
|
+
* Detect whether ripgrep's stderr indicates a regex syntax error (as opposed
|
|
75643
|
+
* to a filesystem or runtime error). Used to decide whether a
|
|
75644
|
+
* `--fixed-strings` fallback retry is worthwhile.
|
|
75645
|
+
*/
|
|
75646
|
+
function isRegexSyntaxError(stderr) {
|
|
75647
|
+
const lower = stderr.toLowerCase();
|
|
75648
|
+
return REGEX_ERROR_MARKERS.some((marker) => lower.includes(marker));
|
|
75649
|
+
}
|
|
75650
|
+
//#endregion
|
|
75419
75651
|
//#region ../../packages/agent-core/src/tools/support/result-builder.ts
|
|
75420
75652
|
const DEFAULT_MAX_CHARS = 5e4;
|
|
75421
75653
|
const DEFAULT_TAIL_CHARS = 2e4;
|
|
@@ -75572,7 +75804,7 @@ function countLines(text) {
|
|
|
75572
75804
|
}
|
|
75573
75805
|
//#endregion
|
|
75574
75806
|
//#region ../../packages/agent-core/src/tools/builtin/file/grep.md
|
|
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
|
|
75807
|
+
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.\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.\n";
|
|
75576
75808
|
//#endregion
|
|
75577
75809
|
//#region ../../packages/agent-core/src/tools/builtin/file/grep.ts
|
|
75578
75810
|
const GrepInputSchema = z.object({
|
|
@@ -75640,11 +75872,12 @@ const SENSITIVE_GLOBS_TO_EXCLUDE = [
|
|
|
75640
75872
|
"**/.gcp/credentials/**"
|
|
75641
75873
|
];
|
|
75642
75874
|
const CONTENT_LINE_RE = /^(.*?)([:-])(\d+)\2/;
|
|
75875
|
+
const GREP_DESCRIPTION = renderPrompt(grep_default, { DEFAULT_HEAD_LIMIT });
|
|
75643
75876
|
var GrepTool = class {
|
|
75644
75877
|
jian;
|
|
75645
75878
|
workspace;
|
|
75646
75879
|
name = "Grep";
|
|
75647
|
-
description =
|
|
75880
|
+
description = GREP_DESCRIPTION;
|
|
75648
75881
|
parameters = toInputJsonSchema(GrepInputSchema);
|
|
75649
75882
|
constructor(jian, workspace) {
|
|
75650
75883
|
this.jian = jian;
|
|
@@ -75701,6 +75934,15 @@ var GrepTool = class {
|
|
|
75701
75934
|
runResult = await runRipgrepOnce(this.jian, buildRgArgs(rgPath, args, searchPaths, true), signal);
|
|
75702
75935
|
if (runResult.kind === "tool-error") return runResult.result;
|
|
75703
75936
|
}
|
|
75937
|
+
if (runResult.exitCode !== 0 && runResult.exitCode !== 1 && !runResult.timedOut && isRegexSyntaxError(runResult.stderrText)) {
|
|
75938
|
+
const fallback = await runRipgrepOnce(this.jian, buildRgArgs(rgPath, args, searchPaths, false, true), signal);
|
|
75939
|
+
if (fallback.kind === "tool-error") return fallback.result;
|
|
75940
|
+
if (shouldRetryRipgrepEagain(fallback)) {
|
|
75941
|
+
const retry = await runRipgrepOnce(this.jian, buildRgArgs(rgPath, args, searchPaths, true, true), signal);
|
|
75942
|
+
if (retry.kind === "tool-error") return retry.result;
|
|
75943
|
+
if (retry.exitCode === 0 || retry.exitCode === 1) runResult = retry;
|
|
75944
|
+
} else if (fallback.exitCode === 0 || fallback.exitCode === 1) runResult = fallback;
|
|
75945
|
+
}
|
|
75704
75946
|
const { exitCode, stderrText, bufferTruncated, stderrTruncated, timedOut } = runResult;
|
|
75705
75947
|
let { stdoutText } = runResult;
|
|
75706
75948
|
if (exitCode !== 0 && exitCode !== 1 && !timedOut) return {
|
|
@@ -75955,10 +76197,11 @@ async function mapWithConcurrency(items, concurrency, signal, mapper) {
|
|
|
75955
76197
|
if (signal.aborted) throw new GrepAbortedError();
|
|
75956
76198
|
return results;
|
|
75957
76199
|
}
|
|
75958
|
-
function buildRgArgs(rgPath, args, searchPaths, singleThreaded = false) {
|
|
76200
|
+
function buildRgArgs(rgPath, args, searchPaths, singleThreaded = false, literal = false) {
|
|
75959
76201
|
const cmd = [rgPath];
|
|
75960
76202
|
if (singleThreaded) cmd.push("-j", "1");
|
|
75961
76203
|
cmd.push("--hidden");
|
|
76204
|
+
if (literal) cmd.push("--fixed-strings");
|
|
75962
76205
|
const mode = args.output_mode ?? "files_with_matches";
|
|
75963
76206
|
if (mode !== "content") cmd.push("--max-columns", String(RG_MAX_COLUMNS));
|
|
75964
76207
|
cmd.push("--null");
|
|
@@ -75981,7 +76224,8 @@ function buildRgArgs(rgPath, args, searchPaths, singleThreaded = false) {
|
|
|
75981
76224
|
if (args.multiline) cmd.push("-U", "--multiline-dotall");
|
|
75982
76225
|
if (args.include_ignored) cmd.push("--no-ignore");
|
|
75983
76226
|
for (const glob of SENSITIVE_GLOBS_TO_EXCLUDE) cmd.push("--glob", `!${glob}`);
|
|
75984
|
-
|
|
76227
|
+
const pattern = literal ? args.pattern : sanitizeRgPattern(args.pattern);
|
|
76228
|
+
cmd.push("--", pattern, ...searchPaths);
|
|
75985
76229
|
return cmd;
|
|
75986
76230
|
}
|
|
75987
76231
|
function splitRgLines(text) {
|
|
@@ -76695,7 +76939,7 @@ async function partitionExistingPaths(paths, jian, workspace) {
|
|
|
76695
76939
|
}
|
|
76696
76940
|
//#endregion
|
|
76697
76941
|
//#region ../../packages/agent-core/src/tools/builtin/file/read.md
|
|
76698
|
-
var read_default = "Read a text file from the local filesystem.\n\nIf the user provides a concrete file path to a text file, call Read directly. Do not `Glob`, `ls`, or otherwise pre-check known text file paths; missing or invalid file paths return errors you can handle. Do not use Read for directories; use `ls` via Bash for a known directory, or Glob when you need files/directories matching a pattern. Use `Grep` only when the task is to search for unknown content or locations.\n\nWhen you need several files, prefer to read them in parallel: emit multiple `Read` calls in a single response instead of reading one file per turn.\n\n-
|
|
76942
|
+
var read_default = "Read a text file from the local filesystem.\n\nIf the user provides a concrete file path to a text file, call Read directly. Do not `Glob`, `ls`, or otherwise pre-check known text file paths; missing or invalid file paths return errors you can handle. Do not use Read for directories; use `ls` via Bash for a known directory, or Glob when you need files/directories matching a pattern. Use `Grep` only when the task is to search for unknown content or locations.\n\nWhen you need several files, prefer to read them in parallel: emit multiple `Read` calls in a single response instead of reading one file per turn.\n\n- Returns up to {{ MAX_BYTES_KB }} KB per call; lines longer than {{ MAX_LINE_LENGTH }} chars are truncated mid-line.\n- Sensitive files (`.env` files, credential stores, SSH keys, and similar secrets) are refused to protect secrets; do not attempt to read them.\n- Only UTF-8 text files can be read. Non-UTF-8 encodings, binary files, and files containing NUL bytes are refused; use `ReadMediaFile` for images or video, and Bash or an MCP tool for other binary formats.\n- Output format: `<line-number>\\t<content>` per line.\n- A `<system>...</system>` status block is appended after the file content; it summarizes how much was read (line and byte counts, truncation, line-ending notes) and is not part of the file itself. The status block includes an `Anchor: <hash>` value that can be passed to `Edit.anchor` to verify the file has not changed before editing.\n- Pure CRLF files are displayed with LF line endings; `Edit` matches this output and preserves CRLF when writing back.\n- Mixed or lone carriage-return line endings are shown as `\\r` and require exact `Edit.old_string` escapes.\n- After a successful `Edit`/`Write`, do not re-read solely to prove the write landed. When the task depends on an exact file, API, or output shape, inspect the final external contract before finishing.\n";
|
|
76699
76943
|
//#endregion
|
|
76700
76944
|
//#region ../../packages/agent-core/src/tools/builtin/file/read.ts
|
|
76701
76945
|
const MAX_LINES = 1e3;
|
|
@@ -77051,7 +77295,7 @@ var ReadTool = class {
|
|
|
77051
77295
|
if (input.maxLinesReached) parts.push(`Max ${String(MAX_LINES)} lines reached.`);
|
|
77052
77296
|
else if (input.maxBytesReached) parts.push(`Max ${String(MAX_BYTES)} bytes reached.`);
|
|
77053
77297
|
else if (lineCount < input.requestedLines) parts.push("End of file reached.");
|
|
77054
|
-
if (input.truncatedLineNumbers.length > 0) parts.push(`Lines [${input.truncatedLineNumbers.join(", ")}] were truncated.`);
|
|
77298
|
+
if (input.truncatedLineNumbers.length > 0) parts.push(`Lines [${input.truncatedLineNumbers.join(", ")}] exceed the ${String(MAX_LINE_LENGTH)}-char limit and were truncated. Re-read with line_offset and n_lines=1 to view individual long lines in full.`);
|
|
77055
77299
|
if (input.lineEndingStyle === "mixed") parts.push("Mixed or lone carriage-return line endings are shown as \\r. Use exact \\r\\n or \\r escapes in Edit.old_string for those lines.");
|
|
77056
77300
|
parts.push(`Anchor: ${input.anchor}`);
|
|
77057
77301
|
return parts.join(" ");
|
|
@@ -77450,7 +77694,7 @@ var ReadMediaFileTool = class {
|
|
|
77450
77694
|
};
|
|
77451
77695
|
//#endregion
|
|
77452
77696
|
//#region ../../packages/agent-core/src/tools/builtin/file/write.md
|
|
77453
|
-
var write_default = "
|
|
77697
|
+
var write_default = "Write does not preserve or infer the previous line-ending style: \\n stays LF, \\r\\n stays CRLF. Use Edit for targeted changes to existing files. When the content is very large, you can split it across multiple calls: write the first chunk with overwrite, then add the remaining chunks with append. Write refuses content containing `<<<<<<<`/`=======`/`>>>>>>>` merge conflict markers - resolve conflicts first.\n";
|
|
77454
77698
|
//#endregion
|
|
77455
77699
|
//#region ../../packages/agent-core/src/tools/builtin/file/write.ts
|
|
77456
77700
|
/** Mask isolating the file-type bits of a stat mode. */
|
|
@@ -77498,10 +77742,11 @@ var WriteTool = class {
|
|
|
77498
77742
|
pathClass: this.jian.pathClass(),
|
|
77499
77743
|
homeDir: this.jian.gethome()
|
|
77500
77744
|
}),
|
|
77501
|
-
execute: () => this.execution(args, path)
|
|
77745
|
+
execute: ({ signal }) => this.execution(args, path, signal)
|
|
77502
77746
|
};
|
|
77503
77747
|
}
|
|
77504
|
-
async execution(args, safePath) {
|
|
77748
|
+
async execution(args, safePath, signal) {
|
|
77749
|
+
signal?.throwIfAborted();
|
|
77505
77750
|
const parentError = await this.checkParentDirectory(safePath);
|
|
77506
77751
|
if (parentError !== void 0) return {
|
|
77507
77752
|
isError: true,
|
|
@@ -77516,6 +77761,7 @@ var WriteTool = class {
|
|
|
77516
77761
|
const mode = args.mode ?? "overwrite";
|
|
77517
77762
|
if (mode === "append") await this.jian.writeText(safePath, args.content, { mode: "a" });
|
|
77518
77763
|
else await this.jian.writeText(safePath, args.content);
|
|
77764
|
+
scanCache.clear();
|
|
77519
77765
|
const bytesWritten = Buffer.byteLength(args.content, "utf8");
|
|
77520
77766
|
const { notice, hasErrors } = await this.appendDiagnostics(safePath);
|
|
77521
77767
|
const output = `${mode === "append" ? "Appended" : "Wrote"} ${String(bytesWritten)} bytes to ${args.path}${notice}`;
|
|
@@ -77565,7 +77811,7 @@ var WriteTool = class {
|
|
|
77565
77811
|
};
|
|
77566
77812
|
//#endregion
|
|
77567
77813
|
//#region ../../packages/agent-core/src/tools/builtin/planning/enter-plan-mode.md
|
|
77568
|
-
var enter_plan_mode_default = "Use this tool proactively when you're about to start a non-trivial implementation task.\nGetting user sign-off on your approach via ExitPlanMode before writing code prevents wasted effort.\n\n## Planning Modes\n\
|
|
77814
|
+
var enter_plan_mode_default = "Use this tool proactively when you're about to start a non-trivial implementation task.\nGetting user sign-off on your approach via ExitPlanMode before writing code prevents wasted effort.\n\n## Planning Modes\n\n- **Normal plan**: You investigate the codebase, design a single approach, write it to the plan file, and present it for approval.\n- **Fusion plan**: Invoke the FusionPlan tool to spawn parallel planning subagents, each exploring a different angle, then synthesize their outputs into one plan. Fusion plan may take longer but tends to surface risks and alternatives you might miss.\n\n### When to choose which mode\n\nPrefer **normal plan** when:\n1. The user gave specific, detailed instructions.\n2. The change is small or localized (1-3 files, single concern).\n3. You are confident about the codebase structure and the right approach.\n4. Speed matters more than exploring alternatives.\n\nPrefer **fusion plan** when:\n1. The task is open-ended or ambiguous (e.g. \"improve performance\", \"refactor auth\").\n2. Multiple valid architectures or approaches exist.\n3. The change spans more than 3-5 files or touches core abstractions.\n4. You are unfamiliar with the relevant code paths and want parallel exploration.\n5. The user explicitly asked for a thorough plan or mentioned comparing options.\n\nIf unsure, choose normal plan for small fixes and fusion plan for larger design tasks.\n\n## When to Use\n\nUse this tool when ANY of these conditions apply:\n\n1. New Feature Implementation - e.g. \"Add a caching layer to the API\"\n2. Multiple Valid Approaches - e.g. \"Optimize database queries\" (indexing vs rewrite vs caching)\n3. Code Modifications - e.g. \"Refactor auth module to support OAuth\"\n4. Architectural Decisions - e.g. \"Add WebSocket support\"\n5. Multi-File Changes - involves more than 2-3 files\n6. Unclear Requirements - need exploration to understand scope\n7. User Preferences Matter - if user input would materially change the implementation approach, use EnterPlanMode to structure the decision\n\nPermission mode notes:\n- EnterPlanMode enters plan mode automatically without an approval prompt in all permission modes.\n- In yolo and manual modes, ExitPlanMode still presents the plan to the user for approval.\n- In auto permission mode, do not use AskUserQuestion; make the best decision from available context.\n- In auto permission mode, ExitPlanMode exits plan mode without asking the user.\n- Use EnterPlanMode only when planning itself adds value.\n\nWhen NOT to use:\n- Single-line or few-line fixes (typos, obvious bugs, small tweaks)\n- User gave very specific, detailed instructions\n- Pure research/exploration tasks\n\n## What Happens in Plan Mode\nIn plan mode, you will:\n1. Identify 2-3 key questions about the codebase that are critical to your plan. If you are not confident about the codebase structure or relevant code paths, use `Agent(subagent_type=\"explore\")` to investigate these questions first - this is strongly recommended for non-trivial tasks.\n2. Explore the codebase using Glob, Grep, Read, and other read-only tools for any remaining quick lookups. Use Bash only when needed; Bash follows the normal permission mode and rules.\n3. Design an implementation approach based on your findings (or, for fusion plan, call FusionPlan to generate the synthesized plan).\n4. Review the generated plan, fill in any gaps, and ensure it matches the user's intent.\n5. Present your plan to the user via ExitPlanMode for approval\n\nFor fusion plan, the FusionPlan tool performs the parallel exploration and synthesis for you; you should still review the result before exiting plan mode.\n";
|
|
77569
77815
|
//#endregion
|
|
77570
77816
|
//#region ../../packages/agent-core/src/tools/builtin/planning/enter-plan-mode.ts
|
|
77571
77817
|
const EnterPlanModeInputSchema = z.object({ mode: z.enum(["normal", "fusion"]).default("normal").describe("Planning strategy. 'normal' (default): you design the plan. 'fusion': the host spawns parallel planning subagents and synthesizes a single plan for you to review. Use 'fusion' for ambiguous, large, or multi-approach tasks.") }).strict();
|
|
@@ -77650,7 +77896,7 @@ function enteredPlanModeMessage(mode, planPath) {
|
|
|
77650
77896
|
}
|
|
77651
77897
|
//#endregion
|
|
77652
77898
|
//#region ../../packages/agent-core/src/tools/builtin/planning/exit-plan-mode.md
|
|
77653
|
-
var exit_plan_mode_default = "Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.\n\n## How This Tool Works\n- You should have already written your plan to the plan file specified in the plan mode reminder.\n- This tool does NOT take the plan content as a parameter - it reads the plan from the file you wrote.\n- The user will see the contents of your plan file when they review it. In auto permission mode, the tool reads the file and exits plan mode without asking the user.\n\n## When to Use\nOnly use this tool for tasks that require planning implementation steps. For research tasks (searching files, reading code, understanding the codebase), do NOT use this tool.\n\n## Multiple Approaches\nIf your plan contains multiple alternative approaches:\n-
|
|
77899
|
+
var exit_plan_mode_default = "Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval.\n\n## How This Tool Works\n- You should have already written your plan to the plan file specified in the plan mode reminder.\n- This tool does NOT take the plan content as a parameter - it reads the plan from the file you wrote.\n- The user will see the contents of your plan file when they review it. In auto permission mode, the tool reads the file and exits plan mode without asking the user.\n\n## When to Use\nOnly use this tool for tasks that require planning implementation steps. For research tasks (searching files, reading code, understanding the codebase), do NOT use this tool.\n\n## Multiple Approaches\nIf your plan contains multiple alternative approaches:\n- If you recommend one option, append \"(Recommended)\" to its label.\n- In yolo and manual modes, the user will see all options alongside Reject and Revise choices.\n- Passing a single option is allowed and is equivalent to a plain plan approval (no approach choice is surfaced to the user).\n\n## Before Using\n- In auto permission mode, do NOT use AskUserQuestion; make the best decision from available context.\n- In auto permission mode, this tool exits plan mode without asking the user.\n- In yolo and manual modes, this tool still presents the plan to the user for approval.\n- If auto permission mode is not active and you have unresolved questions, use AskUserQuestion first.\n- If auto permission mode is not active and you have multiple approaches and haven't narrowed down yet, consider using AskUserQuestion first to let the user choose, then write a plan for the chosen approach only.\n- Once your plan is finalized, use THIS tool to request approval.\n- Do NOT use AskUserQuestion to ask \"Is this plan OK?\" or \"Should I proceed?\" - that is exactly what ExitPlanMode does.\n- If rejected, revise based on feedback and call ExitPlanMode again.\n";
|
|
77654
77900
|
//#endregion
|
|
77655
77901
|
//#region ../../packages/agent-core/src/tools/builtin/planning/exit-plan-mode.ts
|
|
77656
77902
|
const RESERVED_OPTION_LABELS = new Set([
|
|
@@ -80019,7 +80265,9 @@ var MicroCompaction = class {
|
|
|
80019
80265
|
const nextCutoff = computeCutoff(history, config);
|
|
80020
80266
|
if (nextCutoff <= this.cutoff) return;
|
|
80021
80267
|
const { beforeTokens, afterTokens } = this.measureEffect(history, nextCutoff);
|
|
80022
|
-
|
|
80268
|
+
const reclaimTokens = beforeTokens - afterTokens;
|
|
80269
|
+
if (reclaimTokens < config.pruneMinReclaimTokens) return;
|
|
80270
|
+
if (estimateTokensForMessages(history.slice(nextCutoff)) * 1.15 > reclaimTokens) return;
|
|
80023
80271
|
this.apply(nextCutoff);
|
|
80024
80272
|
}
|
|
80025
80273
|
/**
|
|
@@ -81515,22 +81763,38 @@ function toolResultOutputForModel(result) {
|
|
|
81515
81763
|
}, ...truncateContentParts(output)];
|
|
81516
81764
|
return truncateContentParts(output);
|
|
81517
81765
|
}
|
|
81518
|
-
/** Truncate a plain-text tool output that exceeds MAX_TOOL_RESULT_TOKENS.
|
|
81766
|
+
/** Truncate a plain-text tool output that exceeds MAX_TOOL_RESULT_TOKENS.
|
|
81767
|
+
* Tail-biased: keeps 25% head + 75% tail so error messages and test
|
|
81768
|
+
* failures (usually at the end) survive truncation. */
|
|
81519
81769
|
function truncateToolOutput(text) {
|
|
81520
81770
|
if (estimateTokens$1(text) <= MAX_TOOL_RESULT_TOKENS) return text;
|
|
81521
81771
|
const budget = MAX_TOOL_RESULT_TOKENS - estimateTokens$1(TOOL_TRUNCATION_NOTICE);
|
|
81522
81772
|
if (budget <= 0) return TOOL_TRUNCATION_NOTICE.trim();
|
|
81523
|
-
|
|
81524
|
-
|
|
81773
|
+
const headBudget = Math.floor(budget * .25);
|
|
81774
|
+
const tailBudget = budget - headBudget;
|
|
81775
|
+
let head = "";
|
|
81776
|
+
let headTokens = 0;
|
|
81525
81777
|
for (const ch of text) {
|
|
81526
81778
|
const chTokens = ch.codePointAt(0) <= 127 ? 1 / 4 : 1;
|
|
81527
|
-
if (
|
|
81528
|
-
|
|
81529
|
-
|
|
81779
|
+
if (headTokens + chTokens > headBudget) break;
|
|
81780
|
+
head += ch;
|
|
81781
|
+
headTokens += chTokens;
|
|
81782
|
+
}
|
|
81783
|
+
const reversed = [...text].toReversed();
|
|
81784
|
+
const tailChars = [];
|
|
81785
|
+
let tailTokens = 0;
|
|
81786
|
+
for (const ch of reversed) {
|
|
81787
|
+
const chTokens = ch.codePointAt(0) <= 127 ? 1 / 4 : 1;
|
|
81788
|
+
if (tailTokens + chTokens > tailBudget) break;
|
|
81789
|
+
tailChars.push(ch);
|
|
81790
|
+
tailTokens += chTokens;
|
|
81530
81791
|
}
|
|
81531
|
-
|
|
81792
|
+
const tail = tailChars.toReversed().join("");
|
|
81793
|
+
const notice = `\n[content truncated - ~${Math.max(0, Math.round(estimateTokens$1(text) - headTokens - tailTokens))} tokens omitted]\n`;
|
|
81794
|
+
return head + notice + tail;
|
|
81532
81795
|
}
|
|
81533
|
-
/** Truncate oversized text parts in a ContentPart array.
|
|
81796
|
+
/** Truncate oversized text parts in a ContentPart array.
|
|
81797
|
+
* Tail-biased: keeps 25% head + 75% tail. */
|
|
81534
81798
|
function truncateContentParts(parts) {
|
|
81535
81799
|
let totalTokens = 0;
|
|
81536
81800
|
for (const p of parts) if (p.type === "text") totalTokens += estimateTokens$1(p.text);
|
|
@@ -81540,39 +81804,83 @@ function truncateContentParts(parts) {
|
|
|
81540
81804
|
type: "text",
|
|
81541
81805
|
text: TOOL_TRUNCATION_NOTICE.trim()
|
|
81542
81806
|
}];
|
|
81543
|
-
const
|
|
81544
|
-
|
|
81545
|
-
|
|
81807
|
+
const headBudget = Math.floor(budget * .25);
|
|
81808
|
+
const tailBudget = budget - headBudget;
|
|
81809
|
+
const headParts = [];
|
|
81810
|
+
let headUsed = 0;
|
|
81811
|
+
let headEnd = 0;
|
|
81812
|
+
for (let i = 0; i < parts.length; i++) {
|
|
81813
|
+
const p = parts[i];
|
|
81546
81814
|
if (p.type !== "text") {
|
|
81547
|
-
|
|
81815
|
+
headParts.push(p);
|
|
81816
|
+
headEnd = i + 1;
|
|
81548
81817
|
continue;
|
|
81549
81818
|
}
|
|
81550
81819
|
const partTokens = estimateTokens$1(p.text);
|
|
81551
|
-
if (
|
|
81552
|
-
|
|
81553
|
-
|
|
81820
|
+
if (headUsed + partTokens <= headBudget) {
|
|
81821
|
+
headParts.push(p);
|
|
81822
|
+
headUsed += partTokens;
|
|
81823
|
+
headEnd = i + 1;
|
|
81554
81824
|
} else {
|
|
81555
|
-
const remaining =
|
|
81556
|
-
|
|
81557
|
-
|
|
81558
|
-
|
|
81559
|
-
const
|
|
81560
|
-
|
|
81561
|
-
|
|
81562
|
-
|
|
81563
|
-
|
|
81564
|
-
|
|
81565
|
-
|
|
81566
|
-
|
|
81567
|
-
|
|
81825
|
+
const remaining = headBudget - headUsed;
|
|
81826
|
+
if (remaining > 0) {
|
|
81827
|
+
let kept = "";
|
|
81828
|
+
let t = 0;
|
|
81829
|
+
for (const ch of p.text) {
|
|
81830
|
+
const chTokens = ch.codePointAt(0) <= 127 ? 1 / 4 : 1;
|
|
81831
|
+
if (t + chTokens > remaining) break;
|
|
81832
|
+
kept += ch;
|
|
81833
|
+
t += chTokens;
|
|
81834
|
+
}
|
|
81835
|
+
if (kept.length > 0) headParts.push({
|
|
81836
|
+
type: "text",
|
|
81837
|
+
text: kept
|
|
81838
|
+
});
|
|
81839
|
+
}
|
|
81568
81840
|
break;
|
|
81569
81841
|
}
|
|
81570
81842
|
}
|
|
81571
|
-
|
|
81572
|
-
|
|
81573
|
-
|
|
81574
|
-
|
|
81575
|
-
|
|
81843
|
+
const tailParts = [];
|
|
81844
|
+
let tailUsed = 0;
|
|
81845
|
+
for (let i = parts.length - 1; i >= headEnd; i--) {
|
|
81846
|
+
const p = parts[i];
|
|
81847
|
+
if (p.type !== "text") {
|
|
81848
|
+
tailParts.unshift(p);
|
|
81849
|
+
continue;
|
|
81850
|
+
}
|
|
81851
|
+
const partTokens = estimateTokens$1(p.text);
|
|
81852
|
+
if (tailUsed + partTokens <= tailBudget) {
|
|
81853
|
+
tailParts.unshift(p);
|
|
81854
|
+
tailUsed += partTokens;
|
|
81855
|
+
} else {
|
|
81856
|
+
const remaining = tailBudget - tailUsed;
|
|
81857
|
+
if (remaining > 0) {
|
|
81858
|
+
const chars = [...p.text];
|
|
81859
|
+
let kept = "";
|
|
81860
|
+
let t = 0;
|
|
81861
|
+
for (let j = chars.length - 1; j >= 0; j--) {
|
|
81862
|
+
const chTokens = chars[j].codePointAt(0) <= 127 ? 1 / 4 : 1;
|
|
81863
|
+
if (t + chTokens > remaining) break;
|
|
81864
|
+
kept = chars[j] + kept;
|
|
81865
|
+
t += chTokens;
|
|
81866
|
+
}
|
|
81867
|
+
if (kept.length > 0) tailParts.unshift({
|
|
81868
|
+
type: "text",
|
|
81869
|
+
text: kept
|
|
81870
|
+
});
|
|
81871
|
+
}
|
|
81872
|
+
break;
|
|
81873
|
+
}
|
|
81874
|
+
}
|
|
81875
|
+
const omitted = Math.max(0, Math.round(totalTokens - headUsed - tailUsed));
|
|
81876
|
+
return [
|
|
81877
|
+
...headParts,
|
|
81878
|
+
{
|
|
81879
|
+
type: "text",
|
|
81880
|
+
text: `[content truncated - ~${omitted} tokens omitted]`
|
|
81881
|
+
},
|
|
81882
|
+
...tailParts
|
|
81883
|
+
];
|
|
81576
81884
|
}
|
|
81577
81885
|
function isEmptyOutputText(output) {
|
|
81578
81886
|
return output.length === 0 || output.trim() === TOOL_OUTPUT_EMPTY_TEXT;
|
|
@@ -123450,7 +123758,7 @@ function optionalBuildString(value) {
|
|
|
123450
123758
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
123451
123759
|
}
|
|
123452
123760
|
const SCREAM_BUILD_INFO = {
|
|
123453
|
-
version: optionalBuildString("0.10.
|
|
123761
|
+
version: optionalBuildString("0.10.9"),
|
|
123454
123762
|
channel: optionalBuildString(""),
|
|
123455
123763
|
commit: optionalBuildString(""),
|
|
123456
123764
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -126747,6 +127055,30 @@ function formatFooterGitBadge(status, colors) {
|
|
|
126747
127055
|
if (status.pullRequest === null) return base;
|
|
126748
127056
|
return `${base} ${chalk.hex(colors.primary)(formatPullRequestBadge(status.pullRequest, { linkPullRequest: true }))}`;
|
|
126749
127057
|
}
|
|
127058
|
+
/**
|
|
127059
|
+
* Middle-truncate a (possibly ANSI-colored) string to `maxWidth` visible
|
|
127060
|
+
* columns, keeping a head and a tail fragment joined by `ellipsis`. The
|
|
127061
|
+
* remaining budget is split roughly in half so both the start and the end of
|
|
127062
|
+
* the original content stay visible - e.g. `GOAL 3m · 7 turns` becomes
|
|
127063
|
+
* `GOAL… turns` (head `GOAL`, tail `turns`). ANSI styling is preserved via
|
|
127064
|
+
* sliceByColumn, which carries the active SGR state into the tail; a reset is
|
|
127065
|
+
* emitted around the ellipsis so a colour opened in the head never bleeds
|
|
127066
|
+
* across it. Exported for unit testing.
|
|
127067
|
+
*/
|
|
127068
|
+
function truncateMiddle(line, maxWidth, ellipsis) {
|
|
127069
|
+
if (maxWidth <= 0) return "";
|
|
127070
|
+
const textWidth = visibleWidth(line);
|
|
127071
|
+
if (textWidth <= maxWidth) return line;
|
|
127072
|
+
const ellipsisWidth = visibleWidth(ellipsis);
|
|
127073
|
+
if (ellipsisWidth >= maxWidth) return truncateToWidth(line, maxWidth, ellipsis);
|
|
127074
|
+
const keepWidth = maxWidth - ellipsisWidth;
|
|
127075
|
+
const headWidth = Math.max(0, Math.ceil(keepWidth / 2));
|
|
127076
|
+
const tailWidth = keepWidth - headWidth;
|
|
127077
|
+
const head = headWidth > 0 ? sliceByColumn(line, 0, headWidth) : "";
|
|
127078
|
+
const tail = tailWidth > 0 ? sliceByColumn(line, textWidth - tailWidth, tailWidth) : "";
|
|
127079
|
+
const RESET = "\x1B[0m";
|
|
127080
|
+
return head + RESET + ellipsis + RESET + tail;
|
|
127081
|
+
}
|
|
126750
127082
|
var FooterComponent = class {
|
|
126751
127083
|
state;
|
|
126752
127084
|
colors;
|
|
@@ -126855,13 +127187,21 @@ var FooterComponent = class {
|
|
|
126855
127187
|
}
|
|
126856
127188
|
const rightWidth = visibleWidth(rightText);
|
|
126857
127189
|
const gap = 3;
|
|
127190
|
+
const ellipsis = chalk.hex(colors.textDim)("…");
|
|
126858
127191
|
let line1;
|
|
126859
127192
|
if (leftWidth + gap + rightWidth <= width) {
|
|
126860
127193
|
const pad = width - leftWidth - rightWidth;
|
|
126861
127194
|
line1 = leftLine + " ".repeat(pad) + rightText;
|
|
126862
|
-
} else
|
|
126863
|
-
|
|
126864
|
-
|
|
127195
|
+
} else {
|
|
127196
|
+
const targetLeft = width - gap - rightWidth;
|
|
127197
|
+
if (targetLeft > 0) {
|
|
127198
|
+
const shrunkLeft = truncateMiddle(leftLine, targetLeft, ellipsis);
|
|
127199
|
+
const pad = width - visibleWidth(shrunkLeft) - rightWidth;
|
|
127200
|
+
line1 = shrunkLeft + " ".repeat(pad) + rightText;
|
|
127201
|
+
} else if (leftWidth <= width) line1 = leftLine + " ".repeat(width - leftWidth);
|
|
127202
|
+
else line1 = truncateMiddle(leftLine, width, ellipsis);
|
|
127203
|
+
}
|
|
127204
|
+
return [truncateToWidth(line1, width, "…")];
|
|
126865
127205
|
}
|
|
126866
127206
|
};
|
|
126867
127207
|
//#endregion
|
|
@@ -128874,7 +129214,7 @@ async function guidedGoalSetup(host) {
|
|
|
128874
129214
|
host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
|
|
128875
129215
|
return;
|
|
128876
129216
|
}
|
|
128877
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
129217
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-B1ak519Y.mjs");
|
|
128878
129218
|
const initialDesc = await promptText(host, TextInputDialogComponent, {
|
|
128879
129219
|
title: t("goal.setup_title_initial"),
|
|
128880
129220
|
subtitle: t("goal.setup_desc_hint"),
|
|
@@ -128901,7 +129241,7 @@ async function guidedGoalSetup(host) {
|
|
|
128901
129241
|
await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
|
|
128902
129242
|
}
|
|
128903
129243
|
async function showGoalConfigWizard(host, session, objective, replace) {
|
|
128904
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
129244
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-B1ak519Y.mjs");
|
|
128905
129245
|
const turnInput = await promptNumber(host, TextInputDialogComponent, {
|
|
128906
129246
|
title: t("goal.wizard_title", { objective }),
|
|
128907
129247
|
subtitle: t("goal.budget_turns_hint"),
|
|
@@ -130301,6 +130641,18 @@ function makeDiffStyles(colors) {
|
|
|
130301
130641
|
};
|
|
130302
130642
|
}
|
|
130303
130643
|
const ANSI_RE = /\u001B\[[0-9;]*m/;
|
|
130644
|
+
const ANSI_RESET_RE = /\u001B\[(?:0|39)m/g;
|
|
130645
|
+
/**
|
|
130646
|
+
* Extract the leading ANSI SGR prefix a chalk-style color function applies,
|
|
130647
|
+
* e.g. `\x1b[38;2;121;235;0m` for a truecolor hex. Returns `''` when the
|
|
130648
|
+
* function emits no color (chalk level 0 / non-color environment).
|
|
130649
|
+
*/
|
|
130650
|
+
function extractAnsiPrefix(fn) {
|
|
130651
|
+
const sentinel = "\0";
|
|
130652
|
+
const styled = fn(sentinel);
|
|
130653
|
+
const idx = styled.indexOf(sentinel);
|
|
130654
|
+
return idx >= 0 ? styled.slice(0, idx) : "";
|
|
130655
|
+
}
|
|
130304
130656
|
/** Visualize leading whitespace: tabs as `->`, leading spaces as `·`. */
|
|
130305
130657
|
function visualizeIndent(line) {
|
|
130306
130658
|
let i = 0;
|
|
@@ -130330,6 +130682,11 @@ function visualizeIndent(line) {
|
|
|
130330
130682
|
* runs on the raw code first; indent visualization then runs on the
|
|
130331
130683
|
* highlighted string - leading whitespace carries no token color, so it is
|
|
130332
130684
|
* still plain and can be replaced and dimmed without disturbing the tokens.
|
|
130685
|
+
*
|
|
130686
|
+
* When highlighting is on and produced token colors, the diff line color is
|
|
130687
|
+
* layered as the base foreground: syntax tokens override it, but every ANSI
|
|
130688
|
+
* reset re-injects the diff color so non-token spans (punctuation, operators,
|
|
130689
|
+
* whitespace) stay green/red instead of falling back to the terminal default.
|
|
130333
130690
|
* When highlighting is off (streaming) or produced no token colors, the code
|
|
130334
130691
|
* part is colored with the diff line color instead.
|
|
130335
130692
|
*/
|
|
@@ -130338,7 +130695,11 @@ function renderDiffCode(code, colorFn, highlight, lang) {
|
|
|
130338
130695
|
const indent = text.slice(0, indentEnd);
|
|
130339
130696
|
const rest = text.slice(indentEnd);
|
|
130340
130697
|
const dimIndent = indent.length > 0 ? chalk.dim(indent) : indent;
|
|
130341
|
-
if (highlight && ANSI_RE.test(rest))
|
|
130698
|
+
if (highlight && ANSI_RE.test(rest)) {
|
|
130699
|
+
const prefix = extractAnsiPrefix(colorFn);
|
|
130700
|
+
if (prefix.length > 0) return dimIndent + prefix + rest.replace(ANSI_RESET_RE, (m) => m + prefix) + "\x1B[39m";
|
|
130701
|
+
return dimIndent + rest;
|
|
130702
|
+
}
|
|
130342
130703
|
return dimIndent + colorFn(rest);
|
|
130343
130704
|
}
|
|
130344
130705
|
/**
|
|
@@ -130765,34 +131126,43 @@ function truncateTailBytes(text, maxBytes) {
|
|
|
130765
131126
|
}
|
|
130766
131127
|
/**
|
|
130767
131128
|
* Component that renders tool output with wrap-aware line truncation.
|
|
130768
|
-
* Uses pi-tui's Text component to compute actual visual wrapped lines,
|
|
130769
|
-
*
|
|
130770
|
-
*
|
|
131129
|
+
* Uses pi-tui's Text component to compute actual visual wrapped lines, then
|
|
131130
|
+
* caps at `maxLines`. When collapsed the TAIL is shown (newest output, where
|
|
131131
|
+
* command errors land) with an expand hint at the top; when expanded the full
|
|
131132
|
+
* output is shown with a collapse hint at the top. Handles long single-line
|
|
131133
|
+
* output (e.g. JSON blobs) that would otherwise wrap to dozens of visual rows.
|
|
130771
131134
|
*/
|
|
130772
131135
|
var TruncatedOutputComponent = class {
|
|
130773
131136
|
textComponent;
|
|
130774
131137
|
expanded;
|
|
130775
131138
|
maxLines;
|
|
130776
131139
|
hintFormatter;
|
|
131140
|
+
collapseHintFormatter;
|
|
130777
131141
|
constructor(output, options) {
|
|
130778
131142
|
this.expanded = options.expanded;
|
|
130779
131143
|
this.maxLines = options.maxLines ?? PREVIEW_LINES;
|
|
130780
131144
|
this.hintFormatter = options.hintFormatter;
|
|
131145
|
+
this.collapseHintFormatter = options.collapseHintFormatter;
|
|
130781
131146
|
const tint = options.isError ? chalk.hex(options.colors.error) : chalk.dim;
|
|
130782
131147
|
const cleaned = trimTrailingEmptyLines(output.split("\n")).join("\n");
|
|
130783
|
-
const
|
|
130784
|
-
|
|
131148
|
+
const stripped = options.isError ? cleaned.replaceAll(/\u001B\[[0-9;]*m/g, "") : cleaned;
|
|
131149
|
+
const tinted = (options.maxBytes === void 0 ? stripped : truncateTailBytes(stripped, options.maxBytes)).split("\n").map((line) => tint(line)).join("\n");
|
|
131150
|
+
this.textComponent = new Text(tinted, 2, 0);
|
|
130785
131151
|
}
|
|
130786
131152
|
invalidate() {
|
|
130787
131153
|
this.textComponent.invalidate();
|
|
130788
131154
|
}
|
|
130789
131155
|
render(width) {
|
|
130790
131156
|
const contentLines = this.textComponent.render(width);
|
|
130791
|
-
if (
|
|
130792
|
-
|
|
131157
|
+
if (contentLines.length <= this.maxLines) return contentLines;
|
|
131158
|
+
if (this.expanded) {
|
|
131159
|
+
const collapseHint = this.collapseHintFormatter ? this.collapseHintFormatter() : t("shell.collapse_hint");
|
|
131160
|
+
return [chalk.dim(collapseHint), ...contentLines];
|
|
131161
|
+
}
|
|
130793
131162
|
const remaining = contentLines.length - this.maxLines;
|
|
130794
|
-
const
|
|
130795
|
-
|
|
131163
|
+
const tail = contentLines.slice(-this.maxLines);
|
|
131164
|
+
const expandHint = this.hintFormatter ? this.hintFormatter(remaining) : t("shell.more_lines", { count: String(remaining) });
|
|
131165
|
+
return [chalk.dim(expandHint), ...tail];
|
|
130796
131166
|
}
|
|
130797
131167
|
};
|
|
130798
131168
|
const renderTruncated = (_toolCall, result, ctx) => {
|
|
@@ -130809,7 +131179,7 @@ var ShellExecutionComponent = class extends Container {
|
|
|
130809
131179
|
constructor(options) {
|
|
130810
131180
|
super();
|
|
130811
131181
|
if (options.showCommand === true) this.addCommandPreview(options.command ?? "", options.commandPreviewLines);
|
|
130812
|
-
if (options.result !== void 0) this.addResultPreview(options.result, options.colors, options.expanded ?? false, options.resultPreviewLines ??
|
|
131182
|
+
if (options.result !== void 0) this.addResultPreview(options.result, options.colors, options.expanded ?? false, options.resultPreviewLines ?? 15);
|
|
130813
131183
|
}
|
|
130814
131184
|
addCommandPreview(command, previewLines) {
|
|
130815
131185
|
if (command.length === 0) return;
|
|
@@ -130828,7 +131198,8 @@ var ShellExecutionComponent = class extends Container {
|
|
|
130828
131198
|
colors,
|
|
130829
131199
|
maxLines: previewLines,
|
|
130830
131200
|
maxBytes: MAX_SHELL_OUTPUT_BYTES,
|
|
130831
|
-
hintFormatter: (remaining) => t("shell.more_lines", { count: String(remaining) })
|
|
131201
|
+
hintFormatter: (remaining) => t("shell.more_lines", { count: String(remaining) }),
|
|
131202
|
+
collapseHintFormatter: () => t("shell.collapse_hint")
|
|
130832
131203
|
}));
|
|
130833
131204
|
}
|
|
130834
131205
|
};
|
|
@@ -139051,6 +139422,8 @@ var SessionEventHandler = class {
|
|
|
139051
139422
|
this.host.showNotice(title, detail);
|
|
139052
139423
|
}
|
|
139053
139424
|
handleStepRetrying(event) {
|
|
139425
|
+
this.host.streamingUI.resetLiveText();
|
|
139426
|
+
this.host.streamingUI.resetToolUi();
|
|
139054
139427
|
this.host.setAppState({ reconnectAttempt: event.nextAttempt });
|
|
139055
139428
|
}
|
|
139056
139429
|
maybeShowDebugTiming(event) {
|
|
@@ -147665,6 +148038,7 @@ var ScreamTUI = class {
|
|
|
147665
148038
|
deferUserMessages = false;
|
|
147666
148039
|
aborted = false;
|
|
147667
148040
|
isShuttingDown = false;
|
|
148041
|
+
tightModeHandler = null;
|
|
147668
148042
|
reverseRpcDisposers = [];
|
|
147669
148043
|
startupNotice;
|
|
147670
148044
|
updatePrefetched;
|
|
@@ -147761,6 +148135,11 @@ var ScreamTUI = class {
|
|
|
147761
148135
|
this.inputController.setupAutocomplete();
|
|
147762
148136
|
}
|
|
147763
148137
|
async start() {
|
|
148138
|
+
this.tightModeHandler = () => {
|
|
148139
|
+
setTightMode((process.stdout.columns ?? 80) < 60);
|
|
148140
|
+
};
|
|
148141
|
+
this.tightModeHandler();
|
|
148142
|
+
process.stdout.on("resize", this.tightModeHandler);
|
|
147764
148143
|
this.lifecycleController.installSignalHandlers();
|
|
147765
148144
|
try {
|
|
147766
148145
|
const shouldReplayHistory = await this.initMainTui();
|
|
@@ -147838,6 +148217,11 @@ var ScreamTUI = class {
|
|
|
147838
148217
|
async stop(exitCode) {
|
|
147839
148218
|
if (this.isShuttingDown) return;
|
|
147840
148219
|
this.isShuttingDown = true;
|
|
148220
|
+
if (this.tightModeHandler !== null) {
|
|
148221
|
+
process.stdout.off("resize", this.tightModeHandler);
|
|
148222
|
+
this.tightModeHandler = null;
|
|
148223
|
+
}
|
|
148224
|
+
setTightMode(false);
|
|
147841
148225
|
this.lifecycleController.stopCcConnectPolling();
|
|
147842
148226
|
this.lifecycleController.uninstallSignalHandlers();
|
|
147843
148227
|
this.aborted = true;
|