@byfriends/agent-core 0.1.0 → 0.2.1
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-BUBoyyCX.d.mts → index-DiU4aKCD.d.mts} +543 -412
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1250 -1327
- package/dist/session/store/index.d.mts +1 -1
- package/dist/session/store/index.mjs +1 -1
- package/dist/{store-DhTph1cB.mjs → store-7tEWsiOk.mjs} +0 -7
- package/package.json +3 -3
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-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
941
|
+
function joinPath$2(kaos, ...parts) {
|
|
942
|
+
return pathMod$6(kaos).join(...parts);
|
|
1024
943
|
}
|
|
1025
|
-
function pathMod$
|
|
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
|
|
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
|
-
|
|
1143
|
-
|
|
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 -
|
|
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
|
|
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
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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$
|
|
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$
|
|
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/
|
|
6049
|
-
var
|
|
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
|
|
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
|
-
*
|
|
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,124 @@ 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":
|
|
7041
|
+
case "Agent": return "high";
|
|
7042
|
+
case "Bash": return "medium";
|
|
7043
|
+
case "Read":
|
|
7044
|
+
case "Glob":
|
|
7045
|
+
case "Grep": return "low";
|
|
7046
|
+
default: return "low";
|
|
7047
|
+
}
|
|
7048
|
+
}
|
|
7049
|
+
function extractTextFromContent(content) {
|
|
7050
|
+
if (typeof content === "string") return content;
|
|
7051
|
+
return content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
7052
|
+
}
|
|
7053
|
+
function countLines(text) {
|
|
7054
|
+
if (text.length === 0) return 0;
|
|
7055
|
+
let count = 1;
|
|
7056
|
+
for (const char of text) if (char === "\n") count++;
|
|
7057
|
+
return count;
|
|
7058
|
+
}
|
|
7059
|
+
function headTailLines(text, headCount, tailCount) {
|
|
7060
|
+
const lines = text.split("\n");
|
|
7061
|
+
if (lines.length <= headCount + tailCount) return text;
|
|
7062
|
+
return `${lines.slice(0, headCount).join("\n")}\n...\n${lines.slice(-tailCount).join("\n")}`;
|
|
7063
|
+
}
|
|
7064
|
+
function formatSummary(toolName, _args, lineCount, isError) {
|
|
7065
|
+
return `[${toolName}: ${String(lineCount)} lines${isError ? ", error" : ""}]`;
|
|
7066
|
+
}
|
|
7067
|
+
function isAlreadyMasked(text, toolName) {
|
|
7068
|
+
return text.startsWith(`[${toolName}:`);
|
|
7069
|
+
}
|
|
7070
|
+
function maskToolResult(message, toolName, toolArgs) {
|
|
7071
|
+
const text = extractTextFromContent(message.content);
|
|
7072
|
+
if (isAlreadyMasked(text, toolName)) return message;
|
|
7073
|
+
const lineCount = countLines(text);
|
|
7074
|
+
const summary = formatSummary(toolName, toolArgs, lineCount, message.isError === true);
|
|
7075
|
+
const priority = getToolPriority(toolName);
|
|
7076
|
+
let maskedContent;
|
|
7077
|
+
if (priority === "high") maskedContent = summary;
|
|
7078
|
+
else {
|
|
7079
|
+
const headCount = priority === "medium" ? 3 : 3;
|
|
7080
|
+
const tailCount = priority === "medium" ? 5 : toolName === "Read" ? 3 : 2;
|
|
7081
|
+
if (lineCount <= headCount + tailCount) if (lineCount === 0) maskedContent = summary;
|
|
7082
|
+
else maskedContent = `${summary}\n---\n${text}`;
|
|
7083
|
+
else maskedContent = `${summary}\n---\n${headTailLines(text, headCount, tailCount)}`;
|
|
7084
|
+
}
|
|
7085
|
+
return {
|
|
7086
|
+
...message,
|
|
7087
|
+
content: [{
|
|
7088
|
+
type: "text",
|
|
7089
|
+
text: maskedContent
|
|
7090
|
+
}]
|
|
7091
|
+
};
|
|
7092
|
+
}
|
|
7093
|
+
/**
|
|
7094
|
+
* Apply observation masking to tool result messages in history.
|
|
7095
|
+
* Returns a new history array (does not mutate the original) and masking result.
|
|
7096
|
+
*/
|
|
7097
|
+
function applyObservationMasking(history, maxContextSize, toolCallIdToInfo, config = DEFAULT_MASKING_CONFIG) {
|
|
7098
|
+
const effectiveCapacity = maxContextSize * config.effectiveCapacityRatio;
|
|
7099
|
+
const tokensBefore = estimateTokensForMessages(history);
|
|
7100
|
+
const pressure = effectiveCapacity > 0 ? tokensBefore / effectiveCapacity : 0;
|
|
7101
|
+
let maskPriorities;
|
|
7102
|
+
if (pressure < config.lowPriorityThreshold) maskPriorities = /* @__PURE__ */ new Set();
|
|
7103
|
+
else if (pressure < config.mediumPriorityThreshold) maskPriorities = new Set(["low"]);
|
|
7104
|
+
else if (pressure < config.highPriorityThreshold) maskPriorities = new Set(["low", "medium"]);
|
|
7105
|
+
else maskPriorities = new Set(["low", "medium"]);
|
|
7106
|
+
if (maskPriorities.size === 0) return {
|
|
7107
|
+
history: [...history],
|
|
7108
|
+
result: {
|
|
7109
|
+
masked: false,
|
|
7110
|
+
maskedCount: 0,
|
|
7111
|
+
tokensBefore,
|
|
7112
|
+
tokensAfter: tokensBefore
|
|
7113
|
+
}
|
|
7114
|
+
};
|
|
7115
|
+
let maskedCount = 0;
|
|
7116
|
+
const newHistory = [];
|
|
7117
|
+
for (const message of history) {
|
|
7118
|
+
if (message.role !== "tool" || message.toolCallId === void 0) {
|
|
7119
|
+
newHistory.push(message);
|
|
7120
|
+
continue;
|
|
7121
|
+
}
|
|
7122
|
+
const info = toolCallIdToInfo.get(message.toolCallId);
|
|
7123
|
+
if (info === void 0) {
|
|
7124
|
+
newHistory.push(message);
|
|
7125
|
+
continue;
|
|
7126
|
+
}
|
|
7127
|
+
const priority = getToolPriority(info.name);
|
|
7128
|
+
if (!maskPriorities.has(priority)) {
|
|
7129
|
+
newHistory.push(message);
|
|
7130
|
+
continue;
|
|
7131
|
+
}
|
|
7132
|
+
const masked = maskToolResult(message, info.name, info.args);
|
|
7133
|
+
newHistory.push(masked);
|
|
7134
|
+
if (masked !== message) maskedCount++;
|
|
7135
|
+
}
|
|
7136
|
+
const tokensAfter = estimateTokensForMessages(newHistory);
|
|
7137
|
+
return {
|
|
7138
|
+
history: newHistory,
|
|
7139
|
+
result: {
|
|
7140
|
+
masked: maskedCount > 0,
|
|
7141
|
+
maskedCount,
|
|
7142
|
+
tokensBefore,
|
|
7143
|
+
tokensAfter
|
|
7144
|
+
}
|
|
7145
|
+
};
|
|
7146
|
+
}
|
|
7147
|
+
//#endregion
|
|
7235
7148
|
//#region src/agent/compaction/config.ts
|
|
7236
7149
|
const DEFAULT_COMPACTION_CONFIG = {
|
|
7237
7150
|
triggerRatio: .85,
|
|
@@ -7240,7 +7153,8 @@ const DEFAULT_COMPACTION_CONFIG = {
|
|
|
7240
7153
|
maxCompactionPerTurn: 3,
|
|
7241
7154
|
maxRecentSteps: 3,
|
|
7242
7155
|
maxRecentUserMessages: Infinity,
|
|
7243
|
-
maxRecentSizeRatio: .2
|
|
7156
|
+
maxRecentSizeRatio: .2,
|
|
7157
|
+
masking: DEFAULT_MASKING_CONFIG
|
|
7244
7158
|
};
|
|
7245
7159
|
//#endregion
|
|
7246
7160
|
//#region src/agent/compaction/render-messages.ts
|
|
@@ -7320,6 +7234,9 @@ var DefaultCompactionStrategy = class {
|
|
|
7320
7234
|
constructor(config = DEFAULT_COMPACTION_CONFIG) {
|
|
7321
7235
|
this.config = config;
|
|
7322
7236
|
}
|
|
7237
|
+
get maskingConfig() {
|
|
7238
|
+
return this.config.masking;
|
|
7239
|
+
}
|
|
7323
7240
|
shouldCompact(usedSize, maxSize) {
|
|
7324
7241
|
if (maxSize <= 0) return false;
|
|
7325
7242
|
return usedSize >= maxSize * this.config.triggerRatio || this.shouldUseReservedContext(maxSize, usedSize);
|
|
@@ -7471,6 +7388,18 @@ var FullCompaction = class {
|
|
|
7471
7388
|
await this.block(signal);
|
|
7472
7389
|
}
|
|
7473
7390
|
async beforeStep(signal) {
|
|
7391
|
+
const maskingResult = this.agent.context.applyObservationMasking(this.strategy.maskingConfig);
|
|
7392
|
+
if (maskingResult.masked) this.agent.emitEvent({
|
|
7393
|
+
type: "observation_masking.applied",
|
|
7394
|
+
maskedCount: maskingResult.maskedCount,
|
|
7395
|
+
tokensBefore: maskingResult.tokensBefore,
|
|
7396
|
+
tokensAfter: maskingResult.tokensAfter
|
|
7397
|
+
});
|
|
7398
|
+
const pruningResult = this.agent.context.applyPruning();
|
|
7399
|
+
if (pruningResult.pruned) this.agent.emitEvent({
|
|
7400
|
+
type: "pruning.applied",
|
|
7401
|
+
prunedCount: pruningResult.prunedCount
|
|
7402
|
+
});
|
|
7474
7403
|
this.checkAutoCompaction();
|
|
7475
7404
|
if (this.shouldBlock) await this.block(signal);
|
|
7476
7405
|
}
|
|
@@ -7634,6 +7563,19 @@ var FullCompaction = class {
|
|
|
7634
7563
|
computeCompactableCount(history) {
|
|
7635
7564
|
return sliceCompleteMessages(history, this.strategy.computeCompactCount(history, this.maxContextSize));
|
|
7636
7565
|
}
|
|
7566
|
+
restoreRecord(record) {
|
|
7567
|
+
switch (record.type) {
|
|
7568
|
+
case "full_compaction.begin":
|
|
7569
|
+
this.begin(record);
|
|
7570
|
+
break;
|
|
7571
|
+
case "full_compaction.cancel":
|
|
7572
|
+
this.cancel();
|
|
7573
|
+
break;
|
|
7574
|
+
case "full_compaction.complete":
|
|
7575
|
+
this.complete(record);
|
|
7576
|
+
break;
|
|
7577
|
+
}
|
|
7578
|
+
}
|
|
7637
7579
|
};
|
|
7638
7580
|
function extractCompactionSummary(response) {
|
|
7639
7581
|
const summary = typeof response.message.content === "string" ? response.message.content : response.message.content.map((part) => part.type === "text" ? part.text : "").join("");
|
|
@@ -7775,6 +7717,92 @@ var ConfigState = class {
|
|
|
7775
7717
|
return;
|
|
7776
7718
|
}
|
|
7777
7719
|
}
|
|
7720
|
+
restoreRecord(record) {
|
|
7721
|
+
switch (record.type) {
|
|
7722
|
+
case "config.update":
|
|
7723
|
+
this.update(record);
|
|
7724
|
+
break;
|
|
7725
|
+
}
|
|
7726
|
+
}
|
|
7727
|
+
};
|
|
7728
|
+
//#endregion
|
|
7729
|
+
//#region src/agent/context/output-offloading.ts
|
|
7730
|
+
const DEFAULT_OFFLOADING_CONFIG = {
|
|
7731
|
+
threshold: 8e3,
|
|
7732
|
+
previewChars: 1e3
|
|
7733
|
+
};
|
|
7734
|
+
function shouldOffload(output, config = DEFAULT_OFFLOADING_CONFIG) {
|
|
7735
|
+
return estimateTokens(output) > config.threshold;
|
|
7736
|
+
}
|
|
7737
|
+
async function offloadOutput(toolCallId, toolName, result, scratchManager, config = DEFAULT_OFFLOADING_CONFIG) {
|
|
7738
|
+
const output = result.output;
|
|
7739
|
+
if (typeof output !== "string") return { offloaded: false };
|
|
7740
|
+
if (!shouldOffload(output, config)) return { offloaded: false };
|
|
7741
|
+
const filePath = await scratchManager.writeOutput(toolCallId, output);
|
|
7742
|
+
return {
|
|
7743
|
+
offloaded: true,
|
|
7744
|
+
output: buildPreview(output, toolName, filePath, config.previewChars),
|
|
7745
|
+
filePath
|
|
7746
|
+
};
|
|
7747
|
+
}
|
|
7748
|
+
function buildPreview(output, toolName, filePath, previewChars) {
|
|
7749
|
+
const preview = output.slice(0, previewChars);
|
|
7750
|
+
return `[Tool output offloaded to scratch file: ${filePath}]
|
|
7751
|
+
Preview (first ${String(previewChars)} chars):
|
|
7752
|
+
${preview}
|
|
7753
|
+
Use Read(path="${filePath}") to retrieve the full output.`;
|
|
7754
|
+
}
|
|
7755
|
+
//#endregion
|
|
7756
|
+
//#region src/agent/context/scratch-manager.ts
|
|
7757
|
+
var ScratchManager = class {
|
|
7758
|
+
kaos;
|
|
7759
|
+
config;
|
|
7760
|
+
currentSize = 0;
|
|
7761
|
+
files = [];
|
|
7762
|
+
constructor(kaos, config) {
|
|
7763
|
+
this.kaos = kaos;
|
|
7764
|
+
this.config = config;
|
|
7765
|
+
}
|
|
7766
|
+
async writeOutput(toolCallId, output) {
|
|
7767
|
+
await this.kaos.mkdir(this.config.scratchDir, {
|
|
7768
|
+
parents: true,
|
|
7769
|
+
existOk: true
|
|
7770
|
+
});
|
|
7771
|
+
const filePath = join(this.config.scratchDir, `${toolCallId}.txt`);
|
|
7772
|
+
const size = Buffer.byteLength(output, "utf-8");
|
|
7773
|
+
await this.evictIfNeeded(size);
|
|
7774
|
+
await this.kaos.writeText(filePath, output);
|
|
7775
|
+
this.files.push({
|
|
7776
|
+
path: filePath,
|
|
7777
|
+
size,
|
|
7778
|
+
createdAt: Date.now()
|
|
7779
|
+
});
|
|
7780
|
+
this.currentSize += size;
|
|
7781
|
+
return filePath;
|
|
7782
|
+
}
|
|
7783
|
+
async readOutput(filePath) {
|
|
7784
|
+
return this.kaos.readText(filePath);
|
|
7785
|
+
}
|
|
7786
|
+
async evictIfNeeded(neededBytes) {
|
|
7787
|
+
this.files.sort((a, b) => a.createdAt - b.createdAt);
|
|
7788
|
+
while (this.files.length > 0 && (this.files.length >= this.config.maxFileCount || this.currentSize + neededBytes > this.config.maxSessionSize)) {
|
|
7789
|
+
const oldest = this.files.shift();
|
|
7790
|
+
try {
|
|
7791
|
+
await this.kaos.exec("rm", oldest.path);
|
|
7792
|
+
} catch {}
|
|
7793
|
+
this.currentSize -= oldest.size;
|
|
7794
|
+
}
|
|
7795
|
+
}
|
|
7796
|
+
async cleanup() {
|
|
7797
|
+
for (const file of this.files) try {
|
|
7798
|
+
await this.kaos.exec("rm", file.path);
|
|
7799
|
+
} catch {}
|
|
7800
|
+
this.files = [];
|
|
7801
|
+
this.currentSize = 0;
|
|
7802
|
+
try {
|
|
7803
|
+
await this.kaos.exec("rm", "-rf", this.config.scratchDir);
|
|
7804
|
+
} catch {}
|
|
7805
|
+
}
|
|
7778
7806
|
};
|
|
7779
7807
|
//#endregion
|
|
7780
7808
|
//#region src/agent/context/types.ts
|
|
@@ -7793,8 +7821,15 @@ var ContextMemory = class {
|
|
|
7793
7821
|
openSteps = /* @__PURE__ */ new Map();
|
|
7794
7822
|
pendingToolResultIds = /* @__PURE__ */ new Set();
|
|
7795
7823
|
deferredMessages = [];
|
|
7796
|
-
|
|
7824
|
+
toolCallInfo = /* @__PURE__ */ new Map();
|
|
7825
|
+
scratchManager;
|
|
7826
|
+
constructor(agent, sessionId) {
|
|
7797
7827
|
this.agent = agent;
|
|
7828
|
+
if (agent.homedir !== void 0 && sessionId !== void 0) this.scratchManager = new ScratchManager(agent.runtime.kaos, {
|
|
7829
|
+
scratchDir: join(agent.homedir, "sessions", sessionId, "scratch"),
|
|
7830
|
+
maxSessionSize: 5e7,
|
|
7831
|
+
maxFileCount: 100
|
|
7832
|
+
});
|
|
7798
7833
|
}
|
|
7799
7834
|
appendUserMessage(content, origin = USER_PROMPT_ORIGIN) {
|
|
7800
7835
|
this.appendMessage({
|
|
@@ -7842,6 +7877,8 @@ var ContextMemory = class {
|
|
|
7842
7877
|
this.openSteps.clear();
|
|
7843
7878
|
this.pendingToolResultIds.clear();
|
|
7844
7879
|
this.deferredMessages = [];
|
|
7880
|
+
this.toolCallInfo.clear();
|
|
7881
|
+
this.scratchManager?.cleanup();
|
|
7845
7882
|
this.agent.injection.onContextClear();
|
|
7846
7883
|
this.agent.emitStatusUpdated();
|
|
7847
7884
|
}
|
|
@@ -7885,7 +7922,73 @@ var ContextMemory = class {
|
|
|
7885
7922
|
get messages() {
|
|
7886
7923
|
return project(this.history);
|
|
7887
7924
|
}
|
|
7888
|
-
|
|
7925
|
+
applyObservationMasking(config) {
|
|
7926
|
+
const effectiveConfig = config ?? DEFAULT_MASKING_CONFIG;
|
|
7927
|
+
const maxContextSize = this.agent.config.modelCapabilities.max_context_tokens;
|
|
7928
|
+
const { history, result } = applyObservationMasking(this._history, maxContextSize, this.toolCallInfo, effectiveConfig);
|
|
7929
|
+
if (result.masked) {
|
|
7930
|
+
this.agent.records.logRecord({
|
|
7931
|
+
type: "context.observation_masking",
|
|
7932
|
+
maskedCount: result.maskedCount,
|
|
7933
|
+
tokensBefore: result.tokensBefore,
|
|
7934
|
+
tokensAfter: result.tokensAfter
|
|
7935
|
+
});
|
|
7936
|
+
this._history = history;
|
|
7937
|
+
this.agent.emitStatusUpdated();
|
|
7938
|
+
}
|
|
7939
|
+
return result;
|
|
7940
|
+
}
|
|
7941
|
+
applyPruning(config) {
|
|
7942
|
+
const maxContextSize = this.agent.config.modelCapabilities.max_context_tokens;
|
|
7943
|
+
if (maxContextSize <= 0) return {
|
|
7944
|
+
pruned: false,
|
|
7945
|
+
prunedCount: 0
|
|
7946
|
+
};
|
|
7947
|
+
const effectiveCapacity = maxContextSize * (config?.effectiveCapacityRatio ?? .6);
|
|
7948
|
+
const currentTokens = this.tokenCountWithPending;
|
|
7949
|
+
const threshold = effectiveCapacity * (config?.pruningThreshold ?? .85);
|
|
7950
|
+
if (currentTokens < threshold) return {
|
|
7951
|
+
pruned: false,
|
|
7952
|
+
prunedCount: 0
|
|
7953
|
+
};
|
|
7954
|
+
const maskedIndices = [];
|
|
7955
|
+
for (let i = 0; i < this._history.length; i++) {
|
|
7956
|
+
const message = this._history[i];
|
|
7957
|
+
if (message?.role !== "tool" || message.toolCallId === void 0) continue;
|
|
7958
|
+
const info = this.toolCallInfo.get(message.toolCallId);
|
|
7959
|
+
if (info === void 0) continue;
|
|
7960
|
+
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);
|
|
7961
|
+
}
|
|
7962
|
+
let prunedCount = 0;
|
|
7963
|
+
let tokensAfter = currentTokens;
|
|
7964
|
+
for (const index of maskedIndices) {
|
|
7965
|
+
if (tokensAfter < threshold) break;
|
|
7966
|
+
const message = this._history[index];
|
|
7967
|
+
if (message === void 0) continue;
|
|
7968
|
+
const tokensBeforeMessage = estimateTokensForMessages([message]);
|
|
7969
|
+
this._history[index] = {
|
|
7970
|
+
...message,
|
|
7971
|
+
content: [{
|
|
7972
|
+
type: "text",
|
|
7973
|
+
text: "[pruned]"
|
|
7974
|
+
}]
|
|
7975
|
+
};
|
|
7976
|
+
tokensAfter -= tokensBeforeMessage;
|
|
7977
|
+
prunedCount++;
|
|
7978
|
+
}
|
|
7979
|
+
if (prunedCount > 0) {
|
|
7980
|
+
this.agent.records.logRecord({
|
|
7981
|
+
type: "context.pruning",
|
|
7982
|
+
prunedCount
|
|
7983
|
+
});
|
|
7984
|
+
this.agent.emitStatusUpdated();
|
|
7985
|
+
}
|
|
7986
|
+
return {
|
|
7987
|
+
pruned: prunedCount > 0,
|
|
7988
|
+
prunedCount
|
|
7989
|
+
};
|
|
7990
|
+
}
|
|
7991
|
+
async appendLoopEvent(event) {
|
|
7889
7992
|
this.agent.records.logRecord({
|
|
7890
7993
|
type: "context.append_loop_event",
|
|
7891
7994
|
event
|
|
@@ -7928,14 +8031,33 @@ var ContextMemory = class {
|
|
|
7928
8031
|
arguments: event.args === void 0 ? null : JSON.stringify(event.args)
|
|
7929
8032
|
});
|
|
7930
8033
|
this.pendingToolResultIds.add(event.toolCallId);
|
|
8034
|
+
this.toolCallInfo.set(event.toolCallId, {
|
|
8035
|
+
name: event.name,
|
|
8036
|
+
args: event.args
|
|
8037
|
+
});
|
|
7931
8038
|
return;
|
|
7932
8039
|
}
|
|
7933
8040
|
case "tool.result": {
|
|
7934
|
-
|
|
8041
|
+
let result = event.result;
|
|
8042
|
+
if (!this.agent.records.restoring && this.scratchManager !== void 0 && typeof result.output === "string" && (this.toolCallInfo.get(event.toolCallId)?.name ?? "unknown") !== "Agent") {
|
|
8043
|
+
const offloadResult = await offloadOutput(event.toolCallId, this.toolCallInfo.get(event.toolCallId)?.name ?? "unknown", result, this.scratchManager, DEFAULT_OFFLOADING_CONFIG);
|
|
8044
|
+
if (offloadResult.offloaded) {
|
|
8045
|
+
result = {
|
|
8046
|
+
...result,
|
|
8047
|
+
output: offloadResult.output
|
|
8048
|
+
};
|
|
8049
|
+
this.agent.records.logRecord({
|
|
8050
|
+
type: "context.output_offloaded",
|
|
8051
|
+
toolCallId: event.toolCallId,
|
|
8052
|
+
filePath: offloadResult.filePath
|
|
8053
|
+
});
|
|
8054
|
+
}
|
|
8055
|
+
}
|
|
8056
|
+
const message = createToolMessage(event.toolCallId, toolResultOutputForModel(result));
|
|
7935
8057
|
this.pushHistory({
|
|
7936
8058
|
...message,
|
|
7937
8059
|
role: "tool",
|
|
7938
|
-
isError:
|
|
8060
|
+
isError: result.isError
|
|
7939
8061
|
});
|
|
7940
8062
|
this.pendingToolResultIds.delete(event.toolCallId);
|
|
7941
8063
|
this.flushDeferredMessagesIfToolExchangeClosed();
|
|
@@ -7972,6 +8094,83 @@ var ContextMemory = class {
|
|
|
7972
8094
|
});
|
|
7973
8095
|
}
|
|
7974
8096
|
}
|
|
8097
|
+
restoreRecord(record) {
|
|
8098
|
+
switch (record.type) {
|
|
8099
|
+
case "context.append_message":
|
|
8100
|
+
this.appendMessage(record.message);
|
|
8101
|
+
break;
|
|
8102
|
+
case "context.clear":
|
|
8103
|
+
this.restoreClear();
|
|
8104
|
+
break;
|
|
8105
|
+
case "context.apply_compaction":
|
|
8106
|
+
this.restoreApplyCompaction(record);
|
|
8107
|
+
break;
|
|
8108
|
+
case "context.mark_last_user_prompt_blocked":
|
|
8109
|
+
this.restoreMarkLastUserPromptBlocked(record);
|
|
8110
|
+
break;
|
|
8111
|
+
case "context.append_loop_event":
|
|
8112
|
+
this.restoreAppendLoopEvent(record);
|
|
8113
|
+
break;
|
|
8114
|
+
case "context.observation_masking":
|
|
8115
|
+
this.restoreObservationMasking();
|
|
8116
|
+
break;
|
|
8117
|
+
}
|
|
8118
|
+
}
|
|
8119
|
+
restoreClear() {
|
|
8120
|
+
this._history = [];
|
|
8121
|
+
this._tokenCount = 0;
|
|
8122
|
+
this.tokenCountCoveredMessageCount = 0;
|
|
8123
|
+
this.openSteps.clear();
|
|
8124
|
+
this.pendingToolResultIds.clear();
|
|
8125
|
+
this.deferredMessages = [];
|
|
8126
|
+
this.toolCallInfo.clear();
|
|
8127
|
+
this.agent.injection.onContextClear();
|
|
8128
|
+
this.agent.emitStatusUpdated();
|
|
8129
|
+
}
|
|
8130
|
+
restoreApplyCompaction(record) {
|
|
8131
|
+
const compactedCount = record.compactedCount;
|
|
8132
|
+
const summary = record.summary;
|
|
8133
|
+
const tokensAfter = record.tokensAfter;
|
|
8134
|
+
this._history = [{
|
|
8135
|
+
role: "assistant",
|
|
8136
|
+
content: [{
|
|
8137
|
+
type: "text",
|
|
8138
|
+
text: summary
|
|
8139
|
+
}],
|
|
8140
|
+
toolCalls: [],
|
|
8141
|
+
origin: { kind: "compaction_summary" }
|
|
8142
|
+
}, ...this._history.slice(compactedCount)];
|
|
8143
|
+
this.openSteps.clear();
|
|
8144
|
+
this.flushDeferredMessagesIfToolExchangeClosed();
|
|
8145
|
+
this._tokenCount = tokensAfter;
|
|
8146
|
+
this.tokenCountCoveredMessageCount = this._history.length;
|
|
8147
|
+
this.agent.injection.onContextCompacted(compactedCount);
|
|
8148
|
+
this.agent.emitStatusUpdated();
|
|
8149
|
+
}
|
|
8150
|
+
restoreMarkLastUserPromptBlocked(record) {
|
|
8151
|
+
const hookEvent = record.hookEvent;
|
|
8152
|
+
for (let i = this._history.length - 1; i >= 0; i--) {
|
|
8153
|
+
const message = this._history[i];
|
|
8154
|
+
if (message?.role !== "user" || message.origin?.kind !== "user") continue;
|
|
8155
|
+
this._history[i] = {
|
|
8156
|
+
...message,
|
|
8157
|
+
origin: {
|
|
8158
|
+
...message.origin,
|
|
8159
|
+
blockedByHook: hookEvent
|
|
8160
|
+
}
|
|
8161
|
+
};
|
|
8162
|
+
return;
|
|
8163
|
+
}
|
|
8164
|
+
}
|
|
8165
|
+
async restoreAppendLoopEvent(record) {
|
|
8166
|
+
await this.appendLoopEvent(record.event);
|
|
8167
|
+
}
|
|
8168
|
+
restoreObservationMasking() {
|
|
8169
|
+
const maxContextSize = this.agent.config.modelCapabilities.max_context_tokens;
|
|
8170
|
+
const { history } = applyObservationMasking(this._history, maxContextSize, this.toolCallInfo);
|
|
8171
|
+
this._history = history;
|
|
8172
|
+
this.agent.emitStatusUpdated();
|
|
8173
|
+
}
|
|
7975
8174
|
};
|
|
7976
8175
|
function toolResultOutputForModel(result) {
|
|
7977
8176
|
const output = result.output;
|
|
@@ -8382,6 +8581,129 @@ var DynamicInjector = class {
|
|
|
8382
8581
|
}
|
|
8383
8582
|
};
|
|
8384
8583
|
//#endregion
|
|
8584
|
+
//#region src/agent/injection/directory-tree.ts
|
|
8585
|
+
const EXCLUDED_DIRS = new Set([
|
|
8586
|
+
"node_modules",
|
|
8587
|
+
".git",
|
|
8588
|
+
"dist",
|
|
8589
|
+
"build",
|
|
8590
|
+
".next",
|
|
8591
|
+
".nuxt",
|
|
8592
|
+
".vite",
|
|
8593
|
+
"target",
|
|
8594
|
+
".turbo",
|
|
8595
|
+
"coverage",
|
|
8596
|
+
".cache",
|
|
8597
|
+
".DS_Store",
|
|
8598
|
+
".idea",
|
|
8599
|
+
".vscode",
|
|
8600
|
+
"venv",
|
|
8601
|
+
".venv"
|
|
8602
|
+
]);
|
|
8603
|
+
const HIDDEN_DIR_WHITELIST = new Set([
|
|
8604
|
+
".github",
|
|
8605
|
+
".byf",
|
|
8606
|
+
".agents",
|
|
8607
|
+
".changeset",
|
|
8608
|
+
".husky"
|
|
8609
|
+
]);
|
|
8610
|
+
var DirectoryTreeInjector = class extends DynamicInjector {
|
|
8611
|
+
injectionVariant = "directory_tree";
|
|
8612
|
+
lastTree;
|
|
8613
|
+
hasInjected = false;
|
|
8614
|
+
capturedTimestamp;
|
|
8615
|
+
async getInjection() {
|
|
8616
|
+
const kaos = this.agent.runtime.kaos;
|
|
8617
|
+
const workDir = this.agent.config.cwd || kaos.getcwd();
|
|
8618
|
+
const tree = await buildTree(kaos, workDir);
|
|
8619
|
+
if (this.hasInjected && tree === this.lastTree) return;
|
|
8620
|
+
this.lastTree = tree;
|
|
8621
|
+
this.hasInjected = true;
|
|
8622
|
+
if (this.capturedTimestamp === void 0) this.capturedTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
8623
|
+
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.`;
|
|
8624
|
+
}
|
|
8625
|
+
};
|
|
8626
|
+
async function buildTree(kaos, workDir) {
|
|
8627
|
+
const lines = [];
|
|
8628
|
+
const pathClass = kaos.pathClass();
|
|
8629
|
+
const { entries, total, readable } = await collectEntries(kaos, workDir, 30, pathClass);
|
|
8630
|
+
if (!readable) return "[not readable]";
|
|
8631
|
+
const remaining = total - entries.length;
|
|
8632
|
+
for (let i = 0; i < entries.length; i++) {
|
|
8633
|
+
const entry = entries[i];
|
|
8634
|
+
if (entry === void 0) continue;
|
|
8635
|
+
const { name, isDir } = entry;
|
|
8636
|
+
const isLast = i === entries.length - 1 && remaining === 0;
|
|
8637
|
+
const connector = isLast ? "└── " : "├── ";
|
|
8638
|
+
if (isDir) {
|
|
8639
|
+
lines.push(`${connector}${name}/`);
|
|
8640
|
+
const childPrefix = isLast ? " " : "│ ";
|
|
8641
|
+
const child = await collectEntries(kaos, joinPath(workDir, name, pathClass), 10, pathClass);
|
|
8642
|
+
if (!child.readable) {
|
|
8643
|
+
lines.push(`${childPrefix}└── [not readable]`);
|
|
8644
|
+
continue;
|
|
8645
|
+
}
|
|
8646
|
+
const childRemaining = child.total - child.entries.length;
|
|
8647
|
+
for (let j = 0; j < child.entries.length; j++) {
|
|
8648
|
+
const ce = child.entries[j];
|
|
8649
|
+
if (ce === void 0) continue;
|
|
8650
|
+
const cConnector = j === child.entries.length - 1 && childRemaining === 0 ? "└── " : "├── ";
|
|
8651
|
+
const suffix = ce.isDir ? "/" : "";
|
|
8652
|
+
lines.push(`${childPrefix}${cConnector}${ce.name}${suffix}`);
|
|
8653
|
+
}
|
|
8654
|
+
if (childRemaining > 0) lines.push(`${childPrefix}└── ... and ${String(childRemaining)} more`);
|
|
8655
|
+
} else lines.push(`${connector}${name}`);
|
|
8656
|
+
}
|
|
8657
|
+
if (remaining > 0) lines.push(`└── ... and ${String(remaining)} more entries`);
|
|
8658
|
+
return lines.length > 0 ? lines.join("\n") : "(empty directory)";
|
|
8659
|
+
}
|
|
8660
|
+
async function collectEntries(kaos, dirPath, maxWidth, pathClass) {
|
|
8661
|
+
const all = [];
|
|
8662
|
+
try {
|
|
8663
|
+
for await (const fullPath of kaos.iterdir(dirPath)) {
|
|
8664
|
+
const name = basename$1(fullPath, pathClass);
|
|
8665
|
+
if (shouldExclude(name)) continue;
|
|
8666
|
+
let isDir = false;
|
|
8667
|
+
try {
|
|
8668
|
+
isDir = ((await kaos.stat(fullPath)).stMode & 61440) === 16384;
|
|
8669
|
+
} catch {}
|
|
8670
|
+
all.push({
|
|
8671
|
+
name,
|
|
8672
|
+
isDir
|
|
8673
|
+
});
|
|
8674
|
+
}
|
|
8675
|
+
} catch {
|
|
8676
|
+
return {
|
|
8677
|
+
entries: [],
|
|
8678
|
+
total: 0,
|
|
8679
|
+
readable: false
|
|
8680
|
+
};
|
|
8681
|
+
}
|
|
8682
|
+
all.sort((a, b) => {
|
|
8683
|
+
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
8684
|
+
return a.name.localeCompare(b.name);
|
|
8685
|
+
});
|
|
8686
|
+
return {
|
|
8687
|
+
entries: all.slice(0, maxWidth),
|
|
8688
|
+
total: all.length,
|
|
8689
|
+
readable: true
|
|
8690
|
+
};
|
|
8691
|
+
}
|
|
8692
|
+
function shouldExclude(name) {
|
|
8693
|
+
if (EXCLUDED_DIRS.has(name)) return true;
|
|
8694
|
+
if (name.startsWith(".") && !HIDDEN_DIR_WHITELIST.has(name)) return true;
|
|
8695
|
+
return false;
|
|
8696
|
+
}
|
|
8697
|
+
function pathMod$1(pathClass) {
|
|
8698
|
+
return pathClass === "win32" ? win32Path : posixPath;
|
|
8699
|
+
}
|
|
8700
|
+
function basename$1(p, pathClass) {
|
|
8701
|
+
return pathMod$1(pathClass).basename(p);
|
|
8702
|
+
}
|
|
8703
|
+
function joinPath(parent, child, pathClass) {
|
|
8704
|
+
return pathMod$1(pathClass).join(parent, child);
|
|
8705
|
+
}
|
|
8706
|
+
//#endregion
|
|
8385
8707
|
//#region src/agent/injection/permission-mode.ts
|
|
8386
8708
|
const AUTO_MODE_ENTER_REMINDER = [
|
|
8387
8709
|
"Auto permission mode is active. Tool approvals will be handled automatically while this mode remains enabled.",
|
|
@@ -8402,149 +8724,13 @@ var PermissionModeInjector = class extends DynamicInjector {
|
|
|
8402
8724
|
}
|
|
8403
8725
|
};
|
|
8404
8726
|
//#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
8727
|
//#region src/agent/injection/manager.ts
|
|
8542
8728
|
var InjectionManager = class {
|
|
8543
8729
|
agent;
|
|
8544
8730
|
injectors;
|
|
8545
8731
|
constructor(agent) {
|
|
8546
8732
|
this.agent = agent;
|
|
8547
|
-
this.injectors = [new
|
|
8733
|
+
this.injectors = [new PermissionModeInjector(agent), new DirectoryTreeInjector(agent)];
|
|
8548
8734
|
}
|
|
8549
8735
|
async inject() {
|
|
8550
8736
|
for (const injector of this.injectors) await injector.inject();
|
|
@@ -8573,8 +8759,6 @@ const BUILTIN_TOOL_DEFAULT_PERMISSIONS = {
|
|
|
8573
8759
|
FetchURL: "auto_allow",
|
|
8574
8760
|
Agent: "auto_allow",
|
|
8575
8761
|
AskUserQuestion: "auto_allow",
|
|
8576
|
-
EnterPlanMode: "auto_allow",
|
|
8577
|
-
ExitPlanMode: "auto_allow",
|
|
8578
8762
|
Skill: "auto_allow",
|
|
8579
8763
|
Bash: "ask",
|
|
8580
8764
|
Write: "ask",
|
|
@@ -8607,7 +8791,6 @@ const TOOL_NAME_TO_ACTION = {
|
|
|
8607
8791
|
/** Inverse table — action label → the representative tool-name pattern. */
|
|
8608
8792
|
const ACTION_TO_PATTERN = {
|
|
8609
8793
|
"run command": "Bash",
|
|
8610
|
-
"run command in plan mode": null,
|
|
8611
8794
|
"run background command": "BackgroundRun",
|
|
8612
8795
|
"stop background task": "BackgroundStop",
|
|
8613
8796
|
"edit file": "Write",
|
|
@@ -10852,7 +11035,7 @@ function createDefaultGitCwdWritePolicy() {
|
|
|
10852
11035
|
if (pathClass !== "posix") return void 0;
|
|
10853
11036
|
const cwd = agent.config.cwd;
|
|
10854
11037
|
if (cwd.length === 0) return void 0;
|
|
10855
|
-
const path = readStringField$
|
|
11038
|
+
const path = readStringField$1(toolCallContext.args, "path");
|
|
10856
11039
|
if (path === void 0) return void 0;
|
|
10857
11040
|
let access;
|
|
10858
11041
|
try {
|
|
@@ -10884,7 +11067,7 @@ function createDefaultGitCwdWritePolicy() {
|
|
|
10884
11067
|
}
|
|
10885
11068
|
};
|
|
10886
11069
|
}
|
|
10887
|
-
function readStringField$
|
|
11070
|
+
function readStringField$1(args, key) {
|
|
10888
11071
|
if (args === null || typeof args !== "object") return void 0;
|
|
10889
11072
|
const value = args[key];
|
|
10890
11073
|
return typeof value === "string" ? value : void 0;
|
|
@@ -10910,236 +11093,12 @@ async function hasSymlinkInPath(kaos, cwd, targetPath) {
|
|
|
10910
11093
|
return false;
|
|
10911
11094
|
}
|
|
10912
11095
|
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;
|
|
11096
|
+
if (error === null || typeof error !== "object") return false;
|
|
11097
|
+
if (error.name === "KaosFileNotFoundError") return true;
|
|
11098
|
+
const code = error.code;
|
|
11099
|
+
if (code === "ENOENT" || code === "ENOTDIR" || code === 2) return true;
|
|
11100
|
+
const message = error instanceof Error ? error.message : "";
|
|
11101
|
+
return message.includes("ENOENT") || message.includes("ENOTDIR");
|
|
11143
11102
|
}
|
|
11144
11103
|
//#endregion
|
|
11145
11104
|
//#region src/agent/permission/policies/yolo-workspace-access.ts
|
|
@@ -11198,7 +11157,6 @@ function readStringField(args, key) {
|
|
|
11198
11157
|
//#region src/agent/permission/policies/index.ts
|
|
11199
11158
|
function createBuiltinPermissionPolicies() {
|
|
11200
11159
|
return [
|
|
11201
|
-
...createPlanPermissionPolicies(),
|
|
11202
11160
|
YoloOutsideWorkspacePermissionPolicy,
|
|
11203
11161
|
createDefaultGitCwdWritePolicy(),
|
|
11204
11162
|
AskUserQuestionAutoPermissionPolicy
|
|
@@ -11274,16 +11232,20 @@ var PermissionManager = class {
|
|
|
11274
11232
|
block: true,
|
|
11275
11233
|
reason: this.formatMessage(name, matchedRule?.reason)
|
|
11276
11234
|
};
|
|
11277
|
-
const policyResult = await this.evaluatePolicies(context, matchedRule);
|
|
11278
|
-
if (policyResult !== void 0) return this.permissionPolicyResultToPrepare(policyResult, context);
|
|
11279
11235
|
if (mode === "auto") {
|
|
11236
|
+
const policyResult = await this.evaluatePolicies(context, matchedRule);
|
|
11237
|
+
if (policyResult !== void 0) return this.permissionPolicyResultToPrepare(policyResult, context);
|
|
11280
11238
|
if (this.wouldAskInManualMode(name, args)) this.trackToolApproved(name, "afk");
|
|
11281
11239
|
return;
|
|
11282
11240
|
}
|
|
11283
11241
|
if (mode === "yolo") {
|
|
11242
|
+
const policyResult = await this.evaluatePolicies(context, matchedRule);
|
|
11243
|
+
if (policyResult !== void 0) return this.permissionPolicyResultToPrepare(policyResult, context);
|
|
11284
11244
|
if (this.wouldAskInManualMode(name, args)) this.trackToolApproved(name, "yolo");
|
|
11285
11245
|
return;
|
|
11286
11246
|
}
|
|
11247
|
+
const policyResult = await this.evaluatePolicies(context, matchedRule);
|
|
11248
|
+
if (policyResult !== void 0) return this.permissionPolicyResultToPrepare(policyResult, context);
|
|
11287
11249
|
if (decision === "allow") {
|
|
11288
11250
|
if (matchedRule?.scope === "session-runtime") this.trackToolApproved(name, "auto_session", "session");
|
|
11289
11251
|
return;
|
|
@@ -11402,409 +11364,19 @@ var PermissionManager = class {
|
|
|
11402
11364
|
if (scope !== void 0) properties["scope"] = scope;
|
|
11403
11365
|
this.agent.telemetry.track("tool_approved", properties);
|
|
11404
11366
|
}
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11409
|
-
|
|
11410
|
-
|
|
11411
|
-
|
|
11412
|
-
|
|
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;
|
|
11367
|
+
restoreRecord(record) {
|
|
11368
|
+
switch (record.type) {
|
|
11369
|
+
case "permission.set_mode":
|
|
11370
|
+
this.setMode(record.mode);
|
|
11371
|
+
break;
|
|
11372
|
+
case "permission.record_approval_result":
|
|
11373
|
+
this.recordApprovalResult(record);
|
|
11374
|
+
break;
|
|
11799
11375
|
}
|
|
11800
11376
|
}
|
|
11801
|
-
trackPlanLifecycle(stage) {
|
|
11802
|
-
this.agent.telemetry?.track?.("plan_file_lifecycle", { stage });
|
|
11803
|
-
}
|
|
11804
11377
|
};
|
|
11805
|
-
function
|
|
11806
|
-
|
|
11807
|
-
return error.code === "ENOENT";
|
|
11378
|
+
function approvalTelemetryMode(mode) {
|
|
11379
|
+
return mode === "auto" ? "afk" : mode;
|
|
11808
11380
|
}
|
|
11809
11381
|
//#endregion
|
|
11810
11382
|
//#region src/agent/records/persistence.ts
|
|
@@ -11923,83 +11495,12 @@ function parseRecordLine(line, lineNumber, filePath, allowTruncated) {
|
|
|
11923
11495
|
}
|
|
11924
11496
|
//#endregion
|
|
11925
11497
|
//#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
11498
|
var AgentRecords = class {
|
|
11999
11499
|
agent;
|
|
12000
11500
|
persistence;
|
|
12001
11501
|
_restoring = false;
|
|
12002
11502
|
metadataInitialized = false;
|
|
11503
|
+
handlers = {};
|
|
12003
11504
|
constructor(agent, persistence) {
|
|
12004
11505
|
this.agent = agent;
|
|
12005
11506
|
this.persistence = persistence;
|
|
@@ -12007,6 +11508,9 @@ var AgentRecords = class {
|
|
|
12007
11508
|
get restoring() {
|
|
12008
11509
|
return this._restoring;
|
|
12009
11510
|
}
|
|
11511
|
+
registerHandlers(handlers) {
|
|
11512
|
+
this.handlers = { ...handlers };
|
|
11513
|
+
}
|
|
12010
11514
|
logRecord(record) {
|
|
12011
11515
|
if (this._restoring) return;
|
|
12012
11516
|
const stamped = record.time !== void 0 ? record : {
|
|
@@ -12027,11 +11531,30 @@ var AgentRecords = class {
|
|
|
12027
11531
|
restore(record) {
|
|
12028
11532
|
this._restoring = true;
|
|
12029
11533
|
try {
|
|
12030
|
-
|
|
11534
|
+
this.routeToHandler(record);
|
|
12031
11535
|
} finally {
|
|
12032
11536
|
this._restoring = false;
|
|
12033
11537
|
}
|
|
12034
11538
|
}
|
|
11539
|
+
routeToHandler(record) {
|
|
11540
|
+
const handlerKey = this.getHandlerKey(record.type);
|
|
11541
|
+
if (handlerKey === null || this.handlers[handlerKey] === void 0) return;
|
|
11542
|
+
this.handlers[handlerKey].restoreRecord(record);
|
|
11543
|
+
}
|
|
11544
|
+
getHandlerKey(recordType) {
|
|
11545
|
+
if (recordType === "metadata") return null;
|
|
11546
|
+
return {
|
|
11547
|
+
context: "context",
|
|
11548
|
+
config: "config",
|
|
11549
|
+
turn: "turn",
|
|
11550
|
+
permission: "permission",
|
|
11551
|
+
tools: "tools",
|
|
11552
|
+
usage: "usage",
|
|
11553
|
+
background: "background",
|
|
11554
|
+
full_compaction: "fullCompaction",
|
|
11555
|
+
plan_mode: "planMode"
|
|
11556
|
+
}[recordType.split(".")[0]] ?? null;
|
|
11557
|
+
}
|
|
12035
11558
|
async replay() {
|
|
12036
11559
|
if (!this.persistence) throw new Error("No persistence provided for AgentRecords");
|
|
12037
11560
|
let migrations = [];
|
|
@@ -12397,7 +11920,7 @@ async function prepareToolCall(step, call) {
|
|
|
12397
11920
|
const decision = await runPrepareToolExecutionHook(step, call);
|
|
12398
11921
|
if (decision.kind === "blocked") {
|
|
12399
11922
|
await dispatchToolCall(step, call, decision.args);
|
|
12400
|
-
return { task: makeResolvedToolCallTask(makeErrorToolResult(call, decision.args, decision.output)) };
|
|
11923
|
+
return { task: makeResolvedToolCallTask(makeErrorToolResult(call, decision.args, decision.output, decision.blockedReason)) };
|
|
12401
11924
|
}
|
|
12402
11925
|
if (decision.kind === "hookFailed") {
|
|
12403
11926
|
await dispatchToolCall(step, call, decision.args);
|
|
@@ -12433,10 +11956,13 @@ async function prepareToolCall(step, call) {
|
|
|
12433
11956
|
}
|
|
12434
11957
|
await dispatchToolCall(step, call, effectiveArgs, toolCallDisplayFieldsFromExecution(execution));
|
|
12435
11958
|
if (step.signal.aborted) return { task: makeResolvedToolCallTask(makeErrorToolResult(call, effectiveArgs, `Tool "${call.toolName}" was aborted`)) };
|
|
12436
|
-
if (execution.isError === true)
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
11959
|
+
if (execution.isError === true) {
|
|
11960
|
+
const coerced = coerceToolResult(execution, call.toolName);
|
|
11961
|
+
return {
|
|
11962
|
+
task: makeResolvedToolCallTask(makeToolResult(call, effectiveArgs, coerced)),
|
|
11963
|
+
stopBatchAfterThis: toolResultStopsTurn(coerced)
|
|
11964
|
+
};
|
|
11965
|
+
}
|
|
12440
11966
|
return { task: {
|
|
12441
11967
|
accesses: execution.accesses ?? ToolAccesses.all(),
|
|
12442
11968
|
start: async () => ({ result: runRunnableToolCall(step, call, effectiveArgs, decision.metadata, execution) })
|
|
@@ -12491,7 +12017,8 @@ async function runPrepareToolExecutionHook(step, call) {
|
|
|
12491
12017
|
if (hookResult?.block === true) return {
|
|
12492
12018
|
kind: "blocked",
|
|
12493
12019
|
args: effectiveArgs,
|
|
12494
|
-
output: hookResult.reason ?? `Tool call "${call.toolName}" was blocked
|
|
12020
|
+
output: hookResult.reason ?? `Tool call "${call.toolName}" was blocked`,
|
|
12021
|
+
blockedReason: hookResult.blockedReason
|
|
12495
12022
|
};
|
|
12496
12023
|
if (hookResult?.syntheticResult !== void 0) return {
|
|
12497
12024
|
kind: "synthetic",
|
|
@@ -12653,10 +12180,15 @@ function normalizeToolResult(r) {
|
|
|
12653
12180
|
const textJoined = r.output.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
12654
12181
|
output = textJoined.length > 0 ? textJoined : TOOL_OUTPUT_EMPTY;
|
|
12655
12182
|
}
|
|
12656
|
-
|
|
12183
|
+
if (r.isError === true) return r.blockedReason !== void 0 ? {
|
|
12184
|
+
output,
|
|
12185
|
+
isError: true,
|
|
12186
|
+
blockedReason: r.blockedReason
|
|
12187
|
+
} : {
|
|
12657
12188
|
output,
|
|
12658
12189
|
isError: true
|
|
12659
|
-
}
|
|
12190
|
+
};
|
|
12191
|
+
return { output };
|
|
12660
12192
|
}
|
|
12661
12193
|
function makeToolResult(call, args, result) {
|
|
12662
12194
|
return {
|
|
@@ -12670,10 +12202,11 @@ function makeToolResult(call, args, result) {
|
|
|
12670
12202
|
function toolResultStopsTurn(result) {
|
|
12671
12203
|
return result.isError === true && result.stopTurn === true;
|
|
12672
12204
|
}
|
|
12673
|
-
function makeErrorToolResult(call, args, output) {
|
|
12205
|
+
function makeErrorToolResult(call, args, output, blockedReason) {
|
|
12674
12206
|
return makeToolResult(call, args, {
|
|
12675
12207
|
output,
|
|
12676
|
-
isError: true
|
|
12208
|
+
isError: true,
|
|
12209
|
+
blockedReason
|
|
12677
12210
|
});
|
|
12678
12211
|
}
|
|
12679
12212
|
/**
|
|
@@ -12772,7 +12305,9 @@ async function executeLoopStep(deps) {
|
|
|
12772
12305
|
step: currentStep,
|
|
12773
12306
|
usage,
|
|
12774
12307
|
finishReason: effectiveStopReason,
|
|
12775
|
-
...stepEndProviderDiagnostics(response, effectiveStopReason)
|
|
12308
|
+
...stepEndProviderDiagnostics(response, effectiveStopReason),
|
|
12309
|
+
...response.llmFirstTokenLatencyMs !== void 0 ? { llmFirstTokenLatencyMs: response.llmFirstTokenLatencyMs } : {},
|
|
12310
|
+
...response.llmStreamDurationMs !== void 0 ? { llmStreamDurationMs: response.llmStreamDurationMs } : {}
|
|
12776
12311
|
});
|
|
12777
12312
|
if (hooks?.afterStep !== void 0) try {
|
|
12778
12313
|
await hooks.afterStep({
|
|
@@ -14038,8 +13573,6 @@ var ToolManager = class {
|
|
|
14038
13573
|
new GlobTool(kaos, workspace),
|
|
14039
13574
|
new BashTool(kaos, cwd, osEnv, background, { allowBackground }),
|
|
14040
13575
|
(modelCapabilities.image_in || modelCapabilities.video_in) && new ReadMediaFileTool(kaos, workspace, modelCapabilities, videoUploader),
|
|
14041
|
-
new EnterPlanModeTool(this.agent),
|
|
14042
|
-
new ExitPlanModeTool(this.agent),
|
|
14043
13576
|
new AskUserQuestionTool(this.agent),
|
|
14044
13577
|
new TodoListTool(this.toolStore),
|
|
14045
13578
|
new TaskListTool(background),
|
|
@@ -14062,11 +13595,87 @@ var ToolManager = class {
|
|
|
14062
13595
|
return (input) => withProviderRequestAuth(resolveAuth, (auth) => uploadVideo(input, { auth }));
|
|
14063
13596
|
}
|
|
14064
13597
|
get loopTools() {
|
|
13598
|
+
const builtinNames = [...this.builtinTools.keys()].filter((name) => this.enabledTools.has(name)).sort();
|
|
13599
|
+
const userNames = [...this.userTools.keys()].filter((name) => this.enabledTools.has(name)).sort();
|
|
14065
13600
|
const mcpNames = [...this.mcpTools.keys()].filter((name) => this.isMcpToolEnabled(name));
|
|
14066
|
-
return
|
|
13601
|
+
return [
|
|
13602
|
+
...builtinNames.map((name) => this.builtinTools.get(name)),
|
|
13603
|
+
...userNames.map((name) => this.userTools.get(name)),
|
|
13604
|
+
...mcpNames.map((name) => this.mcpTools.get(name)?.tool)
|
|
13605
|
+
].filter((tool) => !!tool);
|
|
13606
|
+
}
|
|
13607
|
+
restoreRecord(record) {
|
|
13608
|
+
switch (record.type) {
|
|
13609
|
+
case "tools.register_user_tool":
|
|
13610
|
+
this.registerUserTool(record);
|
|
13611
|
+
break;
|
|
13612
|
+
case "tools.unregister_user_tool":
|
|
13613
|
+
this.unregisterUserTool(record.name);
|
|
13614
|
+
break;
|
|
13615
|
+
case "tools.set_active_tools":
|
|
13616
|
+
this.setActiveTools(record.names);
|
|
13617
|
+
break;
|
|
13618
|
+
case "tools.update_store":
|
|
13619
|
+
this.updateStore(record.key, record.value);
|
|
13620
|
+
break;
|
|
13621
|
+
}
|
|
14067
13622
|
}
|
|
14068
13623
|
};
|
|
14069
13624
|
//#endregion
|
|
13625
|
+
//#region src/agent/cache-staking/index.ts
|
|
13626
|
+
const DEFAULT_SIZE_THRESHOLD = 2e3;
|
|
13627
|
+
/**
|
|
13628
|
+
* Apply cache staking hints to a message array based on turn boundaries.
|
|
13629
|
+
*
|
|
13630
|
+
* - **Stake 3**: Tags the last assistant message of the previous turn with
|
|
13631
|
+
* `cacheHint.isLastTurnEnd = true`.
|
|
13632
|
+
* - **Stake 4** (conditional): Tags the largest content block in the current
|
|
13633
|
+
* turn that exceeds `sizeThreshold` with `cacheHint.isSuddenLargeContext = true`.
|
|
13634
|
+
*
|
|
13635
|
+
* Returns a new array with shallow-copied messages (original messages are
|
|
13636
|
+
* not mutated).
|
|
13637
|
+
*/
|
|
13638
|
+
function applyCacheStaking(messages, context) {
|
|
13639
|
+
const { previousTurnMessageCount, sizeThreshold = DEFAULT_SIZE_THRESHOLD } = context;
|
|
13640
|
+
if (previousTurnMessageCount <= 0 || messages.length === 0) return messages;
|
|
13641
|
+
const result = messages.map((msg) => ({ ...msg }));
|
|
13642
|
+
const lastTurnIndex = previousTurnMessageCount - 1;
|
|
13643
|
+
if (lastTurnIndex < result.length) {
|
|
13644
|
+
const lastTurnMsg = result[lastTurnIndex];
|
|
13645
|
+
if (lastTurnMsg.role === "assistant") {
|
|
13646
|
+
const existingHint = lastTurnMsg.cacheHint ?? {};
|
|
13647
|
+
result[lastTurnIndex] = {
|
|
13648
|
+
...lastTurnMsg,
|
|
13649
|
+
cacheHint: {
|
|
13650
|
+
...existingHint,
|
|
13651
|
+
isLastTurnEnd: true
|
|
13652
|
+
}
|
|
13653
|
+
};
|
|
13654
|
+
}
|
|
13655
|
+
}
|
|
13656
|
+
let largestIndex = -1;
|
|
13657
|
+
let largestSize = 0;
|
|
13658
|
+
for (let i = previousTurnMessageCount; i < result.length; i++) {
|
|
13659
|
+
const contentLength = result[i].content.filter((p) => p.type === "text").reduce((sum, p) => sum + p.text.length, 0);
|
|
13660
|
+
if (contentLength >= sizeThreshold && contentLength > largestSize) {
|
|
13661
|
+
largestSize = contentLength;
|
|
13662
|
+
largestIndex = i;
|
|
13663
|
+
}
|
|
13664
|
+
}
|
|
13665
|
+
if (largestIndex >= 0) {
|
|
13666
|
+
const target = result[largestIndex];
|
|
13667
|
+
const existingHint = target.cacheHint ?? {};
|
|
13668
|
+
result[largestIndex] = {
|
|
13669
|
+
...target,
|
|
13670
|
+
cacheHint: {
|
|
13671
|
+
...existingHint,
|
|
13672
|
+
isSuddenLargeContext: true
|
|
13673
|
+
}
|
|
13674
|
+
};
|
|
13675
|
+
}
|
|
13676
|
+
return result;
|
|
13677
|
+
}
|
|
13678
|
+
//#endregion
|
|
14070
13679
|
//#region src/agent/turn/canonical-args.ts
|
|
14071
13680
|
/**
|
|
14072
13681
|
* JSON canonicalization used by tool-call telemetry and dedup.
|
|
@@ -14088,6 +13697,231 @@ function isPlainRecord(value) {
|
|
|
14088
13697
|
return proto === Object.prototype || proto === null;
|
|
14089
13698
|
}
|
|
14090
13699
|
//#endregion
|
|
13700
|
+
//#region src/prompt-plan/builder.ts
|
|
13701
|
+
/**
|
|
13702
|
+
* Cache boundary marker used to split the system prompt into cacheable blocks.
|
|
13703
|
+
*
|
|
13704
|
+
* @deprecated This marker is deprecated in favor of implicit boundaries based on section headers.
|
|
13705
|
+
* It is still supported for backward compatibility.
|
|
13706
|
+
*/
|
|
13707
|
+
const CACHE_BOUNDARY_MARKER = "__CACHE_BOUNDARY__";
|
|
13708
|
+
/**
|
|
13709
|
+
* Section headers that define implicit cache boundaries.
|
|
13710
|
+
*
|
|
13711
|
+
* These headers mark natural breaks in the system prompt where cache boundaries should be placed:
|
|
13712
|
+
* - "# Project Information" marks the start of project-specific content
|
|
13713
|
+
* - "# Skills" marks the start of session-specific skills listing
|
|
13714
|
+
*/
|
|
13715
|
+
const IMPLICIT_BOUNDARY_HEADERS = ["# Project Information", "# Skills"];
|
|
13716
|
+
/**
|
|
13717
|
+
* Block names by position.
|
|
13718
|
+
*
|
|
13719
|
+
* - First block (before first marker): 'base'
|
|
13720
|
+
* - Last block (after last marker): 'sessionContext'
|
|
13721
|
+
* - Intermediate blocks: Sequential names from 'projectInstructions', 'skillsListing', etc.
|
|
13722
|
+
*/
|
|
13723
|
+
const BLOCK_NAMES = [
|
|
13724
|
+
"base",
|
|
13725
|
+
"projectInstructions",
|
|
13726
|
+
"skillsListing",
|
|
13727
|
+
"sessionContext"
|
|
13728
|
+
];
|
|
13729
|
+
/**
|
|
13730
|
+
* Get the cache scope for a block by its position.
|
|
13731
|
+
*
|
|
13732
|
+
* @param position - The block position (0-indexed)
|
|
13733
|
+
* @param totalBlocks - Total number of blocks
|
|
13734
|
+
* @returns The default cache scope for this position
|
|
13735
|
+
*/
|
|
13736
|
+
function getDefaultScopeForPosition(position, totalBlocks) {
|
|
13737
|
+
if (position === 0) return "global";
|
|
13738
|
+
if (position === totalBlocks - 1) return "session";
|
|
13739
|
+
if (position === 1) return "project";
|
|
13740
|
+
return "session";
|
|
13741
|
+
}
|
|
13742
|
+
/**
|
|
13743
|
+
* Filter cache scope based on provider's supported scopes.
|
|
13744
|
+
*
|
|
13745
|
+
* @param scope - The desired cache scope
|
|
13746
|
+
* @param capability - The provider's cache capability
|
|
13747
|
+
* @returns The filtered scope (or 'none' if not supported)
|
|
13748
|
+
*/
|
|
13749
|
+
function filterScopeByCapability(scope, capability) {
|
|
13750
|
+
if (capability.strategy === "none") return "none";
|
|
13751
|
+
if (capability.supportedScopes === void 0) return scope;
|
|
13752
|
+
if (capability.supportedScopes.includes(scope)) return scope;
|
|
13753
|
+
return "none";
|
|
13754
|
+
}
|
|
13755
|
+
/**
|
|
13756
|
+
* Find implicit boundary positions in the system prompt.
|
|
13757
|
+
*
|
|
13758
|
+
* Searches for section headers that mark natural cache boundaries.
|
|
13759
|
+
* Returns sorted indices of where each boundary header starts.
|
|
13760
|
+
*
|
|
13761
|
+
* @param prompt - The system prompt to search
|
|
13762
|
+
* @returns Array of character positions where implicit boundaries occur
|
|
13763
|
+
*/
|
|
13764
|
+
function findImplicitBoundaries(prompt) {
|
|
13765
|
+
const boundaries = [];
|
|
13766
|
+
for (const header of IMPLICIT_BOUNDARY_HEADERS) {
|
|
13767
|
+
const index = prompt.indexOf(header);
|
|
13768
|
+
if (index !== -1) boundaries.push(index);
|
|
13769
|
+
}
|
|
13770
|
+
return boundaries.sort((a, b) => a - b);
|
|
13771
|
+
}
|
|
13772
|
+
/**
|
|
13773
|
+
* Split prompt by implicit boundaries into blocks.
|
|
13774
|
+
*
|
|
13775
|
+
* Creates blocks based on the position of section headers that mark natural boundaries.
|
|
13776
|
+
* The block before the first header is the base block.
|
|
13777
|
+
* Blocks between headers are intermediate blocks.
|
|
13778
|
+
* The block after the last header is the session context block.
|
|
13779
|
+
*
|
|
13780
|
+
* @param prompt - The system prompt to split
|
|
13781
|
+
* @param boundaryPositions - Sorted array of boundary positions
|
|
13782
|
+
* @returns Array of text blocks
|
|
13783
|
+
*/
|
|
13784
|
+
function splitByImplicitBoundaries(prompt, boundaryPositions) {
|
|
13785
|
+
if (boundaryPositions.length === 0) return [prompt];
|
|
13786
|
+
const blocks = [];
|
|
13787
|
+
let previousPosition = 0;
|
|
13788
|
+
for (const position of boundaryPositions) {
|
|
13789
|
+
blocks.push(prompt.slice(previousPosition, position));
|
|
13790
|
+
previousPosition = position;
|
|
13791
|
+
}
|
|
13792
|
+
blocks.push(prompt.slice(previousPosition));
|
|
13793
|
+
return blocks;
|
|
13794
|
+
}
|
|
13795
|
+
/**
|
|
13796
|
+
* Detect if a prompt contains implicit cache boundaries.
|
|
13797
|
+
*
|
|
13798
|
+
* A prompt has implicit boundaries if it contains any of the known boundary headers.
|
|
13799
|
+
*
|
|
13800
|
+
* @param prompt - The system prompt to check
|
|
13801
|
+
* @returns true if implicit boundaries are detected
|
|
13802
|
+
*/
|
|
13803
|
+
function hasImplicitBoundaries(prompt) {
|
|
13804
|
+
return IMPLICIT_BOUNDARY_HEADERS.some((header) => prompt.includes(header));
|
|
13805
|
+
}
|
|
13806
|
+
/**
|
|
13807
|
+
* Build a prompt plan from a rendered system prompt and provider cache capability.
|
|
13808
|
+
*
|
|
13809
|
+
* This function parses cache boundary markers from the system prompt and creates
|
|
13810
|
+
* a structured plan with named blocks, each with an appropriate cache scope.
|
|
13811
|
+
*
|
|
13812
|
+
* @param renderedSystemPrompt - The fully rendered system prompt (may contain `__CACHE_BOUNDARY__` markers)
|
|
13813
|
+
* @param providerCacheCapability - The provider's cache capability (for scope filtering)
|
|
13814
|
+
* @returns A prompt plan with cacheable blocks
|
|
13815
|
+
*
|
|
13816
|
+
* @example
|
|
13817
|
+
* ```ts
|
|
13818
|
+
* const prompt = `Base instructions
|
|
13819
|
+
* __CACHE_BOUNDARY__
|
|
13820
|
+
* Session context`;
|
|
13821
|
+
*
|
|
13822
|
+
* const capability = {
|
|
13823
|
+
* strategy: 'explicit-block',
|
|
13824
|
+
* supportedScopes: ['global', 'session'],
|
|
13825
|
+
* };
|
|
13826
|
+
*
|
|
13827
|
+
* const plan = buildPromptPlan(prompt, capability);
|
|
13828
|
+
* // {
|
|
13829
|
+
* // blocks: [
|
|
13830
|
+
* // { name: 'base', text: 'Base instructions\n', cacheScope: 'global' },
|
|
13831
|
+
* // { name: 'sessionContext', text: 'Session context', cacheScope: 'session' },
|
|
13832
|
+
* // ],
|
|
13833
|
+
* // }
|
|
13834
|
+
* ```
|
|
13835
|
+
*/
|
|
13836
|
+
/**
|
|
13837
|
+
* Normalize block text by handling newlines around cache boundaries.
|
|
13838
|
+
*
|
|
13839
|
+
* After splitting by `__CACHE_BOUNDARY__`, the pattern is typically:
|
|
13840
|
+
* - `[text]\n__CACHE_BOUNDARY__\n[next text]`
|
|
13841
|
+
*
|
|
13842
|
+
* Rules:
|
|
13843
|
+
* - First block: Keep as-is (preserves any trailing newline before marker)
|
|
13844
|
+
* - Middle blocks: Keep the newline that was between markers
|
|
13845
|
+
* - Last block: Trim leading newline (the one immediately after the last marker)
|
|
13846
|
+
*
|
|
13847
|
+
* @param text - The raw block text after splitting
|
|
13848
|
+
* @param index - The block index
|
|
13849
|
+
* @param totalBlocks - Total number of blocks
|
|
13850
|
+
* @returns Normalized block text
|
|
13851
|
+
*/
|
|
13852
|
+
function normalizeBlockText(text, index, totalBlocks) {
|
|
13853
|
+
if (index === 0) return text;
|
|
13854
|
+
if (index === totalBlocks - 1) {
|
|
13855
|
+
if (text.startsWith("\n")) return text.slice(1);
|
|
13856
|
+
return text;
|
|
13857
|
+
}
|
|
13858
|
+
return text;
|
|
13859
|
+
}
|
|
13860
|
+
function buildPromptPlan(renderedSystemPrompt, providerCacheCapability) {
|
|
13861
|
+
const parts = renderedSystemPrompt.split(CACHE_BOUNDARY_MARKER);
|
|
13862
|
+
if (parts.length > 1) return createPlanFromParts(parts, providerCacheCapability);
|
|
13863
|
+
if (hasImplicitBoundaries(renderedSystemPrompt)) return createPlanFromParts(splitByImplicitBoundaries(renderedSystemPrompt, findImplicitBoundaries(renderedSystemPrompt)), providerCacheCapability);
|
|
13864
|
+
return { blocks: [{
|
|
13865
|
+
name: "base",
|
|
13866
|
+
text: renderedSystemPrompt,
|
|
13867
|
+
cacheScope: "none"
|
|
13868
|
+
}] };
|
|
13869
|
+
}
|
|
13870
|
+
/**
|
|
13871
|
+
* Create a prompt plan from pre-split parts.
|
|
13872
|
+
*
|
|
13873
|
+
* This is shared logic for both explicit and implicit boundaries.
|
|
13874
|
+
*
|
|
13875
|
+
* @param parts - The pre-split text parts
|
|
13876
|
+
* @param providerCacheCapability - The provider's cache capability
|
|
13877
|
+
* @returns A prompt plan with cacheable blocks
|
|
13878
|
+
*/
|
|
13879
|
+
function createPlanFromParts(parts, providerCacheCapability) {
|
|
13880
|
+
const blocks = parts.map((part, index) => {
|
|
13881
|
+
const totalBlocks = parts.length;
|
|
13882
|
+
const filteredScope = filterScopeByCapability(getDefaultScopeForPosition(index, totalBlocks), providerCacheCapability);
|
|
13883
|
+
return {
|
|
13884
|
+
name: getBlockNameForPart(part, index, totalBlocks),
|
|
13885
|
+
text: normalizeBlockText(part, index, totalBlocks),
|
|
13886
|
+
cacheScope: filteredScope
|
|
13887
|
+
};
|
|
13888
|
+
});
|
|
13889
|
+
const maxCacheable = providerCacheCapability.maxCacheableBlocks;
|
|
13890
|
+
if (maxCacheable !== void 0 && maxCacheable > 0) {
|
|
13891
|
+
let cacheableCount = 0;
|
|
13892
|
+
for (const block of blocks) if (block.cacheScope !== "none") {
|
|
13893
|
+
cacheableCount++;
|
|
13894
|
+
if (cacheableCount > maxCacheable) block.cacheScope = "none";
|
|
13895
|
+
}
|
|
13896
|
+
}
|
|
13897
|
+
return { blocks };
|
|
13898
|
+
}
|
|
13899
|
+
/**
|
|
13900
|
+
* Get the block name for a given part, considering its content.
|
|
13901
|
+
*
|
|
13902
|
+
* This is a smarter version of getBlockName that looks at the content
|
|
13903
|
+
* to determine the appropriate name for the last block.
|
|
13904
|
+
*
|
|
13905
|
+
* @param part - The text content of this block
|
|
13906
|
+
* @param position - The block position (0-indexed)
|
|
13907
|
+
* @param totalBlocks - Total number of blocks
|
|
13908
|
+
* @returns The block name
|
|
13909
|
+
*/
|
|
13910
|
+
function getBlockNameForPart(part, position, totalBlocks) {
|
|
13911
|
+
if (position === 0) return "base";
|
|
13912
|
+
if (position === totalBlocks - 1) {
|
|
13913
|
+
if (part.includes("# Skills")) return "sessionContext";
|
|
13914
|
+
if (part.includes("# Project Information")) return "projectInstructions";
|
|
13915
|
+
return "sessionContext";
|
|
13916
|
+
}
|
|
13917
|
+
const nameIndex = position - 1 + 1;
|
|
13918
|
+
if (nameIndex < BLOCK_NAMES.length && nameIndex < BLOCK_NAMES.length - 1) {
|
|
13919
|
+
const name = BLOCK_NAMES[nameIndex];
|
|
13920
|
+
if (name !== void 0) return name;
|
|
13921
|
+
}
|
|
13922
|
+
return "sessionContext";
|
|
13923
|
+
}
|
|
13924
|
+
//#endregion
|
|
14091
13925
|
//#region src/agent/turn/kosong-llm.ts
|
|
14092
13926
|
/**
|
|
14093
13927
|
* Kosong-backed implementation of the loop `LLM` interface.
|
|
@@ -14134,7 +13968,7 @@ var KosongLLM = class {
|
|
|
14134
13968
|
systemPrompt: this.systemPrompt,
|
|
14135
13969
|
tools: params.tools
|
|
14136
13970
|
});
|
|
14137
|
-
const result = await this.generate(effectiveProvider, this.systemPrompt, [...params.tools], [...params.messages], callbacks, generateOptions(params));
|
|
13971
|
+
const result = await this.generate(effectiveProvider, this.systemPrompt, [...params.tools], [...params.messages], callbacks, generateOptions(params, this.systemPrompt, effectiveProvider));
|
|
14138
13972
|
if (params.onTextPart !== void 0 || params.onThinkPart !== void 0) {
|
|
14139
13973
|
for (const part of result.message.content) if (part.type === "text" && params.onTextPart !== void 0) await params.onTextPart(part);
|
|
14140
13974
|
else if (part.type === "think" && params.onThinkPart !== void 0) await params.onThinkPart(part);
|
|
@@ -14143,7 +13977,9 @@ var KosongLLM = class {
|
|
|
14143
13977
|
toolCalls: [...result.message.toolCalls],
|
|
14144
13978
|
...result.finishReason !== null ? { providerFinishReason: result.finishReason } : {},
|
|
14145
13979
|
...result.rawFinishReason !== null ? { rawFinishReason: result.rawFinishReason } : {},
|
|
14146
|
-
usage: result.usage ?? emptyUsage()
|
|
13980
|
+
usage: result.usage ?? emptyUsage(),
|
|
13981
|
+
...result.llmFirstTokenLatencyMs !== void 0 ? { llmFirstTokenLatencyMs: result.llmFirstTokenLatencyMs } : {},
|
|
13982
|
+
...result.llmStreamDurationMs !== void 0 ? { llmStreamDurationMs: result.llmStreamDurationMs } : {}
|
|
14147
13983
|
};
|
|
14148
13984
|
}
|
|
14149
13985
|
isRetryableError(error) {
|
|
@@ -14158,14 +13994,30 @@ var KosongLLM = class {
|
|
|
14158
13994
|
].includes(error.statusCode);
|
|
14159
13995
|
}
|
|
14160
13996
|
};
|
|
14161
|
-
function generateOptions(params) {
|
|
14162
|
-
const
|
|
13997
|
+
function generateOptions(params, systemPrompt, provider) {
|
|
13998
|
+
const promptPlan = buildPromptPlan(systemPrompt, getProviderCacheCapability(provider));
|
|
13999
|
+
const options = {
|
|
14000
|
+
signal: params.signal,
|
|
14001
|
+
promptPlan
|
|
14002
|
+
};
|
|
14163
14003
|
if (params.requestLogContext !== void 0) return {
|
|
14164
14004
|
...options,
|
|
14165
14005
|
[GENERATE_REQUEST_LOG_CONTEXT]: params.requestLogContext
|
|
14166
14006
|
};
|
|
14167
14007
|
return options;
|
|
14168
14008
|
}
|
|
14009
|
+
/**
|
|
14010
|
+
* Get the cache capability from a provider.
|
|
14011
|
+
*
|
|
14012
|
+
* Safely handles providers that don't implement getCapability or don't have cache.
|
|
14013
|
+
* Returns a default capability with 'none' strategy for non-caching providers.
|
|
14014
|
+
*/
|
|
14015
|
+
function getProviderCacheCapability(provider) {
|
|
14016
|
+
if (typeof provider.getCapability !== "function") return { strategy: "none" };
|
|
14017
|
+
const capability = provider.getCapability();
|
|
14018
|
+
if (capability?.cache !== void 0) return capability.cache;
|
|
14019
|
+
return { strategy: "none" };
|
|
14020
|
+
}
|
|
14169
14021
|
function buildKosongCallbacks(params) {
|
|
14170
14022
|
const toolCallIdentities = /* @__PURE__ */ new Map();
|
|
14171
14023
|
const pendingIndexedToolCallDeltas = /* @__PURE__ */ new Map();
|
|
@@ -14412,6 +14264,7 @@ var TurnFlow = class {
|
|
|
14412
14264
|
agent;
|
|
14413
14265
|
steerBuffer = [];
|
|
14414
14266
|
turnId = -1;
|
|
14267
|
+
_previousTurnMessageCount = 0;
|
|
14415
14268
|
activeTurn = null;
|
|
14416
14269
|
toolCallStartedAt = /* @__PURE__ */ new Map();
|
|
14417
14270
|
toolCallDupType = /* @__PURE__ */ new Map();
|
|
@@ -14456,6 +14309,7 @@ var TurnFlow = class {
|
|
|
14456
14309
|
return null;
|
|
14457
14310
|
}
|
|
14458
14311
|
this.turnId += 1;
|
|
14312
|
+
this._previousTurnMessageCount = this.agent.context.messages.length;
|
|
14459
14313
|
this.currentStep = 0;
|
|
14460
14314
|
this.stepToolCallKeys.clear();
|
|
14461
14315
|
this.toolCallDupType.clear();
|
|
@@ -14692,7 +14546,10 @@ var TurnFlow = class {
|
|
|
14692
14546
|
generate: this.agent.generate,
|
|
14693
14547
|
completionBudgetConfig
|
|
14694
14548
|
}),
|
|
14695
|
-
buildMessages: () =>
|
|
14549
|
+
buildMessages: () => {
|
|
14550
|
+
const messages = this.agent.context.messages;
|
|
14551
|
+
return applyCacheStaking(messages, { previousTurnMessageCount: this._previousTurnMessageCount });
|
|
14552
|
+
},
|
|
14696
14553
|
dispatchEvent: this.buildDispatchEvent(turnId),
|
|
14697
14554
|
tools: this.agent.tools.loopTools,
|
|
14698
14555
|
log: this.agent.log,
|
|
@@ -14798,7 +14655,7 @@ var TurnFlow = class {
|
|
|
14798
14655
|
buildDispatchEvent(turnId) {
|
|
14799
14656
|
return createLoopEventDispatcher({
|
|
14800
14657
|
appendTranscriptRecord: async (event) => {
|
|
14801
|
-
this.agent.context.appendLoopEvent(event);
|
|
14658
|
+
await this.agent.context.appendLoopEvent(event);
|
|
14802
14659
|
},
|
|
14803
14660
|
emitLiveEvent: (event) => {
|
|
14804
14661
|
this.trackLoopTelemetry(event, turnId);
|
|
@@ -14884,12 +14741,25 @@ var TurnFlow = class {
|
|
|
14884
14741
|
});
|
|
14885
14742
|
}
|
|
14886
14743
|
telemetryMode() {
|
|
14887
|
-
return
|
|
14744
|
+
return "agent";
|
|
14888
14745
|
}
|
|
14889
14746
|
shouldTrackApiError(turnId) {
|
|
14890
14747
|
const failure = this.stepFailureByTurn.get(turnId);
|
|
14891
14748
|
return failure?.reason === "error" && failure.activeStep !== void 0;
|
|
14892
14749
|
}
|
|
14750
|
+
restoreRecord(record) {
|
|
14751
|
+
switch (record.type) {
|
|
14752
|
+
case "turn.prompt":
|
|
14753
|
+
if (this.activeTurn === "resuming") this.activeTurn = null;
|
|
14754
|
+
this.restorePrompt();
|
|
14755
|
+
break;
|
|
14756
|
+
case "turn.steer":
|
|
14757
|
+
if (this.activeTurn === "resuming") this.activeTurn = null;
|
|
14758
|
+
this.restoreSteer(record.input, record.origin);
|
|
14759
|
+
break;
|
|
14760
|
+
case "turn.cancel": break;
|
|
14761
|
+
}
|
|
14762
|
+
}
|
|
14893
14763
|
};
|
|
14894
14764
|
function mapLoopEvent(event, turnId) {
|
|
14895
14765
|
switch (event.type) {
|
|
@@ -14907,7 +14777,9 @@ function mapLoopEvent(event, turnId) {
|
|
|
14907
14777
|
usage: event.usage,
|
|
14908
14778
|
finishReason: event.finishReason,
|
|
14909
14779
|
providerFinishReason: event.providerFinishReason,
|
|
14910
|
-
rawFinishReason: event.rawFinishReason
|
|
14780
|
+
rawFinishReason: event.rawFinishReason,
|
|
14781
|
+
...event.llmFirstTokenLatencyMs !== void 0 ? { llmFirstTokenLatencyMs: event.llmFirstTokenLatencyMs } : {},
|
|
14782
|
+
...event.llmStreamDurationMs !== void 0 ? { llmStreamDurationMs: event.llmStreamDurationMs } : {}
|
|
14911
14783
|
};
|
|
14912
14784
|
case "step.retrying": return {
|
|
14913
14785
|
type: "turn.step.retrying",
|
|
@@ -14932,13 +14804,17 @@ function mapLoopEvent(event, turnId) {
|
|
|
14932
14804
|
description: event.description,
|
|
14933
14805
|
display: event.display
|
|
14934
14806
|
};
|
|
14935
|
-
case "tool.result":
|
|
14936
|
-
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
|
|
14940
|
-
|
|
14941
|
-
|
|
14807
|
+
case "tool.result": {
|
|
14808
|
+
const blockedReason = event.result.isError === true ? event.result.blockedReason : void 0;
|
|
14809
|
+
return {
|
|
14810
|
+
type: "tool.result",
|
|
14811
|
+
turnId,
|
|
14812
|
+
toolCallId: event.toolCallId,
|
|
14813
|
+
output: event.result.output,
|
|
14814
|
+
isError: event.result.isError,
|
|
14815
|
+
blockedReason
|
|
14816
|
+
};
|
|
14817
|
+
}
|
|
14942
14818
|
case "turn.interrupted":
|
|
14943
14819
|
if (event.activeStep === void 0) return void 0;
|
|
14944
14820
|
return {
|
|
@@ -15120,10 +14996,12 @@ var UsageRecorder = class {
|
|
|
15120
14996
|
const byModel = this.byModelSnapshot();
|
|
15121
14997
|
const hasByModel = Object.keys(byModel).length > 0;
|
|
15122
14998
|
const currentTurn = this.currentTurn;
|
|
14999
|
+
const total = hasByModel ? totalUsage(byModel) : void 0;
|
|
15123
15000
|
return {
|
|
15124
15001
|
byModel: hasByModel ? byModel : void 0,
|
|
15125
|
-
total
|
|
15126
|
-
currentTurn: currentTurn === void 0 ? void 0 : copyUsage(currentTurn)
|
|
15002
|
+
total,
|
|
15003
|
+
currentTurn: currentTurn === void 0 ? void 0 : copyUsage(currentTurn),
|
|
15004
|
+
cacheHitRate: total !== void 0 ? cacheHitRate(total) : void 0
|
|
15127
15005
|
};
|
|
15128
15006
|
}
|
|
15129
15007
|
status() {
|
|
@@ -15134,6 +15012,13 @@ var UsageRecorder = class {
|
|
|
15134
15012
|
byModelSnapshot() {
|
|
15135
15013
|
return Object.fromEntries(Object.entries(this.byModel).map(([model, usage]) => [model, copyUsage(usage)]));
|
|
15136
15014
|
}
|
|
15015
|
+
restoreRecord(record) {
|
|
15016
|
+
switch (record.type) {
|
|
15017
|
+
case "usage.record":
|
|
15018
|
+
this.record(record.model, record.usage, "session");
|
|
15019
|
+
break;
|
|
15020
|
+
}
|
|
15021
|
+
}
|
|
15137
15022
|
};
|
|
15138
15023
|
function totalUsage(byModel) {
|
|
15139
15024
|
let total;
|
|
@@ -15161,7 +15046,6 @@ var Agent = class {
|
|
|
15161
15046
|
turn;
|
|
15162
15047
|
injection;
|
|
15163
15048
|
permission;
|
|
15164
|
-
planMode;
|
|
15165
15049
|
usage;
|
|
15166
15050
|
tools;
|
|
15167
15051
|
background;
|
|
@@ -15185,12 +15069,11 @@ var Agent = class {
|
|
|
15185
15069
|
this.emitRecordsWriteError(error);
|
|
15186
15070
|
} }) : void 0));
|
|
15187
15071
|
this.fullCompaction = new FullCompaction(this, config.compactionStrategy);
|
|
15188
|
-
this.context = new ContextMemory(this);
|
|
15072
|
+
this.context = new ContextMemory(this, config.sessionId);
|
|
15189
15073
|
this.config = new ConfigState(this);
|
|
15190
15074
|
this.turn = new TurnFlow(this);
|
|
15191
15075
|
this.injection = new InjectionManager(this);
|
|
15192
15076
|
this.permission = new PermissionManager(this, config.permission);
|
|
15193
|
-
this.planMode = new PlanMode(this);
|
|
15194
15077
|
this.usage = new UsageRecorder(this);
|
|
15195
15078
|
this.tools = new ToolManager(this);
|
|
15196
15079
|
this.background = new BackgroundManager(this, {
|
|
@@ -15198,6 +15081,15 @@ var Agent = class {
|
|
|
15198
15081
|
sessionDir: config.backgroundSessionDir
|
|
15199
15082
|
});
|
|
15200
15083
|
this.replayBuilder = new ReplayBuilder(this);
|
|
15084
|
+
this.records.registerHandlers({
|
|
15085
|
+
context: this.context,
|
|
15086
|
+
config: this.config,
|
|
15087
|
+
usage: this.usage,
|
|
15088
|
+
turn: this.turn,
|
|
15089
|
+
permission: this.permission,
|
|
15090
|
+
tools: this.tools,
|
|
15091
|
+
fullCompaction: this.fullCompaction
|
|
15092
|
+
});
|
|
15201
15093
|
}
|
|
15202
15094
|
get generate() {
|
|
15203
15095
|
return async (provider, systemPrompt, tools, history, callbacks, options) => {
|
|
@@ -15218,7 +15110,7 @@ var Agent = class {
|
|
|
15218
15110
|
}
|
|
15219
15111
|
logLlmRequest(provider, systemPrompt, tools, history, options) {
|
|
15220
15112
|
const context = buildLlmRequestContext(options);
|
|
15221
|
-
const configMetadata = buildLlmConfigMetadata(provider, this.config.modelAlias, systemPrompt, tools);
|
|
15113
|
+
const configMetadata = buildLlmConfigMetadata(provider, this.config.modelAlias, systemPrompt, tools, options);
|
|
15222
15114
|
this.logLlmConfigIfChanged(context, configMetadata, buildLlmConfigSignature(configMetadata, systemPrompt, tools));
|
|
15223
15115
|
this.log.info("llm request", {
|
|
15224
15116
|
...context,
|
|
@@ -15239,7 +15131,6 @@ var Agent = class {
|
|
|
15239
15131
|
osEnv: this.runtime.osEnv,
|
|
15240
15132
|
cwd,
|
|
15241
15133
|
skills: this.skills?.registry,
|
|
15242
|
-
cwdListing: context?.cwdListing,
|
|
15243
15134
|
agentsMd: context?.agentsMd
|
|
15244
15135
|
});
|
|
15245
15136
|
this.config.update({
|
|
@@ -15249,11 +15140,15 @@ var Agent = class {
|
|
|
15249
15140
|
this.tools.setActiveTools(profile.tools);
|
|
15250
15141
|
}
|
|
15251
15142
|
async resume() {
|
|
15252
|
-
|
|
15253
|
-
|
|
15254
|
-
|
|
15255
|
-
|
|
15256
|
-
|
|
15143
|
+
try {
|
|
15144
|
+
const result = await this.records.replay();
|
|
15145
|
+
await this.background.loadFromDisk();
|
|
15146
|
+
await this.background.reconcile();
|
|
15147
|
+
this.turn.finishResume();
|
|
15148
|
+
return result;
|
|
15149
|
+
} catch (error) {
|
|
15150
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
15151
|
+
}
|
|
15257
15152
|
}
|
|
15258
15153
|
get rpcMethods() {
|
|
15259
15154
|
return {
|
|
@@ -15298,12 +15193,14 @@ var Agent = class {
|
|
|
15298
15193
|
return this.config.modelAlias ?? "";
|
|
15299
15194
|
},
|
|
15300
15195
|
enterPlan: async () => {
|
|
15301
|
-
|
|
15196
|
+
throw new ByfError(ErrorCodes.NOT_IMPLEMENTED, "Plan mode has been removed");
|
|
15302
15197
|
},
|
|
15303
|
-
cancelPlan: (
|
|
15304
|
-
|
|
15198
|
+
cancelPlan: async () => {
|
|
15199
|
+
throw new ByfError(ErrorCodes.NOT_IMPLEMENTED, "Plan mode has been removed");
|
|
15200
|
+
},
|
|
15201
|
+
clearPlan: async () => {
|
|
15202
|
+
throw new ByfError(ErrorCodes.NOT_IMPLEMENTED, "Plan mode has been removed");
|
|
15305
15203
|
},
|
|
15306
|
-
clearPlan: () => this.planMode.clear(),
|
|
15307
15204
|
beginCompaction: (payload) => {
|
|
15308
15205
|
this.fullCompaction.begin({
|
|
15309
15206
|
source: "manual",
|
|
@@ -15338,7 +15235,7 @@ var Agent = class {
|
|
|
15338
15235
|
getContext: () => this.context.data(),
|
|
15339
15236
|
getConfig: () => this.config.data(),
|
|
15340
15237
|
getPermission: () => this.permission.data(),
|
|
15341
|
-
getPlan: () =>
|
|
15238
|
+
getPlan: async () => null,
|
|
15342
15239
|
getUsage: () => this.usage.data(),
|
|
15343
15240
|
getTools: () => this.tools.data(),
|
|
15344
15241
|
getBackground: (payload) => this.background.list(payload.activeOnly ?? false, payload.limit)
|
|
@@ -15362,7 +15259,6 @@ var Agent = class {
|
|
|
15362
15259
|
contextTokens,
|
|
15363
15260
|
maxContextTokens,
|
|
15364
15261
|
contextUsage,
|
|
15365
|
-
planMode: this.planMode.isActive,
|
|
15366
15262
|
permission: this.permission.mode,
|
|
15367
15263
|
usage
|
|
15368
15264
|
});
|
|
@@ -15408,8 +15304,8 @@ function buildLlmRequestMetadata(systemPrompt, tools, history) {
|
|
|
15408
15304
|
if (partialMessageCount > 0) metadata.partialMessageCount = partialMessageCount;
|
|
15409
15305
|
return metadata;
|
|
15410
15306
|
}
|
|
15411
|
-
function buildLlmConfigMetadata(provider, modelAlias, systemPrompt, tools) {
|
|
15412
|
-
|
|
15307
|
+
function buildLlmConfigMetadata(provider, modelAlias, systemPrompt, tools, options) {
|
|
15308
|
+
const metadata = {
|
|
15413
15309
|
provider: provider.name,
|
|
15414
15310
|
model: provider.modelName,
|
|
15415
15311
|
modelAlias,
|
|
@@ -15417,6 +15313,30 @@ function buildLlmConfigMetadata(provider, modelAlias, systemPrompt, tools) {
|
|
|
15417
15313
|
systemPromptChars: systemPrompt.length,
|
|
15418
15314
|
toolCount: tools.length
|
|
15419
15315
|
};
|
|
15316
|
+
const providerCacheStrategy = getProviderCacheStrategy(provider);
|
|
15317
|
+
if (providerCacheStrategy !== void 0) metadata.providerCacheStrategy = providerCacheStrategy;
|
|
15318
|
+
const promptPlan = options?.promptPlan;
|
|
15319
|
+
if (promptPlan !== void 0 && promptPlan.blocks.length > 0) metadata.cacheBlockHashes = extractCacheBlockHashes(promptPlan);
|
|
15320
|
+
return metadata;
|
|
15321
|
+
}
|
|
15322
|
+
/**
|
|
15323
|
+
* Get the cache strategy from a provider's capability.
|
|
15324
|
+
*
|
|
15325
|
+
* Safely handles providers that don't implement getCapability or don't have cache.
|
|
15326
|
+
*/
|
|
15327
|
+
function getProviderCacheStrategy(provider) {
|
|
15328
|
+
if (typeof provider.getCapability !== "function") return;
|
|
15329
|
+
return provider.getCapability()?.cache?.strategy;
|
|
15330
|
+
}
|
|
15331
|
+
/**
|
|
15332
|
+
* Extract cache block hashes from a PromptPlan.
|
|
15333
|
+
*
|
|
15334
|
+
* Returns a Record mapping block names to their SHA256 hashes.
|
|
15335
|
+
*/
|
|
15336
|
+
function extractCacheBlockHashes(promptPlan) {
|
|
15337
|
+
const hashes = {};
|
|
15338
|
+
for (const block of promptPlan.blocks) hashes[block.name] = fingerprint(block.text);
|
|
15339
|
+
return hashes;
|
|
15420
15340
|
}
|
|
15421
15341
|
function buildLlmConfigSignature(metadata, systemPrompt, tools) {
|
|
15422
15342
|
const toolsForSignature = tools.map(({ name, description, parameters }) => ({
|
|
@@ -15452,8 +15372,7 @@ function proxyWithExtraPayload(methods, extraPayload) {
|
|
|
15452
15372
|
//#region src/config/schema.ts
|
|
15453
15373
|
const ProviderTypeSchema = z.enum([
|
|
15454
15374
|
"anthropic",
|
|
15455
|
-
"openai",
|
|
15456
|
-
"openai-compat",
|
|
15375
|
+
"openai-completions",
|
|
15457
15376
|
"google-genai",
|
|
15458
15377
|
"openai_responses",
|
|
15459
15378
|
"vertexai"
|
|
@@ -15463,6 +15382,7 @@ const OAuthRefSchema = z.object({
|
|
|
15463
15382
|
key: z.string().min(1)
|
|
15464
15383
|
});
|
|
15465
15384
|
const StringRecordSchema = z.record(z.string(), z.string());
|
|
15385
|
+
const ExtraBodySchema = z.record(z.string(), z.unknown());
|
|
15466
15386
|
const ProviderConfigSchema = z.object({
|
|
15467
15387
|
type: ProviderTypeSchema,
|
|
15468
15388
|
apiKey: z.string().optional(),
|
|
@@ -15471,7 +15391,8 @@ const ProviderConfigSchema = z.object({
|
|
|
15471
15391
|
thinkingEffortKey: z.string().optional(),
|
|
15472
15392
|
oauth: OAuthRefSchema.optional(),
|
|
15473
15393
|
env: StringRecordSchema.optional(),
|
|
15474
|
-
customHeaders: StringRecordSchema.optional()
|
|
15394
|
+
customHeaders: StringRecordSchema.optional(),
|
|
15395
|
+
extraBody: ExtraBodySchema.optional()
|
|
15475
15396
|
});
|
|
15476
15397
|
const ModelAliasSchema = z.object({
|
|
15477
15398
|
provider: z.string(),
|
|
@@ -15488,7 +15409,13 @@ const ThinkingConfigSchema = z.object({
|
|
|
15488
15409
|
"on",
|
|
15489
15410
|
"off"
|
|
15490
15411
|
]).optional(),
|
|
15491
|
-
effort: z.
|
|
15412
|
+
effort: z.enum([
|
|
15413
|
+
"low",
|
|
15414
|
+
"medium",
|
|
15415
|
+
"high",
|
|
15416
|
+
"xhigh",
|
|
15417
|
+
"max"
|
|
15418
|
+
]).optional()
|
|
15492
15419
|
});
|
|
15493
15420
|
const PermissionModeSchema = z.enum([
|
|
15494
15421
|
"yolo",
|
|
@@ -15587,11 +15514,9 @@ const ByfConfigSchema = z.object({
|
|
|
15587
15514
|
defaultModel: z.string().optional(),
|
|
15588
15515
|
models: z.record(z.string(), ModelAliasSchema).optional(),
|
|
15589
15516
|
thinking: ThinkingConfigSchema.optional(),
|
|
15590
|
-
planMode: z.boolean().optional(),
|
|
15591
15517
|
yolo: z.boolean().optional(),
|
|
15592
15518
|
defaultThinking: z.boolean().optional(),
|
|
15593
15519
|
defaultPermissionMode: PermissionModeSchema.optional(),
|
|
15594
|
-
defaultPlanMode: z.boolean().optional(),
|
|
15595
15520
|
permission: PermissionConfigSchema.optional(),
|
|
15596
15521
|
hooks: z.array(HookDefSchema).optional(),
|
|
15597
15522
|
services: ServicesConfigSchema.optional(),
|
|
@@ -15599,7 +15524,6 @@ const ByfConfigSchema = z.object({
|
|
|
15599
15524
|
extraSkillDirs: z.array(z.string()).optional(),
|
|
15600
15525
|
loopControl: LoopControlSchema.optional(),
|
|
15601
15526
|
background: BackgroundConfigSchema.optional(),
|
|
15602
|
-
telemetry: z.boolean().optional(),
|
|
15603
15527
|
raw: z.record(z.string(), z.unknown()).optional()
|
|
15604
15528
|
});
|
|
15605
15529
|
const ProviderConfigPatchSchema = ProviderConfigSchema.partial();
|
|
@@ -15619,19 +15543,16 @@ const ByfConfigPatchSchema = z.object({
|
|
|
15619
15543
|
defaultModel: z.string().optional(),
|
|
15620
15544
|
models: z.record(z.string(), ModelAliasPatchSchema).optional(),
|
|
15621
15545
|
thinking: ThinkingConfigPatchSchema.optional(),
|
|
15622
|
-
planMode: z.boolean().optional(),
|
|
15623
15546
|
yolo: z.boolean().optional(),
|
|
15624
15547
|
defaultThinking: z.boolean().optional(),
|
|
15625
15548
|
defaultPermissionMode: PermissionModeSchema.optional(),
|
|
15626
|
-
defaultPlanMode: z.boolean().optional(),
|
|
15627
15549
|
permission: PermissionConfigPatchSchema.optional(),
|
|
15628
15550
|
hooks: z.array(HookDefSchema).optional(),
|
|
15629
15551
|
services: ServicesConfigPatchSchema.optional(),
|
|
15630
15552
|
mergeAllAvailableSkills: z.boolean().optional(),
|
|
15631
15553
|
extraSkillDirs: z.array(z.string()).optional(),
|
|
15632
15554
|
loopControl: LoopControlPatchSchema.optional(),
|
|
15633
|
-
background: BackgroundConfigPatchSchema.optional()
|
|
15634
|
-
telemetry: z.boolean().optional()
|
|
15555
|
+
background: BackgroundConfigPatchSchema.optional()
|
|
15635
15556
|
}).strict();
|
|
15636
15557
|
function getDefaultConfig() {
|
|
15637
15558
|
return { providers: {} };
|
|
@@ -15888,14 +15809,11 @@ function configToTomlData(config) {
|
|
|
15888
15809
|
for (const key of [
|
|
15889
15810
|
"defaultProvider",
|
|
15890
15811
|
"defaultModel",
|
|
15891
|
-
"planMode",
|
|
15892
15812
|
"yolo",
|
|
15893
15813
|
"defaultThinking",
|
|
15894
15814
|
"defaultPermissionMode",
|
|
15895
|
-
"defaultPlanMode",
|
|
15896
15815
|
"mergeAllAvailableSkills",
|
|
15897
|
-
"extraSkillDirs"
|
|
15898
|
-
"telemetry"
|
|
15816
|
+
"extraSkillDirs"
|
|
15899
15817
|
]) setDefined(out, camelToSnake(key), config[key]);
|
|
15900
15818
|
setRecordSection(out, "providers", config.providers, providerToToml);
|
|
15901
15819
|
setRecordSection(out, "models", config.models, modelToToml);
|
|
@@ -16905,6 +16823,11 @@ var summary_continuation_default = "Your previous response was too brief. Please
|
|
|
16905
16823
|
* agent receives a technically complete handoff.
|
|
16906
16824
|
*/
|
|
16907
16825
|
const SUMMARY_MIN_LENGTH = 200;
|
|
16826
|
+
/**
|
|
16827
|
+
* Soft upper bound (characters) communicated to the subagent via prompt so
|
|
16828
|
+
* it self-regulates summary length. Not enforced at runtime.
|
|
16829
|
+
*/
|
|
16830
|
+
const SUMMARY_MAX_LENGTH = 8e3;
|
|
16908
16831
|
const SUMMARY_CONTINUATION_ATTEMPTS = 1;
|
|
16909
16832
|
const HOOK_TEXT_PREVIEW_LENGTH = 500;
|
|
16910
16833
|
const SUBAGENT_MAX_TOKENS_ERROR = "Subagent turn failed before completing its final summary: reason=max_tokens";
|
|
@@ -17017,7 +16940,7 @@ var SessionSubagentHost = class {
|
|
|
17017
16940
|
options.signal.throwIfAborted();
|
|
17018
16941
|
await this.triggerSubagentStart(parent, profileName, options.prompt, options.signal);
|
|
17019
16942
|
options.signal.throwIfAborted();
|
|
17020
|
-
let childPrompt = options.prompt
|
|
16943
|
+
let childPrompt = `${`Summary length constraint: minimum ${SUMMARY_MIN_LENGTH} characters, maximum ${SUMMARY_MAX_LENGTH} characters.`}\n\n${options.prompt}`;
|
|
17021
16944
|
if (profileName === "explore") {
|
|
17022
16945
|
const gitContext = await collectGitContext(child.runtime.kaos, child.config.cwd);
|
|
17023
16946
|
if (gitContext) childPrompt = `${gitContext}\n\n${childPrompt}`;
|
|
@@ -17177,11 +17100,19 @@ var Session = class {
|
|
|
17177
17100
|
const { agents } = await this.readMetadata();
|
|
17178
17101
|
this.agents.clear();
|
|
17179
17102
|
let warning;
|
|
17103
|
+
const failedAgents = [];
|
|
17180
17104
|
const resumeTasks = Object.keys(agents).map(async (id) => {
|
|
17181
17105
|
const result = await this.ensureResumeAgentInstantiated(id, agents).resume();
|
|
17182
17106
|
if (result.warning !== void 0 && warning === void 0) warning = result.warning;
|
|
17107
|
+
if (result.error !== void 0) failedAgents.push({
|
|
17108
|
+
id,
|
|
17109
|
+
error: result.error
|
|
17110
|
+
});
|
|
17183
17111
|
});
|
|
17184
17112
|
await Promise.all(resumeTasks);
|
|
17113
|
+
const mainAgentFailure = failedAgents.find(({ id }) => id === "main");
|
|
17114
|
+
if (mainAgentFailure !== void 0) throw mainAgentFailure.error;
|
|
17115
|
+
for (const { id, error } of failedAgents) this.log.warn(`Sub agent "${id}" failed to resume: ${error.message}`);
|
|
17185
17116
|
const resumeWarning = warning;
|
|
17186
17117
|
const main = this.agents.get("main");
|
|
17187
17118
|
const profile = DEFAULT_AGENT_PROFILES["agent"];
|
|
@@ -19304,6 +19235,9 @@ function resolveModelCapabilities(alias, provider) {
|
|
|
19304
19235
|
audio_in: has("audio_in") || providerCapability.audio_in,
|
|
19305
19236
|
thinking: has("thinking") || has("always_thinking") || providerCapability.thinking,
|
|
19306
19237
|
tool_use: has("tool_use") || providerCapability.tool_use,
|
|
19238
|
+
thinking_effort: has("thinking_effort") || providerCapability.thinking_effort,
|
|
19239
|
+
thinking_xhigh: has("thinking_xhigh") || providerCapability.thinking_xhigh,
|
|
19240
|
+
thinking_max: has("thinking_max") || providerCapability.thinking_max,
|
|
19307
19241
|
max_context_tokens: alias.maxContextSize
|
|
19308
19242
|
};
|
|
19309
19243
|
}
|
|
@@ -19317,33 +19251,31 @@ function toKosongProviderConfig(provider, model, byfRequestHeaders, maxOutputSiz
|
|
|
19317
19251
|
...maxOutputSize !== void 0 ? { defaultMaxTokens: maxOutputSize } : {},
|
|
19318
19252
|
...defaultHeadersField(provider.customHeaders)
|
|
19319
19253
|
};
|
|
19320
|
-
case "openai":
|
|
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": {
|
|
19254
|
+
case "openai-completions": {
|
|
19329
19255
|
const defaultHeaders = {
|
|
19330
19256
|
...byfRequestHeaders,
|
|
19331
19257
|
...provider.customHeaders
|
|
19332
19258
|
};
|
|
19259
|
+
const generationKwargs = {
|
|
19260
|
+
prompt_cache_key: promptCacheKey,
|
|
19261
|
+
extra_body: provider.extraBody
|
|
19262
|
+
};
|
|
19333
19263
|
if (Object.keys(defaultHeaders).length === 0) return {
|
|
19334
|
-
type: "openai-
|
|
19264
|
+
type: "openai-completions",
|
|
19335
19265
|
model,
|
|
19336
19266
|
baseUrl: providerValue(provider.baseUrl, provider.env, "BYF_BASE_URL"),
|
|
19267
|
+
reasoningKey,
|
|
19337
19268
|
thinkingEffortKey: provider.thinkingEffortKey,
|
|
19338
|
-
generationKwargs
|
|
19269
|
+
generationKwargs,
|
|
19339
19270
|
apiKey: providerApiKey(provider)
|
|
19340
19271
|
};
|
|
19341
19272
|
return {
|
|
19342
|
-
type: "openai-
|
|
19273
|
+
type: "openai-completions",
|
|
19343
19274
|
model,
|
|
19344
19275
|
baseUrl: providerValue(provider.baseUrl, provider.env, "BYF_BASE_URL"),
|
|
19276
|
+
reasoningKey,
|
|
19345
19277
|
thinkingEffortKey: provider.thinkingEffortKey,
|
|
19346
|
-
generationKwargs
|
|
19278
|
+
generationKwargs,
|
|
19347
19279
|
defaultHeaders,
|
|
19348
19280
|
apiKey: providerApiKey(provider)
|
|
19349
19281
|
};
|
|
@@ -19395,9 +19327,8 @@ function providerForCapabilityProbe(provider) {
|
|
|
19395
19327
|
function providerApiKey(provider) {
|
|
19396
19328
|
switch (provider.type) {
|
|
19397
19329
|
case "anthropic": return providerValue(provider.apiKey, provider.env, "ANTHROPIC_API_KEY");
|
|
19398
|
-
case "openai":
|
|
19399
19330
|
case "openai_responses": return providerValue(provider.apiKey, provider.env, "OPENAI_API_KEY");
|
|
19400
|
-
case "openai-
|
|
19331
|
+
case "openai-completions": return providerValue(provider.apiKey, provider.env, "BYF_API_KEY");
|
|
19401
19332
|
case "google-genai": return providerValue(provider.apiKey, provider.env, "GOOGLE_API_KEY");
|
|
19402
19333
|
case "vertexai": return nonEmptyString$1(provider.apiKey) ?? envValue(provider.env, "VERTEXAI_API_KEY") ?? envValue(provider.env, "GOOGLE_API_KEY");
|
|
19403
19334
|
default: {
|
|
@@ -19527,7 +19458,7 @@ function resolveRuntimeThinkingRequest(requestedThinking, defaultThinking) {
|
|
|
19527
19458
|
}
|
|
19528
19459
|
//#endregion
|
|
19529
19460
|
//#region src/rpc/core-impl.ts
|
|
19530
|
-
const BYF_CODE_PROVIDER_NAME = "
|
|
19461
|
+
const BYF_CODE_PROVIDER_NAME = "byf";
|
|
19531
19462
|
var ByfCore = class {
|
|
19532
19463
|
rpcClient;
|
|
19533
19464
|
sdk;
|
|
@@ -19554,7 +19485,7 @@ var ByfCore = class {
|
|
|
19554
19485
|
this.byfRequestHeaders = options.byfRequestHeaders;
|
|
19555
19486
|
this.resolveOAuthTokenProvider = options.resolveOAuthTokenProvider;
|
|
19556
19487
|
this.skillDirs = options.skillDirs ?? [];
|
|
19557
|
-
this.telemetry =
|
|
19488
|
+
this.telemetry = noopTelemetryClient;
|
|
19558
19489
|
ensureByfHome(this.homeDir);
|
|
19559
19490
|
this.providerManager = new ProviderManager({
|
|
19560
19491
|
config: readConfigFile(this.configPath),
|
|
@@ -19597,7 +19528,7 @@ var ByfCore = class {
|
|
|
19597
19528
|
permissionRules: config.permission?.rules,
|
|
19598
19529
|
skills: this.resolveSessionSkillConfig(config),
|
|
19599
19530
|
mcpConfig,
|
|
19600
|
-
telemetry:
|
|
19531
|
+
telemetry: this.telemetry
|
|
19601
19532
|
});
|
|
19602
19533
|
try {
|
|
19603
19534
|
session.metadata = {
|
|
@@ -19617,7 +19548,6 @@ var ByfCore = class {
|
|
|
19617
19548
|
thinkingLevel
|
|
19618
19549
|
});
|
|
19619
19550
|
if (permissionMode !== void 0) mainAgent.permission.setMode(permissionMode);
|
|
19620
|
-
if (config.defaultPlanMode === true) await mainAgent.planMode.enter();
|
|
19621
19551
|
await session.writeMetadata();
|
|
19622
19552
|
await session.flushMetadata();
|
|
19623
19553
|
} catch (error) {
|
|
@@ -19659,7 +19589,7 @@ var ByfCore = class {
|
|
|
19659
19589
|
permissionRules: config.permission?.rules,
|
|
19660
19590
|
skills: this.resolveSessionSkillConfig(config),
|
|
19661
19591
|
mcpConfig,
|
|
19662
|
-
telemetry:
|
|
19592
|
+
telemetry: this.telemetry,
|
|
19663
19593
|
initializeMainAgent: false
|
|
19664
19594
|
});
|
|
19665
19595
|
let warning;
|
|
@@ -19668,7 +19598,6 @@ var ByfCore = class {
|
|
|
19668
19598
|
await this.refreshSessionRuntimeConfig(session, config);
|
|
19669
19599
|
} catch (error) {
|
|
19670
19600
|
await session.close().catch(() => {});
|
|
19671
|
-
withTelemetryContext(this.telemetry, { sessionId: summary.id }).track("session_load_failed", { reason: telemetryErrorReason(error) });
|
|
19672
19601
|
throw error;
|
|
19673
19602
|
}
|
|
19674
19603
|
this.sessions.set(summary.id, session);
|
|
@@ -19939,11 +19868,6 @@ function requiredWorkDir(operation, value) {
|
|
|
19939
19868
|
function createSessionId() {
|
|
19940
19869
|
return `session_${randomUUID()}`;
|
|
19941
19870
|
}
|
|
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
19871
|
async function resumeSessionResult(summary, session, warning) {
|
|
19948
19872
|
const api = new SessionAPIImpl(session);
|
|
19949
19873
|
const agents = {};
|
|
@@ -19951,7 +19875,6 @@ async function resumeSessionResult(summary, session, warning) {
|
|
|
19951
19875
|
const config = await api.getConfig({ agentId });
|
|
19952
19876
|
const context = await api.getContext({ agentId });
|
|
19953
19877
|
const permission = await api.getPermission({ agentId });
|
|
19954
|
-
const plan = await api.getPlan({ agentId });
|
|
19955
19878
|
const usage = await api.getUsage({ agentId });
|
|
19956
19879
|
agents[agentId] = {
|
|
19957
19880
|
type: agent.type,
|
|
@@ -19959,7 +19882,7 @@ async function resumeSessionResult(summary, session, warning) {
|
|
|
19959
19882
|
context,
|
|
19960
19883
|
replay: agent.replayBuilder.buildResult(),
|
|
19961
19884
|
permission,
|
|
19962
|
-
plan,
|
|
19885
|
+
plan: null,
|
|
19963
19886
|
usage,
|
|
19964
19887
|
tools: await api.getTools({ agentId }),
|
|
19965
19888
|
toolStore: agent.tools.storeData(),
|
|
@@ -20018,4 +19941,4 @@ function parsePositiveInt(value) {
|
|
|
20018
19941
|
return n;
|
|
20019
19942
|
}
|
|
20020
19943
|
//#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,
|
|
19944
|
+
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 };
|