@byfriends/agent-core 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,10 +1,10 @@
1
- import { a as fromByfErrorPayload, c as toByfErrorPayload, d as ErrorCodes, l as ByfError, o as isByfError, r as normalizeWorkDir, s as makeErrorPayload, t as SessionStore, u as BYF_ERROR_INFO } from "./store-DhTph1cB.mjs";
1
+ import { a as fromByfErrorPayload, c as toByfErrorPayload, d as ErrorCodes, l as ByfError, o as isByfError, r as normalizeWorkDir, s as makeErrorPayload, t as SessionStore, u as BYF_ERROR_INFO } from "./store-7tEWsiOk.mjs";
2
2
  import { a as resolveWireMigrations, n as isNewerWireVersion, r as migrateWireRecord, t as AGENT_WIRE_PROTOCOL_VERSION } from "./migration-Bg8uYnT_.mjs";
3
3
  import { createRequire } from "node:module";
4
- import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
4
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
5
5
  import * as nativePath from "node:path";
6
6
  import path, { basename, dirname, join, posix, relative, resolve } from "node:path";
7
- import { APIConnectionError, APIContextOverflowError, APIEmptyResponseError, APIStatusError, APITimeoutError, UNKNOWN_CAPABILITY, addUsage, createProvider, createToolMessage, emptyUsage, generate, inputTotal } from "@byfriends/kosong";
7
+ import { APIConnectionError, APIContextOverflowError, APIEmptyResponseError, APIStatusError, APITimeoutError, UNKNOWN_CAPABILITY, addUsage, cacheHitRate, createProvider, createToolMessage, emptyUsage, generate, inputTotal } from "@byfriends/kosong";
8
8
  import { access, appendFile, chmod, mkdir, mkdtemp, open, readFile, readdir, rename, rm, stat, unlink } from "node:fs/promises";
9
9
  import * as nodeFs from "node:fs";
10
10
  import { appendFileSync, chmodSync, closeSync, constants, createReadStream, createWriteStream, existsSync, fsyncSync, mkdirSync, openSync, promises, readFileSync, renameSync, statSync, unlinkSync, writeSync } from "node:fs";
@@ -21,7 +21,7 @@ import { pipeline } from "node:stream/promises";
21
21
  import { extract } from "tar";
22
22
  import { fromBuffer } from "yauzl";
23
23
  import { StringDecoder } from "node:string_decoder";
24
- import { createControlledPromise, objectMap, sleep, uniq } from "@antfu/utils";
24
+ import { createControlledPromise, objectMap, sleep } from "@antfu/utils";
25
25
  import * as retry from "retry";
26
26
  import { spawn } from "node:child_process";
27
27
  import Ajv from "ajv";
@@ -118,7 +118,7 @@ function redactCtx(ctx) {
118
118
  };
119
119
  return walk(ctx, 0);
120
120
  }
121
- function truncate$1(value, max) {
121
+ function truncate(value, max) {
122
122
  return value.length <= max ? value : value.slice(0, max - 1) + ELLIPSIS;
123
123
  }
124
124
  function serializeValue(raw) {
@@ -142,7 +142,7 @@ function quote(value) {
142
142
  return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n")}"`;
143
143
  }
144
144
  function formatPair(key, raw) {
145
- const limited = truncate$1(serializeValue(raw), CTX_VALUE_MAX_CHARS);
145
+ const limited = truncate(serializeValue(raw), CTX_VALUE_MAX_CHARS);
146
146
  return `${SAFE_KEY_RE.test(key) ? key : quote(key)}=${/[\s="\\]/.test(limited) || limited.length === 0 ? quote(limited) : limited}`;
147
147
  }
148
148
  function clipBytes(text, maxBytes) {
@@ -166,7 +166,7 @@ function indentStack(stack) {
166
166
  function formatEntry(entry, options = {}) {
167
167
  const ctx = entry.ctx ? redactCtx(entry.ctx) : void 0;
168
168
  const omitContextKeys = new Set(options.omitContextKeys ?? []);
169
- const msg = truncate$1(entry.msg, 200);
169
+ const msg = truncate(entry.msg, 200);
170
170
  const pairs = [];
171
171
  if (ctx) for (const [k, v] of Object.entries(ctx)) {
172
172
  if (omitContextKeys.has(k)) continue;
@@ -813,85 +813,6 @@ const RawAgentProfileSchema = z.object({
813
813
  whenToUse: z.string().optional(),
814
814
  subagents: z.record(z.string(), RawSubagentProfileSchema).optional()
815
815
  });
816
- async function collectEntries(kaos, dirPath, maxWidth, pathClass) {
817
- const all = [];
818
- try {
819
- for await (const fullPath of kaos.iterdir(dirPath)) {
820
- const name = basename$1(fullPath, pathClass);
821
- let isDir = false;
822
- try {
823
- isDir = ((await kaos.stat(fullPath)).stMode & 61440) === 16384;
824
- } catch {}
825
- all.push({
826
- name,
827
- isDir
828
- });
829
- }
830
- } catch {
831
- return {
832
- entries: [],
833
- total: 0,
834
- readable: false
835
- };
836
- }
837
- all.sort((a, b) => {
838
- if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
839
- return a.name.localeCompare(b.name);
840
- });
841
- return {
842
- entries: all.slice(0, maxWidth),
843
- total: all.length,
844
- readable: true
845
- };
846
- }
847
- function pathMod$5(pathClass) {
848
- return pathClass === "win32" ? win32Path : posixPath;
849
- }
850
- function basename$1(p, pathClass) {
851
- return pathMod$5(pathClass).basename(p);
852
- }
853
- /**
854
- * Return a 2-level tree listing of `workDir` suitable for inclusion in a
855
- * tool error message. Returns `"(empty directory)"` if the directory is
856
- * empty, or an error marker line if the directory itself is unreadable.
857
- */
858
- async function listDirectory(kaos, workDir) {
859
- const lines = [];
860
- const pathClass = kaos.pathClass();
861
- const { entries, total, readable } = await collectEntries(kaos, workDir, 30, pathClass);
862
- if (!readable) return "[not readable]";
863
- const remaining = total - entries.length;
864
- for (let i = 0; i < entries.length; i++) {
865
- const entry = entries[i];
866
- if (entry === void 0) continue;
867
- const { name, isDir } = entry;
868
- const isLast = i === entries.length - 1 && remaining === 0;
869
- const connector = isLast ? "└── " : "├── ";
870
- if (isDir) {
871
- lines.push(`${connector}${name}/`);
872
- const childPrefix = isLast ? " " : "│ ";
873
- const child = await collectEntries(kaos, joinPath$1(workDir, name, pathClass), 10, pathClass);
874
- if (!child.readable) {
875
- lines.push(`${childPrefix}└── [not readable]`);
876
- continue;
877
- }
878
- const childRemaining = child.total - child.entries.length;
879
- for (let j = 0; j < child.entries.length; j++) {
880
- const ce = child.entries[j];
881
- if (ce === void 0) continue;
882
- const cConnector = j === child.entries.length - 1 && childRemaining === 0 ? "└── " : "├── ";
883
- const suffix = ce.isDir ? "/" : "";
884
- lines.push(`${childPrefix}${cConnector}${ce.name}${suffix}`);
885
- }
886
- if (childRemaining > 0) lines.push(`${childPrefix}└── ... and ${String(childRemaining)} more`);
887
- } else lines.push(`${connector}${name}`);
888
- }
889
- if (remaining > 0) lines.push(`└── ... and ${String(remaining)} more entries`);
890
- return lines.length > 0 ? lines.join("\n") : "(empty directory)";
891
- }
892
- function joinPath$1(parent, child, pathClass) {
893
- return pathMod$5(pathClass).join(parent, child);
894
- }
895
816
  //#endregion
896
817
  //#region src/profile/context.ts
897
818
  const AGENTS_MD_MAX_BYTES = 32 * 1024;
@@ -902,11 +823,9 @@ function resolveSystemPromptCwd(kaos, cwd) {
902
823
  }
903
824
  async function prepareSystemPromptContext(kaos, cwd) {
904
825
  const resolvedCwd = resolveSystemPromptCwd(kaos, cwd);
905
- const [cwdListing, agentsMd] = await Promise.all([listDirectory(kaos, resolvedCwd), loadAgentsMd(kaos, resolvedCwd)]);
906
826
  return {
907
827
  cwd: resolvedCwd,
908
- cwdListing,
909
- agentsMd
828
+ agentsMd: await loadAgentsMd(kaos, resolvedCwd)
910
829
  };
911
830
  }
912
831
  async function loadAgentsMd(kaos, workDir) {
@@ -923,17 +842,17 @@ async function loadAgentsMd(kaos, workDir) {
923
842
  return true;
924
843
  };
925
844
  const home = kaos.gethome();
926
- await collect(joinPath(kaos, home, ".byf", "AGENTS.md"));
927
- const genericFiles = [joinPath(kaos, home, ".agents")].flatMap((dir) => ["AGENTS.md", "agents.md"].map((name) => joinPath(kaos, dir, name)));
845
+ await collect(joinPath$2(kaos, home, ".byf", "AGENTS.md"));
846
+ const genericFiles = [joinPath$2(kaos, home, ".agents")].flatMap((dir) => ["AGENTS.md", "agents.md"].map((name) => joinPath$2(kaos, dir, name)));
928
847
  for (const file of genericFiles) if (await collect(file)) break;
929
848
  for (const dir of dirs) {
930
- await collect(joinPath(kaos, dir, ".byf", "AGENTS.md"));
931
- for (const fileName of ["AGENTS.md", "agents.md"]) if (await collect(joinPath(kaos, dir, fileName))) break;
849
+ await collect(joinPath$2(kaos, dir, ".byf", "AGENTS.md"));
850
+ for (const fileName of ["AGENTS.md", "agents.md"]) if (await collect(joinPath$2(kaos, dir, fileName))) break;
932
851
  }
933
852
  return renderAgentFiles(discovered);
934
853
  }
935
854
  async function findProjectRoot$1(kaos, workDir) {
936
- const path = pathMod$4(kaos);
855
+ const path = pathMod$6(kaos);
937
856
  const initial = kaos.normpath(workDir);
938
857
  let current = initial;
939
858
  while (true) {
@@ -944,7 +863,7 @@ async function findProjectRoot$1(kaos, workDir) {
944
863
  }
945
864
  }
946
865
  function dirsRootToLeaf(kaos, workDir, projectRoot) {
947
- const path = pathMod$4(kaos);
866
+ const path = pathMod$6(kaos);
948
867
  const dirs = [];
949
868
  let current = kaos.normpath(workDir);
950
869
  while (true) {
@@ -1019,10 +938,10 @@ function byteLength(text) {
1019
938
  function annotationFor(path) {
1020
939
  return `<!-- From: ${path} -->\n`;
1021
940
  }
1022
- function joinPath(kaos, ...parts) {
1023
- return pathMod$4(kaos).join(...parts);
941
+ function joinPath$2(kaos, ...parts) {
942
+ return pathMod$6(kaos).join(...parts);
1024
943
  }
1025
- function pathMod$4(kaos) {
944
+ function pathMod$6(kaos) {
1026
945
  return kaos.pathClass() === "win32" ? win32Path : posixPath;
1027
946
  }
1028
947
  //#endregion
@@ -1047,6 +966,51 @@ function renderPrompt(template, vars) {
1047
966
  return env.renderString(template, vars);
1048
967
  }
1049
968
  //#endregion
969
+ //#region src/utils/tokens.ts
970
+ /**
971
+ * Estimate token count from text using a character-based heuristic.
972
+ * - ASCII (~4 chars per token)
973
+ * - CJK and other non-ASCII (~1 char per token)
974
+ * The estimate is transient — the next LLM call returns the real count
975
+ * and supersedes this value. Used to keep `tokenCountWithPending`
976
+ * monotonic between LLM round-trips without paying for a tokenizer.
977
+ */
978
+ function estimateTokens(text) {
979
+ let asciiCount = 0;
980
+ let nonAsciiCount = 0;
981
+ for (const char of text) if (char.codePointAt(0) <= 127) asciiCount++;
982
+ else nonAsciiCount++;
983
+ return Math.ceil(asciiCount / 4) + nonAsciiCount;
984
+ }
985
+ function estimateTokensForMessages(messages) {
986
+ let total = 0;
987
+ for (const message of messages) total += estimateTokensForMessage(message);
988
+ return total;
989
+ }
990
+ function estimateTokensForTools(tools) {
991
+ let total = 0;
992
+ for (const tool of tools) {
993
+ total += estimateTokens(tool.name);
994
+ total += estimateTokens(tool.description);
995
+ total += estimateTokens(JSON.stringify(tool.parameters));
996
+ }
997
+ return total;
998
+ }
999
+ function estimateTokensForMessage(message) {
1000
+ let total = estimateTokens(message.role);
1001
+ for (const part of message.content) total += estimateTokensForContentPart(part);
1002
+ if (message.toolCalls !== void 0) for (const call of message.toolCalls) {
1003
+ total += estimateTokens(call.name);
1004
+ total += estimateTokens(JSON.stringify(call.arguments));
1005
+ }
1006
+ return total;
1007
+ }
1008
+ function estimateTokensForContentPart(part) {
1009
+ if (part.type === "text") return estimateTokens(part.text);
1010
+ else if (part.type === "think") return estimateTokens(part.think);
1011
+ return 0;
1012
+ }
1013
+ //#endregion
1050
1014
  //#region src/profile/resolve.ts
1051
1015
  /**
1052
1016
  * Resolve agent profiles with extends inheritance.
@@ -1132,15 +1096,14 @@ function createSystemPromptRenderer(merged) {
1132
1096
  }
1133
1097
  function buildTemplateVars(context, promptVars) {
1134
1098
  const skills = typeof context.skills === "string" ? context.skills : context.skills?.getModelSkillListing() ?? "";
1135
- const now = context.now instanceof Date ? context.now.toISOString() : context.now ?? (/* @__PURE__ */ new Date()).toISOString();
1099
+ const agentsMd = context.agentsMd ?? "";
1136
1100
  return {
1137
1101
  ...promptVars,
1138
1102
  BYF_OS: context.osEnv.osKind,
1139
1103
  BYF_SHELL: `${context.osEnv.shellName} (\`${context.osEnv.shellPath}\`)`,
1140
- BYF_NOW: now,
1141
1104
  BYF_WORK_DIR: context.cwd,
1142
- BYF_WORK_DIR_LS: context.cwdListing ?? "",
1143
- BYF_AGENTS_MD: context.agentsMd ?? "",
1105
+ BYF_AGENTS_MD: agentsMd,
1106
+ BYF_AGENTS_MD_TOO_LONG: estimateTokens(agentsMd) > 4e3 ? "true" : "",
1144
1107
  BYF_SKILLS: skills,
1145
1108
  BYF_ADDITIONAL_DIRS_INFO: context.additionalDirsInfo ?? "",
1146
1109
  ROLE_ADDITIONAL: context.roleAdditional ?? promptVars["ROLE_ADDITIONAL"] ?? promptVars["roleAdditional"] ?? ""
@@ -1215,13 +1178,13 @@ function normalizeSourcePath(path) {
1215
1178
  }
1216
1179
  //#endregion
1217
1180
  //#region src/profile/default/agent.yaml
1218
- var agent_default$1 = "name: agent\ndescription: Default BYF agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskStop\n - ReadMediaFile\n - TodoList\n - Skill\n - WebSearch\n - Agent\n - FetchURL\n - AskUserQuestion\n - EnterPlanMode\n - ExitPlanMode\n - mcp__*\n\nsubagents:\n coder:\n description: Good at general software engineering tasks.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n plan:\n description: Read-only implementation planning and architecture design.\n";
1181
+ var agent_default$1 = "name: agent\ndescription: Default BYF agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskStop\n - ReadMediaFile\n - TodoList\n - Skill\n - WebSearch\n - Agent\n - FetchURL\n - AskUserQuestion\n - mcp__*\n\nsubagents:\n coder:\n description: Good at general software engineering tasks.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n";
1219
1182
  //#endregion
1220
1183
  //#region src/profile/default/coder.yaml
1221
- var coder_default = "extends: agent\nname: coder\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\nwhenToUse: |\n Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - mcp__*\n";
1184
+ var coder_default = "extends: agent\nname: coder\npromptVars:\n roleAdditional: |\n You are operating as a subagent instance. The main BYF agent spawned you to handle a specific task. All user messages come from the main agent it cannot see your context and will only see your final result when you finish. Do not address the end user directly. If something is unclear, explain the ambiguity in your summary to the parent agent.\nwhenToUse: |\n Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - mcp__*\n";
1222
1185
  //#endregion
1223
1186
  //#region src/profile/default/explore.yaml
1224
- var explore_default = "extends: agent\nname: explore\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n You are a codebase exploration specialist. Your role is EXCLUSIVELY to search, read, and analyze existing code and resources. You do NOT have access to file editing tools.\n\n Your strengths:\n - Rapidly finding files using glob patterns\n - Searching code and text with powerful regex patterns\n - Reading and analyzing file contents\n - Running read-only shell commands (git log, git diff, ls, find, etc.)\n\n Guidelines:\n - Use Glob for broad file pattern matching. Patterns MUST contain a literal anchor (extension or subdirectory); pure wildcards like `*` or `**/*` are rejected by the tool.\n - Use Grep for searching file contents with regex\n - Use Read when you know the specific file path\n - Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find)\n - NEVER use Bash for any file creation or modification commands\n - Adapt your search depth based on the thoroughness level specified by the caller\n - Wherever possible, spawn multiple parallel tool calls for grepping and reading files to maximize speed\n\n If the prompt includes a <git-context> block, use it to orient yourself about the repository state before starting your investigation.\n\n You are meant to be a fast agent. Complete the search request efficiently and report your findings clearly in a structured format.\nwhenToUse: |\n Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (e.g. \"src/**/*.yaml\"), search code for keywords (e.g. \"database connection\"), or answer questions about the codebase (e.g. \"how does the auth module work?\"). When calling this agent, specify the desired thoroughness level: \"quick\" for basic searches, \"medium\" for moderate exploration, or \"thorough\" for comprehensive analysis across multiple locations and naming conventions. Use this agent for any read-only exploration that will clearly require more than 3 search queries. Prefer launching multiple explore agents concurrently when investigating independent questions.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - WebSearch\n - FetchURL\n";
1187
+ var explore_default = "extends: agent\nname: explore\npromptVars:\n roleAdditional: |\n You are operating as a subagent instance. The main BYF agent spawned you to handle a specific task. All user messages come from the main agent it cannot see your context and will only see your final result when you finish. Do not address the end user directly. If something is unclear, explain the ambiguity in your summary to the parent agent.\n\n You are a codebase exploration specialist. Your role is EXCLUSIVELY to search, read, and analyze existing code and resources. You do NOT have access to file editing tools.\n\n Your strengths:\n - Rapidly finding files using glob patterns\n - Searching code and text with powerful regex patterns\n - Reading and analyzing file contents\n - Running read-only shell commands (git log, git diff, ls, find, etc.)\n\n Guidelines:\n - Use Glob for broad file pattern matching. Patterns MUST contain a literal anchor (extension or subdirectory); pure wildcards like `*` or `**/*` are rejected by the tool.\n - Use Grep for searching file contents with regex\n - Use Read when you know the specific file path\n - Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find)\n - NEVER use Bash for any file creation or modification commands\n - Adapt your search depth based on the thoroughness level specified by the caller\n - Wherever possible, spawn multiple parallel tool calls for grepping and reading files to maximize speed\n\n If the prompt includes a <git-context> block, use it to orient yourself about the repository state before starting your investigation.\n\n You are meant to be a fast agent. Complete the search request efficiently and report your findings clearly in a structured format.\nwhenToUse: |\n Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (e.g. \"src/**/*.yaml\"), search code for keywords (e.g. \"database connection\"), or answer questions about the codebase (e.g. \"how does the auth module work?\"). When calling this agent, specify the desired thoroughness level: \"quick\" for basic searches, \"medium\" for moderate exploration, or \"thorough\" for comprehensive analysis across multiple locations and naming conventions. Use this agent for any read-only exploration that will clearly require more than 3 search queries. Prefer launching multiple explore agents concurrently when investigating independent questions.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - WebSearch\n - FetchURL\n";
1225
1188
  //#endregion
1226
1189
  //#region src/profile/default/init.md
1227
1190
  var init_default = "You are a software engineering expert with many years of programming experience. Please explore the current project directory to understand the project's architecture and main details.\n\nTask requirements:\n1. Analyze the project structure and identify key configuration files (such as pyproject.toml, package.json, Cargo.toml, etc.).\n2. Understand the project's technology stack, build process and runtime architecture.\n3. Identify how the code is organized and main module divisions.\n4. Discover project-specific development conventions, testing strategies, and deployment processes.\n\nAfter the exploration, you should do a thorough summary of your findings and overwrite it into `AGENTS.md` file in the project root. You need to refer to what is already in the file when you do so.\n\nFor your information, `AGENTS.md` is a file intended to be read by AI coding agents. Expect the reader of this file know nothing about the project.\n\nYou should compose this file according to the actual project content. Do not make any assumptions or generalizations. Ensure the information is accurate and useful. You must use the natural language that is mainly used in the project's comments and documentation.\n\nPopular sections that people usually write in `AGENTS.md` are:\n\n- Project overview\n- Build and test commands\n- Code style guidelines\n- Testing instructions\n- Security considerations\n";
@@ -1231,15 +1194,13 @@ const PROFILE_SOURCES = {
1231
1194
  "profile/default/agent.yaml": agent_default$1,
1232
1195
  "profile/default/coder.yaml": coder_default,
1233
1196
  "profile/default/explore.yaml": explore_default,
1234
- "profile/default/plan.yaml": "extends: agent\nname: plan\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n Before designing your implementation plan, consider whether you fully understand the codebase areas relevant to the task. If not, recommend the parent agent to use the explore agent (subagent_type=\"explore\") to investigate key questions first. In your response, clearly state:\n 1. What you already know from the information provided\n 2. What questions remain unanswered that would benefit from explore agent investigation\n 3. Your implementation plan (either preliminary if questions remain, or final if sufficient context exists)\nwhenToUse: |\n Use this agent when the parent agent needs a step-by-step implementation plan, key file identification, and architectural trade-off analysis before code changes are made.\ntools:\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - WebSearch\n - FetchURL\n",
1235
- "profile/default/system.md": "You are BYF, an interactive general AI agent running on a user's computer.\n\nYour primary goal is to help users with software engineering tasks by taking action — use the tools available to you to make real changes on the user's system. You should also answer questions when asked. Always adhere strictly to the following system instructions and the user's requirements.\n\n{{ ROLE_ADDITIONAL }}\n\n# Prompt and Tool Use\n\nThe user's messages may contain questions and/or task descriptions in natural language, code snippets, logs, file paths, or other forms of information. Read them, understand them and do what the user requested. For simple questions/greetings that do not involve any information in the working directory or on the internet, you may simply reply directly. For anything else, default to taking action with tools. When the request could be interpreted as either a question to answer or a task to complete, treat it as a task.\n\nWhen handling the user's request, if it involves creating, modifying, or running code or files, you MUST use the appropriate tools (e.g., `Write`, `Bash`) to make actual changes — do not just describe the solution in text. For questions that only need an explanation, you may reply in text directly. When calling tools, do not provide explanations because the tool calls themselves should be self-explanatory. You MUST follow the description of each tool and its parameters when calling tools.\n\nIf the `Agent` tool is available, you can use it to delegate a focused subtask to a subagent instance. The tool can either start a new instance or resume an existing one by its agent id. Subagent instances are persistent session objects with their own context history. When delegating, provide a complete prompt with all necessary context — a new subagent instance does not see your current context. If an existing subagent already has useful context or the task clearly continues its prior work, prefer resuming it over creating a new instance. Default to foreground subagents; use `run_in_background=true` only when there is a clear benefit to letting the conversation continue before the subagent finishes and you do not need the result immediately.\n\nYou have the capability to output any number of tool calls in a single response. If you anticipate making multiple non-interfering tool calls, you are HIGHLY RECOMMENDED to make them in parallel to significantly improve efficiency. This is very important to your performance.\n\nThe results of the tool calls will be returned to you in a tool message. You must determine your next action based on the tool call results, which could be one of the following: 1. Continue working on the task, 2. Inform the user that the task is completed or has failed, or 3. Ask the user for more information.\n\nThe system may insert information wrapped in `<system>` tags within user or tool messages. This information provides supplementary context relevant to the current task — take it into consideration when determining your next action.\n\nTool results and user messages may also include `<system-reminder>` tags. Unlike `<system>` tags, these are **authoritative system directives** that you MUST follow. They bear no direct relation to the specific tool results or user messages in which they appear. Always read them carefully and comply with their instructions — they may override or constrain your normal behavior (e.g., restricting you to read-only actions during plan mode).\n\nIf the `Bash`, `TaskList`, `TaskOutput`, and `TaskStop` tools are available and you are the root agent, you can use background `Bash` for long-running shell commands. Launch it via `Bash` with `run_in_background=true` and a short `description`. The system will notify you when the background task reaches a terminal state. Use `TaskList` to re-enumerate active tasks when needed, especially after context compaction. Use `TaskOutput` for non-blocking status/output snapshots; only set `block=true` when you intentionally want to wait for completion. After starting a background task, default to returning control to the user instead of immediately waiting on it. Use `TaskStop` only when you need to cancel the task. For human users in the interactive shell, the only task-management slash command is `/tasks`. Do not tell users to run `/task`, `/tasks list`, `/tasks output`, `/tasks stop`, or any other invented slash subcommands. If you are a subagent or these tools are not available, do not assume you can create or control background tasks.\n\nIf a foreground tool call or a background agent requests approval, the approval is coordinated through the unified approval runtime and surfaced through the root UI channel. Do not assume approvals are local to a single subagent turn.\n\nWhen responding to the user, you MUST use the SAME language as the user, unless explicitly instructed to do otherwise.\n\n# General Guidelines for Coding\n\nWhen building something from scratch, you should:\n\n- Understand the user's requirements.\n- Ask the user for clarification if there is anything unclear.\n- Design the architecture and make a plan for the implementation.\n- Write the code in a modular and maintainable way.\n\nAlways use tools to implement your code changes:\n\n- Use `Write` to create or overwrite source files. Code that only appears in your text response is NOT saved to the file system and will not take effect.\n- Use `Bash` to run and test your code after writing it.\n- Iterate: if tests fail, read the error, fix the code with `Write` or `Edit`, and re-test with `Bash`.\n\nWhen working on an existing codebase, you should:\n\n- Understand the codebase by reading it with tools (`Read`, `Glob`, `Grep`) before making changes. Identify the ultimate goal and the most important criteria to achieve the goal.\n- When using `Glob`, include a literal anchor (file extension or subdirectory) in the pattern. Pure wildcards like `*` or `**/*` are rejected by the tool.\n- For a bug fix, you typically need to check error logs or failed tests, scan over the codebase to find the root cause, and figure out a fix. If user mentioned any failed tests, you should make sure they pass after the changes.\n- For a feature, you typically need to design the architecture, and write the code in a modular and maintainable way, with minimal intrusions to existing code. Add new tests if the project already has tests.\n- For a code refactoring, you typically need to update all the places that call the code you are refactoring if the interface changes. DO NOT change any existing logic especially in tests, focus only on fixing any errors caused by the interface changes.\n- Make MINIMAL changes to achieve the goal. This is very important to your performance.\n- Follow the coding style of existing code in the project.\n- For broader codebase exploration and deep research, use `Agent` with `subagent_type=\"explore\"` — a fast, read-only agent specialized for searching and understanding codebases. Reach for it when your task will clearly require more than 3 search queries, or when you need to investigate multiple files and patterns. Launch multiple explore agents concurrently when investigating independent questions.\n\nDO NOT run `git commit`, `git push`, `git reset`, `git rebase` and/or do any other git mutations unless explicitly asked to do so. Ask for confirmation each time when you need to do git mutations, even if the user has confirmed in earlier conversations.\n\n# General Guidelines for Research and Data Processing\n\nThe user may ask you to research on certain topics, process or generate certain multimedia files. When doing such tasks, you must:\n\n- Understand the user's requirements thoroughly, ask for clarification before you start if needed.\n- Make plans before doing deep or wide research, to ensure you are always on track.\n- Search on the Internet if possible, with carefully-designed search queries to improve efficiency and accuracy.\n- Use proper tools or shell commands or Python packages to process or generate images, videos, PDFs, docs, spreadsheets, presentations, or other multimedia files. Detect if there are already such tools in the environment. If you have to install third-party tools/packages, you MUST ensure that they are installed in a virtual/isolated environment.\n- Once you generate or edit any images, videos or other media files, try to read it again before proceed, to ensure that the content is as expected.\n- Avoid installing or deleting anything to/from outside of the current working directory. If you have to do so, ask the user for confirmation.\n\n# Working Environment\n\n## Operating System\n\nYou are running on **{{ BYF_OS }}**. The Bash tool executes commands using **{{ BYF_SHELL }}**.\n{% if BYF_OS == \"Windows\" %}\n\nIMPORTANT: You are on Windows. The Bash tool runs through Git Bash, so use Unix shell syntax inside Bash commands — `/dev/null` not `NUL`, and forward slashes in paths. For file operations, always prefer the built-in tools (Read, Write, Edit, Glob, Grep) over Bash commands — they work reliably across all platforms.\n{% endif %}\n\nThe operating environment is not in a sandbox. Any actions you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.\n\n## Date and Time\n\nThe current date and time in ISO format is `{{ BYF_NOW }}`. This is only a reference for you when searching the web, or checking file modification time, etc. If you need the exact time, use Bash tool with proper command.\n\n## Working Directory\n\nThe current working directory is `{{ BYF_WORK_DIR }}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, IF SO, YOU MUST use absolute paths for these parameters.\n\nThe directory listing of current working directory is:\n\n```\n{{ BYF_WORK_DIR_LS }}\n```\n\nUse this as your basic understanding of the project structure. The tree only shows the first two levels; entries marked \"... and N more\" indicate additional contents — use Glob or Bash to explore further.\n{% if BYF_ADDITIONAL_DIRS_INFO %}\n\n## Additional Directories\n\nThe following directories have been added to the workspace. You can read, write, search, and glob files in these directories as part of your workspace scope.\n\n{{ BYF_ADDITIONAL_DIRS_INFO }}\n{% endif %}\n\n# Project Information\n\nMarkdown files named `AGENTS.md` usually contain the background, structure, coding styles, user preferences and other relevant information about the project. You should use this information to understand the project and the user's preferences. `AGENTS.md` files may exist at different locations in the project, but typically there is one in the project root.\n\n> Why `AGENTS.md`?\n>\n> `README.md` files are for humans: quick starts, project descriptions, and contribution guidelines. `AGENTS.md` complements this by containing the extra, sometimes detailed context coding agents need: build steps, tests, and conventions that might clutter a README or aren’t relevant to human contributors.\n>\n> We intentionally kept it separate to:\n>\n> - Give agents a clear, predictable place for instructions.\n> - Keep `README`s concise and focused on human contributors.\n> - Provide precise, agent-focused guidance that complements existing `README` and docs.\n\nThe `AGENTS.md` instructions (merged from all applicable directories):\n\n`````````\n{{ BYF_AGENTS_MD }}\n`````````\n\n`AGENTS.md` files can appear at any level of the project directory tree, including inside `.byf/` directories. Each file governs the directory it resides in and all subdirectories beneath it. When multiple `AGENTS.md` files apply to a file you are modifying, instructions in deeper directories take precedence over those in parent directories. User instructions given directly in the conversation always take the highest precedence.\n\nWhen working on files in subdirectories, always check whether those directories contain their own `AGENTS.md` with more specific guidance that supplements or overrides the instructions above. You may also check `README`/`README.md` files for more information about the project.\n\nIf you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date.\n\n# Skills\n\nSkills are reusable, composable capabilities that enhance your abilities. Each skill is either a self-contained directory with a `SKILL.md` file or a standalone `.md` file that contains instructions, examples, and/or reference material.\n\n## What are skills?\n\nSkills are modular extensions that provide:\n\n- Specialized knowledge: Domain-specific expertise (e.g., PDF processing, data analysis)\n- Workflow patterns: Best practices for common tasks\n- Tool integrations: Pre-configured tool chains for specific operations\n- Reference material: Documentation, templates, and examples\n\n## Available skills\n\nSkills are grouped by scope (`Project`, `User`, `Extra`, `Built-in`) so you can tell where each came from. When the user refers to \"the skill in this project\" or \"the user-scope skill\", use the scope heading to disambiguate. When multiple scopes define a skill with the same name, the more specific scope takes precedence: **Project overrides User overrides Extra overrides Built-in**.\n\n{{ BYF_SKILLS }}\n\n## How to use skills\n\nIdentify the skills that are likely to be useful for the tasks you are currently working on, read the skill file for detailed instructions, guidelines, scripts and more.\n\nOnly read skill details when needed to conserve the context window.\n\n# Ultimate Reminders\n\nAt any time, you should be HELPFUL, CONCISE, and ACCURATE. Be thorough in your actions — test what you build, verify what you change — not in your explanations.\n\n- Never diverge from the requirements and the goals of the task you work on. Stay on track.\n- Never give the user more than what they want.\n- Try your best to avoid any hallucination. Do fact checking before providing any factual information.\n- Think about the best approach, then take action decisively.\n- Do not give up too early.\n- ALWAYS, keep it stupidly simple. Do not overcomplicate things.\n- When the task requires creating or modifying files, always use tools to do so. Never treat displaying code in your response as a substitute for actually writing it to the file system.\n"
1197
+ "profile/default/system.md": "You are BYF, an AI agent running on the user's computer. Your job is to help\nusers accomplish tasks by taking action — read, write, search, and execute to\nmake real changes on the user's system. Answer questions when asked; otherwise,\nact.\n\nWhen responding, use the same language as the user unless explicitly instructed\notherwise.\n\n{{ ROLE_ADDITIONAL }}\n\n# First Principles\n\nThink from first principles. Strip away assumptions and conventions; every\naction must be traceable to a verifiable fact — the actual file contents,\ncommand output, data, or the user's explicit words. When in doubt, read\nbefore guessing, ask before assuming, verify before claiming.\n\n# Tool Use\n\nUse tools only when the task requires them. If the request can be answered\nwithout reading files, running commands, or searching the web, reply in text\ndirectly. When a request is ambiguous, prefer action — the user can see your\noutput and correct course.\n\nCode that only appears in your text response is NOT saved to the file system\nand will not take effect. To create or modify files, use `Write` or `Edit`.\nTo run commands, use `Bash`.\n\n# Protocol\n\n<system> tags in user or tool messages provide supplementary context. Treat\nthem as background information.\n\n<system-reminder> tags are authoritative directives that override default\nbehavior. They are unrelated to the messages they appear in. Always comply.\n\n# Safety\n\nThe environment is not a sandbox — your actions immediately affect the user's\nsystem.\n\n- Stay within the working directory unless explicitly instructed otherwise.\n- Git operations are destructive and may affect remote repositories. Never\n execute git mutations unless explicitly asked; confirm each time.\n- Avoid installing or deleting anything outside the working directory. If\n necessary, ask for confirmation first.\n\n# Working Environment\n\n## Operating System\n\nYou are running on **{{ BYF_OS }}**. The Bash tool executes commands using **{{ BYF_SHELL }}**.\n{% if BYF_OS == \"Windows\" %}\n\nIMPORTANT: You are on Windows. The Bash tool runs through Git Bash, so use Unix shell syntax inside Bash commands — `/dev/null` not `NUL`, and forward slashes in paths. For file operations, always prefer the built-in tools (Read, Write, Edit, Glob, Grep) over Bash commands — they work reliably across all platforms.\n{% endif %}\n\n## Working Directory\n\nThe current working directory is `{{ BYF_WORK_DIR }}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, IF SO, YOU MUST use absolute paths for these parameters.\n{% if BYF_ADDITIONAL_DIRS_INFO %}\n\n## Additional Directories\n\nThe following directories have been added to the workspace. You can read, write, search, and glob files in these directories as part of your workspace scope.\n\n{{ BYF_ADDITIONAL_DIRS_INFO }}\n{% endif %}\n\n# Project Information\n\n`AGENTS.md` files contain project-specific context, styles, and conventions for agents. They may exist at different locations in the project — each file governs its directory and all subdirectories beneath it. Deeper files take precedence over parent files.\n\nIf instructions conflict:\n- `<system-reminder>` directives override all other instructions, including user messages.\n- Safety rules are hard constraints and must never be violated, even if a user message or AGENTS.md says otherwise.\n- Beyond those two, user messages > AGENTS.md > default system instructions.\n\n{% if BYF_AGENTS_MD_TOO_LONG %}\n> ⚠️ The merged AGENTS.md content exceeds 4,000 tokens. Consider compressing project instructions to reduce context usage.\n{% endif %}\n\nThe `AGENTS.md` instructions (merged from all applicable directories):\n\n`````````\n{{ BYF_AGENTS_MD }}\n`````````\n\nIf you modified anything mentioned in `AGENTS.md` files, update the corresponding files to keep them up-to-date.\n\n# Skills\n\nSkills are reusable capabilities. When a skill from the listing matches the user's request, you MUST call the `Skill` tool (not free-form text).\n\n{{ BYF_SKILLS }}\n"
1236
1198
  };
1237
1199
  const DEFAULT_INIT_PROMPT = init_default;
1238
1200
  const DEFAULT_AGENT_PROFILES = loadAgentProfilesFromSources([
1239
1201
  "agent.yaml",
1240
1202
  "coder.yaml",
1241
- "explore.yaml",
1242
- "plan.yaml"
1203
+ "explore.yaml"
1243
1204
  ].map((file) => `profile/default/${file}`), PROFILE_SOURCES);
1244
1205
  //#endregion
1245
1206
  //#region src/providers/request-auth.ts
@@ -1269,54 +1230,6 @@ const noopTelemetryClient = {
1269
1230
  withContext: () => noopTelemetryClient,
1270
1231
  setContext: () => {}
1271
1232
  };
1272
- function withTelemetryContext(telemetry, patch) {
1273
- return telemetry.withContext?.(patch) ?? telemetry;
1274
- }
1275
- //#endregion
1276
- //#region src/utils/tokens.ts
1277
- /**
1278
- * Estimate token count from text using a character-based heuristic.
1279
- * - ASCII (~4 chars per token)
1280
- * - CJK and other non-ASCII (~1 char per token)
1281
- * The estimate is transient — the next LLM call returns the real count
1282
- * and supersedes this value. Used to keep `tokenCountWithPending`
1283
- * monotonic between LLM round-trips without paying for a tokenizer.
1284
- */
1285
- function estimateTokens(text) {
1286
- let asciiCount = 0;
1287
- let nonAsciiCount = 0;
1288
- for (const char of text) if (char.codePointAt(0) <= 127) asciiCount++;
1289
- else nonAsciiCount++;
1290
- return Math.ceil(asciiCount / 4) + nonAsciiCount;
1291
- }
1292
- function estimateTokensForMessages(messages) {
1293
- let total = 0;
1294
- for (const message of messages) total += estimateTokensForMessage(message);
1295
- return total;
1296
- }
1297
- function estimateTokensForTools(tools) {
1298
- let total = 0;
1299
- for (const tool of tools) {
1300
- total += estimateTokens(tool.name);
1301
- total += estimateTokens(tool.description);
1302
- total += estimateTokens(JSON.stringify(tool.parameters));
1303
- }
1304
- return total;
1305
- }
1306
- function estimateTokensForMessage(message) {
1307
- let total = estimateTokens(message.role);
1308
- for (const part of message.content) total += estimateTokensForContentPart(part);
1309
- if (message.toolCalls !== void 0) for (const call of message.toolCalls) {
1310
- total += estimateTokens(call.name);
1311
- total += estimateTokens(JSON.stringify(call.arguments));
1312
- }
1313
- return total;
1314
- }
1315
- function estimateTokensForContentPart(part) {
1316
- if (part.type === "text") return estimateTokens(part.text);
1317
- else if (part.type === "think") return estimateTokens(part.think);
1318
- return 0;
1319
- }
1320
1233
  //#endregion
1321
1234
  //#region src/loop/errors.ts
1322
1235
  /**
@@ -2357,7 +2270,7 @@ function closeObjectNodes(value) {
2357
2270
  }
2358
2271
  //#endregion
2359
2272
  //#region src/tools/background/task-list.md
2360
- 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- Prefer the default `active_only=true`, which lists only non-terminal tasks.\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- `limit` caps how many tasks are returned. It accepts a value between 1 and\n 100 and defaults to 20 when omitted.\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";
2273
+ 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 stands. It returns a task ID, status, command, description, and PID for each task, plus exit code and stop reason for finished tasks.\n\nGuidelines:\n- After a context compaction, or whenever you are unsure which background tasks are running, call this tool to re-enumerate them instead of guessing a task ID.\n- Prefer the default `active_only=true`, which lists only non-terminal tasks. Pass `active_only=false` only when you need to see finished tasks; the result may include `lost` tasks from a previous process.\n- `limit` caps how many tasks are returned (1-100, defaults to 20).\n- This tool only lists tasks; it does not return their output. Use it first to locate a task ID, then call `TaskOutput` with that ID.\n- This tool is read-only and safe to call at any time.\n";
2361
2274
  //#endregion
2362
2275
  //#region src/tools/background/task-list.ts
2363
2276
  /**
@@ -2410,7 +2323,7 @@ var TaskListTool = class {
2410
2323
  };
2411
2324
  //#endregion
2412
2325
  //#region src/tools/background/task-output.md
2413
- var task_output_default = "Retrieve output from a running or completed background task.\n\nUse this after `Bash(run_in_background=true)` when you need to inspect progress or explicitly wait for completion.\n\nGuidelines:\n- Prefer relying on automatic completion notifications. Use this tool only when you need task output before the automatic notification arrives.\n- By default this tool is non-blocking and returns a current status/output snapshot.\n- Use block=true only when you intentionally want to wait for completion or timeout.\n- This tool returns structured task metadata, a fixed-size output preview, and an output_path for the full log.\n- For a terminal task, the metadata also explains why it ended: `timed_out` when an agent task was aborted by its deadline, and `stop_reason` when the task was explicitly stopped. `terminal_reason` is a categorical label for the same event — its value is `timed_out` or `stopped` — and is emitted alongside the matching `timed_out` / `stop_reason` field. A task that ended on its own emits none of these three fields.\n- The full, never-truncated log is always available at output_path; use the `Read` tool with that path to page through it, whether or not the preview was truncated.\n- This tool works with the generic background task system and should remain the primary read path for future task types, not just bash.\n";
2326
+ var task_output_default = "Retrieve output from a running or completed background task.\n\nUse this after `Bash(run_in_background=true)` when you need to inspect progress or wait for completion.\n\nGuidelines:\n- Prefer automatic completion notifications. Use this tool only when you need output before it arrives.\n- Non-blocking by default; returns a current status/output snapshot.\n- Use block=true only when you want to wait for completion or timeout.\n- Returns structured task metadata, a fixed-size output preview, and an output_path for the full log.\n- For terminal tasks, metadata includes why it ended: `timed_out` (agent deadline abort) or `stop_reason` (explicit stop), plus a categorical `terminal_reason`.\n- The full log is always available at output_path; use `Read` with that path to page through it.\n- Output preview may be truncated; use `Read` with output_path to view the full log.\n";
2414
2327
  //#endregion
2415
2328
  //#region src/tools/background/task-output.ts
2416
2329
  /**
@@ -2499,7 +2412,7 @@ var TaskOutputTool = class {
2499
2412
  };
2500
2413
  //#endregion
2501
2414
  //#region src/tools/background/task-stop.md
2502
- var task_stop_default = "Stop a running background task.\n\nOnly use this when a task must genuinely be cancelled — for a task that is\nfinishing normally, wait for its completion notification or inspect it with\n`TaskOutput` instead of stopping it.\n\nGuidelines:\n- This is a general-purpose stop capability for any background task. It is not\n a bash-specific kill.\n- Stopping a task is destructive: it may leave partial side effects behind.\n Use it with care.\n- If the task has already finished, this tool simply returns its current\n status.\n";
2415
+ var task_stop_default = "Stop a running background task.\n\nOnly use this when a task must genuinely be cancelled — for a task that is finishing normally, wait for its completion notification or inspect it with `TaskOutput` instead.\n\nGuidelines:\n- General-purpose stop for any background task, not a bash-specific kill.\n- Stopping a task is destructive and may leave partial side effects behind. Use it with care.\n- If the task has already finished, this tool returns its current status.\n";
2503
2416
  //#endregion
2504
2417
  //#region src/tools/background/task-stop.ts
2505
2418
  /**
@@ -2693,7 +2606,7 @@ var agent_background_disabled_default = "Background agent execution is disabled
2693
2606
  var agent_background_enabled_default = "When `run_in_background=true`, the subagent runs detached from this turn. The completion arrives in a later turn as a synthetic user-role message containing its result — you do not need to poll, sleep, or check on its progress. Continue with other work or respond to the user. Never fabricate or predict what the result will say.\n\nFor a background task, when `timeout` is omitted it falls back to the operator-configured background timeout, if one is set. If the operator has not configured a background timeout, an omitted `timeout` means the task runs with no time limit.\n";
2694
2607
  //#endregion
2695
2608
  //#region src/tools/builtin/collaboration/agent.md
2696
- var agent_default = "Launch a subagent to handle a task. The subagent runs as a same-process loop instance with its own context and wire file.\n\nWriting the prompt:\n- The subagent starts with zero context — it has not seen this conversation. Brief it like a colleague who just walked into the room: state the goal, list what you already know, hand over the specifics.\n- Lookups (read this file, run that test): put the exact path or command in the prompt. The subagent should not have to search for things you already know.\n- Investigations (figure out X, find why Y): give the question, not prescribed steps — fixed steps become dead weight when the premise is wrong.\n- Do not delegate understanding. If the task hinges on a file path or line number, find it yourself first and write it into the prompt.\n\nUsage notes:\n- When the task continues earlier work a subagent already did, prefer resuming that agent (pass its `resume` id) over spawning a fresh instance — the resumed agent keeps its prior context.\n- A subagent's result is only visible to you, not to the user. When the user needs to see what a subagent produced, summarize the relevant parts yourself in your own reply.\n\nWhen NOT to use Agent: skip delegation for trivial work you can do directly — reading a file whose path you already know, searching a small known set of files, or any task that takes only a step or two. Delegation has a context-handoff cost; it pays off only when the task is substantial enough to outweigh it.\n\nOnce a subagent is running, leave that scope to it: do not redo its searches or reads in parallel, and do not abandon it midway and finish the job manually. Both undo the context savings the delegation was meant to buy.";
2609
+ var agent_default = "Launch a subagent to handle a task. The subagent starts with zero context — it has not seen this conversation. Brief it with the goal, what you already know, and exact paths or commands.\n\n- When the task continues earlier work a subagent already did, prefer resuming that agent (pass its `resume` id) over spawning a fresh instance.\n- A subagent's result is only visible to you, not to the user. Summarize the relevant parts yourself when the user needs to see them.\n- Skip delegation for trivial work you can do directly — reading a known file, searching a small set of files, or any one-step task.\n- Once a subagent is running, do not redo its searches or reads in parallel, and do not abandon it midway and finish the job manually.\n";
2697
2610
  //#endregion
2698
2611
  //#region src/tools/builtin/collaboration/agent.ts
2699
2612
  /**
@@ -2910,14 +2823,12 @@ function buildSubagentDescriptions(subagents) {
2910
2823
  if (subagents === void 0) return "";
2911
2824
  return Object.entries(subagents).map(([name, subagent]) => {
2912
2825
  const details = [subagent.description, subagent.whenToUse].filter((part) => part !== void 0 && part.length > 0);
2913
- const header = details.length === 0 ? `- ${name}` : `- ${name}: ${details.join(" ")}`;
2914
- if (subagent.tools.length === 0) return header;
2915
- return `${header}\n Tools: ${subagent.tools.join(", ")}`;
2826
+ return details.length === 0 ? `- ${name}` : `- ${name}: ${details.join(" ")}`;
2916
2827
  }).join("\n");
2917
2828
  }
2918
2829
  //#endregion
2919
2830
  //#region src/tools/builtin/collaboration/ask-user.md
2920
- 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- Users always have an \"Other\" option for custom input — don't create one yourself\n- Use multi_select to allow multiple answers to be selected for a question\n- Keep option labels concise (1-5 words), use descriptions for trade-offs and details\n- Each question should have 2-4 meaningful, distinct options\n- You can ask 1-4 questions at a time; group related questions to minimize interruptions\n- If you recommend a specific option, list it first and append \"(Recommended)\" to its label";
2831
+ var ask_user_default = "Use this tool ONLY to present structured multiple-choice questions to the user. Use it to collect preferences, resolve ambiguity, or present options when multiple valid directions exist.\n\n**When NOT to use:**\n- For simple greetings, direct questions, or casual conversation — reply in text instead\n- When you can infer the answer from context — be decisive and proceed\n- Trivial decisions that don't materially affect the outcome\n\nOnly use this tool when the user's input genuinely changes your next action.\n\n**Usage notes:**\n- Users always have an \"Other\" option — don't create one yourself\n- Use multi_select to allow multiple answers per question\n- Keep option labels concise (1-5 words), use descriptions for trade-offs\n- Each question needs 2-4 meaningful, distinct options\n- Ask 1-4 questions at a time; group related questions to minimize interruptions\n- If you recommend an option, list it first and append \"(Recommended)\" to its label\n";
2921
2832
  //#endregion
2922
2833
  //#region src/tools/builtin/collaboration/ask-user.ts
2923
2834
  /**
@@ -3458,7 +3369,6 @@ function isWithin(child, parent) {
3458
3369
  }
3459
3370
  //#endregion
3460
3371
  //#region src/skill/registry.ts
3461
- const LISTING_DESC_MAX = 250;
3462
3372
  var SkillRegistry = class {
3463
3373
  byName = /* @__PURE__ */ new Map();
3464
3374
  roots = [];
@@ -3560,13 +3470,7 @@ function formatFullSkill(skill) {
3560
3470
  ];
3561
3471
  }
3562
3472
  function formatModelSkill(skill) {
3563
- const lines = [`- ${skill.name}: ${truncate(skill.description, LISTING_DESC_MAX)}`];
3564
- if (typeof skill.metadata.whenToUse === "string" && skill.metadata.whenToUse.length > 0) lines.push(` When to use: ${skill.metadata.whenToUse}`);
3565
- lines.push(` Path: ${skill.path}`);
3566
- return lines;
3567
- }
3568
- function truncate(value, max) {
3569
- return value.length > max ? value.slice(0, max) : value;
3473
+ return [`- ${skill.name}: ${skill.description}`];
3570
3474
  }
3571
3475
  //#endregion
3572
3476
  //#region src/tools/builtin/collaboration/skill-tool.md
@@ -3695,14 +3599,14 @@ const SENSITIVE_DOT_VARIANT_SUFFIXES = [
3695
3599
  ];
3696
3600
  const SENSITIVE_DOT_VARIANT_SUFFIX_SET = new Set(SENSITIVE_DOT_VARIANT_SUFFIXES);
3697
3601
  const DEFAULT_PATH_CLASS$1 = nativePath.sep === "\\" ? "win32" : "posix";
3698
- function pathMod$3(pathClass) {
3602
+ function pathMod$5(pathClass) {
3699
3603
  return pathClass === "win32" ? win32Path : posixPath;
3700
3604
  }
3701
3605
  function comparable(path, pathClass) {
3702
3606
  return pathClass === "win32" ? path.toLowerCase() : path;
3703
3607
  }
3704
3608
  function isSensitiveFile(path, pathClass = DEFAULT_PATH_CLASS$1) {
3705
- const mod = pathMod$3(pathClass);
3609
+ const mod = pathMod$5(pathClass);
3706
3610
  const comparableName = comparable(mod.basename(path), pathClass);
3707
3611
  const comparablePath = comparable(path, pathClass);
3708
3612
  if (ENV_EXEMPTIONS.has(comparableName)) return false;
@@ -3756,7 +3660,7 @@ var PathSecurityError = class extends Error {
3756
3660
  }
3757
3661
  };
3758
3662
  const DEFAULT_PATH_CLASS = nativePath.sep === "\\" ? "win32" : "posix";
3759
- function pathMod$2(pathClass) {
3663
+ function pathMod$4(pathClass) {
3760
3664
  return pathClass === "win32" ? win32Path : posixPath;
3761
3665
  }
3762
3666
  function comparablePath(path, pathClass) {
@@ -3786,7 +3690,7 @@ function normalizeUserPath(path, pathClass = DEFAULT_PATH_CLASS) {
3786
3690
  function expandUserPath$1(path, homeDir, pathClass) {
3787
3691
  if (homeDir === void 0) return path;
3788
3692
  if (path === "~") return homeDir;
3789
- if (path.startsWith("~/") || pathClass === "win32" && path.startsWith("~\\")) return pathMod$2(pathClass).join(homeDir, path.slice(2));
3693
+ if (path.startsWith("~/") || pathClass === "win32" && path.startsWith("~\\")) return pathMod$4(pathClass).join(homeDir, path.slice(2));
3790
3694
  return path;
3791
3695
  }
3792
3696
  /**
@@ -3795,7 +3699,7 @@ function expandUserPath$1(path, homeDir, pathClass) {
3795
3699
  */
3796
3700
  function canonicalizePath(path, cwd, pathClass = DEFAULT_PATH_CLASS) {
3797
3701
  if (path === "") throw new PathSecurityError("PATH_INVALID", path, path, "Path cannot be empty");
3798
- const mod = pathMod$2(pathClass);
3702
+ const mod = pathMod$4(pathClass);
3799
3703
  const normalizedPath = normalizeUserPath(path, pathClass);
3800
3704
  if (pathClass === "win32" && isWin32DriveRelative(normalizedPath)) throw new PathSecurityError("PATH_INVALID", path, normalizedPath, `"${path}" is a drive-relative Windows path. Use an absolute path like C:\\path or a path relative to the working directory.`);
3801
3705
  if (!mod.isAbsolute(normalizedPath) && !mod.isAbsolute(cwd)) throw new PathSecurityError("PATH_INVALID", path, normalizedPath, `Cannot resolve "${path}" against non-absolute cwd "${cwd}".`);
@@ -3807,7 +3711,7 @@ function canonicalizePath(path, cwd, pathClass = DEFAULT_PATH_CLASS) {
3807
3711
  * on path-component boundaries. Both arguments must already be canonical.
3808
3712
  */
3809
3713
  function isWithinDirectory(candidate, base, pathClass = DEFAULT_PATH_CLASS) {
3810
- const mod = pathMod$2(pathClass);
3714
+ const mod = pathMod$4(pathClass);
3811
3715
  const comparableCandidate = comparablePath(candidate, pathClass);
3812
3716
  const comparableBase = comparablePath(base, pathClass);
3813
3717
  if (comparableCandidate === comparableBase) return true;
@@ -3833,7 +3737,7 @@ function relativeOutsideMessage(path, operation) {
3833
3737
  }
3834
3738
  function resolvePathAccess(path, cwd, config, options) {
3835
3739
  const pathClass = options.pathClass ?? DEFAULT_PATH_CLASS;
3836
- const mod = pathMod$2(pathClass);
3740
+ const mod = pathMod$4(pathClass);
3837
3741
  const expandedPath = expandUserPath$1(normalizeUserPath(path, pathClass), options.homeDir, pathClass);
3838
3742
  const rawIsAbsolute = mod.isAbsolute(expandedPath);
3839
3743
  const canonical = canonicalizePath(expandedPath, cwd, pathClass);
@@ -3900,7 +3804,7 @@ function makeCarriageReturnsVisible(text) {
3900
3804
  }
3901
3805
  //#endregion
3902
3806
  //#region src/tools/builtin/file/edit.md
3903
- var edit_default = "Perform exact string replacements against the text view returned by Read.\n\n- When copying from Read output, omit the line-number prefix and tab; match only the file content.\n- By default, old_string must occur exactly once. If it matches multiple locations, add surrounding context or set replace_all when every occurrence should change.\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- For pure CRLF files, Read shows LF and Edit.old_string/new_string should use LF; Edit writes the file back with CRLF preserved.\n- For mixed line endings or lone carriage returns, Read displays carriage returns as \\r; include actual \\r escapes in old_string/new_string for those positions.";
3807
+ var edit_default = "Perform exact string replacements against the text view returned by Read.\n\n- When copying from Read output, omit the line-number prefix and tab; match only the file content.\n- By default, old_string must occur exactly once. If it matches multiple locations, add surrounding context or set replace_all when every occurrence should change.\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- For pure CRLF files, Read shows LF and Edit.old_string/new_string should use LF; Edit writes the file back with CRLF preserved.\n- For mixed line endings or lone carriage returns, Read displays carriage returns as \\r; include actual \\r escapes in old_string/new_string for those positions.\n";
3904
3808
  //#endregion
3905
3809
  //#region src/tools/builtin/file/edit.ts
3906
3810
  const EditInputSchema = z.object({
@@ -3988,9 +3892,88 @@ var EditTool = class {
3988
3892
  }
3989
3893
  }
3990
3894
  };
3895
+ async function collectEntries$1(kaos, dirPath, maxWidth, pathClass) {
3896
+ const all = [];
3897
+ try {
3898
+ for await (const fullPath of kaos.iterdir(dirPath)) {
3899
+ const name = basename$2(fullPath, pathClass);
3900
+ let isDir = false;
3901
+ try {
3902
+ isDir = ((await kaos.stat(fullPath)).stMode & 61440) === 16384;
3903
+ } catch {}
3904
+ all.push({
3905
+ name,
3906
+ isDir
3907
+ });
3908
+ }
3909
+ } catch {
3910
+ return {
3911
+ entries: [],
3912
+ total: 0,
3913
+ readable: false
3914
+ };
3915
+ }
3916
+ all.sort((a, b) => {
3917
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
3918
+ return a.name.localeCompare(b.name);
3919
+ });
3920
+ return {
3921
+ entries: all.slice(0, maxWidth),
3922
+ total: all.length,
3923
+ readable: true
3924
+ };
3925
+ }
3926
+ function pathMod$3(pathClass) {
3927
+ return pathClass === "win32" ? win32Path : posixPath;
3928
+ }
3929
+ function basename$2(p, pathClass) {
3930
+ return pathMod$3(pathClass).basename(p);
3931
+ }
3932
+ /**
3933
+ * Return a 2-level tree listing of `workDir` suitable for inclusion in a
3934
+ * tool error message. Returns `"(empty directory)"` if the directory is
3935
+ * empty, or an error marker line if the directory itself is unreadable.
3936
+ */
3937
+ async function listDirectory(kaos, workDir) {
3938
+ const lines = [];
3939
+ const pathClass = kaos.pathClass();
3940
+ const { entries, total, readable } = await collectEntries$1(kaos, workDir, 30, pathClass);
3941
+ if (!readable) return "[not readable]";
3942
+ const remaining = total - entries.length;
3943
+ for (let i = 0; i < entries.length; i++) {
3944
+ const entry = entries[i];
3945
+ if (entry === void 0) continue;
3946
+ const { name, isDir } = entry;
3947
+ const isLast = i === entries.length - 1 && remaining === 0;
3948
+ const connector = isLast ? "└── " : "├── ";
3949
+ if (isDir) {
3950
+ lines.push(`${connector}${name}/`);
3951
+ const childPrefix = isLast ? " " : "│ ";
3952
+ const child = await collectEntries$1(kaos, joinPath$1(workDir, name, pathClass), 10, pathClass);
3953
+ if (!child.readable) {
3954
+ lines.push(`${childPrefix}└── [not readable]`);
3955
+ continue;
3956
+ }
3957
+ const childRemaining = child.total - child.entries.length;
3958
+ for (let j = 0; j < child.entries.length; j++) {
3959
+ const ce = child.entries[j];
3960
+ if (ce === void 0) continue;
3961
+ const cConnector = j === child.entries.length - 1 && childRemaining === 0 ? "└── " : "├── ";
3962
+ const suffix = ce.isDir ? "/" : "";
3963
+ lines.push(`${childPrefix}${cConnector}${ce.name}${suffix}`);
3964
+ }
3965
+ if (childRemaining > 0) lines.push(`${childPrefix}└── ... and ${String(childRemaining)} more`);
3966
+ } else lines.push(`${connector}${name}`);
3967
+ }
3968
+ if (remaining > 0) lines.push(`└── ... and ${String(remaining)} more entries`);
3969
+ return lines.length > 0 ? lines.join("\n") : "(empty directory)";
3970
+ }
3971
+ function joinPath$1(parent, child, pathClass) {
3972
+ return pathMod$3(pathClass).join(parent, child);
3973
+ }
3991
3974
  //#endregion
3992
3975
  //#region src/tools/builtin/file/glob.md
3993
- 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`.";
3976
+ 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):\n- `**`, `**/*`, `*/*` — pure wildcards. Add an extension or subdirectory to give the walk a concrete target.\n- Anything starting with `**/` (e.g. `**/*.md`, `**/main/*.py`). 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/**` match technically but typically produce thousands of results that truncate at the match cap. Prefer specific subpaths like `node_modules/react/src/**/*.js`.\n";
3994
3977
  //#endregion
3995
3978
  //#region src/tools/builtin/file/glob.ts
3996
3979
  const GlobInputSchema = z.object({
@@ -4536,7 +4519,7 @@ var ToolResultBuilder = class {
4536
4519
  };
4537
4520
  //#endregion
4538
4521
  //#region src/tools/builtin/file/grep.md
4539
- var grep_default = "Search file contents using regular expressions (powered by ripgrep).\n\nUse Grep when the task is to find unknown content or unknown file locations. Do not use shell `grep` or `rg` directly; this tool applies workspace path policy, output limits, and sensitive-file filtering.\nALWAYS use Grep tool instead of running `grep` or `rg` from a shell — direct shell calls bypass workspace policy, output limits, and sensitive-file filtering.\nIf you already know a concrete file path and need to inspect its contents, use Read directly instead.\n\nWrite patterns in ripgrep regex syntax, which differs from POSIX `grep` syntax. For example, braces are special, so escape them as `\\{` to match a literal `{`.\n\nHidden files (dotfiles such as `.gitlab-ci.yml` or `.eslintrc.json`) are searched by default. To also search files excluded by `.gitignore` (such as `node_modules` or build outputs), set `include_ignored` to `true`. Sensitive files (such as `.env`) are always skipped for safety, even when `include_ignored` is `true`.\n";
4522
+ 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. If you already know a concrete file path and need to inspect its contents, use Read directly instead.\n\nWrite patterns in ripgrep regex syntax, which differs from POSIX `grep` syntax. For example, braces are special, so escape them as `\\{` to match a literal `{`.\n\nHidden files (dotfiles such as `.gitlab-ci.yml` or `.eslintrc.json`) are searched by default. To also search files excluded by `.gitignore` (such as `node_modules` or build outputs), set `include_ignored` to `true`. Sensitive files (such as `.env`) are always skipped for safety, even when `include_ignored` is `true`.\n";
4540
4523
  //#endregion
4541
4524
  //#region src/tools/builtin/file/grep.ts
4542
4525
  const GrepInputSchema = z.object({
@@ -5516,7 +5499,7 @@ function detectFileType(path, header) {
5516
5499
  }
5517
5500
  //#endregion
5518
5501
  //#region src/tools/builtin/file/read.md
5519
- 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- Relative paths resolve against the working directory; a path outside the working directory must be absolute.\n- Returns up to {{ MAX_LINES }} lines or {{ MAX_BYTES_KB }} KB per call, whichever comes first; lines longer than {{ MAX_LINE_LENGTH }} chars are truncated mid-line.\n- Page larger files with `line_offset` (1-based start line) and `n_lines`. Omit `n_lines` to read up to the {{ MAX_LINES }}-line cap.\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- Negative line_offset reads from the end of the file (for example, -100 reads the last 100 lines); the absolute value cannot exceed {{ MAX_LINES }}.\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.\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";
5502
+ 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. Prefer to read several files in parallel: emit multiple `Read` calls in a single response.\n\n- Returns up to {{ MAX_LINES }} lines or {{ MAX_BYTES_KB }} KB per call, whichever comes first; lines longer than {{ MAX_LINE_LENGTH }} chars are truncated mid-line.\n- Page larger files with `line_offset` (1-based start line) and `n_lines`. Omit `n_lines` to read up to the {{ MAX_LINES }}-line cap.\n- Negative line_offset reads from the end of the file (for example, -100 reads the last 100 lines); the absolute value cannot exceed {{ MAX_LINES }}.\n- Sensitive files (`.env`, credential stores, SSH keys, and similar secrets) are refused; 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 and is not part of the file itself.\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";
5520
5503
  //#endregion
5521
5504
  //#region src/tools/builtin/file/read.ts
5522
5505
  const MAX_LINES = 1e3;
@@ -5808,7 +5791,7 @@ var ReadTool = class {
5808
5791
  };
5809
5792
  //#endregion
5810
5793
  //#region src/tools/builtin/file/read-media.md
5811
- var read_media_default = "Read media content from a file.\n\n**Tips:**\n- Make sure you follow the description of each tool parameter.\n- A `<system>` tag is given before the file content; it summarizes the mime type, byte size and, for images, the original pixel dimensions. When outputting coordinates, give relative coordinates first and compute absolute coordinates from the original image size. After generating or editing media via commands or scripts, read the result back before continuing.\n- The system will notify you when there is anything wrong when reading the file.\n- This tool is a tool that you typically want to use in parallel. Always read multiple files in one response when possible.\n- This tool can only read image or video files. To read text files, use the Read tool. To list directories, use `ls` via Bash for a known directory, or Glob for pattern search.\n- If the file doesn't exist or path is invalid, an error will be returned.\n- The maximum size that can be read is {{ MAX_MEDIA_MEGABYTES }}MB. An error will be returned if the file is larger than this limit.\n- The media content will be returned in a form that you can directly view and understand.\n\n**Capabilities**";
5794
+ var read_media_default = "Read media content from a file.\n\n- A `<system>` tag precedes the file content; it summarizes mime type, byte size and, for images, original pixel dimensions. When outputting coordinates, give relative coordinates first and compute absolute coordinates from the original image size. After generating or editing media via commands or scripts, read the result back before continuing.\n- Use this tool in parallel when possible always read multiple files in one response.\n- This tool can only read image or video files. To read text files, use the Read tool. To list directories, use `ls` via Bash for a known directory, or Glob for pattern search.\n- The maximum size is {{ MAX_MEDIA_MEGABYTES }}MB.\n- The media content is returned in a form you can directly view and understand.\n";
5812
5795
  //#endregion
5813
5796
  //#region src/tools/builtin/file/read-media.ts
5814
5797
  const MAX_MEDIA_MEGABYTES = 100;
@@ -5959,14 +5942,14 @@ var ReadMediaFileTool = class {
5959
5942
  };
5960
5943
  //#endregion
5961
5944
  //#region src/tools/builtin/file/write.md
5962
- var write_default = "Overwrite or append to a file with content exactly as provided, creating the file if needed; the parent directory must already exist. Defaults to overwrite; append adds content to the end without adding a newline. Write does not use the Read/Edit model text view and 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.\n";
5945
+ var write_default = "Overwrite or append to a file with content exactly as provided, creating the file if needed; the parent directory must already exist. Defaults to overwrite; append adds content to the end without adding a newline. Write does not use the Read/Edit model text view and does not preserve or infer the previous line-ending style: \\n stays LF, \\r\\n stays CRLF. When the content is very large, split it across multiple calls: write the first chunk with overwrite, then add the remaining chunks with append.\n";
5963
5946
  //#endregion
5964
5947
  //#region src/tools/builtin/file/write.ts
5965
5948
  /** Mask isolating the file-type bits of a stat mode. */
5966
5949
  const S_IFMT$2 = 61440;
5967
5950
  /** File-type bits of a directory. */
5968
5951
  const S_IFDIR$1 = 16384;
5969
- function pathMod$1(pathClass) {
5952
+ function pathMod$2(pathClass) {
5970
5953
  return pathClass === "win32" ? win32Path : posixPath;
5971
5954
  }
5972
5955
  const WriteInputSchema = z.object({
@@ -6033,7 +6016,7 @@ var WriteTool = class {
6033
6016
  * skipped and the write proceeds, surfacing the real I/O error if any.
6034
6017
  */
6035
6018
  async checkParentDirectory(safePath) {
6036
- const parent = pathMod$1(this.kaos.pathClass()).dirname(safePath);
6019
+ const parent = pathMod$2(this.kaos.pathClass()).dirname(safePath);
6037
6020
  let stat;
6038
6021
  try {
6039
6022
  stat = await this.kaos.stat(parent);
@@ -6045,196 +6028,8 @@ var WriteTool = class {
6045
6028
  }
6046
6029
  };
6047
6030
  //#endregion
6048
- //#region src/tools/builtin/planning/enter-plan-mode.md
6049
- 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\nUse it 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\n4. Write your plan to the current plan file with Write or Edit\n5. Present your plan to the user via ExitPlanMode for approval\n";
6050
- //#endregion
6051
- //#region src/tools/builtin/planning/enter-plan-mode.ts
6052
- const EnterPlanModeInputSchema = z.object({}).strict();
6053
- var EnterPlanModeTool = class {
6054
- agent;
6055
- name = "EnterPlanMode";
6056
- description = enter_plan_mode_default;
6057
- parameters = toInputJsonSchema(EnterPlanModeInputSchema);
6058
- constructor(agent) {
6059
- this.agent = agent;
6060
- }
6061
- resolveExecution(_args) {
6062
- return {
6063
- description: "Requesting to enter plan mode",
6064
- execute: async () => {
6065
- if (this.agent.planMode.isActive) return {
6066
- isError: true,
6067
- output: "Plan mode is already active. Use ExitPlanMode when the plan is ready."
6068
- };
6069
- try {
6070
- await this.agent.planMode.enter();
6071
- } catch (error) {
6072
- return {
6073
- isError: true,
6074
- output: `Failed to enter plan mode: ${error instanceof Error ? error.message : "Failed to enter plan mode."}`
6075
- };
6076
- }
6077
- this.agent.telemetry.track("plan_enter_resolved", { outcome: "auto_approved" });
6078
- return { output: enteredPlanModeMessage(this.agent.planMode.planFilePath) };
6079
- }
6080
- };
6081
- }
6082
- };
6083
- function enteredPlanModeMessage(planPath) {
6084
- if (planPath === null) return [
6085
- "Plan mode is now active. Your workflow:",
6086
- "",
6087
- "1. Use read-only tools (Read, Grep, Glob) to investigate the codebase. Use Bash only when needed.",
6088
- "2. Design a concrete, step-by-step plan.",
6089
- "3. Wait for the host to provide a plan file path before calling ExitPlanMode.",
6090
- "",
6091
- "Do NOT use Write or Edit while plan mode is active in this host; no plan file path is available.",
6092
- "Use Bash only when needed; Bash follows the normal permission mode and rules."
6093
- ].join("\n");
6094
- return [
6095
- "Plan mode is now active. Your workflow:",
6096
- "",
6097
- `Plan file: ${planPath}`,
6098
- "",
6099
- "1. Use read-only tools (Read, Grep, Glob) to investigate the codebase. Use Bash only when needed.",
6100
- "2. Design a concrete, step-by-step plan.",
6101
- "3. Write the plan to the plan file with Write or Edit.",
6102
- "4. When the plan is ready, call ExitPlanMode for user approval.",
6103
- "",
6104
- "Do NOT edit files other than the plan file while plan mode is active.",
6105
- "Use Bash only when needed; Bash follows the normal permission mode and rules."
6106
- ].join("\n");
6107
- }
6108
- //#endregion
6109
- //#region src/tools/builtin/planning/exit-plan-mode.md
6110
- 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- Pass them via the `options` parameter so the user can choose which approach to execute.\n- Each option should have a concise label and a brief description of trade-offs.\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- Provide up to 3 options; the host adds the standard rejection and revision controls. When the plan offers a real choice, 2-3 distinct approaches work best.\n- Passing a single option is allowed and is equivalent to a plain plan approval (no approach choice is surfaced to the user).\n- Do NOT use \"Reject\", \"Reject and Exit\", \"Revise\", or \"Approve\" as option labels - these are reserved by the system.\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";
6111
- //#endregion
6112
- //#region src/tools/builtin/planning/exit-plan-mode.ts
6113
- const RESERVED_OPTION_LABELS = new Set([
6114
- "Approve",
6115
- "Reject",
6116
- "Reject and Exit",
6117
- "Revise"
6118
- ].map(normalizeOptionLabel$1));
6119
- const ExitPlanModeOptionSchema = z.object({
6120
- label: z.string().min(1).max(80).describe("Short name for this option (1-8 words). Append \"(Recommended)\" if you recommend this option."),
6121
- description: z.string().default("").describe("Brief summary of this approach and its trade-offs.")
6122
- }).strict();
6123
- const ExitPlanModeInputSchema = z.object({ options: z.array(ExitPlanModeOptionSchema).min(1).max(3).refine(hasUniqueOptionLabels, "Option labels must be unique.").refine(hasNoReservedOptionLabels, "Option labels must not use reserved approval labels.").optional().describe("When the plan contains multiple alternative approaches, list them here so the user can choose which one to execute. Provide up to 3 options; 2-3 distinct approaches work best when the plan offers a real choice. Passing a single option is allowed and is equivalent to a plain plan approval. Each option represents a distinct approach from the plan. Do not use \"Reject\", \"Revise\", \"Approve\", or \"Reject and Exit\" as labels.") }).strict();
6124
- var ExitPlanModeTool = class {
6125
- agent;
6126
- name = "ExitPlanMode";
6127
- description = exit_plan_mode_default;
6128
- parameters = toInputJsonSchema(ExitPlanModeInputSchema);
6129
- constructor(agent) {
6130
- this.agent = agent;
6131
- }
6132
- resolveExecution(args) {
6133
- return {
6134
- description: "Presenting plan and exiting plan mode",
6135
- execute: (ctx) => this.execution(args, ctx)
6136
- };
6137
- }
6138
- async execution(args, { metadata }) {
6139
- if (!this.agent.planMode.isActive) return {
6140
- isError: true,
6141
- output: "ExitPlanMode can only be called while plan mode is active. Use EnterPlanMode (or /plan) first."
6142
- };
6143
- const resolvedPlan = await this.resolvePlan();
6144
- if (!resolvedPlan.ok) return resolvedPlan.error;
6145
- if (!planTelemetryWasSubmitted(metadata)) this.agent.telemetry.track("plan_submitted", { has_options: args.options !== void 0 && args.options.length >= 2 });
6146
- return this.exitWithPlan(resolvedPlan.plan, resolvedPlan.path, selectedOptionFromMetadata(metadata), planTelemetryWasResolved(metadata));
6147
- }
6148
- async exitWithPlan(plan, path, option = void 0, telemetryResolved = false) {
6149
- const failed = this.exitPlanMode();
6150
- if (failed !== void 0) return failed;
6151
- if (!telemetryResolved) this.agent.telemetry.track("plan_resolved", { outcome: "auto_approved" });
6152
- return {
6153
- isError: false,
6154
- output: `Exited plan mode. ${option === void 0 ? "" : `Selected approach: ${option.label}\nExecute ONLY the selected approach. Do not execute any unselected alternatives.\n\n`}${formatPlanForOutput(plan, path)}`
6155
- };
6156
- }
6157
- exitPlanMode() {
6158
- try {
6159
- this.agent.planMode.exit();
6160
- } catch (error) {
6161
- return {
6162
- isError: true,
6163
- output: `Failed to exit plan mode: ${error instanceof Error ? error.message : "Failed to exit plan mode."}`
6164
- };
6165
- }
6166
- }
6167
- async resolvePlan() {
6168
- let source;
6169
- try {
6170
- const data = await this.agent.planMode.data();
6171
- source = data === null ? null : {
6172
- plan: data.content,
6173
- path: data.path
6174
- };
6175
- } catch (error) {
6176
- return {
6177
- ok: false,
6178
- error: {
6179
- isError: true,
6180
- output: `Failed to read plan file: ${error instanceof Error ? error.message : "Failed to read plan file."}`
6181
- }
6182
- };
6183
- }
6184
- if (source !== null && source.plan.trim().length > 0) return {
6185
- ok: true,
6186
- plan: source.plan,
6187
- path: source.path
6188
- };
6189
- const path = source?.path ?? this.agent.planMode.planFilePath;
6190
- return {
6191
- ok: false,
6192
- error: {
6193
- isError: true,
6194
- output: path === null ? "No plan file found. Write the plan to the current plan file first, then call ExitPlanMode." : `No plan file found. Write your plan to ${path} first, then call ExitPlanMode.`
6195
- }
6196
- };
6197
- }
6198
- };
6199
- function hasUniqueOptionLabels(options) {
6200
- const labels = /* @__PURE__ */ new Set();
6201
- for (const option of options) {
6202
- const label = normalizeOptionLabel$1(option.label);
6203
- if (labels.has(label)) return false;
6204
- labels.add(label);
6205
- }
6206
- return true;
6207
- }
6208
- function hasNoReservedOptionLabels(options) {
6209
- return options.every((option) => !RESERVED_OPTION_LABELS.has(normalizeOptionLabel$1(option.label)));
6210
- }
6211
- function normalizeOptionLabel$1(label) {
6212
- return label.trim().toLowerCase();
6213
- }
6214
- function selectedOptionFromMetadata(metadata) {
6215
- if (metadata === null || typeof metadata !== "object") return void 0;
6216
- const selectedOption = metadata.selectedOption;
6217
- if (selectedOption === null || typeof selectedOption !== "object") return void 0;
6218
- const label = selectedOption.label;
6219
- const description = selectedOption.description;
6220
- if (typeof label !== "string" || typeof description !== "string") return void 0;
6221
- return {
6222
- label,
6223
- description
6224
- };
6225
- }
6226
- function planTelemetryWasSubmitted(metadata) {
6227
- return metadata !== null && typeof metadata === "object" && metadata.planTelemetrySubmitted === true;
6228
- }
6229
- function planTelemetryWasResolved(metadata) {
6230
- return metadata !== null && typeof metadata === "object" && metadata.planTelemetryResolved === true;
6231
- }
6232
- function formatPlanForOutput(plan, path) {
6233
- return `Plan mode deactivated. All tools are now available.\n${path !== void 0 ? `Plan saved to: ${path}\n\n` : ""}## Approved Plan:\n${plan}`;
6234
- }
6235
- //#endregion
6236
- //#region src/tools/builtin/shell/bash.md
6237
- var bash_default = "Execute a `{{ SHELL_NAME }}` command. Use this for shell semantics — pipes, env, processes, git, package managers, build/test runners, anything genuinely interactive or multi-step.\n\n**Translate these to a dedicated tool instead:**\n- `cat` / `head` / `tail` (known path) → `Read`\n- `sed` / `awk` (in-place edit) → `Edit`\n- `echo > file` / `cat <<EOF` → `Write`\n- `find` / recursive `ls` to locate files by name pattern → `Glob` (plain `ls <known-directory>` is fine for listing a directory)\n- `grep` / `rg` (search file contents) → `Grep`\n- `echo` / `printf` (talk to the user) → just output text directly\n\nThe dedicated tools render in the per-tool permission UI and keep raw stdout out of the conversation; that is why they are worth reaching for whenever one fits.\n\n**Output:**\nThe stdout and stderr will be combined and returned as a string. The output may be truncated if it is too long. If the command failed, the output will end with a `Command failed with exit code: N` line stating the non-zero exit code.\n\nIf `run_in_background=true`, the command will be started as a background task and this tool will return a task ID instead of waiting for command completion. When doing that, you must provide a short `description`. Background commands default to a {{ DEFAULT_BACKGROUND_TIMEOUT_S }}s timeout and `timeout` is capped at {{ MAX_BACKGROUND_TIMEOUT_S }}s; set `disable_timeout=true` only when the task should run without a timeout. You will be automatically notified when the task completes. Use `TaskOutput` for a non-blocking status/output snapshot, and only set `block=true` when you explicitly want to wait for completion. Use `TaskStop` only if the task must be cancelled. If a human user wants to inspect background tasks themselves, point them to the `/tasks` command, which opens an interactive panel; it has no subcommands.\n\n**Guidelines for safety and security:**\n- Each shell tool call will be executed in a fresh shell environment. The shell variables, current working directory changes, and the shell history is not preserved between calls.\n- The tool call will return after the command is finished. You shall not use this tool to execute an interactive command or a command that may run forever. For possibly long-running foreground commands, set the `timeout` argument in seconds. Foreground commands default to {{ DEFAULT_TIMEOUT_S }}s and allow up to {{ MAX_TIMEOUT_S }}s.\n- Avoid using `..` to access files or directories outside of the working directory.\n- Avoid modifying files outside of the working directory unless explicitly instructed to do so.\n- Never run commands that require superuser privileges unless explicitly instructed to do so.\n\n**Guidelines for efficiency:**\n- For multiple related commands, use `&&` to chain them in a single call, e.g. `cd /path && ls -la`\n- Use `;` to run commands sequentially regardless of success/failure\n- Use `||` for conditional execution (run second command only if first fails)\n- Use pipe operations (`|`) and redirections (`>`, `>>`) to chain input and output between commands\n- Always quote file paths containing spaces with double quotes (e.g., cd \"/path with spaces/\")\n- Compose multi-step logic in a single call with `if` / `case` / `for` / `while` control flows.\n- Prefer `run_in_background=true` for long-running builds, tests, watchers, or servers when you need the conversation to continue before the command finishes.\n\n**Commands available:**\nThe following common command categories are usually available. Availability still depends on the host, so when in doubt run `which <command>` first to confirm a command exists before relying on it.\n- Navigation and inspection: `ls`, `pwd`, `cd`, `stat`, `file`, `du`, `df`, `tree`\n- File and directory management: `cp`, `mv`, `rm`, `mkdir`, `touch`, `ln`, `chmod`, `chown`\n- Text and data processing: `wc`, `sort`, `uniq`, `cut`, `tr`, `diff`, `xargs`\n- Archives and compression: `tar`, `gzip`, `gunzip`, `zip`, `unzip`\n- Networking and transfer: `curl`, `wget`, `ping`, `ssh`, `scp`\n- Version control: `git`\n- Process and system: `ps`, `kill`, `top`, `env`, `date`, `uname`, `whoami`\n- Language and package toolchains: `node`, `npm`, `pnpm`, `yarn`, `python`, `pip` (use whichever the project actually relies on)\n";
6031
+ //#region src/tools/builtin/shell/bash.md
6032
+ var bash_default = "Execute a `{{ SHELL_NAME }}` command. Use this for shell semantics pipes, env, processes, git, package managers, build/test runners, anything genuinely interactive or multi-step.\n\n**Translate these to a dedicated tool instead:**\n- `cat` / `head` / `tail` (known path) `Read`\n- `sed` / `awk` (in-place edit) `Edit`\n- `echo > file` / `cat <<EOF` `Write`\n- `find` / recursive `ls` to locate files by name pattern `Glob` (plain `ls <known-directory>` is fine for listing a directory)\n- `grep` / `rg` (search file contents) `Grep`\n- `echo` / `printf` (talk to the user) just output text directly\n\n**Output:**\nThe stdout and stderr will be combined and returned as a string. The output may be truncated if it is too long. If the command failed, the output will end with a `Command failed with exit code: N` line stating the non-zero exit code.\n\nEach shell call runs in a fresh shell environment. The shell variables, current working directory changes, and the shell history is not preserved between calls.\n\nNever run commands that require superuser privileges unless explicitly instructed to do so.\n\nIf `run_in_background=true`, the command will be started as a background task and this tool will return a task ID instead of waiting for command completion. When doing that, you must provide a short `description`. Background commands default to a {{ DEFAULT_BACKGROUND_TIMEOUT_S }}s timeout and `timeout` is capped at {{ MAX_BACKGROUND_TIMEOUT_S }}s; set `disable_timeout=true` only when the task should run without a timeout. You will be automatically notified when the task completes. Use `TaskOutput` for a non-blocking status/output snapshot, and only set `block=true` when you explicitly want to wait for completion. Use `TaskStop` only if the task must be cancelled. If a human user wants to inspect background tasks themselves, point them to the `/tasks` command, which opens an interactive panel; it has no subcommands.\n";
6238
6033
  //#endregion
6239
6034
  //#region src/tools/builtin/shell/bash.ts
6240
6035
  const MS_PER_SECOND = 1e3;
@@ -6524,14 +6319,14 @@ function rewriteWindowsNullRedirect$1(command) {
6524
6319
  }
6525
6320
  //#endregion
6526
6321
  //#region src/tools/builtin/state/todo-list.md
6527
- var todo_list_default = "Use this tool to maintain a structured TODO list as you work through a multi-step task. This is especially useful in plan mode and for long-running investigations.\n\n**When to use:**\n- Multi-step tasks that span several tool calls\n- Tracking investigation progress across a large codebase search\n- Planning a sequence of edits before making them\n\n**When NOT to use:**\n- Single-shot answers that complete in one or two tool calls\n- Trivial requests where tracking adds no clarity\n\n**Avoid churn:**\n- Do not re-call this tool when nothing meaningful has changed since the last call — update the list only after real progress.\n- When unsure of the current state, call query mode first (omit `todos`) to check the list before deciding what to update.\n- If no available tool can move any task forward, tell the user where you are stuck instead of repeatedly re-ordering the same todos.\n\n**How to use:**\n- Call with `todos: [...]` to replace the full list. Statuses: pending / in_progress / done.\n- Call with no arguments to retrieve the current list without changing it.\n- Call with `todos: []` to clear the list.\n- Keep titles short and actionable (e.g. \"Read session-control.ts\", \"Add planMode flag to TurnManager\").\n- Update statuses as you make progress — mark one item in_progress at a time.";
6322
+ var todo_list_default = "Use this tool to maintain a structured TODO list as you work through a multi-step task.\n\nUse for multi-step tasks, tracking investigation progress, or planning a sequence of edits. Do not use for single-shot answers or trivial requests.\n\n**Avoid churn:**\n- Do not re-call this tool when nothing meaningful has changed since the last call — update the list only after real progress.\n- When unsure of the current state, call query mode first (omit `todos`) to check the list before deciding what to update.\n- If no available tool can move any task forward, tell the user where you are stuck instead of repeatedly re-ordering the same todos.\n\n**How to use:**\n- Call with `todos: [...]` to replace the full list. Statuses: pending / in_progress / done.\n- Call with no arguments to query the current list.\n- Call with `todos: []` to clear the list.\n- Keep titles short and actionable.\n- Update statuses as you make progress — mark one item in_progress at a time.\n";
6528
6323
  //#endregion
6529
6324
  //#region src/tools/builtin/state/todo-list.ts
6530
6325
  /**
6531
6326
  * TodoListTool — structured TODO list management tool.
6532
6327
  *
6533
6328
  * The LLM uses this tool to maintain a visible plan of sub-tasks during
6534
- * plan-mode workflows and multi-step operations. A single tool serves
6329
+ * multi-step workflows and operations. A single tool serves
6535
6330
  * both reads and writes:
6536
6331
  *
6537
6332
  * - `resolveExecution({ todos: [...] })` — replace the full list
@@ -7232,6 +7027,123 @@ function cloneMessage(message) {
7232
7027
  //#region src/agent/compaction/compaction-instruction.md
7233
7028
  var compaction_instruction_default = "\n--- This message is a direct task, not part of the above conversation ---\n\nYou are now given a task to compact this conversation context according to specific priorities and output requirements.\n\nOutput text only. DO NOT CALL ANY TOOLS. Calling tools will be rejected and fails the task. You already have all the information you need in the conversation history. You have only one chance.\n\nThe goal of compaction is to keep essential code patterns, technical details, and architectural decisions for continuing development without losing context after the above messages are cleared work.\n\n{{ customInstruction }}\n\n<!-- Compression Priorities (in order) -->\n\n1. **Current Task State**: What is being worked on RIGHT NOW\n2. **Errors & Solutions**: All encountered errors and their resolutions\n3. **Code Evolution**: Final working versions only (remove intermediate attempts)\n4. **System Context**: Project structure, dependencies, environment setup\n5. **Design Decisions**: Architectural choices and their rationale\n6. **TODO Items**: Unfinished tasks and known issues\n\n<!-- Required Output Structure -->\n\n## Current Focus\n\n[What we're working on now]\n\n## Environment\n\n- [Key setup/config points]\n- ...\n\n## Completed Tasks\n\n- [Task]: [Brief outcome]\n- ...\n\n## Active Issues\n\n- [Issue]: [Status/Next steps]\n- ...\n\n## Code State\n\n### [Critical file name]\n\n[Brief description of the file's purpose and current state]\n\n```\n[The latest version of critical code snippets in this file, <20 lines]\n```\n\n### [Critical file name]\n\n- [Useful classes/methods/functions]: [Brief description/usage]\n- ...\n\n<!-- Omit non-critical code, intermediate attempts, and resolved errors -->\n\n## Important Context\n\n- [Any crucial information not covered above]\n- ...\n\n## All User Messages\n\n- [Detailed non tool use user message]\n- ...\n";
7234
7029
  //#endregion
7030
+ //#region src/agent/context/observation-masking.ts
7031
+ const DEFAULT_MASKING_CONFIG = {
7032
+ effectiveCapacityRatio: .6,
7033
+ lowPriorityThreshold: .6,
7034
+ mediumPriorityThreshold: .8,
7035
+ highPriorityThreshold: .85
7036
+ };
7037
+ function getToolPriority(toolName) {
7038
+ switch (toolName) {
7039
+ case "Write":
7040
+ case "Edit": return "high";
7041
+ case "Bash": return "medium";
7042
+ case "Read":
7043
+ case "Glob":
7044
+ case "Grep": return "low";
7045
+ default: return "low";
7046
+ }
7047
+ }
7048
+ function extractTextFromContent(content) {
7049
+ if (typeof content === "string") return content;
7050
+ return content.filter((part) => part.type === "text").map((part) => part.text).join("");
7051
+ }
7052
+ function countLines(text) {
7053
+ if (text.length === 0) return 0;
7054
+ let count = 1;
7055
+ for (const char of text) if (char === "\n") count++;
7056
+ return count;
7057
+ }
7058
+ function headTailLines(text, headCount, tailCount) {
7059
+ const lines = text.split("\n");
7060
+ if (lines.length <= headCount + tailCount) return text;
7061
+ return `${lines.slice(0, headCount).join("\n")}\n...\n${lines.slice(-tailCount).join("\n")}`;
7062
+ }
7063
+ function formatSummary(toolName, _args, lineCount, isError) {
7064
+ return `[${toolName}: ${String(lineCount)} lines${isError ? ", error" : ""}]`;
7065
+ }
7066
+ function isAlreadyMasked(text, toolName) {
7067
+ return text.startsWith(`[${toolName}:`);
7068
+ }
7069
+ function maskToolResult(message, toolName, toolArgs) {
7070
+ const text = extractTextFromContent(message.content);
7071
+ if (isAlreadyMasked(text, toolName)) return message;
7072
+ const lineCount = countLines(text);
7073
+ const summary = formatSummary(toolName, toolArgs, lineCount, message.isError === true);
7074
+ const priority = getToolPriority(toolName);
7075
+ let maskedContent;
7076
+ if (priority === "high") maskedContent = summary;
7077
+ else {
7078
+ const headCount = priority === "medium" ? 3 : 3;
7079
+ const tailCount = priority === "medium" ? 5 : toolName === "Read" ? 3 : 2;
7080
+ if (lineCount <= headCount + tailCount) if (lineCount === 0) maskedContent = summary;
7081
+ else maskedContent = `${summary}\n---\n${text}`;
7082
+ else maskedContent = `${summary}\n---\n${headTailLines(text, headCount, tailCount)}`;
7083
+ }
7084
+ return {
7085
+ ...message,
7086
+ content: [{
7087
+ type: "text",
7088
+ text: maskedContent
7089
+ }]
7090
+ };
7091
+ }
7092
+ /**
7093
+ * Apply observation masking to tool result messages in history.
7094
+ * Returns a new history array (does not mutate the original) and masking result.
7095
+ */
7096
+ function applyObservationMasking(history, maxContextSize, toolCallIdToInfo, config = DEFAULT_MASKING_CONFIG) {
7097
+ const effectiveCapacity = maxContextSize * config.effectiveCapacityRatio;
7098
+ const tokensBefore = estimateTokensForMessages(history);
7099
+ const pressure = effectiveCapacity > 0 ? tokensBefore / effectiveCapacity : 0;
7100
+ let maskPriorities;
7101
+ if (pressure < config.lowPriorityThreshold) maskPriorities = /* @__PURE__ */ new Set();
7102
+ else if (pressure < config.mediumPriorityThreshold) maskPriorities = new Set(["low"]);
7103
+ else if (pressure < config.highPriorityThreshold) maskPriorities = new Set(["low", "medium"]);
7104
+ else maskPriorities = new Set(["low", "medium"]);
7105
+ if (maskPriorities.size === 0) return {
7106
+ history: [...history],
7107
+ result: {
7108
+ masked: false,
7109
+ maskedCount: 0,
7110
+ tokensBefore,
7111
+ tokensAfter: tokensBefore
7112
+ }
7113
+ };
7114
+ let maskedCount = 0;
7115
+ const newHistory = [];
7116
+ for (const message of history) {
7117
+ if (message.role !== "tool" || message.toolCallId === void 0) {
7118
+ newHistory.push(message);
7119
+ continue;
7120
+ }
7121
+ const info = toolCallIdToInfo.get(message.toolCallId);
7122
+ if (info === void 0) {
7123
+ newHistory.push(message);
7124
+ continue;
7125
+ }
7126
+ const priority = getToolPriority(info.name);
7127
+ if (!maskPriorities.has(priority)) {
7128
+ newHistory.push(message);
7129
+ continue;
7130
+ }
7131
+ const masked = maskToolResult(message, info.name, info.args);
7132
+ newHistory.push(masked);
7133
+ if (masked !== message) maskedCount++;
7134
+ }
7135
+ const tokensAfter = estimateTokensForMessages(newHistory);
7136
+ return {
7137
+ history: newHistory,
7138
+ result: {
7139
+ masked: maskedCount > 0,
7140
+ maskedCount,
7141
+ tokensBefore,
7142
+ tokensAfter
7143
+ }
7144
+ };
7145
+ }
7146
+ //#endregion
7235
7147
  //#region src/agent/compaction/config.ts
7236
7148
  const DEFAULT_COMPACTION_CONFIG = {
7237
7149
  triggerRatio: .85,
@@ -7240,7 +7152,8 @@ const DEFAULT_COMPACTION_CONFIG = {
7240
7152
  maxCompactionPerTurn: 3,
7241
7153
  maxRecentSteps: 3,
7242
7154
  maxRecentUserMessages: Infinity,
7243
- maxRecentSizeRatio: .2
7155
+ maxRecentSizeRatio: .2,
7156
+ masking: DEFAULT_MASKING_CONFIG
7244
7157
  };
7245
7158
  //#endregion
7246
7159
  //#region src/agent/compaction/render-messages.ts
@@ -7320,6 +7233,9 @@ var DefaultCompactionStrategy = class {
7320
7233
  constructor(config = DEFAULT_COMPACTION_CONFIG) {
7321
7234
  this.config = config;
7322
7235
  }
7236
+ get maskingConfig() {
7237
+ return this.config.masking;
7238
+ }
7323
7239
  shouldCompact(usedSize, maxSize) {
7324
7240
  if (maxSize <= 0) return false;
7325
7241
  return usedSize >= maxSize * this.config.triggerRatio || this.shouldUseReservedContext(maxSize, usedSize);
@@ -7471,6 +7387,18 @@ var FullCompaction = class {
7471
7387
  await this.block(signal);
7472
7388
  }
7473
7389
  async beforeStep(signal) {
7390
+ const maskingResult = this.agent.context.applyObservationMasking(this.strategy.maskingConfig);
7391
+ if (maskingResult.masked) this.agent.emitEvent({
7392
+ type: "observation_masking.applied",
7393
+ maskedCount: maskingResult.maskedCount,
7394
+ tokensBefore: maskingResult.tokensBefore,
7395
+ tokensAfter: maskingResult.tokensAfter
7396
+ });
7397
+ const pruningResult = this.agent.context.applyPruning();
7398
+ if (pruningResult.pruned) this.agent.emitEvent({
7399
+ type: "pruning.applied",
7400
+ prunedCount: pruningResult.prunedCount
7401
+ });
7474
7402
  this.checkAutoCompaction();
7475
7403
  if (this.shouldBlock) await this.block(signal);
7476
7404
  }
@@ -7634,6 +7562,19 @@ var FullCompaction = class {
7634
7562
  computeCompactableCount(history) {
7635
7563
  return sliceCompleteMessages(history, this.strategy.computeCompactCount(history, this.maxContextSize));
7636
7564
  }
7565
+ restoreRecord(record) {
7566
+ switch (record.type) {
7567
+ case "full_compaction.begin":
7568
+ this.begin(record);
7569
+ break;
7570
+ case "full_compaction.cancel":
7571
+ this.cancel();
7572
+ break;
7573
+ case "full_compaction.complete":
7574
+ this.complete(record);
7575
+ break;
7576
+ }
7577
+ }
7637
7578
  };
7638
7579
  function extractCompactionSummary(response) {
7639
7580
  const summary = typeof response.message.content === "string" ? response.message.content : response.message.content.map((part) => part.type === "text" ? part.text : "").join("");
@@ -7775,6 +7716,92 @@ var ConfigState = class {
7775
7716
  return;
7776
7717
  }
7777
7718
  }
7719
+ restoreRecord(record) {
7720
+ switch (record.type) {
7721
+ case "config.update":
7722
+ this.update(record);
7723
+ break;
7724
+ }
7725
+ }
7726
+ };
7727
+ //#endregion
7728
+ //#region src/agent/context/output-offloading.ts
7729
+ const DEFAULT_OFFLOADING_CONFIG = {
7730
+ threshold: 8e3,
7731
+ previewChars: 1e3
7732
+ };
7733
+ function shouldOffload(output, config = DEFAULT_OFFLOADING_CONFIG) {
7734
+ return estimateTokens(output) > config.threshold;
7735
+ }
7736
+ async function offloadOutput(toolCallId, toolName, result, scratchManager, config = DEFAULT_OFFLOADING_CONFIG) {
7737
+ const output = result.output;
7738
+ if (typeof output !== "string") return { offloaded: false };
7739
+ if (!shouldOffload(output, config)) return { offloaded: false };
7740
+ const filePath = await scratchManager.writeOutput(toolCallId, output);
7741
+ return {
7742
+ offloaded: true,
7743
+ output: buildPreview(output, toolName, filePath, config.previewChars),
7744
+ filePath
7745
+ };
7746
+ }
7747
+ function buildPreview(output, toolName, filePath, previewChars) {
7748
+ const preview = output.slice(0, previewChars);
7749
+ return `[Tool output offloaded to scratch file: ${filePath}]
7750
+ Preview (first ${String(previewChars)} chars):
7751
+ ${preview}
7752
+ Use Read(path="${filePath}") to retrieve the full output.`;
7753
+ }
7754
+ //#endregion
7755
+ //#region src/agent/context/scratch-manager.ts
7756
+ var ScratchManager = class {
7757
+ kaos;
7758
+ config;
7759
+ currentSize = 0;
7760
+ files = [];
7761
+ constructor(kaos, config) {
7762
+ this.kaos = kaos;
7763
+ this.config = config;
7764
+ }
7765
+ async writeOutput(toolCallId, output) {
7766
+ await this.kaos.mkdir(this.config.scratchDir, {
7767
+ parents: true,
7768
+ existOk: true
7769
+ });
7770
+ const filePath = join(this.config.scratchDir, `${toolCallId}.txt`);
7771
+ const size = Buffer.byteLength(output, "utf-8");
7772
+ await this.evictIfNeeded(size);
7773
+ await this.kaos.writeText(filePath, output);
7774
+ this.files.push({
7775
+ path: filePath,
7776
+ size,
7777
+ createdAt: Date.now()
7778
+ });
7779
+ this.currentSize += size;
7780
+ return filePath;
7781
+ }
7782
+ async readOutput(filePath) {
7783
+ return this.kaos.readText(filePath);
7784
+ }
7785
+ async evictIfNeeded(neededBytes) {
7786
+ this.files.sort((a, b) => a.createdAt - b.createdAt);
7787
+ while (this.files.length > 0 && (this.files.length >= this.config.maxFileCount || this.currentSize + neededBytes > this.config.maxSessionSize)) {
7788
+ const oldest = this.files.shift();
7789
+ try {
7790
+ await this.kaos.exec("rm", oldest.path);
7791
+ } catch {}
7792
+ this.currentSize -= oldest.size;
7793
+ }
7794
+ }
7795
+ async cleanup() {
7796
+ for (const file of this.files) try {
7797
+ await this.kaos.exec("rm", file.path);
7798
+ } catch {}
7799
+ this.files = [];
7800
+ this.currentSize = 0;
7801
+ try {
7802
+ await this.kaos.exec("rm", "-rf", this.config.scratchDir);
7803
+ } catch {}
7804
+ }
7778
7805
  };
7779
7806
  //#endregion
7780
7807
  //#region src/agent/context/types.ts
@@ -7793,8 +7820,15 @@ var ContextMemory = class {
7793
7820
  openSteps = /* @__PURE__ */ new Map();
7794
7821
  pendingToolResultIds = /* @__PURE__ */ new Set();
7795
7822
  deferredMessages = [];
7796
- constructor(agent) {
7823
+ toolCallInfo = /* @__PURE__ */ new Map();
7824
+ scratchManager;
7825
+ constructor(agent, sessionId) {
7797
7826
  this.agent = agent;
7827
+ if (agent.homedir !== void 0 && sessionId !== void 0) this.scratchManager = new ScratchManager(agent.runtime.kaos, {
7828
+ scratchDir: join(agent.homedir, "sessions", sessionId, "scratch"),
7829
+ maxSessionSize: 5e7,
7830
+ maxFileCount: 100
7831
+ });
7798
7832
  }
7799
7833
  appendUserMessage(content, origin = USER_PROMPT_ORIGIN) {
7800
7834
  this.appendMessage({
@@ -7842,6 +7876,8 @@ var ContextMemory = class {
7842
7876
  this.openSteps.clear();
7843
7877
  this.pendingToolResultIds.clear();
7844
7878
  this.deferredMessages = [];
7879
+ this.toolCallInfo.clear();
7880
+ this.scratchManager?.cleanup();
7845
7881
  this.agent.injection.onContextClear();
7846
7882
  this.agent.emitStatusUpdated();
7847
7883
  }
@@ -7885,7 +7921,73 @@ var ContextMemory = class {
7885
7921
  get messages() {
7886
7922
  return project(this.history);
7887
7923
  }
7888
- appendLoopEvent(event) {
7924
+ applyObservationMasking(config) {
7925
+ const effectiveConfig = config ?? DEFAULT_MASKING_CONFIG;
7926
+ const maxContextSize = this.agent.config.modelCapabilities.max_context_tokens;
7927
+ const { history, result } = applyObservationMasking(this._history, maxContextSize, this.toolCallInfo, effectiveConfig);
7928
+ if (result.masked) {
7929
+ this.agent.records.logRecord({
7930
+ type: "context.observation_masking",
7931
+ maskedCount: result.maskedCount,
7932
+ tokensBefore: result.tokensBefore,
7933
+ tokensAfter: result.tokensAfter
7934
+ });
7935
+ this._history = history;
7936
+ this.agent.emitStatusUpdated();
7937
+ }
7938
+ return result;
7939
+ }
7940
+ applyPruning(config) {
7941
+ const maxContextSize = this.agent.config.modelCapabilities.max_context_tokens;
7942
+ if (maxContextSize <= 0) return {
7943
+ pruned: false,
7944
+ prunedCount: 0
7945
+ };
7946
+ const effectiveCapacity = maxContextSize * (config?.effectiveCapacityRatio ?? .6);
7947
+ const currentTokens = this.tokenCountWithPending;
7948
+ const threshold = effectiveCapacity * (config?.pruningThreshold ?? .85);
7949
+ if (currentTokens < threshold) return {
7950
+ pruned: false,
7951
+ prunedCount: 0
7952
+ };
7953
+ const maskedIndices = [];
7954
+ for (let i = 0; i < this._history.length; i++) {
7955
+ const message = this._history[i];
7956
+ if (message?.role !== "tool" || message.toolCallId === void 0) continue;
7957
+ const info = this.toolCallInfo.get(message.toolCallId);
7958
+ if (info === void 0) continue;
7959
+ if ((typeof message.content === "string" ? message.content : message.content.filter((part) => part.type === "text").map((part) => part.text).join("")).startsWith(`[${info.name}:`)) maskedIndices.push(i);
7960
+ }
7961
+ let prunedCount = 0;
7962
+ let tokensAfter = currentTokens;
7963
+ for (const index of maskedIndices) {
7964
+ if (tokensAfter < threshold) break;
7965
+ const message = this._history[index];
7966
+ if (message === void 0) continue;
7967
+ const tokensBeforeMessage = estimateTokensForMessages([message]);
7968
+ this._history[index] = {
7969
+ ...message,
7970
+ content: [{
7971
+ type: "text",
7972
+ text: "[pruned]"
7973
+ }]
7974
+ };
7975
+ tokensAfter -= tokensBeforeMessage;
7976
+ prunedCount++;
7977
+ }
7978
+ if (prunedCount > 0) {
7979
+ this.agent.records.logRecord({
7980
+ type: "context.pruning",
7981
+ prunedCount
7982
+ });
7983
+ this.agent.emitStatusUpdated();
7984
+ }
7985
+ return {
7986
+ pruned: prunedCount > 0,
7987
+ prunedCount
7988
+ };
7989
+ }
7990
+ async appendLoopEvent(event) {
7889
7991
  this.agent.records.logRecord({
7890
7992
  type: "context.append_loop_event",
7891
7993
  event
@@ -7928,14 +8030,33 @@ var ContextMemory = class {
7928
8030
  arguments: event.args === void 0 ? null : JSON.stringify(event.args)
7929
8031
  });
7930
8032
  this.pendingToolResultIds.add(event.toolCallId);
8033
+ this.toolCallInfo.set(event.toolCallId, {
8034
+ name: event.name,
8035
+ args: event.args
8036
+ });
7931
8037
  return;
7932
8038
  }
7933
8039
  case "tool.result": {
7934
- const message = createToolMessage(event.toolCallId, toolResultOutputForModel(event.result));
8040
+ let result = event.result;
8041
+ if (!this.agent.records.restoring && this.scratchManager !== void 0 && typeof result.output === "string") {
8042
+ const offloadResult = await offloadOutput(event.toolCallId, this.toolCallInfo.get(event.toolCallId)?.name ?? "unknown", result, this.scratchManager, DEFAULT_OFFLOADING_CONFIG);
8043
+ if (offloadResult.offloaded) {
8044
+ result = {
8045
+ ...result,
8046
+ output: offloadResult.output
8047
+ };
8048
+ this.agent.records.logRecord({
8049
+ type: "context.output_offloaded",
8050
+ toolCallId: event.toolCallId,
8051
+ filePath: offloadResult.filePath
8052
+ });
8053
+ }
8054
+ }
8055
+ const message = createToolMessage(event.toolCallId, toolResultOutputForModel(result));
7935
8056
  this.pushHistory({
7936
8057
  ...message,
7937
8058
  role: "tool",
7938
- isError: event.result.isError
8059
+ isError: result.isError
7939
8060
  });
7940
8061
  this.pendingToolResultIds.delete(event.toolCallId);
7941
8062
  this.flushDeferredMessagesIfToolExchangeClosed();
@@ -7972,6 +8093,83 @@ var ContextMemory = class {
7972
8093
  });
7973
8094
  }
7974
8095
  }
8096
+ restoreRecord(record) {
8097
+ switch (record.type) {
8098
+ case "context.append_message":
8099
+ this.appendMessage(record.message);
8100
+ break;
8101
+ case "context.clear":
8102
+ this.restoreClear();
8103
+ break;
8104
+ case "context.apply_compaction":
8105
+ this.restoreApplyCompaction(record);
8106
+ break;
8107
+ case "context.mark_last_user_prompt_blocked":
8108
+ this.restoreMarkLastUserPromptBlocked(record);
8109
+ break;
8110
+ case "context.append_loop_event":
8111
+ this.restoreAppendLoopEvent(record);
8112
+ break;
8113
+ case "context.observation_masking":
8114
+ this.restoreObservationMasking();
8115
+ break;
8116
+ }
8117
+ }
8118
+ restoreClear() {
8119
+ this._history = [];
8120
+ this._tokenCount = 0;
8121
+ this.tokenCountCoveredMessageCount = 0;
8122
+ this.openSteps.clear();
8123
+ this.pendingToolResultIds.clear();
8124
+ this.deferredMessages = [];
8125
+ this.toolCallInfo.clear();
8126
+ this.agent.injection.onContextClear();
8127
+ this.agent.emitStatusUpdated();
8128
+ }
8129
+ restoreApplyCompaction(record) {
8130
+ const compactedCount = record.compactedCount;
8131
+ const summary = record.summary;
8132
+ const tokensAfter = record.tokensAfter;
8133
+ this._history = [{
8134
+ role: "assistant",
8135
+ content: [{
8136
+ type: "text",
8137
+ text: summary
8138
+ }],
8139
+ toolCalls: [],
8140
+ origin: { kind: "compaction_summary" }
8141
+ }, ...this._history.slice(compactedCount)];
8142
+ this.openSteps.clear();
8143
+ this.flushDeferredMessagesIfToolExchangeClosed();
8144
+ this._tokenCount = tokensAfter;
8145
+ this.tokenCountCoveredMessageCount = this._history.length;
8146
+ this.agent.injection.onContextCompacted(compactedCount);
8147
+ this.agent.emitStatusUpdated();
8148
+ }
8149
+ restoreMarkLastUserPromptBlocked(record) {
8150
+ const hookEvent = record.hookEvent;
8151
+ for (let i = this._history.length - 1; i >= 0; i--) {
8152
+ const message = this._history[i];
8153
+ if (message?.role !== "user" || message.origin?.kind !== "user") continue;
8154
+ this._history[i] = {
8155
+ ...message,
8156
+ origin: {
8157
+ ...message.origin,
8158
+ blockedByHook: hookEvent
8159
+ }
8160
+ };
8161
+ return;
8162
+ }
8163
+ }
8164
+ async restoreAppendLoopEvent(record) {
8165
+ await this.appendLoopEvent(record.event);
8166
+ }
8167
+ restoreObservationMasking() {
8168
+ const maxContextSize = this.agent.config.modelCapabilities.max_context_tokens;
8169
+ const { history } = applyObservationMasking(this._history, maxContextSize, this.toolCallInfo);
8170
+ this._history = history;
8171
+ this.agent.emitStatusUpdated();
8172
+ }
7975
8173
  };
7976
8174
  function toolResultOutputForModel(result) {
7977
8175
  const output = result.output;
@@ -8382,6 +8580,129 @@ var DynamicInjector = class {
8382
8580
  }
8383
8581
  };
8384
8582
  //#endregion
8583
+ //#region src/agent/injection/directory-tree.ts
8584
+ const EXCLUDED_DIRS = new Set([
8585
+ "node_modules",
8586
+ ".git",
8587
+ "dist",
8588
+ "build",
8589
+ ".next",
8590
+ ".nuxt",
8591
+ ".vite",
8592
+ "target",
8593
+ ".turbo",
8594
+ "coverage",
8595
+ ".cache",
8596
+ ".DS_Store",
8597
+ ".idea",
8598
+ ".vscode",
8599
+ "venv",
8600
+ ".venv"
8601
+ ]);
8602
+ const HIDDEN_DIR_WHITELIST = new Set([
8603
+ ".github",
8604
+ ".byf",
8605
+ ".agents",
8606
+ ".changeset",
8607
+ ".husky"
8608
+ ]);
8609
+ var DirectoryTreeInjector = class extends DynamicInjector {
8610
+ injectionVariant = "directory_tree";
8611
+ lastTree;
8612
+ hasInjected = false;
8613
+ capturedTimestamp;
8614
+ async getInjection() {
8615
+ const kaos = this.agent.runtime.kaos;
8616
+ const workDir = this.agent.config.cwd || kaos.getcwd();
8617
+ const tree = await buildTree(kaos, workDir);
8618
+ if (this.hasInjected && tree === this.lastTree) return;
8619
+ this.lastTree = tree;
8620
+ this.hasInjected = true;
8621
+ if (this.capturedTimestamp === void 0) this.capturedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
8622
+ return `Current working directory structure (${workDir}):\n${tree}\n\nThe current date and time in ISO format is \`${this.capturedTimestamp}\`. This is only a reference for you when searching the web or checking file modification time, etc. If you need the exact time, use Bash tool with proper command.`;
8623
+ }
8624
+ };
8625
+ async function buildTree(kaos, workDir) {
8626
+ const lines = [];
8627
+ const pathClass = kaos.pathClass();
8628
+ const { entries, total, readable } = await collectEntries(kaos, workDir, 30, pathClass);
8629
+ if (!readable) return "[not readable]";
8630
+ const remaining = total - entries.length;
8631
+ for (let i = 0; i < entries.length; i++) {
8632
+ const entry = entries[i];
8633
+ if (entry === void 0) continue;
8634
+ const { name, isDir } = entry;
8635
+ const isLast = i === entries.length - 1 && remaining === 0;
8636
+ const connector = isLast ? "└── " : "├── ";
8637
+ if (isDir) {
8638
+ lines.push(`${connector}${name}/`);
8639
+ const childPrefix = isLast ? " " : "│ ";
8640
+ const child = await collectEntries(kaos, joinPath(workDir, name, pathClass), 10, pathClass);
8641
+ if (!child.readable) {
8642
+ lines.push(`${childPrefix}└── [not readable]`);
8643
+ continue;
8644
+ }
8645
+ const childRemaining = child.total - child.entries.length;
8646
+ for (let j = 0; j < child.entries.length; j++) {
8647
+ const ce = child.entries[j];
8648
+ if (ce === void 0) continue;
8649
+ const cConnector = j === child.entries.length - 1 && childRemaining === 0 ? "└── " : "├── ";
8650
+ const suffix = ce.isDir ? "/" : "";
8651
+ lines.push(`${childPrefix}${cConnector}${ce.name}${suffix}`);
8652
+ }
8653
+ if (childRemaining > 0) lines.push(`${childPrefix}└── ... and ${String(childRemaining)} more`);
8654
+ } else lines.push(`${connector}${name}`);
8655
+ }
8656
+ if (remaining > 0) lines.push(`└── ... and ${String(remaining)} more entries`);
8657
+ return lines.length > 0 ? lines.join("\n") : "(empty directory)";
8658
+ }
8659
+ async function collectEntries(kaos, dirPath, maxWidth, pathClass) {
8660
+ const all = [];
8661
+ try {
8662
+ for await (const fullPath of kaos.iterdir(dirPath)) {
8663
+ const name = basename$1(fullPath, pathClass);
8664
+ if (shouldExclude(name)) continue;
8665
+ let isDir = false;
8666
+ try {
8667
+ isDir = ((await kaos.stat(fullPath)).stMode & 61440) === 16384;
8668
+ } catch {}
8669
+ all.push({
8670
+ name,
8671
+ isDir
8672
+ });
8673
+ }
8674
+ } catch {
8675
+ return {
8676
+ entries: [],
8677
+ total: 0,
8678
+ readable: false
8679
+ };
8680
+ }
8681
+ all.sort((a, b) => {
8682
+ if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
8683
+ return a.name.localeCompare(b.name);
8684
+ });
8685
+ return {
8686
+ entries: all.slice(0, maxWidth),
8687
+ total: all.length,
8688
+ readable: true
8689
+ };
8690
+ }
8691
+ function shouldExclude(name) {
8692
+ if (EXCLUDED_DIRS.has(name)) return true;
8693
+ if (name.startsWith(".") && !HIDDEN_DIR_WHITELIST.has(name)) return true;
8694
+ return false;
8695
+ }
8696
+ function pathMod$1(pathClass) {
8697
+ return pathClass === "win32" ? win32Path : posixPath;
8698
+ }
8699
+ function basename$1(p, pathClass) {
8700
+ return pathMod$1(pathClass).basename(p);
8701
+ }
8702
+ function joinPath(parent, child, pathClass) {
8703
+ return pathMod$1(pathClass).join(parent, child);
8704
+ }
8705
+ //#endregion
8385
8706
  //#region src/agent/injection/permission-mode.ts
8386
8707
  const AUTO_MODE_ENTER_REMINDER = [
8387
8708
  "Auto permission mode is active. Tool approvals will be handled automatically while this mode remains enabled.",
@@ -8402,149 +8723,13 @@ var PermissionModeInjector = class extends DynamicInjector {
8402
8723
  }
8403
8724
  };
8404
8725
  //#endregion
8405
- //#region src/agent/injection/plan-mode.ts
8406
- const PLAN_MODE_DEDUP_MIN_TURNS = 2;
8407
- const PLAN_MODE_FULL_REFRESH_TURNS = 5;
8408
- var PlanModeInjector = class extends DynamicInjector {
8409
- injectionVariant = "plan_mode";
8410
- wasActive = false;
8411
- onContextClear() {
8412
- super.onContextClear();
8413
- this.wasActive = this.agent.planMode.isActive;
8414
- }
8415
- async getInjection() {
8416
- const { isActive, planFilePath } = this.agent.planMode;
8417
- if (!isActive) {
8418
- if (!this.wasActive) return;
8419
- this.wasActive = false;
8420
- this.injectedAt = null;
8421
- return exitReminder();
8422
- }
8423
- if (!this.wasActive) {
8424
- this.injectedAt = null;
8425
- this.wasActive = true;
8426
- if (await this.hasCurrentPlanContent()) return reentryReminder(planFilePath);
8427
- }
8428
- const variant = this.getVariant();
8429
- if (variant === null) return void 0;
8430
- return variant === "full" ? fullReminder(planFilePath) : variant === "sparse" ? sparseReminder(planFilePath) : reentryReminder(planFilePath);
8431
- }
8432
- getVariant() {
8433
- if (this.injectedAt === null) return "full";
8434
- const history = this.agent.context.history;
8435
- let assistantTurnsSince = 0;
8436
- for (let i = this.injectedAt + 1; i < history.length; i++) {
8437
- const msg = history[i];
8438
- if (msg === void 0) continue;
8439
- if (msg.role === "assistant") {
8440
- assistantTurnsSince += 1;
8441
- continue;
8442
- }
8443
- if (msg.role === "user") return "full";
8444
- }
8445
- if (assistantTurnsSince >= PLAN_MODE_FULL_REFRESH_TURNS) return "full";
8446
- if (assistantTurnsSince >= PLAN_MODE_DEDUP_MIN_TURNS) return "sparse";
8447
- return null;
8448
- }
8449
- async hasCurrentPlanContent() {
8450
- try {
8451
- const data = await this.agent.planMode.data();
8452
- return data !== null && data.content.trim().length > 0;
8453
- } catch {
8454
- return false;
8455
- }
8456
- }
8457
- };
8458
- function withPlanFileFooter(body, planFilePath) {
8459
- if (planFilePath === null || planFilePath.length === 0) return body;
8460
- return `${body}\n\nPlan file: ${planFilePath}`;
8461
- }
8462
- function fullReminder(planFilePath) {
8463
- if (planFilePath === null || planFilePath.length === 0) return inlineFullReminder();
8464
- return withPlanFileFooter(`Plan mode is active. You MUST NOT make any edits (with the exception of the current plan file) or otherwise make changes to the system unless a tool request is explicitly approved. Prefer read-only tools. Use Bash only when needed; Bash follows the normal permission mode and rules. This supersedes any other instructions you have received.
8465
-
8466
- Workflow:
8467
- 1. Understand — explore the codebase with Glob, Grep, Read.
8468
- 2. Design — converge on the best approach; consider trade-offs but aim for a single recommendation.
8469
- 3. Review — re-read key files to verify understanding.
8470
- 4. Write Plan — modify the plan file with Write or Edit. Use Write if the plan file does not exist yet.
8471
- 5. Exit — call ExitPlanMode for user approval.
8472
-
8473
- ## Handling multiple approaches
8474
- Keep it focused: at most 2-3 meaningfully different approaches. Do NOT pad with minor variations — if one approach is clearly superior, just propose that one.
8475
- When the best approach depends on user preferences, constraints, or context you don't have, use AskUserQuestion to clarify first. This helps you write a better, more targeted plan rather than dumping multiple options for the user to sort through.
8476
- When you do include multiple approaches in the plan, you MUST pass them as the \`options\` parameter when calling ExitPlanMode, so the user can select which approach to execute at approval time.
8477
- NEVER write multiple approaches in the plan and call ExitPlanMode without the \`options\` parameter — the user will only see the default approval controls with no way to choose a specific approach.
8478
-
8479
- AskUserQuestion is for clarifying missing requirements or user preferences that affect the plan.
8480
- Never ask about plan approval via text or AskUserQuestion.
8481
- Your turn must end with either AskUserQuestion (to clarify requirements or preferences) or ExitPlanMode (to request plan approval). Do NOT end your turn any other way.
8482
- Do NOT use AskUserQuestion to ask about plan approval or reference "the plan" — the user cannot see the plan until you call ExitPlanMode.`, planFilePath);
8483
- }
8484
- function sparseReminder(planFilePath) {
8485
- if (planFilePath === null || planFilePath.length === 0) return inlineSparseReminder();
8486
- return withPlanFileFooter(`Plan mode still active (see full instructions earlier). Prefer read-only tools except the current plan file. Use Write or Edit to modify the plan file. If it does not exist yet, create it with Write first. Use Bash only when needed; Bash follows the normal permission mode and rules. Use AskUserQuestion to clarify user preferences when it helps you write a better plan. If the plan has multiple approaches, pass options to ExitPlanMode so the user can choose. End turns with AskUserQuestion (for clarifications) or ExitPlanMode (for approval). Never ask about plan approval via text or AskUserQuestion.`, planFilePath);
8487
- }
8488
- function reentryReminder(planFilePath) {
8489
- if (planFilePath === null || planFilePath.length === 0) return inlineReentryReminder();
8490
- return withPlanFileFooter(`Plan mode is active. You MUST NOT make any edits (with the exception of the current plan file) or otherwise make changes to the system unless a tool request is explicitly approved. Prefer read-only tools. Use Bash only when needed; Bash follows the normal permission mode and rules. This supersedes any other instructions you have received.
8491
-
8492
- ## Re-entering Plan Mode
8493
- A plan file from a previous planning session already exists.
8494
- Before proceeding:
8495
- 1. Read the existing plan file to understand what was previously planned.
8496
- 2. Evaluate the user's current request against that plan.
8497
- 3. If different task: replace the old plan with a fresh one. If same task: update the existing plan.
8498
- 4. You may use Write or Edit to modify the plan file. If the file does not exist yet, create it with Write first.
8499
- 5. Use AskUserQuestion to clarify missing requirements or user preferences that affect the plan.
8500
- 6. Always edit the plan file before calling ExitPlanMode.
8501
-
8502
- Your turn must end with either AskUserQuestion (to clarify requirements) or ExitPlanMode (to request plan approval).`, planFilePath);
8503
- }
8504
- function inlineFullReminder() {
8505
- return `Plan mode is active. You MUST NOT make any edits or otherwise make changes to the system unless a tool request is explicitly approved. Prefer read-only tools. Use Bash only when needed; Bash follows the normal permission mode and rules. This supersedes any other instructions you have received.
8506
-
8507
- Workflow:
8508
- 1. Understand — explore the codebase with Glob, Grep, Read.
8509
- 2. Design — converge on the best approach; consider trade-offs but aim for a single recommendation.
8510
- 3. Review — re-read key files to verify understanding.
8511
- 4. Wait for the host to provide a plan file path, write the plan there, then call ExitPlanMode.
8512
-
8513
- ## Handling multiple approaches
8514
- Keep it focused: at most 2-3 meaningfully different approaches. Do NOT pad with minor variations — if one approach is clearly superior, just propose that one.
8515
- When the best approach depends on user preferences, constraints, or context you don't have, use AskUserQuestion to clarify first.
8516
- When you do include multiple approaches in the plan, you MUST pass them as the \`options\` parameter when calling ExitPlanMode, so the user can select which approach to execute at approval time.
8517
-
8518
- AskUserQuestion is for clarifying missing requirements or user preferences that affect the plan.
8519
- Never ask about plan approval via text or AskUserQuestion.
8520
- Your turn must end with either AskUserQuestion (to clarify requirements or preferences) or ExitPlanMode (to request plan approval). Do NOT end your turn any other way.`;
8521
- }
8522
- function inlineSparseReminder() {
8523
- return `Plan mode still active (see full instructions earlier). Read-only; no plan file path is available in this host. Wait for the host to provide a plan file path before calling ExitPlanMode. Use AskUserQuestion to clarify user preferences when it helps you write a better plan. If the plan has multiple approaches, pass options to ExitPlanMode so the user can choose. End turns with AskUserQuestion (for clarifications) or ExitPlanMode (for approval).`;
8524
- }
8525
- function inlineReentryReminder() {
8526
- return `Plan mode is active. You MUST NOT make any edits or otherwise make changes to the system unless a tool request is explicitly approved. Prefer read-only tools. Use Bash only when needed; Bash follows the normal permission mode and rules. This supersedes any other instructions you have received.
8527
-
8528
- ## Re-entering Plan Mode
8529
- No plan file path is available in this host.
8530
- Before proceeding:
8531
- 1. Re-evaluate the user request and any existing conversation context.
8532
- 2. Use AskUserQuestion to clarify missing requirements or user preferences that affect the plan.
8533
- 3. Wait for the host to provide a plan file path, write the revised plan there, then call ExitPlanMode.
8534
-
8535
- Your turn must end with either AskUserQuestion (to clarify requirements) or ExitPlanMode (to request plan approval).`;
8536
- }
8537
- function exitReminder() {
8538
- return `Plan mode is no longer active. The read-only and plan-file-only restrictions from plan mode no longer apply. Continue with the approved plan using the normal tool and permission rules.`;
8539
- }
8540
- //#endregion
8541
8726
  //#region src/agent/injection/manager.ts
8542
8727
  var InjectionManager = class {
8543
8728
  agent;
8544
8729
  injectors;
8545
8730
  constructor(agent) {
8546
8731
  this.agent = agent;
8547
- this.injectors = [new PlanModeInjector(agent), new PermissionModeInjector(agent)];
8732
+ this.injectors = [new PermissionModeInjector(agent), new DirectoryTreeInjector(agent)];
8548
8733
  }
8549
8734
  async inject() {
8550
8735
  for (const injector of this.injectors) await injector.inject();
@@ -8573,8 +8758,6 @@ const BUILTIN_TOOL_DEFAULT_PERMISSIONS = {
8573
8758
  FetchURL: "auto_allow",
8574
8759
  Agent: "auto_allow",
8575
8760
  AskUserQuestion: "auto_allow",
8576
- EnterPlanMode: "auto_allow",
8577
- ExitPlanMode: "auto_allow",
8578
8761
  Skill: "auto_allow",
8579
8762
  Bash: "ask",
8580
8763
  Write: "ask",
@@ -8607,7 +8790,6 @@ const TOOL_NAME_TO_ACTION = {
8607
8790
  /** Inverse table — action label → the representative tool-name pattern. */
8608
8791
  const ACTION_TO_PATTERN = {
8609
8792
  "run command": "Bash",
8610
- "run command in plan mode": null,
8611
8793
  "run background command": "BackgroundRun",
8612
8794
  "stop background task": "BackgroundStop",
8613
8795
  "edit file": "Write",
@@ -10852,7 +11034,7 @@ function createDefaultGitCwdWritePolicy() {
10852
11034
  if (pathClass !== "posix") return void 0;
10853
11035
  const cwd = agent.config.cwd;
10854
11036
  if (cwd.length === 0) return void 0;
10855
- const path = readStringField$2(toolCallContext.args, "path");
11037
+ const path = readStringField$1(toolCallContext.args, "path");
10856
11038
  if (path === void 0) return void 0;
10857
11039
  let access;
10858
11040
  try {
@@ -10884,7 +11066,7 @@ function createDefaultGitCwdWritePolicy() {
10884
11066
  }
10885
11067
  };
10886
11068
  }
10887
- function readStringField$2(args, key) {
11069
+ function readStringField$1(args, key) {
10888
11070
  if (args === null || typeof args !== "object") return void 0;
10889
11071
  const value = args[key];
10890
11072
  return typeof value === "string" ? value : void 0;
@@ -10906,240 +11088,16 @@ async function hasSymlinkInPath(kaos, cwd, targetPath) {
10906
11088
  if (((await kaos.stat(parts[index], { followSymlinks: false })).stMode & S_IFMT) === S_IFLNK) return true;
10907
11089
  } catch (error) {
10908
11090
  return !(index === parts.length - 1 && isFileNotFoundError(error));
10909
- }
10910
- return false;
10911
- }
10912
- function isFileNotFoundError(error) {
10913
- if (error === null || typeof error !== "object") return false;
10914
- if (error.name === "KaosFileNotFoundError") return true;
10915
- const code = error.code;
10916
- if (code === "ENOENT" || code === "ENOTDIR" || code === 2) return true;
10917
- const message = error instanceof Error ? error.message : "";
10918
- return message.includes("ENOENT") || message.includes("ENOTDIR");
10919
- }
10920
- //#endregion
10921
- //#region src/agent/permission/policies/plan.ts
10922
- const EnterPlanModePermissionPolicy = {
10923
- name: "plan.enter-plan-mode",
10924
- evaluate({ toolCallContext }) {
10925
- if (toolCallContext.toolCall.name !== "EnterPlanMode") return void 0;
10926
- return { kind: "allow" };
10927
- }
10928
- };
10929
- const ExitPlanModePermissionPolicy = {
10930
- name: "plan.exit-plan-mode",
10931
- async evaluate(context) {
10932
- if (context.toolCallContext.toolCall.name !== "ExitPlanMode") return void 0;
10933
- if (context.mode === "auto") return { kind: "allow" };
10934
- const review = await resolveExitPlanModeReview(context);
10935
- if (review === null) return { kind: "allow" };
10936
- const action = exitPlanModeAction(review.options);
10937
- context.agent.telemetry.track("plan_submitted", { has_options: review.options !== void 0 });
10938
- let result;
10939
- try {
10940
- result = await context.agent.rpc.requestApproval({
10941
- turnId: Number(context.toolCallContext.turnId),
10942
- toolCallId: context.toolCallContext.toolCall.id,
10943
- toolName: "ExitPlanMode",
10944
- action,
10945
- display: {
10946
- kind: "plan_review",
10947
- plan: review.plan,
10948
- path: review.path,
10949
- options: review.options
10950
- }
10951
- }, { signal: context.toolCallContext.signal });
10952
- } catch (error) {
10953
- return {
10954
- kind: "result",
10955
- result: { syntheticResult: {
10956
- isError: true,
10957
- output: `Plan approval failed: ${error instanceof Error ? error.message : "Plan approval failed."}`
10958
- } }
10959
- };
10960
- }
10961
- context.recordApprovalResult({
10962
- turnId: Number(context.toolCallContext.turnId),
10963
- toolCallId: context.toolCallContext.toolCall.id,
10964
- toolName: "ExitPlanMode",
10965
- action,
10966
- result
10967
- });
10968
- trackExitPlanModeResolution(context, result);
10969
- return exitPlanModeApprovalResult(context, result, review.options);
10970
- }
10971
- };
10972
- const PlanModeGuardPermissionPolicy = {
10973
- name: "plan.mode-guard",
10974
- async evaluate({ agent, toolCallContext }) {
10975
- if (!agent.planMode.isActive) return void 0;
10976
- const name = toolCallContext.toolCall.name;
10977
- const args = toolCallContext.args;
10978
- if (name === "Write" || name === "Edit") {
10979
- const path = readStringField$1(args, "path");
10980
- if (path === void 0) return void 0;
10981
- const planFilePath = agent.planMode.planFilePath;
10982
- if (planFilePath !== null && path === planFilePath) {
10983
- await agent.planMode.materializeCurrentPlanFile();
10984
- return { kind: "allow" };
10985
- }
10986
- return {
10987
- kind: "result",
10988
- result: {
10989
- block: true,
10990
- reason: `Plan mode is active. You may only write to the current plan file: ${planFilePath ?? "(no plan file selected yet)"}. Call ExitPlanMode to exit plan mode before editing other files.`
10991
- }
10992
- };
10993
- }
10994
- if (name === "TaskStop") return {
10995
- kind: "result",
10996
- result: {
10997
- block: true,
10998
- reason: "TaskStop is not available in plan mode. Call ExitPlanMode to exit plan mode before stopping a background task."
10999
- }
11000
- };
11001
- }
11002
- };
11003
- function createPlanPermissionPolicies() {
11004
- return [
11005
- EnterPlanModePermissionPolicy,
11006
- ExitPlanModePermissionPolicy,
11007
- PlanModeGuardPermissionPolicy
11008
- ];
11009
- }
11010
- async function resolveExitPlanModeReview(context) {
11011
- if (!context.agent.planMode.isActive) return null;
11012
- let data;
11013
- try {
11014
- data = await context.agent.planMode.data();
11015
- } catch {
11016
- return null;
11017
- }
11018
- if (data === null || data.content.trim().length === 0) return null;
11019
- return {
11020
- plan: data.content,
11021
- path: data.path,
11022
- options: exitPlanModeOptions(context.toolCallContext.args)
11023
- };
11024
- }
11025
- function exitPlanModeApprovalResult(context, result, options) {
11026
- if (result.decision === "approved") return {
11027
- kind: "allow",
11028
- executionMetadata: exitPlanModeExecutionMetadata(selectedExitPlanModeOption(options, result.selectedLabel))
11029
- };
11030
- if (result.decision === "cancelled") return {
11031
- kind: "result",
11032
- result: { syntheticResult: {
11033
- isError: false,
11034
- output: "Plan approval dismissed. Plan mode remains active."
11035
- } }
11036
- };
11037
- if (result.selectedLabel === "Reject and Exit") return {
11038
- kind: "result",
11039
- result: { syntheticResult: exitPlanModeForRejectedPlan(context) ?? {
11040
- isError: true,
11041
- stopTurn: true,
11042
- output: "Plan rejected by user. Plan mode deactivated."
11043
- } }
11044
- };
11045
- const feedback = result.feedback ?? "";
11046
- if (result.selectedLabel === "Revise" || feedback.length > 0) return {
11047
- kind: "result",
11048
- result: { syntheticResult: {
11049
- isError: false,
11050
- output: feedback.length > 0 ? `User rejected the plan. Feedback:\n\n${feedback}` : "User requested revisions. Plan mode remains active."
11051
- } }
11052
- };
11053
- return {
11054
- kind: "result",
11055
- result: { syntheticResult: {
11056
- isError: true,
11057
- stopTurn: true,
11058
- output: "Plan rejected by user. Plan mode remains active."
11059
- } }
11060
- };
11061
- }
11062
- function exitPlanModeExecutionMetadata(selectedOption) {
11063
- return {
11064
- selectedOption,
11065
- planTelemetrySubmitted: true,
11066
- planTelemetryResolved: true
11067
- };
11068
- }
11069
- function trackExitPlanModeResolution(context, result) {
11070
- const selectedLabel = result.selectedLabel ?? "";
11071
- const normalizedSelectedLabel = normalizeOptionLabel(selectedLabel);
11072
- const hasFeedback = (result.feedback ?? "").length > 0;
11073
- if (result.decision === "cancelled") {
11074
- context.agent.telemetry.track("plan_resolved", { outcome: "dismissed" });
11075
- return;
11076
- }
11077
- if (result.decision === "approved") {
11078
- if (selectedLabel.length > 0) {
11079
- context.agent.telemetry.track("plan_resolved", {
11080
- outcome: "approved",
11081
- chosen_option: selectedLabel
11082
- });
11083
- return;
11084
- }
11085
- context.agent.telemetry.track("plan_resolved", { outcome: "approved" });
11086
- return;
11087
- }
11088
- if (normalizedSelectedLabel === "reject and exit") {
11089
- context.agent.telemetry.track("plan_resolved", { outcome: "rejected_and_exited" });
11090
- return;
11091
- }
11092
- if (normalizedSelectedLabel === "revise" || hasFeedback) {
11093
- context.agent.telemetry.track("plan_resolved", {
11094
- outcome: "revise",
11095
- has_feedback: hasFeedback
11096
- });
11097
- return;
11098
- }
11099
- context.agent.telemetry.track("plan_resolved", { outcome: "rejected" });
11100
- }
11101
- function exitPlanModeForRejectedPlan(context) {
11102
- try {
11103
- context.agent.planMode.exit();
11104
- } catch (error) {
11105
- return {
11106
- isError: true,
11107
- output: `Failed to exit plan mode: ${error instanceof Error ? error.message : "Failed to exit plan mode."}`
11108
- };
11109
- }
11110
- }
11111
- function exitPlanModeOptions(args) {
11112
- if (args === null || typeof args !== "object") return void 0;
11113
- const options = args.options;
11114
- if (!Array.isArray(options) || options.length < 2) return void 0;
11115
- const parsed = [];
11116
- for (const option of options) {
11117
- if (option === null || typeof option !== "object") return void 0;
11118
- const label = option.label;
11119
- if (typeof label !== "string") return void 0;
11120
- const description = option.description;
11121
- if (description !== void 0 && typeof description !== "string") return void 0;
11122
- parsed.push({
11123
- label,
11124
- description: description ?? ""
11125
- });
11126
- }
11127
- return parsed;
11128
- }
11129
- function selectedExitPlanModeOption(options, label) {
11130
- if (options === void 0 || label === void 0) return void 0;
11131
- return options.find((option) => option.label === label);
11132
- }
11133
- function exitPlanModeAction(options) {
11134
- return options !== void 0 && options.length >= 2 ? "Review plan and choose an option" : "Review plan";
11135
- }
11136
- function normalizeOptionLabel(label) {
11137
- return label.trim().toLowerCase();
11138
- }
11139
- function readStringField$1(args, key) {
11140
- if (args === null || typeof args !== "object") return void 0;
11141
- const value = args[key];
11142
- return typeof value === "string" ? value : void 0;
11091
+ }
11092
+ return false;
11093
+ }
11094
+ function isFileNotFoundError(error) {
11095
+ if (error === null || typeof error !== "object") return false;
11096
+ if (error.name === "KaosFileNotFoundError") return true;
11097
+ const code = error.code;
11098
+ if (code === "ENOENT" || code === "ENOTDIR" || code === 2) return true;
11099
+ const message = error instanceof Error ? error.message : "";
11100
+ return message.includes("ENOENT") || message.includes("ENOTDIR");
11143
11101
  }
11144
11102
  //#endregion
11145
11103
  //#region src/agent/permission/policies/yolo-workspace-access.ts
@@ -11198,7 +11156,6 @@ function readStringField(args, key) {
11198
11156
  //#region src/agent/permission/policies/index.ts
11199
11157
  function createBuiltinPermissionPolicies() {
11200
11158
  return [
11201
- ...createPlanPermissionPolicies(),
11202
11159
  YoloOutsideWorkspacePermissionPolicy,
11203
11160
  createDefaultGitCwdWritePolicy(),
11204
11161
  AskUserQuestionAutoPermissionPolicy
@@ -11274,16 +11231,20 @@ var PermissionManager = class {
11274
11231
  block: true,
11275
11232
  reason: this.formatMessage(name, matchedRule?.reason)
11276
11233
  };
11277
- const policyResult = await this.evaluatePolicies(context, matchedRule);
11278
- if (policyResult !== void 0) return this.permissionPolicyResultToPrepare(policyResult, context);
11279
11234
  if (mode === "auto") {
11235
+ const policyResult = await this.evaluatePolicies(context, matchedRule);
11236
+ if (policyResult !== void 0) return this.permissionPolicyResultToPrepare(policyResult, context);
11280
11237
  if (this.wouldAskInManualMode(name, args)) this.trackToolApproved(name, "afk");
11281
11238
  return;
11282
11239
  }
11283
11240
  if (mode === "yolo") {
11241
+ const policyResult = await this.evaluatePolicies(context, matchedRule);
11242
+ if (policyResult !== void 0) return this.permissionPolicyResultToPrepare(policyResult, context);
11284
11243
  if (this.wouldAskInManualMode(name, args)) this.trackToolApproved(name, "yolo");
11285
11244
  return;
11286
11245
  }
11246
+ const policyResult = await this.evaluatePolicies(context, matchedRule);
11247
+ if (policyResult !== void 0) return this.permissionPolicyResultToPrepare(policyResult, context);
11287
11248
  if (decision === "allow") {
11288
11249
  if (matchedRule?.scope === "session-runtime") this.trackToolApproved(name, "auto_session", "session");
11289
11250
  return;
@@ -11402,409 +11363,19 @@ var PermissionManager = class {
11402
11363
  if (scope !== void 0) properties["scope"] = scope;
11403
11364
  this.agent.telemetry.track("tool_approved", properties);
11404
11365
  }
11405
- };
11406
- function approvalTelemetryMode(mode) {
11407
- return mode === "auto" ? "afk" : mode;
11408
- }
11409
- //#endregion
11410
- //#region src/utils/hero-slug.ts
11411
- /** Hero-name slug generator: produces "hero-hero-hero" with collision fallback. */
11412
- const HERO_NAMES = [
11413
- "iron-man",
11414
- "spider-man",
11415
- "captain-america",
11416
- "thor",
11417
- "hulk",
11418
- "black-widow",
11419
- "hawkeye",
11420
- "black-panther",
11421
- "doctor-strange",
11422
- "scarlet-witch",
11423
- "vision",
11424
- "falcon",
11425
- "war-machine",
11426
- "ant-man",
11427
- "wasp",
11428
- "captain-marvel",
11429
- "gamora",
11430
- "star-lord",
11431
- "groot",
11432
- "rocket",
11433
- "drax",
11434
- "mantis",
11435
- "nebula",
11436
- "shang-chi",
11437
- "moon-knight",
11438
- "ms-marvel",
11439
- "she-hulk",
11440
- "echo",
11441
- "wolverine",
11442
- "cyclops",
11443
- "storm",
11444
- "jean-grey",
11445
- "rogue",
11446
- "beast",
11447
- "nightcrawler",
11448
- "colossus",
11449
- "shadowcat",
11450
- "jubilee",
11451
- "cable",
11452
- "deadpool",
11453
- "bishop",
11454
- "magik",
11455
- "iceman",
11456
- "archangel",
11457
- "psylocke",
11458
- "dazzler",
11459
- "forge",
11460
- "havok",
11461
- "polaris",
11462
- "emma-frost",
11463
- "namor",
11464
- "silver-surfer",
11465
- "adam-warlock",
11466
- "nova",
11467
- "quasar",
11468
- "sentry",
11469
- "blue-marvel",
11470
- "spectrum",
11471
- "squirrel-girl",
11472
- "cloak",
11473
- "dagger",
11474
- "punisher",
11475
- "elektra",
11476
- "luke-cage",
11477
- "iron-fist",
11478
- "jessica-jones",
11479
- "daredevil",
11480
- "blade",
11481
- "ghost-rider",
11482
- "morbius",
11483
- "venom",
11484
- "carnage",
11485
- "silk",
11486
- "spider-gwen",
11487
- "miles-morales",
11488
- "america-chavez",
11489
- "kate-bishop",
11490
- "yelena-belova",
11491
- "white-tiger",
11492
- "moon-girl",
11493
- "devil-dinosaur",
11494
- "amadeus-cho",
11495
- "riri-williams",
11496
- "kamala-khan",
11497
- "sam-alexander",
11498
- "nova-prime",
11499
- "medusa",
11500
- "black-bolt",
11501
- "crystal",
11502
- "karnak",
11503
- "gorgon",
11504
- "lockjaw",
11505
- "quake",
11506
- "mockingbird",
11507
- "bobbi-morse",
11508
- "maria-hill",
11509
- "nick-fury",
11510
- "phil-coulson",
11511
- "winter-soldier",
11512
- "us-agent",
11513
- "patriot",
11514
- "speed",
11515
- "wiccan",
11516
- "hulkling",
11517
- "stature",
11518
- "yellowjacket",
11519
- "tigra",
11520
- "hellcat",
11521
- "valkyrie",
11522
- "sif",
11523
- "beta-ray-bill",
11524
- "hercules",
11525
- "wonder-man",
11526
- "taskmaster",
11527
- "domino",
11528
- "cannonball",
11529
- "sunspot",
11530
- "wolfsbane",
11531
- "warpath",
11532
- "multiple-man",
11533
- "banshee",
11534
- "siryn",
11535
- "monet",
11536
- "rictor",
11537
- "shatterstar",
11538
- "longshot",
11539
- "daken",
11540
- "x-23",
11541
- "fantomex",
11542
- "batman",
11543
- "superman",
11544
- "wonder-woman",
11545
- "flash",
11546
- "aquaman",
11547
- "green-lantern",
11548
- "martian-manhunter",
11549
- "cyborg",
11550
- "hawkgirl",
11551
- "green-arrow",
11552
- "black-canary",
11553
- "zatanna",
11554
- "constantine",
11555
- "shazam",
11556
- "blue-beetle",
11557
- "booster-gold",
11558
- "firestorm",
11559
- "atom",
11560
- "hawkman",
11561
- "plastic-man",
11562
- "red-tornado",
11563
- "starfire",
11564
- "raven",
11565
- "beast-boy",
11566
- "robin",
11567
- "nightwing",
11568
- "batgirl",
11569
- "batwoman",
11570
- "red-hood",
11571
- "signal",
11572
- "orphan",
11573
- "spoiler",
11574
- "catwoman",
11575
- "huntress",
11576
- "supergirl",
11577
- "superboy",
11578
- "power-girl",
11579
- "steel",
11580
- "stargirl",
11581
- "wildcat",
11582
- "doctor-fate",
11583
- "mister-terrific",
11584
- "hourman",
11585
- "sandman",
11586
- "spectre",
11587
- "phantom-stranger",
11588
- "swamp-thing",
11589
- "animal-man",
11590
- "deadman",
11591
- "vixen",
11592
- "black-lightning",
11593
- "static",
11594
- "icon",
11595
- "rocket-dc",
11596
- "captain-atom",
11597
- "fire",
11598
- "ice",
11599
- "elongated-man",
11600
- "metamorpho",
11601
- "black-hawk",
11602
- "crimson-avenger",
11603
- "doctor-mid-nite",
11604
- "jakeem-thunder",
11605
- "mister-miracle",
11606
- "big-barda",
11607
- "orion",
11608
- "lightray",
11609
- "forager",
11610
- "killer-frost",
11611
- "jessica-cruz",
11612
- "simon-baz",
11613
- "john-stewart",
11614
- "guy-gardner",
11615
- "kyle-rayner",
11616
- "hal-jordan",
11617
- "wally-west",
11618
- "barry-allen",
11619
- "jay-garrick",
11620
- "impulse",
11621
- "kid-flash",
11622
- "donna-troy",
11623
- "tempest",
11624
- "aqualad",
11625
- "miss-martian",
11626
- "terra",
11627
- "jericho",
11628
- "ravager",
11629
- "red-star",
11630
- "pantha",
11631
- "argent",
11632
- "damage",
11633
- "jade",
11634
- "obsidian",
11635
- "cyclone",
11636
- "atom-smasher",
11637
- "maxima",
11638
- "starman",
11639
- "liberty-belle",
11640
- "dove",
11641
- "hawk",
11642
- "blue-devil",
11643
- "creeper",
11644
- "ragman",
11645
- "thunder"
11646
- ];
11647
- const MAX_ATTEMPTS = 20;
11648
- function pickHero() {
11649
- return HERO_NAMES[randomInt(HERO_NAMES.length)];
11650
- }
11651
- function assembleSlug() {
11652
- return `${pickHero()}-${pickHero()}-${pickHero()}`;
11653
- }
11654
- function generateHeroSlug(id, existing) {
11655
- let slug = "";
11656
- let collided = true;
11657
- for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
11658
- slug = assembleSlug();
11659
- if (!existing.has(slug)) {
11660
- collided = false;
11661
- break;
11662
- }
11663
- }
11664
- if (collided) slug = `${slug}-${id.slice(0, 8)}`;
11665
- return slug;
11666
- }
11667
- //#endregion
11668
- //#region src/agent/plan/index.ts
11669
- var PlanMode = class {
11670
- agent;
11671
- _isActive = false;
11672
- _planId = null;
11673
- _planFilePath = null;
11674
- constructor(agent) {
11675
- this.agent = agent;
11676
- }
11677
- createPlanId() {
11678
- return generateHeroSlug(randomUUID(), /* @__PURE__ */ new Set());
11679
- }
11680
- async enter(id = this.createPlanId(), createFile = false, emitStatus = true) {
11681
- if (this._isActive) throw new Error("Already in plan mode");
11682
- this._isActive = true;
11683
- this._planId = id;
11684
- this._planFilePath = null;
11685
- let enterRecorded = false;
11686
- try {
11687
- const planFilePath = this.planFilePathFor(id);
11688
- this._planFilePath = planFilePath;
11689
- this.agent.records.logRecord({
11690
- type: "plan_mode.enter",
11691
- id
11692
- });
11693
- enterRecorded = true;
11694
- if (createFile) await this.materializePlanFile(planFilePath);
11695
- } catch (error) {
11696
- if (enterRecorded) this.cancel(id);
11697
- else {
11698
- this._isActive = false;
11699
- this._planId = null;
11700
- this._planFilePath = null;
11701
- }
11702
- throw error;
11703
- }
11704
- this.trackPlanLifecycle("entered");
11705
- if (emitStatus) this.agent.emitStatusUpdated();
11706
- }
11707
- restoreEnter({ id }) {
11708
- this.agent.replayBuilder.push({
11709
- type: "plan_updated",
11710
- enabled: true
11711
- });
11712
- this._isActive = true;
11713
- this._planId = id;
11714
- this._planFilePath = this.planFilePathFor(id);
11715
- }
11716
- cancel(id) {
11717
- this.agent.records.logRecord({
11718
- type: "plan_mode.cancel",
11719
- id
11720
- });
11721
- this.agent.replayBuilder.push({
11722
- type: "plan_updated",
11723
- enabled: false
11724
- });
11725
- this._isActive = false;
11726
- this._planId = null;
11727
- this._planFilePath = null;
11728
- this.agent.emitStatusUpdated();
11729
- }
11730
- async clear() {
11731
- if (!this._planFilePath) return;
11732
- if (!await this.planFileExists(this._planFilePath)) return;
11733
- await this.agent.runtime.kaos.writeText(this._planFilePath, "");
11734
- }
11735
- exit(id) {
11736
- this.agent.records.logRecord({
11737
- type: "plan_mode.exit",
11738
- id
11739
- });
11740
- this.agent.replayBuilder.push({
11741
- type: "plan_updated",
11742
- enabled: false
11743
- });
11744
- this._isActive = false;
11745
- this._planId = null;
11746
- this._planFilePath = null;
11747
- this.trackPlanLifecycle("exited");
11748
- this.agent.emitStatusUpdated();
11749
- }
11750
- get isActive() {
11751
- return this._isActive;
11752
- }
11753
- get planFilePath() {
11754
- return this._planFilePath;
11755
- }
11756
- async data() {
11757
- if (!this._planId || !this._planFilePath) return null;
11758
- let content = "";
11759
- let exists = true;
11760
- try {
11761
- content = await this.agent.runtime.kaos.readText(this._planFilePath);
11762
- } catch (error) {
11763
- if (!isMissingFileError(error)) throw error;
11764
- exists = false;
11765
- }
11766
- return {
11767
- id: this._planId,
11768
- exists,
11769
- content,
11770
- path: this._planFilePath
11771
- };
11772
- }
11773
- async materializeCurrentPlanFile() {
11774
- if (this._planFilePath === null) return;
11775
- await this.materializePlanFile(this._planFilePath);
11776
- }
11777
- async materializePlanFile(path) {
11778
- if (await this.planFileExists(path)) return;
11779
- await this.ensurePlanDirectory(path);
11780
- await this.agent.runtime.kaos.writeText(path, "");
11781
- this.trackPlanLifecycle("materialized");
11782
- }
11783
- async ensurePlanDirectory(path) {
11784
- await this.agent.runtime.kaos.mkdir(dirname(path), {
11785
- parents: true,
11786
- existOk: true
11787
- });
11788
- }
11789
- planFilePathFor(id) {
11790
- return join(this.agent.homedir === void 0 ? join(this.agent.config.cwd || this.agent.runtime.kaos.getcwd(), "plan") : join(this.agent.homedir, "plans"), `${id}.md`);
11791
- }
11792
- async planFileExists(path) {
11793
- try {
11794
- await this.agent.runtime.kaos.readText(path);
11795
- return true;
11796
- } catch (error) {
11797
- if (isMissingFileError(error)) return false;
11798
- throw error;
11366
+ restoreRecord(record) {
11367
+ switch (record.type) {
11368
+ case "permission.set_mode":
11369
+ this.setMode(record.mode);
11370
+ break;
11371
+ case "permission.record_approval_result":
11372
+ this.recordApprovalResult(record);
11373
+ break;
11799
11374
  }
11800
11375
  }
11801
- trackPlanLifecycle(stage) {
11802
- this.agent.telemetry?.track?.("plan_file_lifecycle", { stage });
11803
- }
11804
11376
  };
11805
- function isMissingFileError(error) {
11806
- if (error === null || typeof error !== "object") return false;
11807
- return error.code === "ENOENT";
11377
+ function approvalTelemetryMode(mode) {
11378
+ return mode === "auto" ? "afk" : mode;
11808
11379
  }
11809
11380
  //#endregion
11810
11381
  //#region src/agent/records/persistence.ts
@@ -11923,83 +11494,12 @@ function parseRecordLine(line, lineNumber, filePath, allowTruncated) {
11923
11494
  }
11924
11495
  //#endregion
11925
11496
  //#region src/agent/records/index.ts
11926
- function restoreAgentRecord(agent, input) {
11927
- switch (input.type) {
11928
- case "metadata": return;
11929
- case "turn.prompt":
11930
- agent.turn.restorePrompt();
11931
- return;
11932
- case "turn.steer":
11933
- agent.turn.restoreSteer(input.input, input.origin);
11934
- return;
11935
- case "turn.cancel":
11936
- agent.turn.cancel(input.turnId);
11937
- return;
11938
- case "background.stop": return;
11939
- case "config.update":
11940
- agent.config.update(input);
11941
- return;
11942
- case "permission.set_mode":
11943
- agent.permission.setMode(input.mode);
11944
- return;
11945
- case "permission.record_approval_result":
11946
- agent.permission.recordApprovalResult(input);
11947
- return;
11948
- case "usage.record":
11949
- agent.usage.record(input.model, input.usage, "session");
11950
- return;
11951
- case "full_compaction.begin":
11952
- agent.fullCompaction.begin(input);
11953
- return;
11954
- case "full_compaction.cancel":
11955
- agent.fullCompaction.cancel();
11956
- return;
11957
- case "full_compaction.complete":
11958
- agent.fullCompaction.complete(input);
11959
- return;
11960
- case "plan_mode.enter":
11961
- agent.planMode.restoreEnter(input);
11962
- return;
11963
- case "plan_mode.cancel":
11964
- agent.planMode.cancel(input.id);
11965
- return;
11966
- case "plan_mode.exit":
11967
- agent.planMode.exit(input.id);
11968
- return;
11969
- case "context.append_message":
11970
- agent.context.appendMessage(input.message);
11971
- return;
11972
- case "context.mark_last_user_prompt_blocked":
11973
- agent.context.markLastUserPromptBlocked(input.hookEvent);
11974
- return;
11975
- case "context.append_loop_event":
11976
- agent.context.appendLoopEvent(input.event);
11977
- return;
11978
- case "context.clear":
11979
- agent.context.clear();
11980
- return;
11981
- case "context.apply_compaction":
11982
- agent.context.applyCompaction(input);
11983
- return;
11984
- case "tools.register_user_tool":
11985
- agent.tools.registerUserTool(input);
11986
- return;
11987
- case "tools.unregister_user_tool":
11988
- agent.tools.unregisterUserTool(input.name);
11989
- return;
11990
- case "tools.set_active_tools":
11991
- agent.tools.setActiveTools(input.names);
11992
- return;
11993
- case "tools.update_store":
11994
- agent.tools.updateStore(input.key, input.value);
11995
- return;
11996
- }
11997
- }
11998
11497
  var AgentRecords = class {
11999
11498
  agent;
12000
11499
  persistence;
12001
11500
  _restoring = false;
12002
11501
  metadataInitialized = false;
11502
+ handlers = {};
12003
11503
  constructor(agent, persistence) {
12004
11504
  this.agent = agent;
12005
11505
  this.persistence = persistence;
@@ -12007,6 +11507,9 @@ var AgentRecords = class {
12007
11507
  get restoring() {
12008
11508
  return this._restoring;
12009
11509
  }
11510
+ registerHandlers(handlers) {
11511
+ this.handlers = { ...handlers };
11512
+ }
12010
11513
  logRecord(record) {
12011
11514
  if (this._restoring) return;
12012
11515
  const stamped = record.time !== void 0 ? record : {
@@ -12027,11 +11530,30 @@ var AgentRecords = class {
12027
11530
  restore(record) {
12028
11531
  this._restoring = true;
12029
11532
  try {
12030
- restoreAgentRecord(this.agent, record);
11533
+ this.routeToHandler(record);
12031
11534
  } finally {
12032
11535
  this._restoring = false;
12033
11536
  }
12034
11537
  }
11538
+ routeToHandler(record) {
11539
+ const handlerKey = this.getHandlerKey(record.type);
11540
+ if (handlerKey === null || this.handlers[handlerKey] === void 0) return;
11541
+ this.handlers[handlerKey].restoreRecord(record);
11542
+ }
11543
+ getHandlerKey(recordType) {
11544
+ if (recordType === "metadata") return null;
11545
+ return {
11546
+ context: "context",
11547
+ config: "config",
11548
+ turn: "turn",
11549
+ permission: "permission",
11550
+ tools: "tools",
11551
+ usage: "usage",
11552
+ background: "background",
11553
+ full_compaction: "fullCompaction",
11554
+ plan_mode: "planMode"
11555
+ }[recordType.split(".")[0]] ?? null;
11556
+ }
12035
11557
  async replay() {
12036
11558
  if (!this.persistence) throw new Error("No persistence provided for AgentRecords");
12037
11559
  let migrations = [];
@@ -12397,7 +11919,7 @@ async function prepareToolCall(step, call) {
12397
11919
  const decision = await runPrepareToolExecutionHook(step, call);
12398
11920
  if (decision.kind === "blocked") {
12399
11921
  await dispatchToolCall(step, call, decision.args);
12400
- return { task: makeResolvedToolCallTask(makeErrorToolResult(call, decision.args, decision.output)) };
11922
+ return { task: makeResolvedToolCallTask(makeErrorToolResult(call, decision.args, decision.output, decision.blockedReason)) };
12401
11923
  }
12402
11924
  if (decision.kind === "hookFailed") {
12403
11925
  await dispatchToolCall(step, call, decision.args);
@@ -12433,10 +11955,13 @@ async function prepareToolCall(step, call) {
12433
11955
  }
12434
11956
  await dispatchToolCall(step, call, effectiveArgs, toolCallDisplayFieldsFromExecution(execution));
12435
11957
  if (step.signal.aborted) return { task: makeResolvedToolCallTask(makeErrorToolResult(call, effectiveArgs, `Tool "${call.toolName}" was aborted`)) };
12436
- if (execution.isError === true) return {
12437
- task: makeResolvedToolCallTask(makeToolResult(call, effectiveArgs, execution)),
12438
- stopBatchAfterThis: execution.stopTurn
12439
- };
11958
+ if (execution.isError === true) {
11959
+ const coerced = coerceToolResult(execution, call.toolName);
11960
+ return {
11961
+ task: makeResolvedToolCallTask(makeToolResult(call, effectiveArgs, coerced)),
11962
+ stopBatchAfterThis: toolResultStopsTurn(coerced)
11963
+ };
11964
+ }
12440
11965
  return { task: {
12441
11966
  accesses: execution.accesses ?? ToolAccesses.all(),
12442
11967
  start: async () => ({ result: runRunnableToolCall(step, call, effectiveArgs, decision.metadata, execution) })
@@ -12491,7 +12016,8 @@ async function runPrepareToolExecutionHook(step, call) {
12491
12016
  if (hookResult?.block === true) return {
12492
12017
  kind: "blocked",
12493
12018
  args: effectiveArgs,
12494
- output: hookResult.reason ?? `Tool call "${call.toolName}" was blocked`
12019
+ output: hookResult.reason ?? `Tool call "${call.toolName}" was blocked`,
12020
+ blockedReason: hookResult.blockedReason
12495
12021
  };
12496
12022
  if (hookResult?.syntheticResult !== void 0) return {
12497
12023
  kind: "synthetic",
@@ -12653,10 +12179,15 @@ function normalizeToolResult(r) {
12653
12179
  const textJoined = r.output.filter((c) => c.type === "text").map((c) => c.text).join("");
12654
12180
  output = textJoined.length > 0 ? textJoined : TOOL_OUTPUT_EMPTY;
12655
12181
  }
12656
- return r.isError === true ? {
12182
+ if (r.isError === true) return r.blockedReason !== void 0 ? {
12183
+ output,
12184
+ isError: true,
12185
+ blockedReason: r.blockedReason
12186
+ } : {
12657
12187
  output,
12658
12188
  isError: true
12659
- } : { output };
12189
+ };
12190
+ return { output };
12660
12191
  }
12661
12192
  function makeToolResult(call, args, result) {
12662
12193
  return {
@@ -12670,10 +12201,11 @@ function makeToolResult(call, args, result) {
12670
12201
  function toolResultStopsTurn(result) {
12671
12202
  return result.isError === true && result.stopTurn === true;
12672
12203
  }
12673
- function makeErrorToolResult(call, args, output) {
12204
+ function makeErrorToolResult(call, args, output, blockedReason) {
12674
12205
  return makeToolResult(call, args, {
12675
12206
  output,
12676
- isError: true
12207
+ isError: true,
12208
+ blockedReason
12677
12209
  });
12678
12210
  }
12679
12211
  /**
@@ -12772,7 +12304,9 @@ async function executeLoopStep(deps) {
12772
12304
  step: currentStep,
12773
12305
  usage,
12774
12306
  finishReason: effectiveStopReason,
12775
- ...stepEndProviderDiagnostics(response, effectiveStopReason)
12307
+ ...stepEndProviderDiagnostics(response, effectiveStopReason),
12308
+ ...response.llmFirstTokenLatencyMs !== void 0 ? { llmFirstTokenLatencyMs: response.llmFirstTokenLatencyMs } : {},
12309
+ ...response.llmStreamDurationMs !== void 0 ? { llmStreamDurationMs: response.llmStreamDurationMs } : {}
12776
12310
  });
12777
12311
  if (hooks?.afterStep !== void 0) try {
12778
12312
  await hooks.afterStep({
@@ -14038,8 +13572,6 @@ var ToolManager = class {
14038
13572
  new GlobTool(kaos, workspace),
14039
13573
  new BashTool(kaos, cwd, osEnv, background, { allowBackground }),
14040
13574
  (modelCapabilities.image_in || modelCapabilities.video_in) && new ReadMediaFileTool(kaos, workspace, modelCapabilities, videoUploader),
14041
- new EnterPlanModeTool(this.agent),
14042
- new ExitPlanModeTool(this.agent),
14043
13575
  new AskUserQuestionTool(this.agent),
14044
13576
  new TodoListTool(this.toolStore),
14045
13577
  new TaskListTool(background),
@@ -14062,11 +13594,87 @@ var ToolManager = class {
14062
13594
  return (input) => withProviderRequestAuth(resolveAuth, (auth) => uploadVideo(input, { auth }));
14063
13595
  }
14064
13596
  get loopTools() {
13597
+ const builtinNames = [...this.builtinTools.keys()].filter((name) => this.enabledTools.has(name)).sort();
13598
+ const userNames = [...this.userTools.keys()].filter((name) => this.enabledTools.has(name)).sort();
14065
13599
  const mcpNames = [...this.mcpTools.keys()].filter((name) => this.isMcpToolEnabled(name));
14066
- return uniq([...this.enabledTools, ...mcpNames]).toSorted((a, b) => a.localeCompare(b)).map((name) => this.userTools.get(name) ?? this.mcpTools.get(name)?.tool ?? this.builtinTools.get(name)).filter((tool) => !!tool);
13600
+ return [
13601
+ ...builtinNames.map((name) => this.builtinTools.get(name)),
13602
+ ...userNames.map((name) => this.userTools.get(name)),
13603
+ ...mcpNames.map((name) => this.mcpTools.get(name)?.tool)
13604
+ ].filter((tool) => !!tool);
13605
+ }
13606
+ restoreRecord(record) {
13607
+ switch (record.type) {
13608
+ case "tools.register_user_tool":
13609
+ this.registerUserTool(record);
13610
+ break;
13611
+ case "tools.unregister_user_tool":
13612
+ this.unregisterUserTool(record.name);
13613
+ break;
13614
+ case "tools.set_active_tools":
13615
+ this.setActiveTools(record.names);
13616
+ break;
13617
+ case "tools.update_store":
13618
+ this.updateStore(record.key, record.value);
13619
+ break;
13620
+ }
14067
13621
  }
14068
13622
  };
14069
13623
  //#endregion
13624
+ //#region src/agent/cache-staking/index.ts
13625
+ const DEFAULT_SIZE_THRESHOLD = 2e3;
13626
+ /**
13627
+ * Apply cache staking hints to a message array based on turn boundaries.
13628
+ *
13629
+ * - **Stake 3**: Tags the last assistant message of the previous turn with
13630
+ * `cacheHint.isLastTurnEnd = true`.
13631
+ * - **Stake 4** (conditional): Tags the largest content block in the current
13632
+ * turn that exceeds `sizeThreshold` with `cacheHint.isSuddenLargeContext = true`.
13633
+ *
13634
+ * Returns a new array with shallow-copied messages (original messages are
13635
+ * not mutated).
13636
+ */
13637
+ function applyCacheStaking(messages, context) {
13638
+ const { previousTurnMessageCount, sizeThreshold = DEFAULT_SIZE_THRESHOLD } = context;
13639
+ if (previousTurnMessageCount <= 0 || messages.length === 0) return messages;
13640
+ const result = messages.map((msg) => ({ ...msg }));
13641
+ const lastTurnIndex = previousTurnMessageCount - 1;
13642
+ if (lastTurnIndex < result.length) {
13643
+ const lastTurnMsg = result[lastTurnIndex];
13644
+ if (lastTurnMsg.role === "assistant") {
13645
+ const existingHint = lastTurnMsg.cacheHint ?? {};
13646
+ result[lastTurnIndex] = {
13647
+ ...lastTurnMsg,
13648
+ cacheHint: {
13649
+ ...existingHint,
13650
+ isLastTurnEnd: true
13651
+ }
13652
+ };
13653
+ }
13654
+ }
13655
+ let largestIndex = -1;
13656
+ let largestSize = 0;
13657
+ for (let i = previousTurnMessageCount; i < result.length; i++) {
13658
+ const contentLength = result[i].content.filter((p) => p.type === "text").reduce((sum, p) => sum + p.text.length, 0);
13659
+ if (contentLength >= sizeThreshold && contentLength > largestSize) {
13660
+ largestSize = contentLength;
13661
+ largestIndex = i;
13662
+ }
13663
+ }
13664
+ if (largestIndex >= 0) {
13665
+ const target = result[largestIndex];
13666
+ const existingHint = target.cacheHint ?? {};
13667
+ result[largestIndex] = {
13668
+ ...target,
13669
+ cacheHint: {
13670
+ ...existingHint,
13671
+ isSuddenLargeContext: true
13672
+ }
13673
+ };
13674
+ }
13675
+ return result;
13676
+ }
13677
+ //#endregion
14070
13678
  //#region src/agent/turn/canonical-args.ts
14071
13679
  /**
14072
13680
  * JSON canonicalization used by tool-call telemetry and dedup.
@@ -14088,6 +13696,231 @@ function isPlainRecord(value) {
14088
13696
  return proto === Object.prototype || proto === null;
14089
13697
  }
14090
13698
  //#endregion
13699
+ //#region src/prompt-plan/builder.ts
13700
+ /**
13701
+ * Cache boundary marker used to split the system prompt into cacheable blocks.
13702
+ *
13703
+ * @deprecated This marker is deprecated in favor of implicit boundaries based on section headers.
13704
+ * It is still supported for backward compatibility.
13705
+ */
13706
+ const CACHE_BOUNDARY_MARKER = "__CACHE_BOUNDARY__";
13707
+ /**
13708
+ * Section headers that define implicit cache boundaries.
13709
+ *
13710
+ * These headers mark natural breaks in the system prompt where cache boundaries should be placed:
13711
+ * - "# Project Information" marks the start of project-specific content
13712
+ * - "# Skills" marks the start of session-specific skills listing
13713
+ */
13714
+ const IMPLICIT_BOUNDARY_HEADERS = ["# Project Information", "# Skills"];
13715
+ /**
13716
+ * Block names by position.
13717
+ *
13718
+ * - First block (before first marker): 'base'
13719
+ * - Last block (after last marker): 'sessionContext'
13720
+ * - Intermediate blocks: Sequential names from 'projectInstructions', 'skillsListing', etc.
13721
+ */
13722
+ const BLOCK_NAMES = [
13723
+ "base",
13724
+ "projectInstructions",
13725
+ "skillsListing",
13726
+ "sessionContext"
13727
+ ];
13728
+ /**
13729
+ * Get the cache scope for a block by its position.
13730
+ *
13731
+ * @param position - The block position (0-indexed)
13732
+ * @param totalBlocks - Total number of blocks
13733
+ * @returns The default cache scope for this position
13734
+ */
13735
+ function getDefaultScopeForPosition(position, totalBlocks) {
13736
+ if (position === 0) return "global";
13737
+ if (position === totalBlocks - 1) return "session";
13738
+ if (position === 1) return "project";
13739
+ return "session";
13740
+ }
13741
+ /**
13742
+ * Filter cache scope based on provider's supported scopes.
13743
+ *
13744
+ * @param scope - The desired cache scope
13745
+ * @param capability - The provider's cache capability
13746
+ * @returns The filtered scope (or 'none' if not supported)
13747
+ */
13748
+ function filterScopeByCapability(scope, capability) {
13749
+ if (capability.strategy === "none") return "none";
13750
+ if (capability.supportedScopes === void 0) return scope;
13751
+ if (capability.supportedScopes.includes(scope)) return scope;
13752
+ return "none";
13753
+ }
13754
+ /**
13755
+ * Find implicit boundary positions in the system prompt.
13756
+ *
13757
+ * Searches for section headers that mark natural cache boundaries.
13758
+ * Returns sorted indices of where each boundary header starts.
13759
+ *
13760
+ * @param prompt - The system prompt to search
13761
+ * @returns Array of character positions where implicit boundaries occur
13762
+ */
13763
+ function findImplicitBoundaries(prompt) {
13764
+ const boundaries = [];
13765
+ for (const header of IMPLICIT_BOUNDARY_HEADERS) {
13766
+ const index = prompt.indexOf(header);
13767
+ if (index !== -1) boundaries.push(index);
13768
+ }
13769
+ return boundaries.sort((a, b) => a - b);
13770
+ }
13771
+ /**
13772
+ * Split prompt by implicit boundaries into blocks.
13773
+ *
13774
+ * Creates blocks based on the position of section headers that mark natural boundaries.
13775
+ * The block before the first header is the base block.
13776
+ * Blocks between headers are intermediate blocks.
13777
+ * The block after the last header is the session context block.
13778
+ *
13779
+ * @param prompt - The system prompt to split
13780
+ * @param boundaryPositions - Sorted array of boundary positions
13781
+ * @returns Array of text blocks
13782
+ */
13783
+ function splitByImplicitBoundaries(prompt, boundaryPositions) {
13784
+ if (boundaryPositions.length === 0) return [prompt];
13785
+ const blocks = [];
13786
+ let previousPosition = 0;
13787
+ for (const position of boundaryPositions) {
13788
+ blocks.push(prompt.slice(previousPosition, position));
13789
+ previousPosition = position;
13790
+ }
13791
+ blocks.push(prompt.slice(previousPosition));
13792
+ return blocks;
13793
+ }
13794
+ /**
13795
+ * Detect if a prompt contains implicit cache boundaries.
13796
+ *
13797
+ * A prompt has implicit boundaries if it contains any of the known boundary headers.
13798
+ *
13799
+ * @param prompt - The system prompt to check
13800
+ * @returns true if implicit boundaries are detected
13801
+ */
13802
+ function hasImplicitBoundaries(prompt) {
13803
+ return IMPLICIT_BOUNDARY_HEADERS.some((header) => prompt.includes(header));
13804
+ }
13805
+ /**
13806
+ * Build a prompt plan from a rendered system prompt and provider cache capability.
13807
+ *
13808
+ * This function parses cache boundary markers from the system prompt and creates
13809
+ * a structured plan with named blocks, each with an appropriate cache scope.
13810
+ *
13811
+ * @param renderedSystemPrompt - The fully rendered system prompt (may contain `__CACHE_BOUNDARY__` markers)
13812
+ * @param providerCacheCapability - The provider's cache capability (for scope filtering)
13813
+ * @returns A prompt plan with cacheable blocks
13814
+ *
13815
+ * @example
13816
+ * ```ts
13817
+ * const prompt = `Base instructions
13818
+ * __CACHE_BOUNDARY__
13819
+ * Session context`;
13820
+ *
13821
+ * const capability = {
13822
+ * strategy: 'explicit-block',
13823
+ * supportedScopes: ['global', 'session'],
13824
+ * };
13825
+ *
13826
+ * const plan = buildPromptPlan(prompt, capability);
13827
+ * // {
13828
+ * // blocks: [
13829
+ * // { name: 'base', text: 'Base instructions\n', cacheScope: 'global' },
13830
+ * // { name: 'sessionContext', text: 'Session context', cacheScope: 'session' },
13831
+ * // ],
13832
+ * // }
13833
+ * ```
13834
+ */
13835
+ /**
13836
+ * Normalize block text by handling newlines around cache boundaries.
13837
+ *
13838
+ * After splitting by `__CACHE_BOUNDARY__`, the pattern is typically:
13839
+ * - `[text]\n__CACHE_BOUNDARY__\n[next text]`
13840
+ *
13841
+ * Rules:
13842
+ * - First block: Keep as-is (preserves any trailing newline before marker)
13843
+ * - Middle blocks: Keep the newline that was between markers
13844
+ * - Last block: Trim leading newline (the one immediately after the last marker)
13845
+ *
13846
+ * @param text - The raw block text after splitting
13847
+ * @param index - The block index
13848
+ * @param totalBlocks - Total number of blocks
13849
+ * @returns Normalized block text
13850
+ */
13851
+ function normalizeBlockText(text, index, totalBlocks) {
13852
+ if (index === 0) return text;
13853
+ if (index === totalBlocks - 1) {
13854
+ if (text.startsWith("\n")) return text.slice(1);
13855
+ return text;
13856
+ }
13857
+ return text;
13858
+ }
13859
+ function buildPromptPlan(renderedSystemPrompt, providerCacheCapability) {
13860
+ const parts = renderedSystemPrompt.split(CACHE_BOUNDARY_MARKER);
13861
+ if (parts.length > 1) return createPlanFromParts(parts, providerCacheCapability);
13862
+ if (hasImplicitBoundaries(renderedSystemPrompt)) return createPlanFromParts(splitByImplicitBoundaries(renderedSystemPrompt, findImplicitBoundaries(renderedSystemPrompt)), providerCacheCapability);
13863
+ return { blocks: [{
13864
+ name: "base",
13865
+ text: renderedSystemPrompt,
13866
+ cacheScope: "none"
13867
+ }] };
13868
+ }
13869
+ /**
13870
+ * Create a prompt plan from pre-split parts.
13871
+ *
13872
+ * This is shared logic for both explicit and implicit boundaries.
13873
+ *
13874
+ * @param parts - The pre-split text parts
13875
+ * @param providerCacheCapability - The provider's cache capability
13876
+ * @returns A prompt plan with cacheable blocks
13877
+ */
13878
+ function createPlanFromParts(parts, providerCacheCapability) {
13879
+ const blocks = parts.map((part, index) => {
13880
+ const totalBlocks = parts.length;
13881
+ const filteredScope = filterScopeByCapability(getDefaultScopeForPosition(index, totalBlocks), providerCacheCapability);
13882
+ return {
13883
+ name: getBlockNameForPart(part, index, totalBlocks),
13884
+ text: normalizeBlockText(part, index, totalBlocks),
13885
+ cacheScope: filteredScope
13886
+ };
13887
+ });
13888
+ const maxCacheable = providerCacheCapability.maxCacheableBlocks;
13889
+ if (maxCacheable !== void 0 && maxCacheable > 0) {
13890
+ let cacheableCount = 0;
13891
+ for (const block of blocks) if (block.cacheScope !== "none") {
13892
+ cacheableCount++;
13893
+ if (cacheableCount > maxCacheable) block.cacheScope = "none";
13894
+ }
13895
+ }
13896
+ return { blocks };
13897
+ }
13898
+ /**
13899
+ * Get the block name for a given part, considering its content.
13900
+ *
13901
+ * This is a smarter version of getBlockName that looks at the content
13902
+ * to determine the appropriate name for the last block.
13903
+ *
13904
+ * @param part - The text content of this block
13905
+ * @param position - The block position (0-indexed)
13906
+ * @param totalBlocks - Total number of blocks
13907
+ * @returns The block name
13908
+ */
13909
+ function getBlockNameForPart(part, position, totalBlocks) {
13910
+ if (position === 0) return "base";
13911
+ if (position === totalBlocks - 1) {
13912
+ if (part.includes("# Skills")) return "sessionContext";
13913
+ if (part.includes("# Project Information")) return "projectInstructions";
13914
+ return "sessionContext";
13915
+ }
13916
+ const nameIndex = position - 1 + 1;
13917
+ if (nameIndex < BLOCK_NAMES.length && nameIndex < BLOCK_NAMES.length - 1) {
13918
+ const name = BLOCK_NAMES[nameIndex];
13919
+ if (name !== void 0) return name;
13920
+ }
13921
+ return "sessionContext";
13922
+ }
13923
+ //#endregion
14091
13924
  //#region src/agent/turn/kosong-llm.ts
14092
13925
  /**
14093
13926
  * Kosong-backed implementation of the loop `LLM` interface.
@@ -14134,7 +13967,7 @@ var KosongLLM = class {
14134
13967
  systemPrompt: this.systemPrompt,
14135
13968
  tools: params.tools
14136
13969
  });
14137
- const result = await this.generate(effectiveProvider, this.systemPrompt, [...params.tools], [...params.messages], callbacks, generateOptions(params));
13970
+ const result = await this.generate(effectiveProvider, this.systemPrompt, [...params.tools], [...params.messages], callbacks, generateOptions(params, this.systemPrompt, effectiveProvider));
14138
13971
  if (params.onTextPart !== void 0 || params.onThinkPart !== void 0) {
14139
13972
  for (const part of result.message.content) if (part.type === "text" && params.onTextPart !== void 0) await params.onTextPart(part);
14140
13973
  else if (part.type === "think" && params.onThinkPart !== void 0) await params.onThinkPart(part);
@@ -14143,7 +13976,9 @@ var KosongLLM = class {
14143
13976
  toolCalls: [...result.message.toolCalls],
14144
13977
  ...result.finishReason !== null ? { providerFinishReason: result.finishReason } : {},
14145
13978
  ...result.rawFinishReason !== null ? { rawFinishReason: result.rawFinishReason } : {},
14146
- usage: result.usage ?? emptyUsage()
13979
+ usage: result.usage ?? emptyUsage(),
13980
+ ...result.llmFirstTokenLatencyMs !== void 0 ? { llmFirstTokenLatencyMs: result.llmFirstTokenLatencyMs } : {},
13981
+ ...result.llmStreamDurationMs !== void 0 ? { llmStreamDurationMs: result.llmStreamDurationMs } : {}
14147
13982
  };
14148
13983
  }
14149
13984
  isRetryableError(error) {
@@ -14158,14 +13993,30 @@ var KosongLLM = class {
14158
13993
  ].includes(error.statusCode);
14159
13994
  }
14160
13995
  };
14161
- function generateOptions(params) {
14162
- const options = { signal: params.signal };
13996
+ function generateOptions(params, systemPrompt, provider) {
13997
+ const promptPlan = buildPromptPlan(systemPrompt, getProviderCacheCapability(provider));
13998
+ const options = {
13999
+ signal: params.signal,
14000
+ promptPlan
14001
+ };
14163
14002
  if (params.requestLogContext !== void 0) return {
14164
14003
  ...options,
14165
14004
  [GENERATE_REQUEST_LOG_CONTEXT]: params.requestLogContext
14166
14005
  };
14167
14006
  return options;
14168
14007
  }
14008
+ /**
14009
+ * Get the cache capability from a provider.
14010
+ *
14011
+ * Safely handles providers that don't implement getCapability or don't have cache.
14012
+ * Returns a default capability with 'none' strategy for non-caching providers.
14013
+ */
14014
+ function getProviderCacheCapability(provider) {
14015
+ if (typeof provider.getCapability !== "function") return { strategy: "none" };
14016
+ const capability = provider.getCapability();
14017
+ if (capability?.cache !== void 0) return capability.cache;
14018
+ return { strategy: "none" };
14019
+ }
14169
14020
  function buildKosongCallbacks(params) {
14170
14021
  const toolCallIdentities = /* @__PURE__ */ new Map();
14171
14022
  const pendingIndexedToolCallDeltas = /* @__PURE__ */ new Map();
@@ -14412,6 +14263,7 @@ var TurnFlow = class {
14412
14263
  agent;
14413
14264
  steerBuffer = [];
14414
14265
  turnId = -1;
14266
+ _previousTurnMessageCount = 0;
14415
14267
  activeTurn = null;
14416
14268
  toolCallStartedAt = /* @__PURE__ */ new Map();
14417
14269
  toolCallDupType = /* @__PURE__ */ new Map();
@@ -14456,6 +14308,7 @@ var TurnFlow = class {
14456
14308
  return null;
14457
14309
  }
14458
14310
  this.turnId += 1;
14311
+ this._previousTurnMessageCount = this.agent.context.messages.length;
14459
14312
  this.currentStep = 0;
14460
14313
  this.stepToolCallKeys.clear();
14461
14314
  this.toolCallDupType.clear();
@@ -14692,7 +14545,10 @@ var TurnFlow = class {
14692
14545
  generate: this.agent.generate,
14693
14546
  completionBudgetConfig
14694
14547
  }),
14695
- buildMessages: () => this.agent.context.messages,
14548
+ buildMessages: () => {
14549
+ const messages = this.agent.context.messages;
14550
+ return applyCacheStaking(messages, { previousTurnMessageCount: this._previousTurnMessageCount });
14551
+ },
14696
14552
  dispatchEvent: this.buildDispatchEvent(turnId),
14697
14553
  tools: this.agent.tools.loopTools,
14698
14554
  log: this.agent.log,
@@ -14798,7 +14654,7 @@ var TurnFlow = class {
14798
14654
  buildDispatchEvent(turnId) {
14799
14655
  return createLoopEventDispatcher({
14800
14656
  appendTranscriptRecord: async (event) => {
14801
- this.agent.context.appendLoopEvent(event);
14657
+ await this.agent.context.appendLoopEvent(event);
14802
14658
  },
14803
14659
  emitLiveEvent: (event) => {
14804
14660
  this.trackLoopTelemetry(event, turnId);
@@ -14884,12 +14740,25 @@ var TurnFlow = class {
14884
14740
  });
14885
14741
  }
14886
14742
  telemetryMode() {
14887
- return this.agent.planMode.isActive ? "plan" : "agent";
14743
+ return "agent";
14888
14744
  }
14889
14745
  shouldTrackApiError(turnId) {
14890
14746
  const failure = this.stepFailureByTurn.get(turnId);
14891
14747
  return failure?.reason === "error" && failure.activeStep !== void 0;
14892
14748
  }
14749
+ restoreRecord(record) {
14750
+ switch (record.type) {
14751
+ case "turn.prompt":
14752
+ if (this.activeTurn === "resuming") this.activeTurn = null;
14753
+ this.restorePrompt();
14754
+ break;
14755
+ case "turn.steer":
14756
+ if (this.activeTurn === "resuming") this.activeTurn = null;
14757
+ this.restoreSteer(record.input, record.origin);
14758
+ break;
14759
+ case "turn.cancel": break;
14760
+ }
14761
+ }
14893
14762
  };
14894
14763
  function mapLoopEvent(event, turnId) {
14895
14764
  switch (event.type) {
@@ -14907,7 +14776,9 @@ function mapLoopEvent(event, turnId) {
14907
14776
  usage: event.usage,
14908
14777
  finishReason: event.finishReason,
14909
14778
  providerFinishReason: event.providerFinishReason,
14910
- rawFinishReason: event.rawFinishReason
14779
+ rawFinishReason: event.rawFinishReason,
14780
+ ...event.llmFirstTokenLatencyMs !== void 0 ? { llmFirstTokenLatencyMs: event.llmFirstTokenLatencyMs } : {},
14781
+ ...event.llmStreamDurationMs !== void 0 ? { llmStreamDurationMs: event.llmStreamDurationMs } : {}
14911
14782
  };
14912
14783
  case "step.retrying": return {
14913
14784
  type: "turn.step.retrying",
@@ -14932,13 +14803,17 @@ function mapLoopEvent(event, turnId) {
14932
14803
  description: event.description,
14933
14804
  display: event.display
14934
14805
  };
14935
- case "tool.result": return {
14936
- type: "tool.result",
14937
- turnId,
14938
- toolCallId: event.toolCallId,
14939
- output: event.result.output,
14940
- isError: event.result.isError
14941
- };
14806
+ case "tool.result": {
14807
+ const blockedReason = event.result.isError === true ? event.result.blockedReason : void 0;
14808
+ return {
14809
+ type: "tool.result",
14810
+ turnId,
14811
+ toolCallId: event.toolCallId,
14812
+ output: event.result.output,
14813
+ isError: event.result.isError,
14814
+ blockedReason
14815
+ };
14816
+ }
14942
14817
  case "turn.interrupted":
14943
14818
  if (event.activeStep === void 0) return void 0;
14944
14819
  return {
@@ -15120,10 +14995,12 @@ var UsageRecorder = class {
15120
14995
  const byModel = this.byModelSnapshot();
15121
14996
  const hasByModel = Object.keys(byModel).length > 0;
15122
14997
  const currentTurn = this.currentTurn;
14998
+ const total = hasByModel ? totalUsage(byModel) : void 0;
15123
14999
  return {
15124
15000
  byModel: hasByModel ? byModel : void 0,
15125
- total: hasByModel ? totalUsage(byModel) : void 0,
15126
- currentTurn: currentTurn === void 0 ? void 0 : copyUsage(currentTurn)
15001
+ total,
15002
+ currentTurn: currentTurn === void 0 ? void 0 : copyUsage(currentTurn),
15003
+ cacheHitRate: total !== void 0 ? cacheHitRate(total) : void 0
15127
15004
  };
15128
15005
  }
15129
15006
  status() {
@@ -15134,6 +15011,13 @@ var UsageRecorder = class {
15134
15011
  byModelSnapshot() {
15135
15012
  return Object.fromEntries(Object.entries(this.byModel).map(([model, usage]) => [model, copyUsage(usage)]));
15136
15013
  }
15014
+ restoreRecord(record) {
15015
+ switch (record.type) {
15016
+ case "usage.record":
15017
+ this.record(record.model, record.usage, "session");
15018
+ break;
15019
+ }
15020
+ }
15137
15021
  };
15138
15022
  function totalUsage(byModel) {
15139
15023
  let total;
@@ -15161,7 +15045,6 @@ var Agent = class {
15161
15045
  turn;
15162
15046
  injection;
15163
15047
  permission;
15164
- planMode;
15165
15048
  usage;
15166
15049
  tools;
15167
15050
  background;
@@ -15185,12 +15068,11 @@ var Agent = class {
15185
15068
  this.emitRecordsWriteError(error);
15186
15069
  } }) : void 0));
15187
15070
  this.fullCompaction = new FullCompaction(this, config.compactionStrategy);
15188
- this.context = new ContextMemory(this);
15071
+ this.context = new ContextMemory(this, config.sessionId);
15189
15072
  this.config = new ConfigState(this);
15190
15073
  this.turn = new TurnFlow(this);
15191
15074
  this.injection = new InjectionManager(this);
15192
15075
  this.permission = new PermissionManager(this, config.permission);
15193
- this.planMode = new PlanMode(this);
15194
15076
  this.usage = new UsageRecorder(this);
15195
15077
  this.tools = new ToolManager(this);
15196
15078
  this.background = new BackgroundManager(this, {
@@ -15198,6 +15080,15 @@ var Agent = class {
15198
15080
  sessionDir: config.backgroundSessionDir
15199
15081
  });
15200
15082
  this.replayBuilder = new ReplayBuilder(this);
15083
+ this.records.registerHandlers({
15084
+ context: this.context,
15085
+ config: this.config,
15086
+ usage: this.usage,
15087
+ turn: this.turn,
15088
+ permission: this.permission,
15089
+ tools: this.tools,
15090
+ fullCompaction: this.fullCompaction
15091
+ });
15201
15092
  }
15202
15093
  get generate() {
15203
15094
  return async (provider, systemPrompt, tools, history, callbacks, options) => {
@@ -15218,7 +15109,7 @@ var Agent = class {
15218
15109
  }
15219
15110
  logLlmRequest(provider, systemPrompt, tools, history, options) {
15220
15111
  const context = buildLlmRequestContext(options);
15221
- const configMetadata = buildLlmConfigMetadata(provider, this.config.modelAlias, systemPrompt, tools);
15112
+ const configMetadata = buildLlmConfigMetadata(provider, this.config.modelAlias, systemPrompt, tools, options);
15222
15113
  this.logLlmConfigIfChanged(context, configMetadata, buildLlmConfigSignature(configMetadata, systemPrompt, tools));
15223
15114
  this.log.info("llm request", {
15224
15115
  ...context,
@@ -15239,7 +15130,6 @@ var Agent = class {
15239
15130
  osEnv: this.runtime.osEnv,
15240
15131
  cwd,
15241
15132
  skills: this.skills?.registry,
15242
- cwdListing: context?.cwdListing,
15243
15133
  agentsMd: context?.agentsMd
15244
15134
  });
15245
15135
  this.config.update({
@@ -15249,11 +15139,15 @@ var Agent = class {
15249
15139
  this.tools.setActiveTools(profile.tools);
15250
15140
  }
15251
15141
  async resume() {
15252
- const result = await this.records.replay();
15253
- await this.background.loadFromDisk();
15254
- await this.background.reconcile();
15255
- this.turn.finishResume();
15256
- return result;
15142
+ try {
15143
+ const result = await this.records.replay();
15144
+ await this.background.loadFromDisk();
15145
+ await this.background.reconcile();
15146
+ this.turn.finishResume();
15147
+ return result;
15148
+ } catch (error) {
15149
+ return { error: error instanceof Error ? error : new Error(String(error)) };
15150
+ }
15257
15151
  }
15258
15152
  get rpcMethods() {
15259
15153
  return {
@@ -15298,12 +15192,14 @@ var Agent = class {
15298
15192
  return this.config.modelAlias ?? "";
15299
15193
  },
15300
15194
  enterPlan: async () => {
15301
- await this.planMode.enter();
15195
+ throw new ByfError(ErrorCodes.NOT_IMPLEMENTED, "Plan mode has been removed");
15302
15196
  },
15303
- cancelPlan: (payload) => {
15304
- this.planMode.cancel(payload.id);
15197
+ cancelPlan: async () => {
15198
+ throw new ByfError(ErrorCodes.NOT_IMPLEMENTED, "Plan mode has been removed");
15199
+ },
15200
+ clearPlan: async () => {
15201
+ throw new ByfError(ErrorCodes.NOT_IMPLEMENTED, "Plan mode has been removed");
15305
15202
  },
15306
- clearPlan: () => this.planMode.clear(),
15307
15203
  beginCompaction: (payload) => {
15308
15204
  this.fullCompaction.begin({
15309
15205
  source: "manual",
@@ -15338,7 +15234,7 @@ var Agent = class {
15338
15234
  getContext: () => this.context.data(),
15339
15235
  getConfig: () => this.config.data(),
15340
15236
  getPermission: () => this.permission.data(),
15341
- getPlan: () => this.planMode.data(),
15237
+ getPlan: async () => null,
15342
15238
  getUsage: () => this.usage.data(),
15343
15239
  getTools: () => this.tools.data(),
15344
15240
  getBackground: (payload) => this.background.list(payload.activeOnly ?? false, payload.limit)
@@ -15362,7 +15258,6 @@ var Agent = class {
15362
15258
  contextTokens,
15363
15259
  maxContextTokens,
15364
15260
  contextUsage,
15365
- planMode: this.planMode.isActive,
15366
15261
  permission: this.permission.mode,
15367
15262
  usage
15368
15263
  });
@@ -15408,8 +15303,8 @@ function buildLlmRequestMetadata(systemPrompt, tools, history) {
15408
15303
  if (partialMessageCount > 0) metadata.partialMessageCount = partialMessageCount;
15409
15304
  return metadata;
15410
15305
  }
15411
- function buildLlmConfigMetadata(provider, modelAlias, systemPrompt, tools) {
15412
- return {
15306
+ function buildLlmConfigMetadata(provider, modelAlias, systemPrompt, tools, options) {
15307
+ const metadata = {
15413
15308
  provider: provider.name,
15414
15309
  model: provider.modelName,
15415
15310
  modelAlias,
@@ -15417,6 +15312,30 @@ function buildLlmConfigMetadata(provider, modelAlias, systemPrompt, tools) {
15417
15312
  systemPromptChars: systemPrompt.length,
15418
15313
  toolCount: tools.length
15419
15314
  };
15315
+ const providerCacheStrategy = getProviderCacheStrategy(provider);
15316
+ if (providerCacheStrategy !== void 0) metadata.providerCacheStrategy = providerCacheStrategy;
15317
+ const promptPlan = options?.promptPlan;
15318
+ if (promptPlan !== void 0 && promptPlan.blocks.length > 0) metadata.cacheBlockHashes = extractCacheBlockHashes(promptPlan);
15319
+ return metadata;
15320
+ }
15321
+ /**
15322
+ * Get the cache strategy from a provider's capability.
15323
+ *
15324
+ * Safely handles providers that don't implement getCapability or don't have cache.
15325
+ */
15326
+ function getProviderCacheStrategy(provider) {
15327
+ if (typeof provider.getCapability !== "function") return;
15328
+ return provider.getCapability()?.cache?.strategy;
15329
+ }
15330
+ /**
15331
+ * Extract cache block hashes from a PromptPlan.
15332
+ *
15333
+ * Returns a Record mapping block names to their SHA256 hashes.
15334
+ */
15335
+ function extractCacheBlockHashes(promptPlan) {
15336
+ const hashes = {};
15337
+ for (const block of promptPlan.blocks) hashes[block.name] = fingerprint(block.text);
15338
+ return hashes;
15420
15339
  }
15421
15340
  function buildLlmConfigSignature(metadata, systemPrompt, tools) {
15422
15341
  const toolsForSignature = tools.map(({ name, description, parameters }) => ({
@@ -15452,8 +15371,7 @@ function proxyWithExtraPayload(methods, extraPayload) {
15452
15371
  //#region src/config/schema.ts
15453
15372
  const ProviderTypeSchema = z.enum([
15454
15373
  "anthropic",
15455
- "openai",
15456
- "openai-compat",
15374
+ "openai-completions",
15457
15375
  "google-genai",
15458
15376
  "openai_responses",
15459
15377
  "vertexai"
@@ -15488,7 +15406,13 @@ const ThinkingConfigSchema = z.object({
15488
15406
  "on",
15489
15407
  "off"
15490
15408
  ]).optional(),
15491
- effort: z.string().optional()
15409
+ effort: z.enum([
15410
+ "low",
15411
+ "medium",
15412
+ "high",
15413
+ "xhigh",
15414
+ "max"
15415
+ ]).optional()
15492
15416
  });
15493
15417
  const PermissionModeSchema = z.enum([
15494
15418
  "yolo",
@@ -15587,11 +15511,9 @@ const ByfConfigSchema = z.object({
15587
15511
  defaultModel: z.string().optional(),
15588
15512
  models: z.record(z.string(), ModelAliasSchema).optional(),
15589
15513
  thinking: ThinkingConfigSchema.optional(),
15590
- planMode: z.boolean().optional(),
15591
15514
  yolo: z.boolean().optional(),
15592
15515
  defaultThinking: z.boolean().optional(),
15593
15516
  defaultPermissionMode: PermissionModeSchema.optional(),
15594
- defaultPlanMode: z.boolean().optional(),
15595
15517
  permission: PermissionConfigSchema.optional(),
15596
15518
  hooks: z.array(HookDefSchema).optional(),
15597
15519
  services: ServicesConfigSchema.optional(),
@@ -15599,7 +15521,6 @@ const ByfConfigSchema = z.object({
15599
15521
  extraSkillDirs: z.array(z.string()).optional(),
15600
15522
  loopControl: LoopControlSchema.optional(),
15601
15523
  background: BackgroundConfigSchema.optional(),
15602
- telemetry: z.boolean().optional(),
15603
15524
  raw: z.record(z.string(), z.unknown()).optional()
15604
15525
  });
15605
15526
  const ProviderConfigPatchSchema = ProviderConfigSchema.partial();
@@ -15619,19 +15540,16 @@ const ByfConfigPatchSchema = z.object({
15619
15540
  defaultModel: z.string().optional(),
15620
15541
  models: z.record(z.string(), ModelAliasPatchSchema).optional(),
15621
15542
  thinking: ThinkingConfigPatchSchema.optional(),
15622
- planMode: z.boolean().optional(),
15623
15543
  yolo: z.boolean().optional(),
15624
15544
  defaultThinking: z.boolean().optional(),
15625
15545
  defaultPermissionMode: PermissionModeSchema.optional(),
15626
- defaultPlanMode: z.boolean().optional(),
15627
15546
  permission: PermissionConfigPatchSchema.optional(),
15628
15547
  hooks: z.array(HookDefSchema).optional(),
15629
15548
  services: ServicesConfigPatchSchema.optional(),
15630
15549
  mergeAllAvailableSkills: z.boolean().optional(),
15631
15550
  extraSkillDirs: z.array(z.string()).optional(),
15632
15551
  loopControl: LoopControlPatchSchema.optional(),
15633
- background: BackgroundConfigPatchSchema.optional(),
15634
- telemetry: z.boolean().optional()
15552
+ background: BackgroundConfigPatchSchema.optional()
15635
15553
  }).strict();
15636
15554
  function getDefaultConfig() {
15637
15555
  return { providers: {} };
@@ -15888,14 +15806,11 @@ function configToTomlData(config) {
15888
15806
  for (const key of [
15889
15807
  "defaultProvider",
15890
15808
  "defaultModel",
15891
- "planMode",
15892
15809
  "yolo",
15893
15810
  "defaultThinking",
15894
15811
  "defaultPermissionMode",
15895
- "defaultPlanMode",
15896
15812
  "mergeAllAvailableSkills",
15897
- "extraSkillDirs",
15898
- "telemetry"
15813
+ "extraSkillDirs"
15899
15814
  ]) setDefined(out, camelToSnake(key), config[key]);
15900
15815
  setRecordSection(out, "providers", config.providers, providerToToml);
15901
15816
  setRecordSection(out, "models", config.models, modelToToml);
@@ -17177,11 +17092,19 @@ var Session = class {
17177
17092
  const { agents } = await this.readMetadata();
17178
17093
  this.agents.clear();
17179
17094
  let warning;
17095
+ const failedAgents = [];
17180
17096
  const resumeTasks = Object.keys(agents).map(async (id) => {
17181
17097
  const result = await this.ensureResumeAgentInstantiated(id, agents).resume();
17182
17098
  if (result.warning !== void 0 && warning === void 0) warning = result.warning;
17099
+ if (result.error !== void 0) failedAgents.push({
17100
+ id,
17101
+ error: result.error
17102
+ });
17183
17103
  });
17184
17104
  await Promise.all(resumeTasks);
17105
+ const mainAgentFailure = failedAgents.find(({ id }) => id === "main");
17106
+ if (mainAgentFailure !== void 0) throw mainAgentFailure.error;
17107
+ for (const { id, error } of failedAgents) this.log.warn(`Sub agent "${id}" failed to resume: ${error.message}`);
17185
17108
  const resumeWarning = warning;
17186
17109
  const main = this.agents.get("main");
17187
17110
  const profile = DEFAULT_AGENT_PROFILES["agent"];
@@ -19304,6 +19227,9 @@ function resolveModelCapabilities(alias, provider) {
19304
19227
  audio_in: has("audio_in") || providerCapability.audio_in,
19305
19228
  thinking: has("thinking") || has("always_thinking") || providerCapability.thinking,
19306
19229
  tool_use: has("tool_use") || providerCapability.tool_use,
19230
+ thinking_effort: has("thinking_effort") || providerCapability.thinking_effort,
19231
+ thinking_xhigh: has("thinking_xhigh") || providerCapability.thinking_xhigh,
19232
+ thinking_max: has("thinking_max") || providerCapability.thinking_max,
19307
19233
  max_context_tokens: alias.maxContextSize
19308
19234
  };
19309
19235
  }
@@ -19317,31 +19243,25 @@ function toKosongProviderConfig(provider, model, byfRequestHeaders, maxOutputSiz
19317
19243
  ...maxOutputSize !== void 0 ? { defaultMaxTokens: maxOutputSize } : {},
19318
19244
  ...defaultHeadersField(provider.customHeaders)
19319
19245
  };
19320
- case "openai": return {
19321
- type: "openai",
19322
- model,
19323
- baseUrl: providerValue(provider.baseUrl, provider.env, "OPENAI_BASE_URL"),
19324
- apiKey: providerApiKey(provider),
19325
- reasoningKey,
19326
- ...defaultHeadersField(provider.customHeaders)
19327
- };
19328
- case "openai-compat": {
19246
+ case "openai-completions": {
19329
19247
  const defaultHeaders = {
19330
19248
  ...byfRequestHeaders,
19331
19249
  ...provider.customHeaders
19332
19250
  };
19333
19251
  if (Object.keys(defaultHeaders).length === 0) return {
19334
- type: "openai-compat",
19252
+ type: "openai-completions",
19335
19253
  model,
19336
19254
  baseUrl: providerValue(provider.baseUrl, provider.env, "BYF_BASE_URL"),
19255
+ reasoningKey,
19337
19256
  thinkingEffortKey: provider.thinkingEffortKey,
19338
19257
  generationKwargs: { prompt_cache_key: promptCacheKey },
19339
19258
  apiKey: providerApiKey(provider)
19340
19259
  };
19341
19260
  return {
19342
- type: "openai-compat",
19261
+ type: "openai-completions",
19343
19262
  model,
19344
19263
  baseUrl: providerValue(provider.baseUrl, provider.env, "BYF_BASE_URL"),
19264
+ reasoningKey,
19345
19265
  thinkingEffortKey: provider.thinkingEffortKey,
19346
19266
  generationKwargs: { prompt_cache_key: promptCacheKey },
19347
19267
  defaultHeaders,
@@ -19395,9 +19315,8 @@ function providerForCapabilityProbe(provider) {
19395
19315
  function providerApiKey(provider) {
19396
19316
  switch (provider.type) {
19397
19317
  case "anthropic": return providerValue(provider.apiKey, provider.env, "ANTHROPIC_API_KEY");
19398
- case "openai":
19399
19318
  case "openai_responses": return providerValue(provider.apiKey, provider.env, "OPENAI_API_KEY");
19400
- case "openai-compat": return providerValue(provider.apiKey, provider.env, "BYF_API_KEY");
19319
+ case "openai-completions": return providerValue(provider.apiKey, provider.env, "BYF_API_KEY");
19401
19320
  case "google-genai": return providerValue(provider.apiKey, provider.env, "GOOGLE_API_KEY");
19402
19321
  case "vertexai": return nonEmptyString$1(provider.apiKey) ?? envValue(provider.env, "VERTEXAI_API_KEY") ?? envValue(provider.env, "GOOGLE_API_KEY");
19403
19322
  default: {
@@ -19527,7 +19446,7 @@ function resolveRuntimeThinkingRequest(requestedThinking, defaultThinking) {
19527
19446
  }
19528
19447
  //#endregion
19529
19448
  //#region src/rpc/core-impl.ts
19530
- const BYF_CODE_PROVIDER_NAME = "managed:byf";
19449
+ const BYF_CODE_PROVIDER_NAME = "byf";
19531
19450
  var ByfCore = class {
19532
19451
  rpcClient;
19533
19452
  sdk;
@@ -19554,7 +19473,7 @@ var ByfCore = class {
19554
19473
  this.byfRequestHeaders = options.byfRequestHeaders;
19555
19474
  this.resolveOAuthTokenProvider = options.resolveOAuthTokenProvider;
19556
19475
  this.skillDirs = options.skillDirs ?? [];
19557
- this.telemetry = options.telemetry ?? noopTelemetryClient;
19476
+ this.telemetry = noopTelemetryClient;
19558
19477
  ensureByfHome(this.homeDir);
19559
19478
  this.providerManager = new ProviderManager({
19560
19479
  config: readConfigFile(this.configPath),
@@ -19597,7 +19516,7 @@ var ByfCore = class {
19597
19516
  permissionRules: config.permission?.rules,
19598
19517
  skills: this.resolveSessionSkillConfig(config),
19599
19518
  mcpConfig,
19600
- telemetry: withTelemetryContext(this.telemetry, { sessionId: summary.id })
19519
+ telemetry: this.telemetry
19601
19520
  });
19602
19521
  try {
19603
19522
  session.metadata = {
@@ -19617,7 +19536,6 @@ var ByfCore = class {
19617
19536
  thinkingLevel
19618
19537
  });
19619
19538
  if (permissionMode !== void 0) mainAgent.permission.setMode(permissionMode);
19620
- if (config.defaultPlanMode === true) await mainAgent.planMode.enter();
19621
19539
  await session.writeMetadata();
19622
19540
  await session.flushMetadata();
19623
19541
  } catch (error) {
@@ -19659,7 +19577,7 @@ var ByfCore = class {
19659
19577
  permissionRules: config.permission?.rules,
19660
19578
  skills: this.resolveSessionSkillConfig(config),
19661
19579
  mcpConfig,
19662
- telemetry: withTelemetryContext(this.telemetry, { sessionId: summary.id }),
19580
+ telemetry: this.telemetry,
19663
19581
  initializeMainAgent: false
19664
19582
  });
19665
19583
  let warning;
@@ -19668,7 +19586,6 @@ var ByfCore = class {
19668
19586
  await this.refreshSessionRuntimeConfig(session, config);
19669
19587
  } catch (error) {
19670
19588
  await session.close().catch(() => {});
19671
- withTelemetryContext(this.telemetry, { sessionId: summary.id }).track("session_load_failed", { reason: telemetryErrorReason(error) });
19672
19589
  throw error;
19673
19590
  }
19674
19591
  this.sessions.set(summary.id, session);
@@ -19939,11 +19856,6 @@ function requiredWorkDir(operation, value) {
19939
19856
  function createSessionId() {
19940
19857
  return `session_${randomUUID()}`;
19941
19858
  }
19942
- function telemetryErrorReason(error) {
19943
- if (error instanceof ByfError) return error.code;
19944
- if (error instanceof Error && error.name.length > 0) return error.name;
19945
- return typeof error;
19946
- }
19947
19859
  async function resumeSessionResult(summary, session, warning) {
19948
19860
  const api = new SessionAPIImpl(session);
19949
19861
  const agents = {};
@@ -19951,7 +19863,6 @@ async function resumeSessionResult(summary, session, warning) {
19951
19863
  const config = await api.getConfig({ agentId });
19952
19864
  const context = await api.getContext({ agentId });
19953
19865
  const permission = await api.getPermission({ agentId });
19954
- const plan = await api.getPlan({ agentId });
19955
19866
  const usage = await api.getUsage({ agentId });
19956
19867
  agents[agentId] = {
19957
19868
  type: agent.type,
@@ -19959,7 +19870,7 @@ async function resumeSessionResult(summary, session, warning) {
19959
19870
  context,
19960
19871
  replay: agent.replayBuilder.buildResult(),
19961
19872
  permission,
19962
- plan,
19873
+ plan: null,
19963
19874
  usage,
19964
19875
  tools: await api.getTools({ agentId }),
19965
19876
  toolStore: agent.tools.storeData(),
@@ -20018,4 +19929,4 @@ function parsePositiveInt(value) {
20018
19929
  return n;
20019
19930
  }
20020
19931
  //#endregion
20021
- export { AGENT_WIRE_PROTOCOL_VERSION, Agent, BYF_ERROR_INFO, BackgroundConfigSchema, ByfConfigPatchSchema, ByfConfigSchema, ByfCore, ByfError, ByfServiceConfigSchema, ErrorCodes, HookDefSchema, LoopControlSchema, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE, McpServerConfigSchema, McpServerHttpConfigSchema, McpServerStdioConfigSchema, ModelAliasSchema, OAuthRefSchema, PermissionConfigSchema, PermissionModeSchema, PermissionRuleDecisionSchema, PermissionRuleSchema, PermissionRuleScopeSchema, ProviderConfigSchema, ProviderTypeSchema, ServicesConfigSchema, Session, SessionSubagentHost, ThinkingConfigSchema, USER_PROMPT_ORIGIN, WIRE_PROTOCOL_VERSION, buildExportManifest, collectFilesRecursive, configToTomlData, createRPC, ensureByfHome, ensureConfigFile, exportSessionDirectory, flushDiagnosticLogs, formatConfigValidationError, fromByfErrorPayload, getDefaultConfig, getRootLogger, isByfError, log, makeErrorPayload, mergeConfigPatch, noopTelemetryClient, normalizeTimestampMs, parseBooleanEnv, parseConfigString, proxyWithExtraPayload, readConfigFile, redact, resolveByfHome, resolveConfigPath, resolveConfigValue, resolveGlobalLogPath, resolveLoggingConfig, scanSessionWire, toByfErrorPayload, transformTomlData, validateConfig, withTelemetryContext, writeConfigFile, writeExportZip };
19932
+ export { AGENT_WIRE_PROTOCOL_VERSION, Agent, BYF_ERROR_INFO, BackgroundConfigSchema, ByfConfigPatchSchema, ByfConfigSchema, ByfCore, ByfError, ByfServiceConfigSchema, ErrorCodes, HookDefSchema, LoopControlSchema, MCP_OAUTH_AUTHORIZATION_URL_TOOL_UPDATE, McpServerConfigSchema, McpServerHttpConfigSchema, McpServerStdioConfigSchema, ModelAliasSchema, OAuthRefSchema, PermissionConfigSchema, PermissionModeSchema, PermissionRuleDecisionSchema, PermissionRuleSchema, PermissionRuleScopeSchema, ProviderConfigSchema, ProviderTypeSchema, ServicesConfigSchema, Session, SessionSubagentHost, ThinkingConfigSchema, USER_PROMPT_ORIGIN, WIRE_PROTOCOL_VERSION, buildExportManifest, buildPromptPlan, collectFilesRecursive, configToTomlData, createRPC, ensureByfHome, ensureConfigFile, exportSessionDirectory, flushDiagnosticLogs, formatConfigValidationError, fromByfErrorPayload, getDefaultConfig, getRootLogger, isByfError, log, makeErrorPayload, mergeConfigPatch, normalizeTimestampMs, parseBooleanEnv, parseConfigString, proxyWithExtraPayload, readConfigFile, redact, resolveByfHome, resolveConfigPath, resolveConfigValue, resolveGlobalLogPath, resolveLoggingConfig, scanSessionWire, toByfErrorPayload, transformTomlData, validateConfig, writeConfigFile, writeExportZip };