@cjhyy/code-shell 0.1.0-alpha.3 → 0.1.6
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/CHANGELOG.md +107 -0
- package/dist/anthropic-IBTI4WL2.cjs +10 -0
- package/dist/anthropic-PRZZKHEZ.js +10 -0
- package/dist/arena/index.cjs +7 -6
- package/dist/arena/index.js +6 -5
- package/dist/arena/model-presets.d.ts +10 -1
- package/dist/{arena-ENHBHB2W.js → arena-2LTLIXOL.js} +9 -7
- package/dist/{arena-HHDYYVHJ.cjs → arena-ADB7DRFI.cjs} +25 -23
- package/dist/{chunk-EL2RL5DG.cjs → chunk-2YW4FPFG.cjs} +2 -2
- package/dist/chunk-3OA5G4TM.js +47 -0
- package/dist/{chunk-FPFTIKEJ.js → chunk-3QO25N6E.js} +131 -173
- package/dist/chunk-3SBSUOWZ.js +447 -0
- package/dist/chunk-52LGZKT3.cjs +47 -0
- package/dist/{chunk-KOGV6PDQ.cjs → chunk-55XGPMAP.cjs} +44 -9
- package/dist/{chunk-YD7XTI7T.cjs → chunk-6QVAYW7C.cjs} +116 -28
- package/dist/{chunk-SCDKLTN3.js → chunk-6S4BJ65N.js} +5 -4
- package/dist/{chunk-ZLPJJZUW.js → chunk-76HYEPDM.js} +2 -2
- package/dist/chunk-7BI3V74Y.cjs +447 -0
- package/dist/{chunk-GZBD6QRS.cjs → chunk-7EG3V4JY.cjs} +13 -12
- package/dist/{chunk-LMEKCQX4.js → chunk-7JVTYEEC.js} +6 -6
- package/dist/{chunk-SW3L7HIM.js → chunk-7QIM3BKD.js} +2 -2
- package/dist/{chunk-5C5HIOML.js → chunk-7Y6S3OCX.js} +109 -21
- package/dist/{chunk-REFCCVDM.js → chunk-ABRRONJ7.js} +2 -2
- package/dist/{chunk-F5BS3GUC.js → chunk-BSW22MPA.js} +39 -4
- package/dist/{chunk-ZRVPCXI7.js → chunk-BXCY2FSE.js} +2 -1
- package/dist/{chunk-Y7TSCDF6.js → chunk-BZ5TNE7L.js} +347 -6
- package/dist/{chunk-X2DNIMPC.cjs → chunk-DDB6QOKJ.cjs} +32 -32
- package/dist/{chunk-C7C7ZZY4.cjs → chunk-HEKQCI7B.cjs} +10 -10
- package/dist/{chunk-JHNCK7JP.cjs → chunk-IK4EWJBP.cjs} +4 -3
- package/dist/{chunk-3CAZIWUA.cjs → chunk-ILHE7XLT.cjs} +9 -9
- package/dist/{chunk-M2QI5X3X.cjs → chunk-JMR33QVF.cjs} +2 -2
- package/dist/{chunk-TDXBHD24.js → chunk-KAQHULYN.js} +2 -2
- package/dist/chunk-KE4EQOWA.js +294 -0
- package/dist/{chunk-YAFCN7ZW.cjs → chunk-KOVYSDHZ.cjs} +15 -15
- package/dist/{chunk-IJSHQGMP.cjs → chunk-KS3RYAYL.cjs} +4 -4
- package/dist/{chunk-DI7RDLOS.js → chunk-MD32R22I.js} +7 -4
- package/dist/chunk-MV7YI4WO.cjs +293 -0
- package/dist/chunk-MY5XHADU.cjs +216 -0
- package/dist/{chunk-OP57WUWQ.cjs → chunk-NUNC3VFB.cjs} +6 -6
- package/dist/{chunk-ID6EJUHP.js → chunk-PKGRTCCY.js} +1307 -212
- package/dist/{chunk-OUC2DFA7.js → chunk-QARL3IDQ.js} +3 -3
- package/dist/{chunk-VIPLU4RY.cjs → chunk-S754JHLR.cjs} +4 -4
- package/dist/{chunk-FUOKLKR2.js → chunk-SFXHZSRF.js} +1 -1
- package/dist/{chunk-2EUAE7AY.js → chunk-SIHHIQLO.js} +2 -2
- package/dist/{chunk-SK62UVS6.js → chunk-T6VYG46N.js} +1 -1
- package/dist/{chunk-JHXNOB5G.cjs → chunk-TNLGBJYS.cjs} +6 -6
- package/dist/{chunk-3QR52LL5.cjs → chunk-TUY2T5KP.cjs} +2 -2
- package/dist/{chunk-VWFET3MG.cjs → chunk-UXOCVJJ6.cjs} +1519 -424
- package/dist/{chunk-I57ZD5IQ.js → chunk-VIAGTYYW.js} +1 -1
- package/dist/chunk-WASS5YWB.js +215 -0
- package/dist/{chunk-HOXQLX5R.cjs → chunk-WZJV6DJC.cjs} +444 -103
- package/dist/{chunk-BUJ5KYCI.cjs → chunk-YIQBXAYU.cjs} +13 -13
- package/dist/{chunk-BF4LXLQV.cjs → chunk-YKIAELDZ.cjs} +132 -174
- package/dist/{chunk-M2SVOKC6.js → chunk-ZBPSQCPU.js} +6 -6
- package/dist/cli/commands/builtin/export-md.d.ts +19 -0
- package/dist/cli/commands/builtin/init/detect.d.ts +25 -0
- package/dist/cli/commands/builtin/init/index.d.ts +19 -0
- package/dist/cli/main.cjs +2372 -1453
- package/dist/cli/main.js +2502 -1583
- package/dist/cli/migrate-models.d.ts +45 -0
- package/dist/cli/onboarding.d.ts +110 -5
- package/dist/cli/updater.d.ts +59 -4
- package/dist/client-base-HTF236ZD.cjs +9 -0
- package/dist/client-base-QKRKCOWB.js +9 -0
- package/dist/{compaction-YNWCEF7B.js → compaction-6VU2T643.js} +4 -2
- package/dist/compaction-OKUNTCA7.cjs +33 -0
- package/dist/context/compaction.d.ts +35 -3
- package/dist/context/manager.d.ts +40 -3
- package/dist/context/tool-result-storage.d.ts +96 -0
- package/dist/devtools-T2NIZBJK.cjs +6 -0
- package/dist/{devtools-IRQQPIRY.js → devtools-YXWHOVKR.js} +1 -1
- package/dist/engine/engine.d.ts +47 -0
- package/dist/engine/turn-loop.d.ts +6 -0
- package/dist/export-md-RXDW6F5W.cjs +135 -0
- package/dist/export-md-XXKZANW5.js +135 -0
- package/dist/file-history-KYPE3ECT.cjs +7 -0
- package/dist/file-history-LF4F7X26.js +7 -0
- package/dist/index.cjs +42 -22
- package/dist/index.d.ts +5 -2
- package/dist/index.js +38 -18
- package/dist/llm/client-base.d.ts +1 -1
- package/dist/llm/model-cache.d.ts +21 -0
- package/dist/llm/model-fetcher.d.ts +29 -0
- package/dist/llm/model-pool.d.ts +13 -0
- package/dist/llm/provider-catalog.d.ts +30 -0
- package/dist/llm/provider-kinds.d.ts +21 -0
- package/dist/llm/types.d.ts +8 -0
- package/dist/logging/session-recorder.d.ts +77 -0
- package/dist/manager-D65VFAPX.js +7 -0
- package/dist/manager-TTF5VHJC.cjs +7 -0
- package/dist/mcp-manager-QMKWPBZ4.js +8 -0
- package/dist/mcp-manager-Z3OKI74O.cjs +8 -0
- package/dist/memory-5LWZ6FI2.cjs +9 -0
- package/dist/{memory-EPFDBPII.js → memory-VIJLBAMQ.js} +2 -2
- package/dist/model-cache-5KIYL5SL.js +13 -0
- package/dist/model-cache-OED3IM4R.cjs +13 -0
- package/dist/model-fetcher-HKGICACL.js +8 -0
- package/dist/model-fetcher-UR6WJOQP.cjs +8 -0
- package/dist/openai-JR2WEKOF.js +10 -0
- package/dist/openai-YBTOET4O.cjs +10 -0
- package/dist/{openrouter-models-2FM3H4SQ.js → openrouter-models-H56GHBHN.js} +2 -2
- package/dist/openrouter-models-TWUFWZ5T.cjs +15 -0
- package/dist/openrouter-sync-5VMGN6C2.cjs +11 -0
- package/dist/{openrouter-sync-SO3AWEAM.js → openrouter-sync-ALEMF5IU.js} +3 -3
- package/dist/product/index.cjs +17 -15
- package/dist/product/index.js +16 -14
- package/dist/protocol/server.d.ts +3 -1
- package/dist/protocol/types.d.ts +36 -2
- package/dist/run/index.cjs +17 -15
- package/dist/run/index.js +16 -14
- package/dist/services/index.d.ts +0 -1
- package/dist/session-manager-Q22BBMD3.js +8 -0
- package/dist/session-manager-XODQVL3C.cjs +8 -0
- package/dist/settings/schema.d.ts +267 -42
- package/dist/{tool-summary-RYNR4DDB.cjs → tool-summary-63DPIS7G.cjs} +4 -4
- package/dist/{tool-summary-2RUFL5QK.js → tool-summary-NP5CDBKG.js} +2 -2
- package/dist/tool-system/builtin/ask-user.d.ts +9 -1
- package/dist/tool-system/context.d.ts +31 -2
- package/dist/tool-system/executor.d.ts +4 -0
- package/dist/tool-system/investigation-guard.d.ts +46 -0
- package/dist/types.d.ts +13 -2
- package/dist/ui/components/AskUserPrompt.d.ts +8 -1
- package/dist/ui/components/DiffLine.d.ts +33 -0
- package/dist/ui/components/ModelManager.d.ts +34 -1
- package/dist/ui/components/OnboardingPrompt.d.ts +10 -1
- package/dist/ui/components/ProviderModelFlow.d.ts +58 -0
- package/dist/ui/components/UpdateBanner.d.ts +1 -0
- package/dist/updater-I7QBW4DP.cjs +17 -0
- package/dist/updater-NVME5S3Z.js +17 -0
- package/dist/utils/semver.d.ts +1 -0
- package/dist/utils/task-sanitizer.d.ts +6 -0
- package/package.json +2 -1
- package/skills-builtin/codeshell-help.md +135 -0
- package/dist/anthropic-P5VETAFI.cjs +0 -10
- package/dist/anthropic-W2GU3D76.js +0 -10
- package/dist/chunk-75E4BBFC.js +0 -224
- package/dist/chunk-FV2ARONZ.cjs +0 -224
- package/dist/client-base-TOVE25FA.js +0 -9
- package/dist/client-base-VDDIADAC.cjs +0 -9
- package/dist/compaction-D3KV2NWY.cjs +0 -31
- package/dist/devtools-WTC3NFNE.cjs +0 -6
- package/dist/file-history-HZWO2WQ5.js +0 -7
- package/dist/file-history-UQQILIY7.cjs +0 -7
- package/dist/manager-FWLOVEEK.cjs +0 -7
- package/dist/manager-GEXJLOIS.js +0 -7
- package/dist/mcp-manager-37GQLFGX.js +0 -8
- package/dist/mcp-manager-U77NEXBY.cjs +0 -8
- package/dist/memory-ADGOWADP.cjs +0 -9
- package/dist/openai-RXU5URYI.js +0 -10
- package/dist/openai-SQW4H2K4.cjs +0 -10
- package/dist/openrouter-models-PLCWZZYE.cjs +0 -15
- package/dist/openrouter-sync-K2WK3A2A.cjs +0 -11
- package/dist/services/compact.d.ts +0 -56
- package/dist/session-manager-HRSKENKB.cjs +0 -8
- package/dist/session-manager-NEKFEHF3.js +0 -8
- package/dist/updater-J7L7R6BY.cjs +0 -185
- package/dist/updater-LDBNYCAO.js +0 -185
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ModelPool
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-BSW22MPA.js";
|
|
4
|
+
import {
|
|
5
|
+
defaultCacheDir
|
|
6
|
+
} from "./chunk-3OA5G4TM.js";
|
|
4
7
|
import {
|
|
5
8
|
MCPManager
|
|
6
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-SIHHIQLO.js";
|
|
7
10
|
import {
|
|
8
11
|
applySummaryCompaction,
|
|
9
12
|
applyToolResultBudget,
|
|
@@ -15,48 +18,52 @@ import {
|
|
|
15
18
|
snipCompact,
|
|
16
19
|
truncateToolResult,
|
|
17
20
|
windowCompact
|
|
18
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-7Y6S3OCX.js";
|
|
19
22
|
import {
|
|
20
23
|
MemoryManager
|
|
21
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-VIAGTYYW.js";
|
|
22
25
|
import {
|
|
23
26
|
SessionManager
|
|
24
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-76HYEPDM.js";
|
|
25
28
|
import {
|
|
26
29
|
SettingsManager
|
|
27
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-3SBSUOWZ.js";
|
|
28
31
|
import {
|
|
29
32
|
FileHistory
|
|
30
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-T6VYG46N.js";
|
|
31
34
|
import {
|
|
32
35
|
webFetchTool,
|
|
33
36
|
webFetchToolDef,
|
|
34
37
|
webSearchTool,
|
|
35
38
|
webSearchToolDef
|
|
36
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-QARL3IDQ.js";
|
|
37
40
|
import {
|
|
38
41
|
Arena,
|
|
39
42
|
MODEL_PRESETS,
|
|
43
|
+
buildModelPool,
|
|
40
44
|
createLLMClient,
|
|
41
45
|
createProgressRenderer,
|
|
46
|
+
detectProviderFromApiKey,
|
|
42
47
|
formatArenaResultForSession,
|
|
43
48
|
getMaxOutputTokens
|
|
44
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-BZ5TNE7L.js";
|
|
45
50
|
import {
|
|
46
51
|
ConfigError,
|
|
47
52
|
ContextLimitError,
|
|
48
53
|
ToolExecutionError,
|
|
49
54
|
ToolNotFoundError,
|
|
50
55
|
ToolTimeoutError
|
|
51
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-SFXHZSRF.js";
|
|
52
57
|
import {
|
|
58
|
+
getCurrentSid,
|
|
53
59
|
logger
|
|
54
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-BXCY2FSE.js";
|
|
55
61
|
import {
|
|
62
|
+
__dirname,
|
|
56
63
|
__require,
|
|
57
64
|
__toCommonJS,
|
|
58
65
|
init_esm_shims
|
|
59
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-MD32R22I.js";
|
|
60
67
|
|
|
61
68
|
// src/tool-system/builtin/task.ts
|
|
62
69
|
init_esm_shims();
|
|
@@ -380,7 +387,7 @@ var fileCache = new FileStateCache();
|
|
|
380
387
|
// src/tool-system/builtin/read.ts
|
|
381
388
|
var readToolDef = {
|
|
382
389
|
name: "Read",
|
|
383
|
-
description: "Read a file from the local filesystem. Returns the file content with line numbers. By default reads up to 2000 lines from the beginning. Use offset and limit to read specific portions of large files. For large files, consider using Grep first to find the relevant lines.",
|
|
390
|
+
description: "Read a file from the local filesystem. Returns the file content with line numbers. By default reads up to 2000 lines from the beginning. Use offset and limit to read specific portions of large files. For large files, consider using Grep first to find the relevant lines.\n\nDo NOT re-read a file you just edited to verify \u2014 Edit/Write would have errored if the change failed.\nDo NOT re-read a file (or the same range of a file) you've already read earlier in this conversation. The content is already in your context. If you need a different part of the file, read a different offset; otherwise decide with what you have.",
|
|
384
391
|
inputSchema: {
|
|
385
392
|
type: "object",
|
|
386
393
|
properties: {
|
|
@@ -1364,11 +1371,40 @@ ${output}`;
|
|
|
1364
1371
|
init_esm_shims();
|
|
1365
1372
|
var askUserToolDef = {
|
|
1366
1373
|
name: "AskUserQuestion",
|
|
1367
|
-
description: "Ask the user a question and wait for their response. Use this when you need clarification, confirmation, or additional input from the user. In headless mode this tool will return an error.",
|
|
1374
|
+
description: "Ask the user a question and wait for their response. Use this when you need clarification, confirmation, or additional input from the user. Pass `options` to present a multiple-choice list (recommended when the answer is one of a small known set \u2014 much better UX than free text). Each option needs a short `label` (the choice the user sees) and a `description` (what it means). Lead with the recommended option and append a recommendation marker to its label in the same language as the question (e.g. '(Recommended)' for an English question, '(\u63A8\u8350)' for a Chinese question). Set `multiSelect: true` when more than one choice can apply. ALWAYS write the `question`, every option `label`, every option `description`, and the `header` in the SAME LANGUAGE the user has been writing in. If the user wrote to you in Chinese, write all of these fields in Chinese; if Japanese, in Japanese; etc. The framework's UI chrome (separator hints, the implicit 'Other...' entry) is fixed \u2014 only your fields need to match the user's language. In headless mode this tool will return an error.",
|
|
1368
1375
|
inputSchema: {
|
|
1369
1376
|
type: "object",
|
|
1370
1377
|
properties: {
|
|
1371
|
-
question: {
|
|
1378
|
+
question: {
|
|
1379
|
+
type: "string",
|
|
1380
|
+
description: "The question to ask the user \u2014 in the user's language"
|
|
1381
|
+
},
|
|
1382
|
+
header: {
|
|
1383
|
+
type: "string",
|
|
1384
|
+
description: "Optional short label (\u226412 chars) shown as a chip above the question (e.g. 'Language', 'Approach', '\u8BED\u8A00', '\u65B9\u6848'). Use the user's language."
|
|
1385
|
+
},
|
|
1386
|
+
options: {
|
|
1387
|
+
type: "array",
|
|
1388
|
+
description: "Optional 2-4 multiple-choice options. Omit for free-text input. An implicit 'Other...' entry is appended automatically by the framework.",
|
|
1389
|
+
items: {
|
|
1390
|
+
type: "object",
|
|
1391
|
+
properties: {
|
|
1392
|
+
label: {
|
|
1393
|
+
type: "string",
|
|
1394
|
+
description: "Short display text (1-5 words) \u2014 in the user's language"
|
|
1395
|
+
},
|
|
1396
|
+
description: {
|
|
1397
|
+
type: "string",
|
|
1398
|
+
description: "What this option means or implies \u2014 shown next to the label, in the user's language"
|
|
1399
|
+
}
|
|
1400
|
+
},
|
|
1401
|
+
required: ["label", "description"]
|
|
1402
|
+
}
|
|
1403
|
+
},
|
|
1404
|
+
multiSelect: {
|
|
1405
|
+
type: "boolean",
|
|
1406
|
+
description: "If true, the user can select multiple options. Defaults to false (single choice)."
|
|
1407
|
+
}
|
|
1372
1408
|
},
|
|
1373
1409
|
required: ["question"]
|
|
1374
1410
|
}
|
|
@@ -1379,13 +1415,28 @@ async function askUserTool(args, ctx) {
|
|
|
1379
1415
|
if (!ctx?.askUser) {
|
|
1380
1416
|
return "Error: AskUserQuestion is not available in headless mode. Make a reasonable assumption and proceed.";
|
|
1381
1417
|
}
|
|
1418
|
+
const opts = parseOptions(args);
|
|
1382
1419
|
try {
|
|
1383
|
-
const answer = await ctx.askUser(question);
|
|
1420
|
+
const answer = await ctx.askUser(question, opts);
|
|
1384
1421
|
return answer || "(user provided empty response)";
|
|
1385
1422
|
} catch (err) {
|
|
1386
1423
|
return `Error asking user: ${err.message}`;
|
|
1387
1424
|
}
|
|
1388
1425
|
}
|
|
1426
|
+
function parseOptions(args) {
|
|
1427
|
+
const rawOptions = args.options;
|
|
1428
|
+
const header = typeof args.header === "string" ? args.header : void 0;
|
|
1429
|
+
const multiSelect = args.multiSelect === true;
|
|
1430
|
+
if (!Array.isArray(rawOptions) || rawOptions.length === 0) {
|
|
1431
|
+
if (header === void 0 && !multiSelect) return void 0;
|
|
1432
|
+
return { header, multiSelect, options: void 0 };
|
|
1433
|
+
}
|
|
1434
|
+
const options = rawOptions.filter((o) => {
|
|
1435
|
+
return typeof o === "object" && o !== null && typeof o.label === "string" && typeof o.description === "string";
|
|
1436
|
+
}).map((o) => ({ label: o.label, description: o.description }));
|
|
1437
|
+
if (options.length === 0) return { header, multiSelect, options: void 0 };
|
|
1438
|
+
return { header, multiSelect, options };
|
|
1439
|
+
}
|
|
1389
1440
|
|
|
1390
1441
|
// src/tool-system/builtin/agent.ts
|
|
1391
1442
|
init_esm_shims();
|
|
@@ -2379,8 +2430,8 @@ async function lspTool(args) {
|
|
|
2379
2430
|
${content}`;
|
|
2380
2431
|
}
|
|
2381
2432
|
case "getDiagnostics": {
|
|
2382
|
-
const { readFileSync:
|
|
2383
|
-
const text =
|
|
2433
|
+
const { readFileSync: readFileSync9 } = await import("fs");
|
|
2434
|
+
const text = readFileSync9(filePath, "utf-8");
|
|
2384
2435
|
await client.notify("textDocument/didOpen", {
|
|
2385
2436
|
textDocument: { uri, languageId: serverConfig.language, version: 1, text }
|
|
2386
2437
|
});
|
|
@@ -2695,7 +2746,7 @@ async function mcpToolExecute(args) {
|
|
|
2695
2746
|
const tool = args.tool;
|
|
2696
2747
|
const toolArgs = args.arguments ?? {};
|
|
2697
2748
|
try {
|
|
2698
|
-
const { MCPManager: MCPManager2 } = await import("./mcp-manager-
|
|
2749
|
+
const { MCPManager: MCPManager2 } = await import("./mcp-manager-QMKWPBZ4.js");
|
|
2699
2750
|
const manager = MCPManager2.getInstance();
|
|
2700
2751
|
const result = await manager.callTool(server, tool, toolArgs);
|
|
2701
2752
|
return typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
@@ -2719,7 +2770,7 @@ var listMcpResourcesToolDef = {
|
|
|
2719
2770
|
async function listMcpResourcesTool(args) {
|
|
2720
2771
|
const server = args.server ?? "";
|
|
2721
2772
|
try {
|
|
2722
|
-
const { MCPManager: MCPManager2 } = await import("./mcp-manager-
|
|
2773
|
+
const { MCPManager: MCPManager2 } = await import("./mcp-manager-QMKWPBZ4.js");
|
|
2723
2774
|
const manager = MCPManager2.getInstance();
|
|
2724
2775
|
const resources = await manager.listResources(server || void 0);
|
|
2725
2776
|
if (!resources || resources.length === 0) {
|
|
@@ -2754,7 +2805,7 @@ async function readMcpResourceTool(args) {
|
|
|
2754
2805
|
const server = args.server;
|
|
2755
2806
|
const uri = args.uri;
|
|
2756
2807
|
try {
|
|
2757
|
-
const { MCPManager: MCPManager2 } = await import("./mcp-manager-
|
|
2808
|
+
const { MCPManager: MCPManager2 } = await import("./mcp-manager-QMKWPBZ4.js");
|
|
2758
2809
|
const manager = MCPManager2.getInstance();
|
|
2759
2810
|
const content = await manager.readResource(server, uri);
|
|
2760
2811
|
return typeof content === "string" ? content : JSON.stringify(content, null, 2);
|
|
@@ -2792,11 +2843,11 @@ async function remoteTriggerTool(args) {
|
|
|
2792
2843
|
const prompt = args.prompt;
|
|
2793
2844
|
const config = args.config;
|
|
2794
2845
|
try {
|
|
2795
|
-
const { existsSync:
|
|
2796
|
-
const { join:
|
|
2846
|
+
const { existsSync: existsSync15, readFileSync: readFileSync9, writeFileSync: writeFileSync7, mkdirSync: mkdirSync6 } = await import("fs");
|
|
2847
|
+
const { join: join12 } = await import("path");
|
|
2797
2848
|
const { homedir: homedir8 } = await import("os");
|
|
2798
|
-
const triggerDir =
|
|
2799
|
-
|
|
2849
|
+
const triggerDir = join12(homedir8(), ".code-shell", "triggers");
|
|
2850
|
+
mkdirSync6(triggerDir, { recursive: true });
|
|
2800
2851
|
const trigger = {
|
|
2801
2852
|
id: `trigger_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
2802
2853
|
name,
|
|
@@ -2805,8 +2856,8 @@ async function remoteTriggerTool(args) {
|
|
|
2805
2856
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2806
2857
|
status: "pending"
|
|
2807
2858
|
};
|
|
2808
|
-
const triggerFile =
|
|
2809
|
-
|
|
2859
|
+
const triggerFile = join12(triggerDir, `${trigger.id}.json`);
|
|
2860
|
+
writeFileSync7(triggerFile, JSON.stringify(trigger, null, 2), "utf-8");
|
|
2810
2861
|
return `Remote trigger "${name}" dispatched (ID: ${trigger.id}).
|
|
2811
2862
|
Stored at: ${triggerFile}`;
|
|
2812
2863
|
} catch (err) {
|
|
@@ -3306,7 +3357,9 @@ var BUILTIN_TOOLS = [
|
|
|
3306
3357
|
source: "builtin",
|
|
3307
3358
|
permissionDefault: "allow",
|
|
3308
3359
|
isReadOnly: true,
|
|
3309
|
-
isConcurrencySafe: false
|
|
3360
|
+
isConcurrencySafe: false,
|
|
3361
|
+
timeoutMs: 0
|
|
3362
|
+
// 纯等待用户操作 — 不设超时,由用户 Esc/取消
|
|
3310
3363
|
},
|
|
3311
3364
|
execute: askUserTool
|
|
3312
3365
|
},
|
|
@@ -3745,6 +3798,209 @@ var ToolRegistry = class {
|
|
|
3745
3798
|
// src/tool-system/executor.ts
|
|
3746
3799
|
init_esm_shims();
|
|
3747
3800
|
|
|
3801
|
+
// src/logging/session-recorder.ts
|
|
3802
|
+
init_esm_shims();
|
|
3803
|
+
import {
|
|
3804
|
+
appendFileSync,
|
|
3805
|
+
existsSync as existsSync8,
|
|
3806
|
+
mkdirSync as mkdirSync2,
|
|
3807
|
+
readdirSync as readdirSync3,
|
|
3808
|
+
rmSync,
|
|
3809
|
+
statSync
|
|
3810
|
+
} from "fs";
|
|
3811
|
+
import { join as join4 } from "path";
|
|
3812
|
+
var RETENTION_DAYS = 7;
|
|
3813
|
+
var MAX_RECORD_BYTES = 256 * 1024;
|
|
3814
|
+
function clip(s) {
|
|
3815
|
+
if (s === void 0) return void 0;
|
|
3816
|
+
if (s.length <= MAX_RECORD_BYTES) return s;
|
|
3817
|
+
return s.slice(0, MAX_RECORD_BYTES) + `
|
|
3818
|
+
\u2026(truncated, ${s.length - MAX_RECORD_BYTES} more bytes)`;
|
|
3819
|
+
}
|
|
3820
|
+
function isLocalDev() {
|
|
3821
|
+
if (process.env.CODE_SHELL_VERBOSE_LOG === "0") return false;
|
|
3822
|
+
if (process.env.CODE_SHELL_DEV === "1") return true;
|
|
3823
|
+
if (process.argv.includes("--debug") || process.argv.includes("-d")) return true;
|
|
3824
|
+
if (process.argv.some((a) => a.startsWith("--debug="))) return true;
|
|
3825
|
+
if (process.env.NODE_ENV === "test" || process.env.BUN_TEST === "1") return false;
|
|
3826
|
+
const entry = process.argv[1] ?? "";
|
|
3827
|
+
if (entry.includes(`${"/"}src${"/"}`)) return true;
|
|
3828
|
+
return false;
|
|
3829
|
+
}
|
|
3830
|
+
var ENABLED = isLocalDev();
|
|
3831
|
+
function resolveLogDir() {
|
|
3832
|
+
let dir = new URL(".", import.meta.url).pathname;
|
|
3833
|
+
if (dir.endsWith("/")) dir = dir.slice(0, -1);
|
|
3834
|
+
for (let i = 0; i < 8; i++) {
|
|
3835
|
+
if (existsSync8(join4(dir, "package.json"))) return join4(dir, "log");
|
|
3836
|
+
const parent = join4(dir, "..");
|
|
3837
|
+
if (parent === dir) break;
|
|
3838
|
+
dir = parent;
|
|
3839
|
+
}
|
|
3840
|
+
return join4(process.cwd(), "log");
|
|
3841
|
+
}
|
|
3842
|
+
var LOG_ROOT = ENABLED ? resolveLogDir() : "";
|
|
3843
|
+
var SENSITIVE_FLAG_RE = /^--?(api[-_]?key|auth[-_]?token|token|password|secret|bearer)(=|$)/i;
|
|
3844
|
+
function redactArgv(argv) {
|
|
3845
|
+
const out = [];
|
|
3846
|
+
for (let i = 0; i < argv.length; i++) {
|
|
3847
|
+
const a = argv[i] ?? "";
|
|
3848
|
+
const m = SENSITIVE_FLAG_RE.exec(a);
|
|
3849
|
+
if (!m) {
|
|
3850
|
+
out.push(a);
|
|
3851
|
+
continue;
|
|
3852
|
+
}
|
|
3853
|
+
if (m[2] === "=") {
|
|
3854
|
+
out.push(`${a.slice(0, a.indexOf("=") + 1)}<redacted>`);
|
|
3855
|
+
} else {
|
|
3856
|
+
out.push(a);
|
|
3857
|
+
if (i + 1 < argv.length) {
|
|
3858
|
+
out.push("<redacted>");
|
|
3859
|
+
i += 1;
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
return out;
|
|
3864
|
+
}
|
|
3865
|
+
var states = /* @__PURE__ */ new Map();
|
|
3866
|
+
var rotated = false;
|
|
3867
|
+
function todayStr() {
|
|
3868
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
3869
|
+
}
|
|
3870
|
+
function rotateOnce() {
|
|
3871
|
+
if (rotated || !ENABLED) return;
|
|
3872
|
+
rotated = true;
|
|
3873
|
+
try {
|
|
3874
|
+
if (!existsSync8(LOG_ROOT)) return;
|
|
3875
|
+
const cutoff = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
3876
|
+
for (const entry of readdirSync3(LOG_ROOT)) {
|
|
3877
|
+
const full = join4(LOG_ROOT, entry);
|
|
3878
|
+
try {
|
|
3879
|
+
const st = statSync(full);
|
|
3880
|
+
if (st.isDirectory() && st.mtimeMs < cutoff) {
|
|
3881
|
+
rmSync(full, { recursive: true, force: true });
|
|
3882
|
+
}
|
|
3883
|
+
} catch {
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
} catch {
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
function ensureState(sid) {
|
|
3890
|
+
if (!ENABLED) return null;
|
|
3891
|
+
rotateOnce();
|
|
3892
|
+
let st = states.get(sid);
|
|
3893
|
+
if (st) return st;
|
|
3894
|
+
const dateStr = todayStr();
|
|
3895
|
+
const dayDir = join4(LOG_ROOT, dateStr);
|
|
3896
|
+
const path = join4(dayDir, `session-${sid}.jsonl`);
|
|
3897
|
+
try {
|
|
3898
|
+
if (!existsSync8(dayDir)) mkdirSync2(dayDir, { recursive: true });
|
|
3899
|
+
} catch {
|
|
3900
|
+
return null;
|
|
3901
|
+
}
|
|
3902
|
+
st = { path, startedAt: Date.now() };
|
|
3903
|
+
states.set(sid, st);
|
|
3904
|
+
return st;
|
|
3905
|
+
}
|
|
3906
|
+
function write(sid, event) {
|
|
3907
|
+
const st = ensureState(sid);
|
|
3908
|
+
if (!st) return;
|
|
3909
|
+
const record = { t: (/* @__PURE__ */ new Date()).toISOString(), sid, ...event };
|
|
3910
|
+
try {
|
|
3911
|
+
appendFileSync(st.path, JSON.stringify(record) + "\n", "utf-8");
|
|
3912
|
+
} catch {
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
function recordSessionStart(sid, info) {
|
|
3916
|
+
if (!ENABLED) return;
|
|
3917
|
+
write(sid, {
|
|
3918
|
+
type: "session_start",
|
|
3919
|
+
pid: process.pid,
|
|
3920
|
+
argv: redactArgv(process.argv.slice(1)),
|
|
3921
|
+
cwd: info.cwd,
|
|
3922
|
+
model: info.model,
|
|
3923
|
+
provider: info.provider,
|
|
3924
|
+
permissionMode: info.permissionMode,
|
|
3925
|
+
resumed: info.resumed,
|
|
3926
|
+
task: info.task
|
|
3927
|
+
});
|
|
3928
|
+
}
|
|
3929
|
+
function recordLLMRequest(sid, req, reqId) {
|
|
3930
|
+
if (!ENABLED) return;
|
|
3931
|
+
write(sid, {
|
|
3932
|
+
type: "llm.request",
|
|
3933
|
+
reqId,
|
|
3934
|
+
provider: req.provider,
|
|
3935
|
+
model: req.model,
|
|
3936
|
+
stream: req.stream,
|
|
3937
|
+
temperature: req.temperature,
|
|
3938
|
+
maxTokens: req.maxTokens,
|
|
3939
|
+
toolCount: Array.isArray(req.tools) ? req.tools.length : 0,
|
|
3940
|
+
messageCount: Array.isArray(req.messages) ? req.messages.length : void 0,
|
|
3941
|
+
systemPrompt: req.systemPrompt,
|
|
3942
|
+
tools: req.tools,
|
|
3943
|
+
messages: req.messages
|
|
3944
|
+
});
|
|
3945
|
+
}
|
|
3946
|
+
function recordLLMResponse(sid, resp, reqId) {
|
|
3947
|
+
if (!ENABLED) return;
|
|
3948
|
+
write(sid, {
|
|
3949
|
+
type: "llm.response",
|
|
3950
|
+
reqId,
|
|
3951
|
+
stopReason: resp.stopReason,
|
|
3952
|
+
durationMs: resp.durationMs,
|
|
3953
|
+
ttftMs: resp.ttftMs,
|
|
3954
|
+
usage: resp.usage,
|
|
3955
|
+
toolCallCount: resp.toolCalls?.length ?? 0,
|
|
3956
|
+
text: resp.text,
|
|
3957
|
+
toolCalls: resp.toolCalls
|
|
3958
|
+
});
|
|
3959
|
+
}
|
|
3960
|
+
function recordLLMError(sid, reqId, err, durationMs) {
|
|
3961
|
+
if (!ENABLED) return;
|
|
3962
|
+
write(sid, {
|
|
3963
|
+
type: "llm.error",
|
|
3964
|
+
reqId,
|
|
3965
|
+
durationMs,
|
|
3966
|
+
message: err instanceof Error ? err.message : String(err),
|
|
3967
|
+
stack: err instanceof Error ? err.stack : void 0
|
|
3968
|
+
});
|
|
3969
|
+
}
|
|
3970
|
+
function recordToolCall(sid, call) {
|
|
3971
|
+
if (!ENABLED) return;
|
|
3972
|
+
write(sid, {
|
|
3973
|
+
type: "tool.call",
|
|
3974
|
+
toolCallId: call.id,
|
|
3975
|
+
toolName: call.toolName,
|
|
3976
|
+
args: call.args
|
|
3977
|
+
});
|
|
3978
|
+
}
|
|
3979
|
+
function recordToolResult(sid, result) {
|
|
3980
|
+
if (!ENABLED) return;
|
|
3981
|
+
write(sid, {
|
|
3982
|
+
type: "tool.result",
|
|
3983
|
+
toolCallId: result.id,
|
|
3984
|
+
toolName: result.toolName,
|
|
3985
|
+
ok: result.ok,
|
|
3986
|
+
durationMs: result.durationMs,
|
|
3987
|
+
chars: (result.output ?? result.error ?? "").length,
|
|
3988
|
+
output: clip(result.output),
|
|
3989
|
+
error: clip(result.error)
|
|
3990
|
+
});
|
|
3991
|
+
}
|
|
3992
|
+
function recordSessionEnd(sid, info) {
|
|
3993
|
+
if (!ENABLED) return;
|
|
3994
|
+
write(sid, {
|
|
3995
|
+
type: "session_end",
|
|
3996
|
+
reason: info.reason,
|
|
3997
|
+
turns: info.turns,
|
|
3998
|
+
durationMs: info.durationMs,
|
|
3999
|
+
cost: info.cost
|
|
4000
|
+
});
|
|
4001
|
+
states.delete(sid);
|
|
4002
|
+
}
|
|
4003
|
+
|
|
3748
4004
|
// src/tool-system/validation.ts
|
|
3749
4005
|
init_esm_shims();
|
|
3750
4006
|
function validateToolArgs(toolName, args, schema) {
|
|
@@ -3794,6 +4050,13 @@ var ToolExecutor = class {
|
|
|
3794
4050
|
* (tests, ad-hoc tool runs) still write log lines, just without turn tags.
|
|
3795
4051
|
*/
|
|
3796
4052
|
log = logger;
|
|
4053
|
+
guard;
|
|
4054
|
+
setInvestigationGuard(guard) {
|
|
4055
|
+
this.guard = guard;
|
|
4056
|
+
}
|
|
4057
|
+
getInvestigationGuard() {
|
|
4058
|
+
return this.guard;
|
|
4059
|
+
}
|
|
3797
4060
|
/** Set the abort signal for cascading cancellation. */
|
|
3798
4061
|
setSignal(signal) {
|
|
3799
4062
|
this.signal = signal;
|
|
@@ -3867,6 +4130,16 @@ var ToolExecutor = class {
|
|
|
3867
4130
|
isError: true
|
|
3868
4131
|
};
|
|
3869
4132
|
}
|
|
4133
|
+
const guardDecision = this.guard?.preToolCheck(call);
|
|
4134
|
+
if (guardDecision?.block) {
|
|
4135
|
+
this.log.info("guard.block", { cat: "guard", tool: call.toolName, reason: guardDecision.block.slice(0, 200) });
|
|
4136
|
+
return {
|
|
4137
|
+
id: call.id,
|
|
4138
|
+
toolName: call.toolName,
|
|
4139
|
+
error: guardDecision.block,
|
|
4140
|
+
isError: true
|
|
4141
|
+
};
|
|
4142
|
+
}
|
|
3870
4143
|
const decision = this.permission.classify(call.toolName, call.args);
|
|
3871
4144
|
this.log.info("permission.classify", {
|
|
3872
4145
|
cat: "permission",
|
|
@@ -3904,6 +4177,9 @@ var ToolExecutor = class {
|
|
|
3904
4177
|
toolCallId: call.id,
|
|
3905
4178
|
args: JSON.stringify(call.args).slice(0, 2e3)
|
|
3906
4179
|
});
|
|
4180
|
+
const sid = getCurrentSid();
|
|
4181
|
+
recordToolCall(sid, { id: call.id, toolName: call.toolName, args: call.args });
|
|
4182
|
+
const toolStartedAt = Date.now();
|
|
3907
4183
|
let result;
|
|
3908
4184
|
try {
|
|
3909
4185
|
result = await this.registry.executeTool(call.toolName, call.args, {
|
|
@@ -3912,9 +4188,20 @@ var ToolExecutor = class {
|
|
|
3912
4188
|
});
|
|
3913
4189
|
} catch (err) {
|
|
3914
4190
|
span.fail(err);
|
|
4191
|
+
recordToolResult(sid, {
|
|
4192
|
+
id: call.id,
|
|
4193
|
+
toolName: call.toolName,
|
|
4194
|
+
ok: false,
|
|
4195
|
+
durationMs: Date.now() - toolStartedAt,
|
|
4196
|
+
error: err instanceof Error ? err.stack ?? err.message : String(err)
|
|
4197
|
+
});
|
|
3915
4198
|
throw err;
|
|
3916
4199
|
}
|
|
3917
4200
|
result.id = call.id;
|
|
4201
|
+
if (guardDecision?.prepend && !result.error && result.result) {
|
|
4202
|
+
result.result = `${guardDecision.prepend}
|
|
4203
|
+
${result.result}`;
|
|
4204
|
+
}
|
|
3918
4205
|
const payload = result.result ?? result.error ?? "";
|
|
3919
4206
|
span.end({
|
|
3920
4207
|
ok: !result.error,
|
|
@@ -3924,6 +4211,14 @@ var ToolExecutor = class {
|
|
|
3924
4211
|
// usually contain the message we need.
|
|
3925
4212
|
...result.error ? { errorSnippet: payload.slice(0, 500) } : {}
|
|
3926
4213
|
});
|
|
4214
|
+
recordToolResult(sid, {
|
|
4215
|
+
id: call.id,
|
|
4216
|
+
toolName: call.toolName,
|
|
4217
|
+
ok: !result.error,
|
|
4218
|
+
durationMs: Date.now() - toolStartedAt,
|
|
4219
|
+
output: result.error ? void 0 : result.result,
|
|
4220
|
+
error: result.error
|
|
4221
|
+
});
|
|
3927
4222
|
await this.hooks.emit("on_tool_end", {
|
|
3928
4223
|
toolName: call.toolName,
|
|
3929
4224
|
toolCallId: call.id,
|
|
@@ -4503,8 +4798,20 @@ var PermissionClassifier = class {
|
|
|
4503
4798
|
const argVal = String(args[key] ?? "");
|
|
4504
4799
|
if (pattern instanceof RegExp) {
|
|
4505
4800
|
if (!pattern.test(argVal)) return false;
|
|
4506
|
-
} else {
|
|
4507
|
-
|
|
4801
|
+
} else if (typeof pattern === "string") {
|
|
4802
|
+
const looksLikeRegex = /[\\^$.*+?()[\]|{}]/.test(pattern);
|
|
4803
|
+
if (looksLikeRegex) {
|
|
4804
|
+
let re;
|
|
4805
|
+
try {
|
|
4806
|
+
re = new RegExp(pattern);
|
|
4807
|
+
} catch {
|
|
4808
|
+
if (!argVal.includes(pattern)) return false;
|
|
4809
|
+
continue;
|
|
4810
|
+
}
|
|
4811
|
+
if (!re.test(argVal)) return false;
|
|
4812
|
+
} else if (!argVal.includes(pattern)) {
|
|
4813
|
+
return false;
|
|
4814
|
+
}
|
|
4508
4815
|
}
|
|
4509
4816
|
}
|
|
4510
4817
|
}
|
|
@@ -4606,15 +4913,201 @@ var HookRegistry = class {
|
|
|
4606
4913
|
}
|
|
4607
4914
|
};
|
|
4608
4915
|
|
|
4916
|
+
// src/context/tool-result-storage.ts
|
|
4917
|
+
init_esm_shims();
|
|
4918
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
4919
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
4920
|
+
var DEFAULT_PERSIST_THRESHOLD = 5e4;
|
|
4921
|
+
var PER_MESSAGE_AGGREGATE_CAP = 2e5;
|
|
4922
|
+
var PREVIEW_SIZE = 2e3;
|
|
4923
|
+
var PERSISTED_OPEN = "<persisted-output>";
|
|
4924
|
+
var PERSISTED_CLOSE = "</persisted-output>";
|
|
4925
|
+
var CLEARED_PREFIX = "[Old tool result cleared";
|
|
4926
|
+
function createContentReplacementState() {
|
|
4927
|
+
return { seenIds: /* @__PURE__ */ new Set(), replacements: /* @__PURE__ */ new Map() };
|
|
4928
|
+
}
|
|
4929
|
+
function reconstructContentReplacementState(messages) {
|
|
4930
|
+
const state = createContentReplacementState();
|
|
4931
|
+
for (const msg of messages) {
|
|
4932
|
+
if (!Array.isArray(msg.content)) continue;
|
|
4933
|
+
for (const block of msg.content) {
|
|
4934
|
+
if (block.type !== "tool_result" || !block.tool_use_id) continue;
|
|
4935
|
+
state.seenIds.add(block.tool_use_id);
|
|
4936
|
+
if (typeof block.content === "string" && block.content.startsWith(PERSISTED_OPEN)) {
|
|
4937
|
+
state.replacements.set(block.tool_use_id, block.content);
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
}
|
|
4941
|
+
return state;
|
|
4942
|
+
}
|
|
4943
|
+
function resolveToolResultsDir(transcriptPath) {
|
|
4944
|
+
return join5(dirname3(transcriptPath), "tool-results");
|
|
4945
|
+
}
|
|
4946
|
+
function ensureDir(dir) {
|
|
4947
|
+
try {
|
|
4948
|
+
mkdirSync3(dir, { recursive: true });
|
|
4949
|
+
} catch {
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
function persistToFile(dir, toolUseId, content) {
|
|
4953
|
+
ensureDir(dir);
|
|
4954
|
+
const filepath = join5(dir, `${toolUseId}.txt`);
|
|
4955
|
+
try {
|
|
4956
|
+
writeFileSync3(filepath, content, { encoding: "utf-8", flag: "wx" });
|
|
4957
|
+
} catch (err) {
|
|
4958
|
+
const code = err.code;
|
|
4959
|
+
if (code !== "EEXIST") {
|
|
4960
|
+
logger.debug("tool_result.persist_failed", {
|
|
4961
|
+
toolUseId,
|
|
4962
|
+
code,
|
|
4963
|
+
error: err.message
|
|
4964
|
+
});
|
|
4965
|
+
throw err;
|
|
4966
|
+
}
|
|
4967
|
+
}
|
|
4968
|
+
return filepath;
|
|
4969
|
+
}
|
|
4970
|
+
function buildReplacement(filepath, originalSize, content) {
|
|
4971
|
+
const sizeKb = (originalSize / 1024).toFixed(1);
|
|
4972
|
+
const previewRaw = content.slice(0, PREVIEW_SIZE);
|
|
4973
|
+
const lastNl = previewRaw.lastIndexOf("\n");
|
|
4974
|
+
const preview = lastNl > PREVIEW_SIZE * 0.5 ? previewRaw.slice(0, lastNl) : previewRaw;
|
|
4975
|
+
const hasMore = content.length > preview.length;
|
|
4976
|
+
return `${PERSISTED_OPEN}
|
|
4977
|
+
Output too large (${sizeKb} KB). Full output saved to: ${filepath}
|
|
4978
|
+
|
|
4979
|
+
Preview (first ${preview.length} chars${hasMore ? ", truncated" : ""}):
|
|
4980
|
+
` + preview + (hasMore ? "\n..." : "") + `
|
|
4981
|
+
${PERSISTED_CLOSE}`;
|
|
4982
|
+
}
|
|
4983
|
+
function collectCandidates(msg) {
|
|
4984
|
+
if (!Array.isArray(msg.content)) return [];
|
|
4985
|
+
const out = [];
|
|
4986
|
+
for (const block of msg.content) {
|
|
4987
|
+
if (block.type !== "tool_result" || !block.tool_use_id) continue;
|
|
4988
|
+
if (typeof block.content !== "string") continue;
|
|
4989
|
+
if (block.content.startsWith(PERSISTED_OPEN) || block.content.startsWith(CLEARED_PREFIX))
|
|
4990
|
+
continue;
|
|
4991
|
+
out.push({
|
|
4992
|
+
toolUseId: block.tool_use_id,
|
|
4993
|
+
block,
|
|
4994
|
+
content: block.content,
|
|
4995
|
+
size: block.content.length
|
|
4996
|
+
});
|
|
4997
|
+
}
|
|
4998
|
+
return out;
|
|
4999
|
+
}
|
|
5000
|
+
function applyToolResultPersistence(messages, options) {
|
|
5001
|
+
const {
|
|
5002
|
+
perResultThreshold = DEFAULT_PERSIST_THRESHOLD,
|
|
5003
|
+
perMessageCap = PER_MESSAGE_AGGREGATE_CAP,
|
|
5004
|
+
toolResultsDir,
|
|
5005
|
+
state,
|
|
5006
|
+
onPersist
|
|
5007
|
+
} = options;
|
|
5008
|
+
const newReplacements = /* @__PURE__ */ new Map();
|
|
5009
|
+
for (const msg of messages) {
|
|
5010
|
+
if (msg.role !== "user") continue;
|
|
5011
|
+
const candidates = collectCandidates(msg);
|
|
5012
|
+
if (candidates.length === 0) continue;
|
|
5013
|
+
const fresh = [];
|
|
5014
|
+
for (const c of candidates) {
|
|
5015
|
+
if (state.replacements.has(c.toolUseId)) {
|
|
5016
|
+
continue;
|
|
5017
|
+
}
|
|
5018
|
+
if (state.seenIds.has(c.toolUseId)) {
|
|
5019
|
+
continue;
|
|
5020
|
+
}
|
|
5021
|
+
fresh.push(c);
|
|
5022
|
+
}
|
|
5023
|
+
if (fresh.length === 0) continue;
|
|
5024
|
+
const toPersist = /* @__PURE__ */ new Set();
|
|
5025
|
+
for (const c of fresh) {
|
|
5026
|
+
if (c.size > perResultThreshold) {
|
|
5027
|
+
toPersist.add(c.toolUseId);
|
|
5028
|
+
}
|
|
5029
|
+
}
|
|
5030
|
+
const frozenSize = candidates.filter((c) => state.seenIds.has(c.toolUseId) && !state.replacements.has(c.toolUseId)).reduce((s, c) => s + c.size, 0);
|
|
5031
|
+
let projected = frozenSize + fresh.reduce((s, c) => s + (toPersist.has(c.toolUseId) ? 0 : c.size), 0);
|
|
5032
|
+
if (projected > perMessageCap) {
|
|
5033
|
+
const remaining = fresh.filter((c) => !toPersist.has(c.toolUseId)).sort((a, b) => b.size - a.size);
|
|
5034
|
+
for (const c of remaining) {
|
|
5035
|
+
if (projected <= perMessageCap) break;
|
|
5036
|
+
toPersist.add(c.toolUseId);
|
|
5037
|
+
projected -= c.size;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
for (const c of fresh) {
|
|
5041
|
+
if (toPersist.has(c.toolUseId)) {
|
|
5042
|
+
try {
|
|
5043
|
+
const filepath = persistToFile(toolResultsDir, c.toolUseId, c.content);
|
|
5044
|
+
const replacement = buildReplacement(filepath, c.size, c.content);
|
|
5045
|
+
state.replacements.set(c.toolUseId, replacement);
|
|
5046
|
+
newReplacements.set(c.toolUseId, replacement);
|
|
5047
|
+
state.seenIds.add(c.toolUseId);
|
|
5048
|
+
onPersist?.({
|
|
5049
|
+
toolUseId: c.toolUseId,
|
|
5050
|
+
filepath,
|
|
5051
|
+
originalSize: c.size,
|
|
5052
|
+
reason: c.size > perResultThreshold ? "per-result-cap" : "per-message-budget"
|
|
5053
|
+
});
|
|
5054
|
+
} catch {
|
|
5055
|
+
state.seenIds.add(c.toolUseId);
|
|
5056
|
+
}
|
|
5057
|
+
} else {
|
|
5058
|
+
state.seenIds.add(c.toolUseId);
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
5061
|
+
}
|
|
5062
|
+
if (state.replacements.size === 0) return messages;
|
|
5063
|
+
let mutated = false;
|
|
5064
|
+
const out = messages.map((msg) => {
|
|
5065
|
+
if (msg.role !== "user" || !Array.isArray(msg.content)) return msg;
|
|
5066
|
+
let blockChanged = false;
|
|
5067
|
+
const newContent = msg.content.map((block) => {
|
|
5068
|
+
if (block.type !== "tool_result" || !block.tool_use_id) return block;
|
|
5069
|
+
const replacement = state.replacements.get(block.tool_use_id);
|
|
5070
|
+
if (replacement === void 0) return block;
|
|
5071
|
+
if (block.content === replacement) return block;
|
|
5072
|
+
if (typeof block.content === "string" && block.content.startsWith(CLEARED_PREFIX)) {
|
|
5073
|
+
return block;
|
|
5074
|
+
}
|
|
5075
|
+
blockChanged = true;
|
|
5076
|
+
return { ...block, content: replacement };
|
|
5077
|
+
});
|
|
5078
|
+
if (!blockChanged) return msg;
|
|
5079
|
+
mutated = true;
|
|
5080
|
+
return { ...msg, content: newContent };
|
|
5081
|
+
});
|
|
5082
|
+
if (mutated && newReplacements.size > 0) {
|
|
5083
|
+
logger.info("tool_result.persisted", {
|
|
5084
|
+
count: newReplacements.size,
|
|
5085
|
+
ids: [...newReplacements.keys()]
|
|
5086
|
+
});
|
|
5087
|
+
}
|
|
5088
|
+
return mutated ? out : messages;
|
|
5089
|
+
}
|
|
5090
|
+
function isPersistedReplacement(s) {
|
|
5091
|
+
return s.startsWith(PERSISTED_OPEN);
|
|
5092
|
+
}
|
|
5093
|
+
|
|
4609
5094
|
// src/context/manager.ts
|
|
4610
5095
|
init_esm_shims();
|
|
4611
5096
|
init_compaction();
|
|
4612
5097
|
var DEFAULT_CONFIG = {
|
|
5098
|
+
// 0.85 ≈ effectiveContextWindow − reserved-output buffer (CC's
|
|
5099
|
+
// autoCompactThreshold pattern: window − 13k buffer − 20k output budget
|
|
5100
|
+
// ≈ 0.83 on a 200k window, 0.97 on 1M). 0.6 was wasting 40% of any model
|
|
5101
|
+
// with a window over ~150k.
|
|
4613
5102
|
maxTokens: 2e5,
|
|
4614
|
-
compactAtRatio: 0.
|
|
4615
|
-
summarizeAtRatio: 0.
|
|
4616
|
-
maxToolResultChars: 3e4
|
|
5103
|
+
compactAtRatio: 0.85,
|
|
5104
|
+
summarizeAtRatio: 0.92,
|
|
5105
|
+
maxToolResultChars: 3e4,
|
|
5106
|
+
microcompactFloorRatio: 0.3
|
|
4617
5107
|
};
|
|
5108
|
+
function defaultKeepRecent(maxTokens) {
|
|
5109
|
+
return Math.max(5, Math.floor(maxTokens / 1e5));
|
|
5110
|
+
}
|
|
4618
5111
|
var ContextManager = class {
|
|
4619
5112
|
config;
|
|
4620
5113
|
toolCallHashes = /* @__PURE__ */ new Map();
|
|
@@ -4627,8 +5120,15 @@ var ContextManager = class {
|
|
|
4627
5120
|
lastActualAtMessageCount;
|
|
4628
5121
|
/** Path to session transcript — passed to summary compaction for on-demand access. */
|
|
4629
5122
|
transcriptPath;
|
|
4630
|
-
/** Notified whenever
|
|
5123
|
+
/** Notified whenever any compaction tier fires, including microcompact. */
|
|
4631
5124
|
onCompact;
|
|
5125
|
+
/**
|
|
5126
|
+
* Tool-result persistence state. Created lazily once we know where to
|
|
5127
|
+
* write files (i.e. once setTranscriptPath has been called).
|
|
5128
|
+
* `null` = persistence has been explicitly disabled.
|
|
5129
|
+
*/
|
|
5130
|
+
replacementState;
|
|
5131
|
+
toolResultsDir;
|
|
4632
5132
|
constructor(config) {
|
|
4633
5133
|
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
4634
5134
|
}
|
|
@@ -4662,10 +5162,23 @@ var ContextManager = class {
|
|
|
4662
5162
|
this.summarizeFn = fn;
|
|
4663
5163
|
}
|
|
4664
5164
|
/**
|
|
4665
|
-
* Set the transcript path so compaction can reference it.
|
|
5165
|
+
* Set the transcript path so compaction can reference it. The
|
|
5166
|
+
* tool-results directory lives alongside the transcript file:
|
|
5167
|
+
* <transcriptDir>/tool-results/<toolUseId>.txt
|
|
4666
5168
|
*/
|
|
4667
5169
|
setTranscriptPath(path) {
|
|
4668
5170
|
this.transcriptPath = path;
|
|
5171
|
+
this.toolResultsDir = resolveToolResultsDir(path);
|
|
5172
|
+
}
|
|
5173
|
+
/**
|
|
5174
|
+
* Initialize tool-result-persistence state from already-loaded messages.
|
|
5175
|
+
* Call this once per session, after setTranscriptPath, when resuming so
|
|
5176
|
+
* the manager re-enters the same "frozen decision" state it had on the
|
|
5177
|
+
* previous run. Without this, a resumed session would re-evaluate
|
|
5178
|
+
* every result against the current threshold and might flip choices.
|
|
5179
|
+
*/
|
|
5180
|
+
initReplacementStateFromMessages(messages) {
|
|
5181
|
+
this.replacementState = reconstructContentReplacementState(messages);
|
|
4669
5182
|
}
|
|
4670
5183
|
/**
|
|
4671
5184
|
* Apply progressive context management (sync path).
|
|
@@ -4673,11 +5186,36 @@ var ContextManager = class {
|
|
|
4673
5186
|
*/
|
|
4674
5187
|
manage(messages) {
|
|
4675
5188
|
let result = messages;
|
|
5189
|
+
result = this.persistLargeToolResults(result);
|
|
4676
5190
|
result = this.truncateToolResults(result);
|
|
4677
5191
|
result = applyToolResultBudget(result);
|
|
4678
|
-
|
|
5192
|
+
const preTier1Tokens = this.estimateTokensHybrid(result);
|
|
5193
|
+
if (preTier1Tokens > this.config.maxTokens * this.config.microcompactFloorRatio) {
|
|
5194
|
+
const keepRecentN = this.config.microcompactKeepRecent ?? defaultKeepRecent(this.config.maxTokens);
|
|
5195
|
+
let clearedInfo = null;
|
|
5196
|
+
result = microcompact(result, {
|
|
5197
|
+
keepRecentN,
|
|
5198
|
+
onClear: (info) => {
|
|
5199
|
+
clearedInfo = info;
|
|
5200
|
+
}
|
|
5201
|
+
});
|
|
5202
|
+
if (clearedInfo) {
|
|
5203
|
+
const after = this.estimateTokensHybrid(result);
|
|
5204
|
+
logger.info("context.microcompact", {
|
|
5205
|
+
before: preTier1Tokens,
|
|
5206
|
+
after,
|
|
5207
|
+
keepRecentN,
|
|
5208
|
+
clearedRounds: clearedInfo.clearedRounds,
|
|
5209
|
+
toolNames: clearedInfo.toolNames
|
|
5210
|
+
});
|
|
5211
|
+
this.onCompact?.({ strategy: "micro", before: preTier1Tokens, after });
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
4679
5214
|
let tokens = this.estimateTokensHybrid(result);
|
|
4680
|
-
|
|
5215
|
+
const snipGate = this.config.maxTokens * this.config.compactAtRatio;
|
|
5216
|
+
const windowGate = this.config.maxTokens * (this.config.compactAtRatio + 0.05);
|
|
5217
|
+
const emergencyGate = this.config.maxTokens * this.config.summarizeAtRatio;
|
|
5218
|
+
if (tokens > snipGate) {
|
|
4681
5219
|
const before = tokens;
|
|
4682
5220
|
if (this.lastSummary) {
|
|
4683
5221
|
const keepN = Math.max(8, Math.floor(result.length * 0.3));
|
|
@@ -4686,19 +5224,19 @@ var ContextManager = class {
|
|
|
4686
5224
|
tokens = this.estimateTokensHybrid(result);
|
|
4687
5225
|
this.onCompact?.({ strategy: "summary", before, after: tokens });
|
|
4688
5226
|
} else {
|
|
4689
|
-
|
|
4690
|
-
result = windowCompact(result, keepN);
|
|
5227
|
+
result = snipCompact(result, 3, 8);
|
|
4691
5228
|
tokens = this.estimateTokensHybrid(result);
|
|
4692
|
-
this.onCompact?.({ strategy: "
|
|
5229
|
+
this.onCompact?.({ strategy: "snip", before, after: tokens });
|
|
4693
5230
|
}
|
|
4694
5231
|
}
|
|
4695
|
-
if (tokens >
|
|
5232
|
+
if (tokens > windowGate) {
|
|
4696
5233
|
const before = tokens;
|
|
4697
|
-
|
|
5234
|
+
const keepN = Math.max(10, Math.floor(result.length * 0.4));
|
|
5235
|
+
result = windowCompact(result, keepN);
|
|
4698
5236
|
tokens = this.estimateTokensHybrid(result);
|
|
4699
|
-
this.onCompact?.({ strategy: "
|
|
5237
|
+
this.onCompact?.({ strategy: "window", before, after: tokens });
|
|
4700
5238
|
}
|
|
4701
|
-
if (tokens >
|
|
5239
|
+
if (tokens > emergencyGate) {
|
|
4702
5240
|
const before = tokens;
|
|
4703
5241
|
result = windowCompact(result, 6);
|
|
4704
5242
|
tokens = this.estimateTokensHybrid(result);
|
|
@@ -4712,9 +5250,31 @@ var ContextManager = class {
|
|
|
4712
5250
|
*/
|
|
4713
5251
|
async manageAsync(messages) {
|
|
4714
5252
|
let result = messages;
|
|
5253
|
+
result = this.persistLargeToolResults(result);
|
|
4715
5254
|
result = this.truncateToolResults(result);
|
|
4716
5255
|
result = applyToolResultBudget(result);
|
|
4717
|
-
|
|
5256
|
+
const preTier1Tokens = this.estimateTokensHybrid(result);
|
|
5257
|
+
if (preTier1Tokens > this.config.maxTokens * this.config.microcompactFloorRatio) {
|
|
5258
|
+
const keepRecentN = this.config.microcompactKeepRecent ?? defaultKeepRecent(this.config.maxTokens);
|
|
5259
|
+
let clearedInfo = null;
|
|
5260
|
+
result = microcompact(result, {
|
|
5261
|
+
keepRecentN,
|
|
5262
|
+
onClear: (info) => {
|
|
5263
|
+
clearedInfo = info;
|
|
5264
|
+
}
|
|
5265
|
+
});
|
|
5266
|
+
if (clearedInfo) {
|
|
5267
|
+
const after = this.estimateTokensHybrid(result);
|
|
5268
|
+
logger.info("context.microcompact", {
|
|
5269
|
+
before: preTier1Tokens,
|
|
5270
|
+
after,
|
|
5271
|
+
keepRecentN,
|
|
5272
|
+
clearedRounds: clearedInfo.clearedRounds,
|
|
5273
|
+
toolNames: clearedInfo.toolNames
|
|
5274
|
+
});
|
|
5275
|
+
this.onCompact?.({ strategy: "micro", before: preTier1Tokens, after });
|
|
5276
|
+
}
|
|
5277
|
+
}
|
|
4718
5278
|
const tokens = this.estimateTokensHybrid(result);
|
|
4719
5279
|
const ratio = tokens / this.config.maxTokens;
|
|
4720
5280
|
if (ratio >= this.config.compactAtRatio && this.summarizeFn && this.consecutiveSummaryFailures < 3) {
|
|
@@ -4747,20 +5307,23 @@ var ContextManager = class {
|
|
|
4747
5307
|
}
|
|
4748
5308
|
}
|
|
4749
5309
|
let live = tokens;
|
|
4750
|
-
|
|
5310
|
+
const snipGate = this.config.maxTokens * this.config.compactAtRatio;
|
|
5311
|
+
const windowGate = this.config.maxTokens * (this.config.compactAtRatio + 0.05);
|
|
5312
|
+
const emergencyGate = this.config.maxTokens * this.config.summarizeAtRatio;
|
|
5313
|
+
if (live > snipGate) {
|
|
4751
5314
|
const before = live;
|
|
4752
|
-
|
|
4753
|
-
result = windowCompact(result, keepN);
|
|
5315
|
+
result = snipCompact(result, 3, 8);
|
|
4754
5316
|
live = this.estimateTokensHybrid(result);
|
|
4755
|
-
this.onCompact?.({ strategy: "
|
|
5317
|
+
this.onCompact?.({ strategy: "snip", before, after: live });
|
|
4756
5318
|
}
|
|
4757
|
-
if (live >
|
|
5319
|
+
if (live > windowGate) {
|
|
4758
5320
|
const before = live;
|
|
4759
|
-
|
|
5321
|
+
const keepN = Math.max(10, Math.floor(result.length * 0.4));
|
|
5322
|
+
result = windowCompact(result, keepN);
|
|
4760
5323
|
live = this.estimateTokensHybrid(result);
|
|
4761
|
-
this.onCompact?.({ strategy: "
|
|
5324
|
+
this.onCompact?.({ strategy: "window", before, after: live });
|
|
4762
5325
|
}
|
|
4763
|
-
if (live >
|
|
5326
|
+
if (live > emergencyGate) {
|
|
4764
5327
|
const before = live;
|
|
4765
5328
|
result = windowCompact(result, 6);
|
|
4766
5329
|
live = this.estimateTokensHybrid(result);
|
|
@@ -4768,6 +5331,24 @@ var ContextManager = class {
|
|
|
4768
5331
|
}
|
|
4769
5332
|
return result;
|
|
4770
5333
|
}
|
|
5334
|
+
/**
|
|
5335
|
+
* Persist large tool_result blocks to disk and replace them with a
|
|
5336
|
+
* preview + filepath. No-op when transcript path hasn't been set
|
|
5337
|
+
* (e.g. unit tests that exercise compaction directly without a session).
|
|
5338
|
+
*/
|
|
5339
|
+
persistLargeToolResults(messages) {
|
|
5340
|
+
if (!this.toolResultsDir) return messages;
|
|
5341
|
+
if (!this.replacementState) {
|
|
5342
|
+
this.replacementState = createContentReplacementState();
|
|
5343
|
+
}
|
|
5344
|
+
return applyToolResultPersistence(messages, {
|
|
5345
|
+
toolResultsDir: this.toolResultsDir,
|
|
5346
|
+
state: this.replacementState,
|
|
5347
|
+
onPersist: (info) => {
|
|
5348
|
+
logger.info("context.tool_result_persisted", info);
|
|
5349
|
+
}
|
|
5350
|
+
});
|
|
5351
|
+
}
|
|
4771
5352
|
/**
|
|
4772
5353
|
* Truncate oversized tool results in messages.
|
|
4773
5354
|
*/
|
|
@@ -4797,7 +5378,7 @@ var ContextManager = class {
|
|
|
4797
5378
|
shouldReactiveCompact(messages, currentResponseTokens) {
|
|
4798
5379
|
const msgTokens = this.estimateTokensHybrid(messages);
|
|
4799
5380
|
const total = msgTokens + currentResponseTokens;
|
|
4800
|
-
return total > this.config.maxTokens *
|
|
5381
|
+
return total > this.config.maxTokens * this.config.summarizeAtRatio;
|
|
4801
5382
|
}
|
|
4802
5383
|
/**
|
|
4803
5384
|
* Check if context is approaching limits.
|
|
@@ -4879,8 +5460,8 @@ var SectionCache = class {
|
|
|
4879
5460
|
|
|
4880
5461
|
// src/prompt/instruction-scanner.ts
|
|
4881
5462
|
init_esm_shims();
|
|
4882
|
-
import { readFileSync as readFileSync4, existsSync as
|
|
4883
|
-
import { join as
|
|
5463
|
+
import { readFileSync as readFileSync4, existsSync as existsSync9, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
|
|
5464
|
+
import { join as join6, dirname as dirname4, resolve as resolve4 } from "path";
|
|
4884
5465
|
import { execSync as execSync5 } from "child_process";
|
|
4885
5466
|
import { homedir as homedir2 } from "os";
|
|
4886
5467
|
function scanInstructions(cwd, options = {}) {
|
|
@@ -4889,32 +5470,32 @@ function scanInstructions(cwd, options = {}) {
|
|
|
4889
5470
|
const allNames = [fileName, ...compatNames];
|
|
4890
5471
|
const configDirs = [".codeshell", ...options.scanDirs ?? [], ".claude"];
|
|
4891
5472
|
const entries = [];
|
|
4892
|
-
const homeConfigDir =
|
|
4893
|
-
const homeCompatDir =
|
|
5473
|
+
const homeConfigDir = join6(homedir2(), ".code-shell");
|
|
5474
|
+
const homeCompatDir = join6(homedir2(), ".claude");
|
|
4894
5475
|
for (const name of allNames) {
|
|
4895
|
-
tryAddFile(
|
|
5476
|
+
tryAddFile(join6(homeConfigDir, name), "user", -1, entries);
|
|
4896
5477
|
}
|
|
4897
|
-
tryAddRulesDir(
|
|
5478
|
+
tryAddRulesDir(join6(homeConfigDir, "rules"), "user", -1, entries);
|
|
4898
5479
|
for (const name of compatNames) {
|
|
4899
|
-
tryAddFile(
|
|
5480
|
+
tryAddFile(join6(homeCompatDir, name), "user", -1, entries);
|
|
4900
5481
|
}
|
|
4901
|
-
tryAddRulesDir(
|
|
5482
|
+
tryAddRulesDir(join6(homeCompatDir, "rules"), "user", -1, entries);
|
|
4902
5483
|
const ceiling = options.ignoreGitBoundary ? "/" : findGitRoot2(cwd) ?? "/";
|
|
4903
5484
|
const dirs = collectDirsDownward(resolve4(cwd), ceiling);
|
|
4904
5485
|
for (let depth = 0; depth < dirs.length; depth++) {
|
|
4905
5486
|
const dir = dirs[depth];
|
|
4906
5487
|
for (const name of allNames) {
|
|
4907
|
-
tryAddFile(
|
|
5488
|
+
tryAddFile(join6(dir, name), "project", depth, entries);
|
|
4908
5489
|
}
|
|
4909
5490
|
for (const cfgDir of configDirs) {
|
|
4910
5491
|
for (const name of allNames) {
|
|
4911
|
-
tryAddFile(
|
|
5492
|
+
tryAddFile(join6(dir, cfgDir, name), "project", depth, entries);
|
|
4912
5493
|
}
|
|
4913
|
-
tryAddRulesDir(
|
|
5494
|
+
tryAddRulesDir(join6(dir, cfgDir, "rules"), "project", depth, entries);
|
|
4914
5495
|
}
|
|
4915
5496
|
for (const name of allNames) {
|
|
4916
5497
|
const localName = name.replace(/\.md$/, ".local.md");
|
|
4917
|
-
tryAddFile(
|
|
5498
|
+
tryAddFile(join6(dir, localName), "local", depth, entries);
|
|
4918
5499
|
}
|
|
4919
5500
|
}
|
|
4920
5501
|
return dedup(entries);
|
|
@@ -4929,9 +5510,9 @@ ${e.content}`;
|
|
|
4929
5510
|
}).join("\n\n---\n\n");
|
|
4930
5511
|
}
|
|
4931
5512
|
function tryAddFile(path, source, depth, entries) {
|
|
4932
|
-
if (!
|
|
5513
|
+
if (!existsSync9(path)) return;
|
|
4933
5514
|
try {
|
|
4934
|
-
const stat5 =
|
|
5515
|
+
const stat5 = statSync2(path);
|
|
4935
5516
|
if (!stat5.isFile()) return;
|
|
4936
5517
|
const content = readFileSync4(path, "utf-8").trim();
|
|
4937
5518
|
if (content) {
|
|
@@ -4941,11 +5522,11 @@ function tryAddFile(path, source, depth, entries) {
|
|
|
4941
5522
|
}
|
|
4942
5523
|
}
|
|
4943
5524
|
function tryAddRulesDir(dir, source, depth, entries) {
|
|
4944
|
-
if (!
|
|
5525
|
+
if (!existsSync9(dir)) return;
|
|
4945
5526
|
try {
|
|
4946
|
-
const files =
|
|
5527
|
+
const files = readdirSync4(dir).filter((f) => f.endsWith(".md")).sort();
|
|
4947
5528
|
for (const file of files) {
|
|
4948
|
-
tryAddFile(
|
|
5529
|
+
tryAddFile(join6(dir, file), source, depth, entries);
|
|
4949
5530
|
}
|
|
4950
5531
|
} catch {
|
|
4951
5532
|
}
|
|
@@ -4970,7 +5551,7 @@ function collectDirsDownward(target, ceiling) {
|
|
|
4970
5551
|
while (true) {
|
|
4971
5552
|
dirs.push(dir);
|
|
4972
5553
|
if (dir === resolvedCeiling) break;
|
|
4973
|
-
const parent =
|
|
5554
|
+
const parent = dirname4(dir);
|
|
4974
5555
|
if (parent === dir) break;
|
|
4975
5556
|
dir = parent;
|
|
4976
5557
|
}
|
|
@@ -4999,28 +5580,33 @@ function sourceLabel(entry) {
|
|
|
4999
5580
|
|
|
5000
5581
|
// src/skills/scanner.ts
|
|
5001
5582
|
init_esm_shims();
|
|
5002
|
-
import { existsSync as
|
|
5003
|
-
import { join as
|
|
5583
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
5584
|
+
import { dirname as dirname5, join as join7, resolve as resolve5 } from "path";
|
|
5004
5585
|
import { homedir as homedir3 } from "os";
|
|
5586
|
+
import { fileURLToPath } from "url";
|
|
5005
5587
|
function scanSkills(cwd) {
|
|
5006
5588
|
const skills = [];
|
|
5007
5589
|
const seen = /* @__PURE__ */ new Set();
|
|
5590
|
+
const builtin = resolveBuiltinSkillsDir();
|
|
5008
5591
|
const dirs = [
|
|
5009
5592
|
// Project-level
|
|
5010
|
-
|
|
5011
|
-
|
|
5593
|
+
join7(cwd, ".code-shell", "skills"),
|
|
5594
|
+
join7(cwd, ".claude", "skills"),
|
|
5012
5595
|
// User-level
|
|
5013
|
-
|
|
5014
|
-
|
|
5596
|
+
join7(homedir3(), ".code-shell", "skills"),
|
|
5597
|
+
join7(homedir3(), ".claude", "skills"),
|
|
5598
|
+
// Built-in (shipped inside the npm package). Listed last so user/project
|
|
5599
|
+
// skills with the same filename win deduplication on filePath.
|
|
5600
|
+
...builtin ? [builtin] : []
|
|
5015
5601
|
];
|
|
5016
5602
|
for (const dir of dirs) {
|
|
5017
|
-
if (!
|
|
5603
|
+
if (!existsSync10(dir)) continue;
|
|
5018
5604
|
try {
|
|
5019
|
-
const files =
|
|
5020
|
-
(f) => f.endsWith(".md") &&
|
|
5605
|
+
const files = readdirSync5(dir).filter(
|
|
5606
|
+
(f) => f.endsWith(".md") && statSync3(join7(dir, f)).isFile()
|
|
5021
5607
|
);
|
|
5022
5608
|
for (const file of files) {
|
|
5023
|
-
const filePath = resolve5(
|
|
5609
|
+
const filePath = resolve5(join7(dir, file));
|
|
5024
5610
|
if (seen.has(filePath)) continue;
|
|
5025
5611
|
seen.add(filePath);
|
|
5026
5612
|
const skill = parseSkillFile(filePath);
|
|
@@ -5031,6 +5617,30 @@ function scanSkills(cwd) {
|
|
|
5031
5617
|
}
|
|
5032
5618
|
return skills;
|
|
5033
5619
|
}
|
|
5620
|
+
function resolveBuiltinSkillsDir() {
|
|
5621
|
+
let here;
|
|
5622
|
+
try {
|
|
5623
|
+
here = typeof import.meta !== "undefined" && import.meta.url ? dirname5(fileURLToPath(import.meta.url)) : __dirname;
|
|
5624
|
+
} catch {
|
|
5625
|
+
return null;
|
|
5626
|
+
}
|
|
5627
|
+
let dir = here;
|
|
5628
|
+
for (let i = 0; i < 6; i++) {
|
|
5629
|
+
const pkgPath = join7(dir, "package.json");
|
|
5630
|
+
try {
|
|
5631
|
+
const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
|
|
5632
|
+
if (pkg?.name === "@cjhyy/code-shell") {
|
|
5633
|
+
const candidate = join7(dir, "skills-builtin");
|
|
5634
|
+
return existsSync10(candidate) ? candidate : null;
|
|
5635
|
+
}
|
|
5636
|
+
} catch {
|
|
5637
|
+
}
|
|
5638
|
+
const parent = dirname5(dir);
|
|
5639
|
+
if (parent === dir) break;
|
|
5640
|
+
dir = parent;
|
|
5641
|
+
}
|
|
5642
|
+
return null;
|
|
5643
|
+
}
|
|
5034
5644
|
function parseSkillFile(filePath) {
|
|
5035
5645
|
try {
|
|
5036
5646
|
const raw = readFileSync5(filePath, "utf-8");
|
|
@@ -5137,6 +5747,13 @@ The default job in this preset is software engineering in the current working di
|
|
|
5137
5747
|
- Do not propose changes to code you haven't read. If a user asks about or wants you to modify a file, read it first. Understand existing code before suggesting modifications.
|
|
5138
5748
|
- Read code before editing it, prefer modifying existing files, and avoid unrelated refactors.
|
|
5139
5749
|
|
|
5750
|
+
## Investigation has a budget \u2014 looking is not acting
|
|
5751
|
+
- Read, search, and investigate freely, but treat read-only exploration as a means to action, not an end in itself. After about 3 read-only tool calls (Read/Grep/Glob) on the same question, you should either (a) make a change, (b) run a command with side effects (Bash, debug log, repro), or (c) ask the user a specific question. Continuing to read more files usually means your current strategy isn't working \u2014 change strategy instead.
|
|
5752
|
+
- Never re-read a file (or the same line range of a file) you've already read in this conversation. The content is already in your context. If you've re-derived the same conclusion twice from the same code, the answer isn't in static reading \u2014 switch to runtime verification.
|
|
5753
|
+
- For bug reports where static analysis hasn't pinpointed the cause in 2-3 reads, the right next step is almost always to add a debug log line and ask the user to reproduce, or to inspect runtime state \u2014 not to read more files.
|
|
5754
|
+
- Don't narrate the same code path twice in your reasoning. If you've already traced "input X \u2192 parser Y \u2192 handler Z" once and concluded "this looks correct", stop. Do not re-trace it from a different angle hoping for a different answer. Trust your first analysis and move to verification.
|
|
5755
|
+
- These rules are enforced at runtime by an investigation guard. A second read of the same target prepends a \`<system-reminder>\` to that tool's result; a third read of the same target is hard-blocked with an error. Four or more consecutive read-only calls without a side-effecting action also surface a reminder, and several consecutive turns of read-only work with no text update to the user surface another. When you see one of these reminders, treat it as a signal that your current strategy isn't producing new information \u2014 switch to a side-effecting verification (Bash, debug log, repro, edit) or ask the user a specific question rather than reading more.
|
|
5756
|
+
|
|
5140
5757
|
## Code quality discipline
|
|
5141
5758
|
- Don't add features, refactor code, or make "improvements" beyond what was asked. A bug fix doesn't need surrounding code cleaned up. A simple feature doesn't need extra configurability. Don't add docstrings, comments, or type annotations to code you didn't change. Only add comments where the logic isn't self-evident.
|
|
5142
5759
|
- Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs). Don't use feature flags or backwards-compatibility shims when you can just change the code.
|
|
@@ -5158,6 +5775,7 @@ The default job in this preset is software engineering in the current working di
|
|
|
5158
5775
|
// src/prompt/sections/tone.md
|
|
5159
5776
|
var tone_default = `# Tone and output
|
|
5160
5777
|
- Only use emojis if the user explicitly requests it.
|
|
5778
|
+
- Match the language of the user's latest message in your reply; keep code, paths, and identifiers in their original form.
|
|
5161
5779
|
- Be concise, direct, and collaborative. Your responses should be short.
|
|
5162
5780
|
- Do not use a colon before tool calls. Your tool calls may not be shown directly in the output, so text like "Let me read the file:" followed by a read tool call should just be "Let me read the file." with a period.
|
|
5163
5781
|
- Lead with the answer or action, not the reasoning. Skip filler words, preamble, and unnecessary transitions. Do not restate what the user said.
|
|
@@ -5480,6 +6098,124 @@ ${toolLines.join("\n\n")}`;
|
|
|
5480
6098
|
// src/engine/engine.ts
|
|
5481
6099
|
init_esm_shims();
|
|
5482
6100
|
|
|
6101
|
+
// src/tool-system/investigation-guard.ts
|
|
6102
|
+
init_esm_shims();
|
|
6103
|
+
var READ_ONLY_TOOLS = /* @__PURE__ */ new Set(["Read", "Grep", "Glob", "WebFetch", "WebSearch", "ToolSearch"]);
|
|
6104
|
+
var READ_BUDGET = 3;
|
|
6105
|
+
var SILENT_TURN_BUDGET = 3;
|
|
6106
|
+
var DEDUPE_OFFSET_BUCKET = 50;
|
|
6107
|
+
var READ_HISTORY_MAX = 512;
|
|
6108
|
+
var InvestigationGuard = class {
|
|
6109
|
+
readHistory = /* @__PURE__ */ new Map();
|
|
6110
|
+
consecutiveReads = 0;
|
|
6111
|
+
silentTurns = 0;
|
|
6112
|
+
turnHasText = false;
|
|
6113
|
+
turnHasSideEffect = false;
|
|
6114
|
+
lastReminderSilent = -1;
|
|
6115
|
+
/**
|
|
6116
|
+
* Soft mode replaces hard blocks (3rd-read dedupe) with a stronger
|
|
6117
|
+
* reminder. Used in headless runs (`code-shell run …`) where there is
|
|
6118
|
+
* no human to retry the task, so an unattended automation should not
|
|
6119
|
+
* be killed by the guard — it should still get the signal to change
|
|
6120
|
+
* strategy, but the tool call goes through.
|
|
6121
|
+
*/
|
|
6122
|
+
softMode = false;
|
|
6123
|
+
setSoftMode(soft) {
|
|
6124
|
+
this.softMode = soft;
|
|
6125
|
+
}
|
|
6126
|
+
preToolCheck(call) {
|
|
6127
|
+
const tool = call.toolName;
|
|
6128
|
+
if (!READ_ONLY_TOOLS.has(tool)) {
|
|
6129
|
+
if (this.isMutatingTool(tool)) {
|
|
6130
|
+
this.consecutiveReads = 0;
|
|
6131
|
+
this.turnHasSideEffect = true;
|
|
6132
|
+
}
|
|
6133
|
+
return void 0;
|
|
6134
|
+
}
|
|
6135
|
+
const key = this.buildKey(call);
|
|
6136
|
+
if (!key) return void 0;
|
|
6137
|
+
const hits = (this.readHistory.get(key.signature) ?? 0) + 1;
|
|
6138
|
+
this.readHistory.set(key.signature, hits);
|
|
6139
|
+
if (this.readHistory.size > READ_HISTORY_MAX) {
|
|
6140
|
+
const oldest = this.readHistory.keys().next().value;
|
|
6141
|
+
if (oldest !== void 0) this.readHistory.delete(oldest);
|
|
6142
|
+
}
|
|
6143
|
+
this.consecutiveReads += 1;
|
|
6144
|
+
const prependParts = [];
|
|
6145
|
+
if (hits >= 3) {
|
|
6146
|
+
const message = `Investigation guard: ${tool} on this exact target has been called ${hits} times in this session. Re-reading the same content will not produce new information. Switch strategy: run a command with side effects (Bash, debug log, repro), make a code change, or ask the user a specific question. If you genuinely need this content, summarize what you already know about it from prior reads instead of re-fetching.`;
|
|
6147
|
+
if (this.softMode) {
|
|
6148
|
+
prependParts.push(`<system-reminder>${message}</system-reminder>`);
|
|
6149
|
+
} else {
|
|
6150
|
+
return { block: message };
|
|
6151
|
+
}
|
|
6152
|
+
}
|
|
6153
|
+
if (hits === 2) {
|
|
6154
|
+
prependParts.push(
|
|
6155
|
+
`<system-reminder>Investigation guard: you already read this target once in this session. Re-reading rarely yields new information. After this result, change strategy (Bash side-effect, edit, or ask user) rather than reading further.</system-reminder>`
|
|
6156
|
+
);
|
|
6157
|
+
}
|
|
6158
|
+
if (this.consecutiveReads > READ_BUDGET) {
|
|
6159
|
+
prependParts.push(
|
|
6160
|
+
`<system-reminder>Investigation guard: ${this.consecutiveReads} consecutive read-only calls with no action taken. Per coding.md, change strategy now \u2014 make a code change, run a command with side effects, or ask the user.</system-reminder>`
|
|
6161
|
+
);
|
|
6162
|
+
}
|
|
6163
|
+
return prependParts.length ? { prepend: prependParts.join("\n") } : void 0;
|
|
6164
|
+
}
|
|
6165
|
+
noteText(text) {
|
|
6166
|
+
if (text && text.trim().length > 0) this.turnHasText = true;
|
|
6167
|
+
}
|
|
6168
|
+
noteToolResult(_call, _result) {
|
|
6169
|
+
}
|
|
6170
|
+
turnEnded(turnNumber) {
|
|
6171
|
+
if (!this.turnHasText && !this.turnHasSideEffect) {
|
|
6172
|
+
this.silentTurns += 1;
|
|
6173
|
+
} else {
|
|
6174
|
+
this.silentTurns = 0;
|
|
6175
|
+
}
|
|
6176
|
+
const wasSilent = this.silentTurns;
|
|
6177
|
+
this.turnHasText = false;
|
|
6178
|
+
this.turnHasSideEffect = false;
|
|
6179
|
+
if (wasSilent >= SILENT_TURN_BUDGET && turnNumber !== this.lastReminderSilent) {
|
|
6180
|
+
this.lastReminderSilent = turnNumber;
|
|
6181
|
+
return `<system-reminder>Investigation guard: you have taken ${wasSilent} consecutive turns of read-only investigation without any text update to the user or side-effecting action. Either surface a status update (what have you ruled out, what's your current hypothesis?), or change tactic \u2014 verify at runtime (logs, repro, Bash) instead of reading further.</system-reminder>`;
|
|
6182
|
+
}
|
|
6183
|
+
return void 0;
|
|
6184
|
+
}
|
|
6185
|
+
isMutatingTool(name) {
|
|
6186
|
+
if (name === "Bash") return true;
|
|
6187
|
+
if (name === "Edit" || name === "Write" || name === "NotebookEdit") return true;
|
|
6188
|
+
if (name === "AskUserQuestion") return true;
|
|
6189
|
+
if (name === "TaskCreate" || name === "TaskUpdate") return false;
|
|
6190
|
+
return false;
|
|
6191
|
+
}
|
|
6192
|
+
buildKey(call) {
|
|
6193
|
+
const tool = call.toolName;
|
|
6194
|
+
const args = call.args ?? {};
|
|
6195
|
+
if (tool === "Read") {
|
|
6196
|
+
const fp = typeof args.file_path === "string" ? args.file_path : "";
|
|
6197
|
+
if (!fp) return void 0;
|
|
6198
|
+
const off = typeof args.offset === "number" ? args.offset : 0;
|
|
6199
|
+
const bucket = Math.floor(off / DEDUPE_OFFSET_BUCKET);
|
|
6200
|
+
return { tool, signature: `Read::${fp}::${bucket}` };
|
|
6201
|
+
}
|
|
6202
|
+
if (tool === "Grep") {
|
|
6203
|
+
const pat = typeof args.pattern === "string" ? args.pattern : "";
|
|
6204
|
+
const path = typeof args.path === "string" ? args.path : "";
|
|
6205
|
+
const glob2 = typeof args.glob === "string" ? args.glob : "";
|
|
6206
|
+
if (!pat) return void 0;
|
|
6207
|
+
return { tool, signature: `Grep::${pat}::${path}::${glob2}` };
|
|
6208
|
+
}
|
|
6209
|
+
if (tool === "Glob") {
|
|
6210
|
+
const pat = typeof args.pattern === "string" ? args.pattern : "";
|
|
6211
|
+
const path = typeof args.path === "string" ? args.path : "";
|
|
6212
|
+
if (!pat) return void 0;
|
|
6213
|
+
return { tool, signature: `Glob::${pat}::${path}` };
|
|
6214
|
+
}
|
|
6215
|
+
return void 0;
|
|
6216
|
+
}
|
|
6217
|
+
};
|
|
6218
|
+
|
|
5483
6219
|
// src/engine/model-facade.ts
|
|
5484
6220
|
init_esm_shims();
|
|
5485
6221
|
|
|
@@ -5521,6 +6257,11 @@ function addOutputTokens(n) {
|
|
|
5521
6257
|
}
|
|
5522
6258
|
|
|
5523
6259
|
// src/engine/model-facade.ts
|
|
6260
|
+
var _reqSeq = 0;
|
|
6261
|
+
function nextReqId() {
|
|
6262
|
+
_reqSeq += 1;
|
|
6263
|
+
return `r${_reqSeq.toString(36)}`;
|
|
6264
|
+
}
|
|
5524
6265
|
var ModelFacade = class {
|
|
5525
6266
|
constructor(client, transcript) {
|
|
5526
6267
|
this.client = client;
|
|
@@ -5529,34 +6270,54 @@ var ModelFacade = class {
|
|
|
5529
6270
|
async call(systemPrompt, messages, tools, onStream, signal) {
|
|
5530
6271
|
const startMs = Date.now();
|
|
5531
6272
|
const msgCount = messages.length;
|
|
5532
|
-
const
|
|
5533
|
-
|
|
5534
|
-
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
5543
|
-
type: "tool_use_start",
|
|
5544
|
-
toolCall: {
|
|
5545
|
-
id: chunk.toolCall.id ?? "",
|
|
5546
|
-
toolName: chunk.toolCall.toolName ?? "",
|
|
5547
|
-
args: chunk.toolCall.args ?? {}
|
|
5548
|
-
}
|
|
5549
|
-
});
|
|
5550
|
-
} else if (chunk.type === "tool_use_delta" && chunk.toolCall?.id) {
|
|
5551
|
-
onStream({
|
|
5552
|
-
type: "tool_use_args_delta",
|
|
5553
|
-
toolCallId: chunk.toolCall.id,
|
|
5554
|
-
args: chunk.toolCall.args ?? {}
|
|
5555
|
-
});
|
|
5556
|
-
}
|
|
6273
|
+
const sid = getCurrentSid();
|
|
6274
|
+
const reqId = nextReqId();
|
|
6275
|
+
recordLLMRequest(
|
|
6276
|
+
sid,
|
|
6277
|
+
{
|
|
6278
|
+
provider: this.client.provider ?? "?",
|
|
6279
|
+
model: this.client.model ?? "?",
|
|
6280
|
+
stream: true,
|
|
6281
|
+
messages,
|
|
6282
|
+
tools,
|
|
6283
|
+
systemPrompt
|
|
5557
6284
|
},
|
|
5558
|
-
|
|
5559
|
-
|
|
6285
|
+
reqId
|
|
6286
|
+
);
|
|
6287
|
+
let response;
|
|
6288
|
+
try {
|
|
6289
|
+
response = await this.client.createMessage({
|
|
6290
|
+
systemPrompt,
|
|
6291
|
+
messages,
|
|
6292
|
+
tools,
|
|
6293
|
+
stream: true,
|
|
6294
|
+
onChunk: (chunk) => {
|
|
6295
|
+
if (!onStream) return;
|
|
6296
|
+
if (chunk.type === "text" && chunk.text) {
|
|
6297
|
+
onStream({ type: "text_delta", text: chunk.text });
|
|
6298
|
+
} else if (chunk.type === "tool_use_start" && chunk.toolCall) {
|
|
6299
|
+
onStream({
|
|
6300
|
+
type: "tool_use_start",
|
|
6301
|
+
toolCall: {
|
|
6302
|
+
id: chunk.toolCall.id ?? "",
|
|
6303
|
+
toolName: chunk.toolCall.toolName ?? "",
|
|
6304
|
+
args: chunk.toolCall.args ?? {}
|
|
6305
|
+
}
|
|
6306
|
+
});
|
|
6307
|
+
} else if (chunk.type === "tool_use_delta" && chunk.toolCall?.id) {
|
|
6308
|
+
onStream({
|
|
6309
|
+
type: "tool_use_args_delta",
|
|
6310
|
+
toolCallId: chunk.toolCall.id,
|
|
6311
|
+
args: chunk.toolCall.args ?? {}
|
|
6312
|
+
});
|
|
6313
|
+
}
|
|
6314
|
+
},
|
|
6315
|
+
signal
|
|
6316
|
+
});
|
|
6317
|
+
} catch (err) {
|
|
6318
|
+
recordLLMError(sid, reqId, err, Date.now() - startMs);
|
|
6319
|
+
throw err;
|
|
6320
|
+
}
|
|
5560
6321
|
const latencyMs = Date.now() - startMs;
|
|
5561
6322
|
logger.info("llm.request", {
|
|
5562
6323
|
stream: true,
|
|
@@ -5567,6 +6328,17 @@ var ModelFacade = class {
|
|
|
5567
6328
|
textLen: response.text?.length ?? 0,
|
|
5568
6329
|
usage: response.usage
|
|
5569
6330
|
});
|
|
6331
|
+
recordLLMResponse(
|
|
6332
|
+
sid,
|
|
6333
|
+
{
|
|
6334
|
+
text: response.text,
|
|
6335
|
+
toolCalls: response.toolCalls,
|
|
6336
|
+
stopReason: response.stopReason,
|
|
6337
|
+
usage: response.usage,
|
|
6338
|
+
durationMs: latencyMs
|
|
6339
|
+
},
|
|
6340
|
+
reqId
|
|
6341
|
+
);
|
|
5570
6342
|
this.recordUsage(response, latencyMs);
|
|
5571
6343
|
this.recordResponse(response);
|
|
5572
6344
|
return response;
|
|
@@ -5577,13 +6349,33 @@ var ModelFacade = class {
|
|
|
5577
6349
|
async callWithoutStreaming(systemPrompt, messages, tools, signal) {
|
|
5578
6350
|
const startMs = Date.now();
|
|
5579
6351
|
const msgCount = messages.length;
|
|
5580
|
-
const
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
6352
|
+
const sid = getCurrentSid();
|
|
6353
|
+
const reqId = nextReqId();
|
|
6354
|
+
recordLLMRequest(
|
|
6355
|
+
sid,
|
|
6356
|
+
{
|
|
6357
|
+
provider: this.client.provider ?? "?",
|
|
6358
|
+
model: this.client.model ?? "?",
|
|
6359
|
+
stream: false,
|
|
6360
|
+
messages,
|
|
6361
|
+
tools,
|
|
6362
|
+
systemPrompt
|
|
6363
|
+
},
|
|
6364
|
+
reqId
|
|
6365
|
+
);
|
|
6366
|
+
let response;
|
|
6367
|
+
try {
|
|
6368
|
+
response = await this.client.createMessage({
|
|
6369
|
+
systemPrompt,
|
|
6370
|
+
messages,
|
|
6371
|
+
tools,
|
|
6372
|
+
stream: false,
|
|
6373
|
+
signal
|
|
6374
|
+
});
|
|
6375
|
+
} catch (err) {
|
|
6376
|
+
recordLLMError(sid, reqId, err, Date.now() - startMs);
|
|
6377
|
+
throw err;
|
|
6378
|
+
}
|
|
5587
6379
|
const latencyMs = Date.now() - startMs;
|
|
5588
6380
|
logger.info("llm.request", {
|
|
5589
6381
|
stream: false,
|
|
@@ -5594,6 +6386,17 @@ var ModelFacade = class {
|
|
|
5594
6386
|
textLen: response.text?.length ?? 0,
|
|
5595
6387
|
usage: response.usage
|
|
5596
6388
|
});
|
|
6389
|
+
recordLLMResponse(
|
|
6390
|
+
sid,
|
|
6391
|
+
{
|
|
6392
|
+
text: response.text,
|
|
6393
|
+
toolCalls: response.toolCalls,
|
|
6394
|
+
stopReason: response.stopReason,
|
|
6395
|
+
usage: response.usage,
|
|
6396
|
+
durationMs: latencyMs
|
|
6397
|
+
},
|
|
6398
|
+
reqId
|
|
6399
|
+
);
|
|
5597
6400
|
this.recordUsage(response, latencyMs);
|
|
5598
6401
|
this.recordResponse(response);
|
|
5599
6402
|
return response;
|
|
@@ -5781,7 +6584,7 @@ var TurnLoop = class {
|
|
|
5781
6584
|
response = await this.callModelWithFallback(messages);
|
|
5782
6585
|
} catch (err) {
|
|
5783
6586
|
if (err instanceof ContextLimitError) {
|
|
5784
|
-
const { dropOldestRounds } = await import("./compaction-
|
|
6587
|
+
const { dropOldestRounds } = await import("./compaction-6VU2T643.js");
|
|
5785
6588
|
let recovered = false;
|
|
5786
6589
|
for (let retry = 1; retry <= 3; retry++) {
|
|
5787
6590
|
tlog.warn("turn.ptl_recovery", { cat: "turn", retry, roundsToDrop: retry });
|
|
@@ -5811,6 +6614,12 @@ var TurnLoop = class {
|
|
|
5811
6614
|
return { text: finalText, reason: "model_error", messages };
|
|
5812
6615
|
}
|
|
5813
6616
|
}
|
|
6617
|
+
if (response.usage?.promptTokens !== void 0) {
|
|
6618
|
+
this.config.onStream?.({
|
|
6619
|
+
type: "usage_update",
|
|
6620
|
+
promptTokens: response.usage.promptTokens
|
|
6621
|
+
});
|
|
6622
|
+
}
|
|
5814
6623
|
if (response.usage?.promptTokens !== void 0) {
|
|
5815
6624
|
this.deps.contextManager.recordActualUsage(response.usage.promptTokens, messages.length);
|
|
5816
6625
|
}
|
|
@@ -5845,6 +6654,12 @@ var TurnLoop = class {
|
|
|
5845
6654
|
}
|
|
5846
6655
|
response = { ...response, text: combinedText };
|
|
5847
6656
|
}
|
|
6657
|
+
if (response.usage?.promptTokens !== void 0) {
|
|
6658
|
+
this.config.onStream?.({
|
|
6659
|
+
type: "usage_update",
|
|
6660
|
+
promptTokens: response.usage.promptTokens
|
|
6661
|
+
});
|
|
6662
|
+
}
|
|
5848
6663
|
if (this.config.signal?.aborted) {
|
|
5849
6664
|
return { text: finalText, reason: "aborted_streaming", messages };
|
|
5850
6665
|
}
|
|
@@ -5903,7 +6718,7 @@ var TurnLoop = class {
|
|
|
5903
6718
|
this.config.onStream?.({ type: "tool_result", result });
|
|
5904
6719
|
}
|
|
5905
6720
|
if (this.config.onStream) {
|
|
5906
|
-
import("./tool-summary-
|
|
6721
|
+
import("./tool-summary-NP5CDBKG.js").then(({ generateToolUseSummary }) => {
|
|
5907
6722
|
if (!this.deps.model.summarize) return;
|
|
5908
6723
|
generateToolUseSummary(toolCalls, results, this.deps.model.summarize).then((summary) => {
|
|
5909
6724
|
if (summary) {
|
|
@@ -5938,12 +6753,22 @@ var TurnLoop = class {
|
|
|
5938
6753
|
content: "<system-reminder>You are approaching the token budget limit. Please start wrapping up your work and provide a summary.</system-reminder>"
|
|
5939
6754
|
});
|
|
5940
6755
|
}
|
|
6756
|
+
const guard = this.deps.toolExecutor.getInvestigationGuard();
|
|
6757
|
+
if (guard) {
|
|
6758
|
+
guard.noteText(response.text);
|
|
6759
|
+
const turnReminder = guard.turnEnded(this.turnCount);
|
|
6760
|
+
if (turnReminder) {
|
|
6761
|
+
messages.push({ role: "user", content: turnReminder });
|
|
6762
|
+
tlog.info("guard.silent_turn", { cat: "guard", turn: this.turnCount });
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
5941
6765
|
await this.deps.hooks.emit("on_turn_end", {
|
|
5942
6766
|
turnNumber: this.turnCount,
|
|
5943
6767
|
hasToolUse: true,
|
|
5944
6768
|
toolCallCount: toolCalls.length
|
|
5945
6769
|
});
|
|
5946
6770
|
this.deps.transcript.appendTurnBoundary();
|
|
6771
|
+
this.config.onTurnBoundary?.(this.turnCount);
|
|
5947
6772
|
tlog.info("turn.end", {
|
|
5948
6773
|
cat: "turn",
|
|
5949
6774
|
duration_ms: Date.now() - turnStartedAt,
|
|
@@ -6112,9 +6937,83 @@ var TurnLoop = class {
|
|
|
6112
6937
|
}
|
|
6113
6938
|
};
|
|
6114
6939
|
|
|
6940
|
+
// src/llm/provider-catalog.ts
|
|
6941
|
+
init_esm_shims();
|
|
6942
|
+
var ProviderCatalog = class {
|
|
6943
|
+
byKey = /* @__PURE__ */ new Map();
|
|
6944
|
+
constructor(entries) {
|
|
6945
|
+
for (const e of entries ?? []) this.byKey.set(e.key, e);
|
|
6946
|
+
}
|
|
6947
|
+
list() {
|
|
6948
|
+
return [...this.byKey.values()];
|
|
6949
|
+
}
|
|
6950
|
+
get(key) {
|
|
6951
|
+
return this.byKey.get(key);
|
|
6952
|
+
}
|
|
6953
|
+
has(key) {
|
|
6954
|
+
return this.byKey.has(key);
|
|
6955
|
+
}
|
|
6956
|
+
add(entry) {
|
|
6957
|
+
if (this.byKey.has(entry.key)) {
|
|
6958
|
+
throw new Error(`duplicate provider key: ${entry.key}`);
|
|
6959
|
+
}
|
|
6960
|
+
this.byKey.set(entry.key, entry);
|
|
6961
|
+
}
|
|
6962
|
+
update(key, patch) {
|
|
6963
|
+
const cur = this.byKey.get(key);
|
|
6964
|
+
if (!cur) throw new Error(`no such provider: ${key}`);
|
|
6965
|
+
this.byKey.set(key, { ...cur, ...patch, key: cur.key });
|
|
6966
|
+
}
|
|
6967
|
+
remove(key, opts) {
|
|
6968
|
+
if (opts.referencedBy.length) {
|
|
6969
|
+
throw new Error(
|
|
6970
|
+
`provider ${key} still referenced by models: ${opts.referencedBy.join(", ")}`
|
|
6971
|
+
);
|
|
6972
|
+
}
|
|
6973
|
+
this.byKey.delete(key);
|
|
6974
|
+
}
|
|
6975
|
+
/** Compute an unused key for a new provider, given a desired base. */
|
|
6976
|
+
deriveKey(base) {
|
|
6977
|
+
if (!this.byKey.has(base)) return base;
|
|
6978
|
+
for (let i = 2; ; i++) {
|
|
6979
|
+
const candidate = `${base}-${i}`;
|
|
6980
|
+
if (!this.byKey.has(candidate)) return candidate;
|
|
6981
|
+
}
|
|
6982
|
+
}
|
|
6983
|
+
};
|
|
6984
|
+
|
|
6985
|
+
// src/utils/task-sanitizer.ts
|
|
6986
|
+
init_esm_shims();
|
|
6987
|
+
var ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
6988
|
+
var BOX_RE = /[─━│┃┌┐└┘├┤┬┴┼╔╗╚╝═║╠╣╦╩╬▀▄█▌▐░▒▓·]+/g;
|
|
6989
|
+
function detectPastedNoise(task) {
|
|
6990
|
+
const original = task.length;
|
|
6991
|
+
if (original < 10) return { isNoise: false, reason: "short", cleaned: task };
|
|
6992
|
+
const stripped = task.replace(ANSI_RE, "").replace(BOX_RE, "").replace(/\r/g, "\n").trim();
|
|
6993
|
+
if (stripped.length === 0) {
|
|
6994
|
+
return { isNoise: true, reason: "all ANSI/box chars", cleaned: stripped };
|
|
6995
|
+
}
|
|
6996
|
+
const signalRatio = stripped.length / original;
|
|
6997
|
+
if (signalRatio < 0.3 && original > 100) {
|
|
6998
|
+
return {
|
|
6999
|
+
isNoise: true,
|
|
7000
|
+
reason: `signal/noise ratio ${(signalRatio * 100).toFixed(0)}%`,
|
|
7001
|
+
cleaned: stripped
|
|
7002
|
+
};
|
|
7003
|
+
}
|
|
7004
|
+
return { isNoise: false, reason: "ok", cleaned: stripped };
|
|
7005
|
+
}
|
|
7006
|
+
|
|
6115
7007
|
// src/engine/engine.ts
|
|
6116
|
-
import { join as
|
|
7008
|
+
import { join as join8 } from "path";
|
|
6117
7009
|
import { homedir as homedir4 } from "os";
|
|
7010
|
+
import {
|
|
7011
|
+
existsSync as existsSync11,
|
|
7012
|
+
mkdirSync as mkdirSync4,
|
|
7013
|
+
readFileSync as readFileSync6,
|
|
7014
|
+
renameSync,
|
|
7015
|
+
writeFileSync as writeFileSync4
|
|
7016
|
+
} from "fs";
|
|
6118
7017
|
var Engine = class _Engine {
|
|
6119
7018
|
constructor(config) {
|
|
6120
7019
|
this.config = config;
|
|
@@ -6132,28 +7031,7 @@ var Engine = class _Engine {
|
|
|
6132
7031
|
}
|
|
6133
7032
|
this.sessionManager = new SessionManager(config.sessionStorageDir);
|
|
6134
7033
|
this.modelPool = new ModelPool();
|
|
6135
|
-
|
|
6136
|
-
const sm = new SettingsManager(config.cwd);
|
|
6137
|
-
const settings = sm.get();
|
|
6138
|
-
if (settings.models?.length) {
|
|
6139
|
-
for (const m of settings.models) {
|
|
6140
|
-
this.modelPool.register({
|
|
6141
|
-
key: m.key,
|
|
6142
|
-
label: m.label,
|
|
6143
|
-
provider: m.provider,
|
|
6144
|
-
model: m.model,
|
|
6145
|
-
baseUrl: m.baseUrl,
|
|
6146
|
-
apiKey: m.apiKey,
|
|
6147
|
-
maxOutputTokens: m.maxOutputTokens,
|
|
6148
|
-
maxContextTokens: m.maxContextTokens
|
|
6149
|
-
});
|
|
6150
|
-
}
|
|
6151
|
-
const currentModel = config.llm.model;
|
|
6152
|
-
const match = settings.models.find((m) => m.model === currentModel);
|
|
6153
|
-
if (match) this.modelPool.switch(match.key);
|
|
6154
|
-
}
|
|
6155
|
-
} catch {
|
|
6156
|
-
}
|
|
7034
|
+
this.populateModelPoolFromSettings();
|
|
6157
7035
|
}
|
|
6158
7036
|
preset;
|
|
6159
7037
|
toolRegistry;
|
|
@@ -6172,10 +7050,99 @@ var Engine = class _Engine {
|
|
|
6172
7050
|
lastSessionId;
|
|
6173
7051
|
compactedMessagesBySession = /* @__PURE__ */ new Map();
|
|
6174
7052
|
activePermission;
|
|
7053
|
+
/** Public accessor so UI/clients can read the resolved per-model window. */
|
|
7054
|
+
get maxContextTokens() {
|
|
7055
|
+
return this.resolveMaxContextTokens();
|
|
7056
|
+
}
|
|
6175
7057
|
resolveMaxContextTokens() {
|
|
6176
7058
|
const modelEntry = this.modelPool.get();
|
|
6177
7059
|
return modelEntry?.maxContextTokens ?? this.config.maxContextTokens ?? 2e5;
|
|
6178
7060
|
}
|
|
7061
|
+
/**
|
|
7062
|
+
* Load models[] / providers[] from settings into the active ModelPool and
|
|
7063
|
+
* resync this.config.llm with the matching entry. Called from the ctor and
|
|
7064
|
+
* from reloadModelPool() (e.g. after onboarding writes new entries to disk).
|
|
7065
|
+
*/
|
|
7066
|
+
populateModelPoolFromSettings() {
|
|
7067
|
+
try {
|
|
7068
|
+
const sm = this.getSettingsManager();
|
|
7069
|
+
sm.invalidate();
|
|
7070
|
+
const settings = sm.get();
|
|
7071
|
+
if (settings.models?.length) {
|
|
7072
|
+
for (const m of settings.models) {
|
|
7073
|
+
this.modelPool.register({
|
|
7074
|
+
key: m.key,
|
|
7075
|
+
label: m.label,
|
|
7076
|
+
provider: m.provider ?? "",
|
|
7077
|
+
model: m.model,
|
|
7078
|
+
baseUrl: m.baseUrl,
|
|
7079
|
+
apiKey: m.apiKey,
|
|
7080
|
+
maxOutputTokens: m.maxOutputTokens,
|
|
7081
|
+
maxContextTokens: m.maxContextTokens,
|
|
7082
|
+
providerKey: m.providerKey
|
|
7083
|
+
});
|
|
7084
|
+
}
|
|
7085
|
+
if (settings.providers?.length) {
|
|
7086
|
+
this.modelPool.setProviderCatalog(
|
|
7087
|
+
new ProviderCatalog(settings.providers)
|
|
7088
|
+
);
|
|
7089
|
+
}
|
|
7090
|
+
this.modelPool.setCacheDir(defaultCacheDir());
|
|
7091
|
+
this.modelPool.reloadCachedContextWindows();
|
|
7092
|
+
const activeKey = settings.activeKey;
|
|
7093
|
+
let match;
|
|
7094
|
+
if (activeKey) {
|
|
7095
|
+
match = settings.models.find((m) => m.key === activeKey);
|
|
7096
|
+
}
|
|
7097
|
+
if (!match) {
|
|
7098
|
+
const currentModel = this.config.llm.model;
|
|
7099
|
+
match = settings.models.find(
|
|
7100
|
+
(m) => m.model === currentModel || currentModel && m.model?.endsWith(`/${currentModel}`)
|
|
7101
|
+
);
|
|
7102
|
+
}
|
|
7103
|
+
if (match) {
|
|
7104
|
+
const entry = this.modelPool.switch(match.key);
|
|
7105
|
+
this.config = {
|
|
7106
|
+
...this.config,
|
|
7107
|
+
llm: this.modelPool.toLLMConfig(entry, this.config.llm)
|
|
7108
|
+
};
|
|
7109
|
+
}
|
|
7110
|
+
} else if (this.config.llm.apiKey) {
|
|
7111
|
+
this.autoPopulatePool(this.config.llm.apiKey, this.config.llm.baseUrl);
|
|
7112
|
+
}
|
|
7113
|
+
} catch {
|
|
7114
|
+
}
|
|
7115
|
+
}
|
|
7116
|
+
/**
|
|
7117
|
+
* Re-read settings and refresh the model pool. Used after onboarding /login
|
|
7118
|
+
* writes new providers[] / models[] to disk so the running engine picks them
|
|
7119
|
+
* up without a process restart. Existing pool entries are kept (re-registering
|
|
7120
|
+
* the same key overwrites them), so callers don't need to clear first.
|
|
7121
|
+
*/
|
|
7122
|
+
reloadModelPool() {
|
|
7123
|
+
this.populateModelPoolFromSettings();
|
|
7124
|
+
}
|
|
7125
|
+
/**
|
|
7126
|
+
* Auto-populate the model pool when settings.models[] is empty but
|
|
7127
|
+
* the user has configured an API key. Detects the provider from the
|
|
7128
|
+
* key prefix / baseUrl and registers all its known models.
|
|
7129
|
+
*/
|
|
7130
|
+
autoPopulatePool(apiKey, baseUrl) {
|
|
7131
|
+
const provider = detectProviderFromApiKey(apiKey, baseUrl);
|
|
7132
|
+
if (!provider) return;
|
|
7133
|
+
const entries = buildModelPool(provider, apiKey);
|
|
7134
|
+
for (const e of entries) {
|
|
7135
|
+
this.modelPool.register(e);
|
|
7136
|
+
}
|
|
7137
|
+
const defaultEntry = provider.defaultModel ? entries.find((e) => e.model === provider.defaultModel) : entries[0];
|
|
7138
|
+
if (defaultEntry) {
|
|
7139
|
+
const entry = this.modelPool.switch(defaultEntry.key);
|
|
7140
|
+
this.config = {
|
|
7141
|
+
...this.config,
|
|
7142
|
+
llm: this.modelPool.toLLMConfig(entry, this.config.llm)
|
|
7143
|
+
};
|
|
7144
|
+
}
|
|
7145
|
+
}
|
|
6179
7146
|
/**
|
|
6180
7147
|
* Register a custom tool (from product adapter) into the tool registry.
|
|
6181
7148
|
* Must be called before run().
|
|
@@ -6196,6 +7163,20 @@ var Engine = class _Engine {
|
|
|
6196
7163
|
*/
|
|
6197
7164
|
async run(task, options) {
|
|
6198
7165
|
const cwd = this.config.cwd ?? process.cwd();
|
|
7166
|
+
const noise = detectPastedNoise(task);
|
|
7167
|
+
if (noise.isNoise) {
|
|
7168
|
+
const hint = `Your input looks like pasted terminal output (${noise.reason}). I didn't start a task. What would you like to ask?` + (noise.cleaned ? `
|
|
7169
|
+
|
|
7170
|
+
Extracted text: ${noise.cleaned.slice(0, 200)}` : "");
|
|
7171
|
+
logger.info("engine.run.rejected", { reason: noise.reason, len: task.length });
|
|
7172
|
+
return {
|
|
7173
|
+
text: hint,
|
|
7174
|
+
reason: "completed",
|
|
7175
|
+
sessionId: options?.sessionId ?? "noise-rejected",
|
|
7176
|
+
turnCount: 0,
|
|
7177
|
+
usage: { promptTokens: 0, completionTokens: 0, totalTokens: 0 }
|
|
7178
|
+
};
|
|
7179
|
+
}
|
|
6199
7180
|
const subAgentSpawner = {
|
|
6200
7181
|
parentStream: options?.onStream,
|
|
6201
7182
|
describe: () => ({
|
|
@@ -6247,6 +7228,7 @@ var Engine = class _Engine {
|
|
|
6247
7228
|
const userMsg = { role: "user", content: task };
|
|
6248
7229
|
messages.push(userMsg);
|
|
6249
7230
|
session.transcript.appendMessage("user", task);
|
|
7231
|
+
this.sessionManager.saveState(session.state);
|
|
6250
7232
|
} else {
|
|
6251
7233
|
session = this.sessionManager.create(cwd, this.config.llm.model, this.config.llm.provider);
|
|
6252
7234
|
messages = [{ role: "user", content: task }];
|
|
@@ -6255,6 +7237,14 @@ var Engine = class _Engine {
|
|
|
6255
7237
|
this.sessionManager.saveState(session.state);
|
|
6256
7238
|
}
|
|
6257
7239
|
logger.setSid(session.state.sessionId);
|
|
7240
|
+
recordSessionStart(session.state.sessionId, {
|
|
7241
|
+
task,
|
|
7242
|
+
cwd,
|
|
7243
|
+
model: this.config.llm.model,
|
|
7244
|
+
provider: this.config.llm.provider,
|
|
7245
|
+
permissionMode: this.config.permissionMode ?? "acceptEdits",
|
|
7246
|
+
resumed: !!options?.sessionId
|
|
7247
|
+
});
|
|
6258
7248
|
options?.onStream?.({ type: "session_started", sessionId: session.state.sessionId });
|
|
6259
7249
|
const llmClientPromise = createLLMClient(this.config.llm);
|
|
6260
7250
|
const mode = this.config.permissionMode ?? "acceptEdits";
|
|
@@ -6268,6 +7258,9 @@ var Engine = class _Engine {
|
|
|
6268
7258
|
});
|
|
6269
7259
|
}
|
|
6270
7260
|
const toolExecutor = new ToolExecutor(this.toolRegistry, permission, this.hooks);
|
|
7261
|
+
const investigationGuard = new InvestigationGuard();
|
|
7262
|
+
if (this.config.headless) investigationGuard.setSoftMode(true);
|
|
7263
|
+
toolExecutor.setInvestigationGuard(investigationGuard);
|
|
6271
7264
|
toolExecutor.setSignal(options?.signal);
|
|
6272
7265
|
toolExecutor.setContext(toolCtx);
|
|
6273
7266
|
const contextManager = new ContextManager({
|
|
@@ -6319,6 +7312,7 @@ var Engine = class _Engine {
|
|
|
6319
7312
|
this.lastSessionId = session.state.sessionId;
|
|
6320
7313
|
this.lastMessages = messages;
|
|
6321
7314
|
contextManager.setTranscriptPath(session.transcript.getFilePath());
|
|
7315
|
+
contextManager.initReplacementStateFromMessages(messages);
|
|
6322
7316
|
contextManager.setSummarizeFn(async (prompt) => {
|
|
6323
7317
|
const summaryResponse = await llmClient.createMessage({
|
|
6324
7318
|
systemPrompt: "You are a conversation summarizer. Be concise and factual.",
|
|
@@ -6338,12 +7332,23 @@ var Engine = class _Engine {
|
|
|
6338
7332
|
systemPrompt: sysPrompt,
|
|
6339
7333
|
messages: [{ role: "user", content: userMsg }],
|
|
6340
7334
|
tools: [],
|
|
6341
|
-
maxTokens: 256
|
|
7335
|
+
maxTokens: 256,
|
|
7336
|
+
recordUsage: false
|
|
7337
|
+
});
|
|
7338
|
+
logger.debug("summarize.call", {
|
|
7339
|
+
sysPromptLen: sysPrompt.length,
|
|
7340
|
+
userMsgLen: userMsg.length,
|
|
7341
|
+
userMsgPreview: userMsg.slice(0, 300),
|
|
7342
|
+
completionLen: resp.text.length,
|
|
7343
|
+
completionPreview: resp.text.slice(0, 300),
|
|
7344
|
+
stopReason: resp.stopReason,
|
|
7345
|
+
promptTokens: resp.usage?.promptTokens,
|
|
7346
|
+
completionTokens: resp.usage?.completionTokens
|
|
6342
7347
|
});
|
|
6343
7348
|
return resp.text;
|
|
6344
7349
|
};
|
|
6345
|
-
const sessionDir =
|
|
6346
|
-
this.config.sessionStorageDir ??
|
|
7350
|
+
const sessionDir = join8(
|
|
7351
|
+
this.config.sessionStorageDir ?? join8(homedir4(), ".code-shell", "sessions"),
|
|
6347
7352
|
session.state.sessionId
|
|
6348
7353
|
);
|
|
6349
7354
|
const fileHistory = FileHistory.loadFromDir(sessionDir);
|
|
@@ -6382,7 +7387,24 @@ var Engine = class _Engine {
|
|
|
6382
7387
|
maxTurns: this.config.maxTurns ?? 100,
|
|
6383
7388
|
maxToolCallsPerTurn: this.config.maxToolCallsPerTurn ?? 10,
|
|
6384
7389
|
onStream: options?.onStream,
|
|
6385
|
-
signal: options?.signal
|
|
7390
|
+
signal: options?.signal,
|
|
7391
|
+
// Heartbeat: flush turnCount + tokens to state.json after every turn
|
|
7392
|
+
// so external observers (other CLI processes, /sid, the session list)
|
|
7393
|
+
// see live progress instead of a stale snapshot from the last
|
|
7394
|
+
// completed run.
|
|
7395
|
+
onTurnBoundary: (turnCount) => {
|
|
7396
|
+
session.state.turnCount = turnCount;
|
|
7397
|
+
const u = modelFacade.getUsage();
|
|
7398
|
+
session.state.tokenUsage = {
|
|
7399
|
+
promptTokens: u.totalPromptTokens,
|
|
7400
|
+
completionTokens: u.totalCompletionTokens,
|
|
7401
|
+
totalTokens: u.totalTokens
|
|
7402
|
+
};
|
|
7403
|
+
if (this.config.costStore) {
|
|
7404
|
+
session.state.costState = this.config.costStore.serialize();
|
|
7405
|
+
}
|
|
7406
|
+
this.sessionManager.saveState(session.state);
|
|
7407
|
+
}
|
|
6386
7408
|
}
|
|
6387
7409
|
);
|
|
6388
7410
|
const result = await turnLoop.run(messages);
|
|
@@ -6397,8 +7419,13 @@ var Engine = class _Engine {
|
|
|
6397
7419
|
turns: turnLoop.currentTurn,
|
|
6398
7420
|
tokens: modelFacade.getUsage().totalTokens
|
|
6399
7421
|
});
|
|
7422
|
+
recordSessionEnd(session.state.sessionId, {
|
|
7423
|
+
reason: result.reason,
|
|
7424
|
+
turns: turnLoop.currentTurn,
|
|
7425
|
+
cost: modelFacade.getUsage()
|
|
7426
|
+
});
|
|
6400
7427
|
session.state.turnCount = turnLoop.currentTurn;
|
|
6401
|
-
session.state.status = result.reason
|
|
7428
|
+
session.state.status = result.reason;
|
|
6402
7429
|
const usage = modelFacade.getUsage();
|
|
6403
7430
|
session.state.tokenUsage = {
|
|
6404
7431
|
promptTokens: usage.totalPromptTokens,
|
|
@@ -6433,15 +7460,67 @@ var Engine = class _Engine {
|
|
|
6433
7460
|
/**
|
|
6434
7461
|
* Switch the active model by pool key. Takes effect on the next run() call.
|
|
6435
7462
|
* Returns the new model entry.
|
|
7463
|
+
*
|
|
7464
|
+
* Persists settings.activeKey (and a legacy settings.model.* mirror) so the
|
|
7465
|
+
* next process startup defaults to the same model — without this, switches
|
|
7466
|
+
* only live in memory and every restart reverts to the previously persisted
|
|
7467
|
+
* activeKey.
|
|
6436
7468
|
*/
|
|
6437
7469
|
switchModel(key) {
|
|
6438
7470
|
const entry = this.modelPool.switch(key);
|
|
6439
|
-
this.config
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
};
|
|
7471
|
+
const nextLlm = this.modelPool.toLLMConfig(entry, this.config.llm);
|
|
7472
|
+
this.config = { ...this.config, llm: nextLlm };
|
|
7473
|
+
this.persistActiveModel(entry, nextLlm);
|
|
6443
7474
|
return entry;
|
|
6444
7475
|
}
|
|
7476
|
+
/**
|
|
7477
|
+
* Write the active model selection to ~/.code-shell/settings.json.
|
|
7478
|
+
*
|
|
7479
|
+
* We mirror into the legacy settings.model.* block (provider/name/apiKey/
|
|
7480
|
+
* baseUrl) because boot paths in cli/main.ts, repl.ts, run.ts still read it.
|
|
7481
|
+
* The mirror uses resolved llm values (not raw entry.*) so credentials that
|
|
7482
|
+
* live on settings.providers[] flow through correctly — entry.apiKey is
|
|
7483
|
+
* undefined when the entry resolves via providerCatalog.
|
|
7484
|
+
*/
|
|
7485
|
+
persistActiveModel(entry, llm) {
|
|
7486
|
+
try {
|
|
7487
|
+
const dir = join8(homedir4(), ".code-shell");
|
|
7488
|
+
const file = join8(dir, "settings.json");
|
|
7489
|
+
mkdirSync4(dir, { recursive: true });
|
|
7490
|
+
let existing = {};
|
|
7491
|
+
if (existsSync11(file)) {
|
|
7492
|
+
try {
|
|
7493
|
+
existing = JSON.parse(readFileSync6(file, "utf-8"));
|
|
7494
|
+
} catch {
|
|
7495
|
+
return;
|
|
7496
|
+
}
|
|
7497
|
+
}
|
|
7498
|
+
const prevModel = typeof existing.model === "object" && existing.model ? existing.model : {};
|
|
7499
|
+
const updated = {
|
|
7500
|
+
...existing,
|
|
7501
|
+
activeKey: entry.key,
|
|
7502
|
+
model: {
|
|
7503
|
+
...prevModel,
|
|
7504
|
+
provider: llm.provider,
|
|
7505
|
+
name: entry.model,
|
|
7506
|
+
apiKey: llm.apiKey,
|
|
7507
|
+
baseUrl: llm.baseUrl
|
|
7508
|
+
}
|
|
7509
|
+
};
|
|
7510
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
7511
|
+
const payload = JSON.stringify(updated, null, 2) + "\n";
|
|
7512
|
+
writeFileSync4(tmp, payload, "utf-8");
|
|
7513
|
+
try {
|
|
7514
|
+
renameSync(tmp, file);
|
|
7515
|
+
} catch {
|
|
7516
|
+
writeFileSync4(file, payload, "utf-8");
|
|
7517
|
+
}
|
|
7518
|
+
} catch (err) {
|
|
7519
|
+
logger.warn(
|
|
7520
|
+
`persistActiveModel failed: ${err.message}`
|
|
7521
|
+
);
|
|
7522
|
+
}
|
|
7523
|
+
}
|
|
6445
7524
|
/** Get the model pool. */
|
|
6446
7525
|
getModelPool() {
|
|
6447
7526
|
return this.modelPool;
|
|
@@ -6463,10 +7542,18 @@ var Engine = class _Engine {
|
|
|
6463
7542
|
* Inject context into a session's transcript without triggering a LLM turn.
|
|
6464
7543
|
* The injected content appears as an assistant message so the LLM can see it
|
|
6465
7544
|
* in subsequent conversations. The transcript auto-flushes to disk.
|
|
7545
|
+
*
|
|
7546
|
+
* Also updates the in-memory compacted message cache so the next
|
|
7547
|
+
* engine.run() call for this session picks up the injected content
|
|
7548
|
+
* instead of a stale snapshot from the previous run.
|
|
6466
7549
|
*/
|
|
6467
7550
|
injectContext(sessionId, content) {
|
|
6468
7551
|
const session = this.sessionManager.resume(sessionId);
|
|
6469
7552
|
session.transcript.appendMessage("assistant", content);
|
|
7553
|
+
const cached = this.compactedMessagesBySession.get(sessionId);
|
|
7554
|
+
if (cached) {
|
|
7555
|
+
cached.push({ role: "assistant", content });
|
|
7556
|
+
}
|
|
6470
7557
|
}
|
|
6471
7558
|
/**
|
|
6472
7559
|
* Force context compaction on the current session.
|
|
@@ -6593,83 +7680,83 @@ var VALID_TRANSITIONS = {
|
|
|
6593
7680
|
// src/run/FileRunStore.ts
|
|
6594
7681
|
init_esm_shims();
|
|
6595
7682
|
import {
|
|
6596
|
-
mkdirSync as
|
|
6597
|
-
existsSync as
|
|
6598
|
-
readFileSync as
|
|
6599
|
-
writeFileSync as
|
|
6600
|
-
appendFileSync,
|
|
6601
|
-
readdirSync as
|
|
6602
|
-
renameSync,
|
|
6603
|
-
rmSync
|
|
7683
|
+
mkdirSync as mkdirSync5,
|
|
7684
|
+
existsSync as existsSync12,
|
|
7685
|
+
readFileSync as readFileSync7,
|
|
7686
|
+
writeFileSync as writeFileSync5,
|
|
7687
|
+
appendFileSync as appendFileSync2,
|
|
7688
|
+
readdirSync as readdirSync6,
|
|
7689
|
+
renameSync as renameSync2,
|
|
7690
|
+
rmSync as rmSync2
|
|
6604
7691
|
} from "fs";
|
|
6605
|
-
import { join as
|
|
7692
|
+
import { join as join9 } from "path";
|
|
6606
7693
|
import { homedir as homedir5 } from "os";
|
|
6607
7694
|
var FileRunStore = class {
|
|
6608
7695
|
runsDir;
|
|
6609
7696
|
constructor(storageDir) {
|
|
6610
|
-
this.runsDir = storageDir ??
|
|
6611
|
-
|
|
7697
|
+
this.runsDir = storageDir ?? join9(homedir5(), ".code-shell", "runs");
|
|
7698
|
+
mkdirSync5(this.runsDir, { recursive: true });
|
|
6612
7699
|
}
|
|
6613
7700
|
// ─── Helpers ───────────────────────────────────────────────────
|
|
6614
7701
|
runDir(runId) {
|
|
6615
|
-
return
|
|
7702
|
+
return join9(this.runsDir, runId);
|
|
6616
7703
|
}
|
|
6617
7704
|
ensureRunDir(runId) {
|
|
6618
7705
|
const dir = this.runDir(runId);
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
7706
|
+
mkdirSync5(dir, { recursive: true });
|
|
7707
|
+
mkdirSync5(join9(dir, "checkpoints"), { recursive: true });
|
|
7708
|
+
mkdirSync5(join9(dir, "approvals"), { recursive: true });
|
|
7709
|
+
mkdirSync5(join9(dir, "artifacts"), { recursive: true });
|
|
6623
7710
|
return dir;
|
|
6624
7711
|
}
|
|
6625
7712
|
writeJson(filePath, data) {
|
|
6626
7713
|
const tmp = filePath + ".tmp";
|
|
6627
|
-
|
|
6628
|
-
|
|
7714
|
+
writeFileSync5(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
7715
|
+
renameSync2(tmp, filePath);
|
|
6629
7716
|
}
|
|
6630
7717
|
readJson(filePath) {
|
|
6631
|
-
if (!
|
|
6632
|
-
return JSON.parse(
|
|
7718
|
+
if (!existsSync12(filePath)) return null;
|
|
7719
|
+
return JSON.parse(readFileSync7(filePath, "utf-8"));
|
|
6633
7720
|
}
|
|
6634
7721
|
/** Serializes concurrent JSONL appends per file path. */
|
|
6635
7722
|
appendLocks = /* @__PURE__ */ new Map();
|
|
6636
7723
|
async appendJsonl(filePath, data) {
|
|
6637
7724
|
const prev = this.appendLocks.get(filePath) ?? Promise.resolve();
|
|
6638
7725
|
const current = prev.then(() => {
|
|
6639
|
-
|
|
7726
|
+
appendFileSync2(filePath, JSON.stringify(data) + "\n", "utf-8");
|
|
6640
7727
|
});
|
|
6641
7728
|
this.appendLocks.set(filePath, current);
|
|
6642
7729
|
await current;
|
|
6643
7730
|
}
|
|
6644
7731
|
readJsonl(filePath) {
|
|
6645
|
-
if (!
|
|
6646
|
-
const content =
|
|
7732
|
+
if (!existsSync12(filePath)) return [];
|
|
7733
|
+
const content = readFileSync7(filePath, "utf-8");
|
|
6647
7734
|
if (!content) return [];
|
|
6648
7735
|
return content.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
6649
7736
|
}
|
|
6650
7737
|
// ─── Snapshot ──────────────────────────────────────────────────
|
|
6651
7738
|
async create(snapshot) {
|
|
6652
7739
|
const dir = this.ensureRunDir(snapshot.runId);
|
|
6653
|
-
this.writeJson(
|
|
7740
|
+
this.writeJson(join9(dir, "run.json"), snapshot);
|
|
6654
7741
|
}
|
|
6655
7742
|
async update(snapshot) {
|
|
6656
7743
|
const dir = this.runDir(snapshot.runId);
|
|
6657
|
-
if (!
|
|
7744
|
+
if (!existsSync12(dir)) {
|
|
6658
7745
|
throw new Error(`Run not found: ${snapshot.runId}`);
|
|
6659
7746
|
}
|
|
6660
|
-
this.writeJson(
|
|
7747
|
+
this.writeJson(join9(dir, "run.json"), snapshot);
|
|
6661
7748
|
}
|
|
6662
7749
|
async get(runId) {
|
|
6663
|
-
return this.readJson(
|
|
7750
|
+
return this.readJson(join9(this.runDir(runId), "run.json"));
|
|
6664
7751
|
}
|
|
6665
7752
|
async list(query) {
|
|
6666
|
-
if (!
|
|
6667
|
-
const entries =
|
|
7753
|
+
if (!existsSync12(this.runsDir)) return [];
|
|
7754
|
+
const entries = readdirSync6(this.runsDir, { withFileTypes: true });
|
|
6668
7755
|
const snapshots = [];
|
|
6669
7756
|
for (const entry of entries) {
|
|
6670
7757
|
if (!entry.isDirectory()) continue;
|
|
6671
7758
|
const snapshot = this.readJson(
|
|
6672
|
-
|
|
7759
|
+
join9(this.runsDir, entry.name, "run.json")
|
|
6673
7760
|
);
|
|
6674
7761
|
if (!snapshot) continue;
|
|
6675
7762
|
if (query?.status) {
|
|
@@ -6686,32 +7773,32 @@ var FileRunStore = class {
|
|
|
6686
7773
|
}
|
|
6687
7774
|
async delete(runId) {
|
|
6688
7775
|
const dir = this.runDir(runId);
|
|
6689
|
-
if (
|
|
6690
|
-
|
|
7776
|
+
if (existsSync12(dir)) {
|
|
7777
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
6691
7778
|
}
|
|
6692
7779
|
}
|
|
6693
7780
|
// ─── Events ────────────────────────────────────────────────────
|
|
6694
7781
|
async appendEvent(event) {
|
|
6695
7782
|
this.ensureRunDir(event.runId);
|
|
6696
7783
|
const dir = this.runDir(event.runId);
|
|
6697
|
-
await this.appendJsonl(
|
|
7784
|
+
await this.appendJsonl(join9(dir, "events.jsonl"), event);
|
|
6698
7785
|
}
|
|
6699
7786
|
async listEvents(runId) {
|
|
6700
|
-
return this.readJsonl(
|
|
7787
|
+
return this.readJsonl(join9(this.runDir(runId), "events.jsonl"));
|
|
6701
7788
|
}
|
|
6702
7789
|
// ─── Checkpoints ───────────────────────────────────────────────
|
|
6703
7790
|
async saveCheckpoint(cp) {
|
|
6704
7791
|
const dir = this.runDir(cp.runId);
|
|
6705
|
-
this.writeJson(
|
|
7792
|
+
this.writeJson(join9(dir, "checkpoints", `${cp.checkpointId}.json`), cp);
|
|
6706
7793
|
}
|
|
6707
7794
|
async getLatestCheckpoint(runId) {
|
|
6708
|
-
const cpDir =
|
|
6709
|
-
if (!
|
|
6710
|
-
const files =
|
|
7795
|
+
const cpDir = join9(this.runDir(runId), "checkpoints");
|
|
7796
|
+
if (!existsSync12(cpDir)) return null;
|
|
7797
|
+
const files = readdirSync6(cpDir).filter((f) => f.endsWith(".json"));
|
|
6711
7798
|
if (files.length === 0) return null;
|
|
6712
7799
|
let latest = null;
|
|
6713
7800
|
for (const file of files) {
|
|
6714
|
-
const cp = this.readJson(
|
|
7801
|
+
const cp = this.readJson(join9(cpDir, file));
|
|
6715
7802
|
if (cp && (!latest || cp.createdAt > latest.createdAt)) {
|
|
6716
7803
|
latest = cp;
|
|
6717
7804
|
}
|
|
@@ -6722,21 +7809,21 @@ var FileRunStore = class {
|
|
|
6722
7809
|
async saveApproval(approval) {
|
|
6723
7810
|
const dir = this.runDir(approval.runId);
|
|
6724
7811
|
this.writeJson(
|
|
6725
|
-
|
|
7812
|
+
join9(dir, "approvals", `${approval.approvalId}.json`),
|
|
6726
7813
|
approval
|
|
6727
7814
|
);
|
|
6728
7815
|
}
|
|
6729
7816
|
async getApproval(runId, approvalId) {
|
|
6730
7817
|
return this.readJson(
|
|
6731
|
-
|
|
7818
|
+
join9(this.runDir(runId), "approvals", `${approvalId}.json`)
|
|
6732
7819
|
);
|
|
6733
7820
|
}
|
|
6734
7821
|
async getPendingApproval(runId) {
|
|
6735
|
-
const approvalDir =
|
|
6736
|
-
if (!
|
|
6737
|
-
const files =
|
|
7822
|
+
const approvalDir = join9(this.runDir(runId), "approvals");
|
|
7823
|
+
if (!existsSync12(approvalDir)) return null;
|
|
7824
|
+
const files = readdirSync6(approvalDir).filter((f) => f.endsWith(".json"));
|
|
6738
7825
|
for (const file of files) {
|
|
6739
|
-
const approval = this.readJson(
|
|
7826
|
+
const approval = this.readJson(join9(approvalDir, file));
|
|
6740
7827
|
if (approval?.status === "pending") return approval;
|
|
6741
7828
|
}
|
|
6742
7829
|
return null;
|
|
@@ -6745,11 +7832,11 @@ var FileRunStore = class {
|
|
|
6745
7832
|
async appendArtifactRef(ref) {
|
|
6746
7833
|
this.ensureRunDir(ref.runId);
|
|
6747
7834
|
const dir = this.runDir(ref.runId);
|
|
6748
|
-
await this.appendJsonl(
|
|
7835
|
+
await this.appendJsonl(join9(dir, "artifacts", "refs.jsonl"), ref);
|
|
6749
7836
|
}
|
|
6750
7837
|
async listArtifactRefs(runId) {
|
|
6751
7838
|
return this.readJsonl(
|
|
6752
|
-
|
|
7839
|
+
join9(this.runDir(runId), "artifacts", "refs.jsonl")
|
|
6753
7840
|
);
|
|
6754
7841
|
}
|
|
6755
7842
|
};
|
|
@@ -7116,8 +8203,8 @@ function extractFileName(filePath) {
|
|
|
7116
8203
|
|
|
7117
8204
|
// src/run/RunLock.ts
|
|
7118
8205
|
init_esm_shims();
|
|
7119
|
-
import { existsSync as
|
|
7120
|
-
import { join as
|
|
8206
|
+
import { existsSync as existsSync13 } from "fs";
|
|
8207
|
+
import { join as join10 } from "path";
|
|
7121
8208
|
import { homedir as homedir6 } from "os";
|
|
7122
8209
|
|
|
7123
8210
|
// src/utils/lockfile.ts
|
|
@@ -7145,7 +8232,7 @@ var RunLock = class {
|
|
|
7145
8232
|
staleMs;
|
|
7146
8233
|
releaseFns = /* @__PURE__ */ new Map();
|
|
7147
8234
|
constructor(config) {
|
|
7148
|
-
this.runsDir = config?.runsDir ??
|
|
8235
|
+
this.runsDir = config?.runsDir ?? join10(homedir6(), ".code-shell", "runs");
|
|
7149
8236
|
this.staleMs = config?.staleMs ?? 6e4;
|
|
7150
8237
|
}
|
|
7151
8238
|
/**
|
|
@@ -7153,7 +8240,7 @@ var RunLock = class {
|
|
|
7153
8240
|
*/
|
|
7154
8241
|
async acquire(runId) {
|
|
7155
8242
|
const lockTarget = this.lockTarget(runId);
|
|
7156
|
-
if (!
|
|
8243
|
+
if (!existsSync13(lockTarget)) return false;
|
|
7157
8244
|
try {
|
|
7158
8245
|
const release = await lock(lockTarget, {
|
|
7159
8246
|
stale: this.staleMs,
|
|
@@ -7185,7 +8272,7 @@ var RunLock = class {
|
|
|
7185
8272
|
*/
|
|
7186
8273
|
async isLocked(runId) {
|
|
7187
8274
|
const lockTarget = this.lockTarget(runId);
|
|
7188
|
-
if (!
|
|
8275
|
+
if (!existsSync13(lockTarget)) return false;
|
|
7189
8276
|
try {
|
|
7190
8277
|
return await check(lockTarget, { stale: this.staleMs });
|
|
7191
8278
|
} catch {
|
|
@@ -7197,7 +8284,7 @@ var RunLock = class {
|
|
|
7197
8284
|
*/
|
|
7198
8285
|
async forceUnlock(runId) {
|
|
7199
8286
|
const lockTarget = this.lockTarget(runId);
|
|
7200
|
-
if (!
|
|
8287
|
+
if (!existsSync13(lockTarget)) return;
|
|
7201
8288
|
try {
|
|
7202
8289
|
await unlock(lockTarget);
|
|
7203
8290
|
logger.info("run.lock.force_unlocked", { runId });
|
|
@@ -7213,21 +8300,21 @@ var RunLock = class {
|
|
|
7213
8300
|
}
|
|
7214
8301
|
}
|
|
7215
8302
|
lockTarget(runId) {
|
|
7216
|
-
return
|
|
8303
|
+
return join10(this.runsDir, runId, "run.json");
|
|
7217
8304
|
}
|
|
7218
8305
|
};
|
|
7219
8306
|
|
|
7220
8307
|
// src/run/Heartbeat.ts
|
|
7221
8308
|
init_esm_shims();
|
|
7222
|
-
import { existsSync as
|
|
7223
|
-
import { join as
|
|
8309
|
+
import { existsSync as existsSync14, readFileSync as readFileSync8, writeFileSync as writeFileSync6, unlinkSync } from "fs";
|
|
8310
|
+
import { join as join11 } from "path";
|
|
7224
8311
|
import { homedir as homedir7 } from "os";
|
|
7225
8312
|
var Heartbeat = class {
|
|
7226
8313
|
runsDir;
|
|
7227
8314
|
intervalMs;
|
|
7228
8315
|
timers = /* @__PURE__ */ new Map();
|
|
7229
8316
|
constructor(config) {
|
|
7230
|
-
this.runsDir = config?.runsDir ??
|
|
8317
|
+
this.runsDir = config?.runsDir ?? join11(homedir7(), ".code-shell", "runs");
|
|
7231
8318
|
this.intervalMs = config?.intervalMs ?? 5e3;
|
|
7232
8319
|
}
|
|
7233
8320
|
/**
|
|
@@ -7265,9 +8352,9 @@ var Heartbeat = class {
|
|
|
7265
8352
|
*/
|
|
7266
8353
|
read(runId) {
|
|
7267
8354
|
const filePath = this.filePath(runId);
|
|
7268
|
-
if (!
|
|
8355
|
+
if (!existsSync14(filePath)) return null;
|
|
7269
8356
|
try {
|
|
7270
|
-
return JSON.parse(
|
|
8357
|
+
return JSON.parse(readFileSync8(filePath, "utf-8"));
|
|
7271
8358
|
} catch {
|
|
7272
8359
|
return null;
|
|
7273
8360
|
}
|
|
@@ -7302,21 +8389,21 @@ var Heartbeat = class {
|
|
|
7302
8389
|
runId
|
|
7303
8390
|
};
|
|
7304
8391
|
try {
|
|
7305
|
-
|
|
8392
|
+
writeFileSync6(this.filePath(runId), JSON.stringify(data), "utf-8");
|
|
7306
8393
|
} catch {
|
|
7307
8394
|
}
|
|
7308
8395
|
}
|
|
7309
8396
|
remove(runId) {
|
|
7310
8397
|
try {
|
|
7311
8398
|
const filePath = this.filePath(runId);
|
|
7312
|
-
if (
|
|
8399
|
+
if (existsSync14(filePath)) {
|
|
7313
8400
|
unlinkSync(filePath);
|
|
7314
8401
|
}
|
|
7315
8402
|
} catch {
|
|
7316
8403
|
}
|
|
7317
8404
|
}
|
|
7318
8405
|
filePath(runId) {
|
|
7319
|
-
return
|
|
8406
|
+
return join11(this.runsDir, runId, "heartbeat");
|
|
7320
8407
|
}
|
|
7321
8408
|
};
|
|
7322
8409
|
|
|
@@ -7957,6 +9044,14 @@ export {
|
|
|
7957
9044
|
setRuntimeBypass,
|
|
7958
9045
|
PermissionClassifier,
|
|
7959
9046
|
HookRegistry,
|
|
9047
|
+
DEFAULT_PERSIST_THRESHOLD,
|
|
9048
|
+
PER_MESSAGE_AGGREGATE_CAP,
|
|
9049
|
+
PREVIEW_SIZE,
|
|
9050
|
+
createContentReplacementState,
|
|
9051
|
+
reconstructContentReplacementState,
|
|
9052
|
+
resolveToolResultsDir,
|
|
9053
|
+
applyToolResultPersistence,
|
|
9054
|
+
isPersistedReplacement,
|
|
7960
9055
|
ContextManager,
|
|
7961
9056
|
SectionCache,
|
|
7962
9057
|
scanInstructions,
|