tianshu-harness 3.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +189 -0
- package/README.en.md +1066 -0
- package/README.ja.md +1214 -0
- package/README.ko.md +1216 -0
- package/README.md +309 -0
- package/completions/rivet.bash +118 -0
- package/completions/rivet.fish +110 -0
- package/completions/rivet.ps1 +230 -0
- package/completions/rivet.zsh +187 -0
- package/dist/account-OAEKORZE.js +47 -0
- package/dist/account-login-V6FOLHJR.js +65 -0
- package/dist/account-status-KHNPPAFL.js +23 -0
- package/dist/agent/worker-process/child.js +407 -0
- package/dist/browser-cli-FLNY5ZHI.js +19 -0
- package/dist/browser-readiness-LWVGSBD5.js +12 -0
- package/dist/build-backends-5KXKR2S4.js +13 -0
- package/dist/build-options-66Y4ZE7C.js +9 -0
- package/dist/bundled-skills/brainstorming/SKILL.md +139 -0
- package/dist/bundled-skills/visual-acceptance/SKILL.md +64 -0
- package/dist/chain-7MAMFXZ3.js +10 -0
- package/dist/checkpoint-MND4DUIX.js +33 -0
- package/dist/chunk-2E6CK2RC.js +392 -0
- package/dist/chunk-2EBWGAXD.js +51 -0
- package/dist/chunk-2HDP7RMR.js +119 -0
- package/dist/chunk-2HKD2UFM.js +1000 -0
- package/dist/chunk-2VYRLULP.js +51 -0
- package/dist/chunk-2XN7PM4N.js +125 -0
- package/dist/chunk-35H7GVNR.js +66 -0
- package/dist/chunk-3C3R7JXY.js +77 -0
- package/dist/chunk-3VGD2FXB.js +118 -0
- package/dist/chunk-424AP7FM.js +534 -0
- package/dist/chunk-4J5TZGOE.js +3707 -0
- package/dist/chunk-4QJ3Y5JM.js +898 -0
- package/dist/chunk-4U2ET7ZY.js +1326 -0
- package/dist/chunk-4XPFLICE.js +876 -0
- package/dist/chunk-5B2OCU5P.js +31707 -0
- package/dist/chunk-5GGOGLSN.js +73 -0
- package/dist/chunk-5JRZPLI6.js +251 -0
- package/dist/chunk-5TQ3JYBU.js +397 -0
- package/dist/chunk-66JPZSGX.js +55 -0
- package/dist/chunk-6HSVXHCZ.js +764 -0
- package/dist/chunk-6S5FPUS7.js +21 -0
- package/dist/chunk-722LQO64.js +131 -0
- package/dist/chunk-73NJ7IT7.js +354 -0
- package/dist/chunk-7FW3I7WC.js +95 -0
- package/dist/chunk-7KUCBNLB.js +173 -0
- package/dist/chunk-7RDOV3DF.js +201 -0
- package/dist/chunk-A5MNH4ER.js +79 -0
- package/dist/chunk-ADIVZ2D5.js +15 -0
- package/dist/chunk-AE3P3F6W.js +366 -0
- package/dist/chunk-AERM3VGP.js +280 -0
- package/dist/chunk-AEWHC4WM.js +246 -0
- package/dist/chunk-AOVQ7VIF.js +169 -0
- package/dist/chunk-AOYHP3WI.js +12411 -0
- package/dist/chunk-B3YEBABJ.js +7160 -0
- package/dist/chunk-BCQXEZPJ.js +154 -0
- package/dist/chunk-BKTQAYO2.js +418 -0
- package/dist/chunk-BSECIPAQ.js +80 -0
- package/dist/chunk-BUIMGNDD.js +52 -0
- package/dist/chunk-BXHV3GSB.js +10320 -0
- package/dist/chunk-C6ZVZCPP.js +139 -0
- package/dist/chunk-CEZNQVWM.js +82 -0
- package/dist/chunk-CGFZGR7G.js +1492 -0
- package/dist/chunk-CGOADM2X.js +337 -0
- package/dist/chunk-CI4R6P2B.js +24 -0
- package/dist/chunk-D2TCGRSX.js +73 -0
- package/dist/chunk-D4PTYFOG.js +154 -0
- package/dist/chunk-D54ZWG4T.js +717 -0
- package/dist/chunk-DEBI37QB.js +145 -0
- package/dist/chunk-DWROHPXP.js +1051 -0
- package/dist/chunk-DZ6UOJ5D.js +92 -0
- package/dist/chunk-EBJTVRQN.js +81 -0
- package/dist/chunk-EGEI2RQA.js +1167 -0
- package/dist/chunk-EHHUASO5.js +1305 -0
- package/dist/chunk-EPKTIOED.js +41 -0
- package/dist/chunk-ER5JW2QZ.js +5681 -0
- package/dist/chunk-ETHFACAB.js +315 -0
- package/dist/chunk-EXUE5W2F.js +112 -0
- package/dist/chunk-EXXICUIW.js +73 -0
- package/dist/chunk-FFQSZJOO.js +169 -0
- package/dist/chunk-FHFKPGDO.js +85 -0
- package/dist/chunk-FW3QKMFX.js +158 -0
- package/dist/chunk-GD4ZNZ7W.js +30 -0
- package/dist/chunk-GHVMWF2I.js +581 -0
- package/dist/chunk-GMKAXSYQ.js +297 -0
- package/dist/chunk-HBVF6BWO.js +325 -0
- package/dist/chunk-HV4YGVMS.js +379 -0
- package/dist/chunk-ILBCKAV6.js +89 -0
- package/dist/chunk-J4U4AW46.js +440 -0
- package/dist/chunk-J6ESSM47.js +1791 -0
- package/dist/chunk-JBZHIC6T.js +2309 -0
- package/dist/chunk-JCFWOULR.js +245 -0
- package/dist/chunk-JRF5L6OD.js +131 -0
- package/dist/chunk-JSS75KCG.js +2014 -0
- package/dist/chunk-JSXWIWWJ.js +225 -0
- package/dist/chunk-JYV7I2QH.js +98 -0
- package/dist/chunk-KIOXTQQL.js +1596 -0
- package/dist/chunk-KWVTXT5B.js +89 -0
- package/dist/chunk-KX22RD6R.js +33 -0
- package/dist/chunk-LA6IPTGH.js +709 -0
- package/dist/chunk-LH5VCGLB.js +1796 -0
- package/dist/chunk-LQQ6XJ2Y.js +269 -0
- package/dist/chunk-MJQ55WZZ.js +65 -0
- package/dist/chunk-ML3UKMH3.js +40 -0
- package/dist/chunk-MOZPLHSU.js +5526 -0
- package/dist/chunk-NGEWJ4DF.js +89 -0
- package/dist/chunk-NH6VIOAR.js +211 -0
- package/dist/chunk-O5VZQKCB.js +26106 -0
- package/dist/chunk-OHSV42QB.js +748 -0
- package/dist/chunk-OO3TAHO5.js +4070 -0
- package/dist/chunk-OTNHSAGN.js +38282 -0
- package/dist/chunk-OUWAC7AQ.js +442 -0
- package/dist/chunk-OXMIV4WE.js +270 -0
- package/dist/chunk-P5KJURUR.js +229 -0
- package/dist/chunk-P6GJOVO6.js +307 -0
- package/dist/chunk-QARAGUXX.js +23 -0
- package/dist/chunk-R43HJCDX.js +2711 -0
- package/dist/chunk-RAXBAWRN.js +210 -0
- package/dist/chunk-RK3JHWPT.js +39 -0
- package/dist/chunk-RWA23GMM.js +24 -0
- package/dist/chunk-S4K4L6IM.js +179 -0
- package/dist/chunk-SAXK6LVF.js +124 -0
- package/dist/chunk-SEPGEJNK.js +1607 -0
- package/dist/chunk-SUJERP5I.js +285 -0
- package/dist/chunk-T3ED6BY2.js +913 -0
- package/dist/chunk-THKDVSEC.js +90 -0
- package/dist/chunk-TI4GSEL5.js +52 -0
- package/dist/chunk-TPI73L57.js +164 -0
- package/dist/chunk-TVHW77C7.js +338 -0
- package/dist/chunk-TWNHOSDT.js +1073 -0
- package/dist/chunk-UFMN76QD.js +347 -0
- package/dist/chunk-UHGLFRZE.js +205 -0
- package/dist/chunk-UTZYPKBF.js +15231 -0
- package/dist/chunk-UXNLELW6.js +25 -0
- package/dist/chunk-UXXS53EA.js +166 -0
- package/dist/chunk-V5DHFGI7.js +171 -0
- package/dist/chunk-V5YIPMHV.js +2353 -0
- package/dist/chunk-WMUWEO2C.js +754 -0
- package/dist/chunk-WOE3D7JM.js +945 -0
- package/dist/chunk-X2IK5LVR.js +2592 -0
- package/dist/chunk-XU2YXCDL.js +459 -0
- package/dist/chunk-YJG5HIX5.js +203 -0
- package/dist/chunk-Z3QE7TZM.js +72 -0
- package/dist/chunk-Z5Z3JHJY.js +64 -0
- package/dist/chunk-ZDIFBJFT.js +168 -0
- package/dist/chunk-ZGGCEXDC.js +176 -0
- package/dist/chunk-ZMZCZNKU.js +263 -0
- package/dist/chunk-ZNC5T65N.js +17 -0
- package/dist/chunk-ZO3I3A7O.js +308 -0
- package/dist/chunk-ZQKXBUB7.js +217 -0
- package/dist/chunk-ZTRINFM2.js +78 -0
- package/dist/chunk-ZU7JZCVP.js +193 -0
- package/dist/chunk-ZZAWNALS.js +350 -0
- package/dist/cli/entry.js +54 -0
- package/dist/connector-L5QQTR6Y.js +26 -0
- package/dist/context-62DC6EC3.js +31 -0
- package/dist/council-convene-NJWPNBDU.js +46 -0
- package/dist/create-agent-config-ODSQWNVC.js +55 -0
- package/dist/default-registry-2PU3BTAU.js +68 -0
- package/dist/deliver-task-KONV4C4M.js +57 -0
- package/dist/delivery-gate-v2-F2YVMVGK.js +27 -0
- package/dist/domain-knowledge-store-DDZ3F4J7.js +11 -0
- package/dist/fetch-core-ZPPYDSZH.js +14 -0
- package/dist/galaxy-K72BBDQN.js +91 -0
- package/dist/goal-criteria-SCALAU2D.js +141 -0
- package/dist/goal-persist-XWY2GDQX.js +19 -0
- package/dist/goal-tracker-FXMJ2GVK.js +11 -0
- package/dist/headless-LSB6I7PT.js +162 -0
- package/dist/headless-coordinator-VDPDMVUT.js +90 -0
- package/dist/inject-U55SMB6A.js +39 -0
- package/dist/login-flow-CD4ZGXOC.js +59 -0
- package/dist/logs-cli-SQVYFMMN.js +283 -0
- package/dist/loop-ULZYBDA3.js +76 -0
- package/dist/main.js +4064 -0
- package/dist/manager-ROZBCWPA.js +171 -0
- package/dist/manager-Z7XFFUOM.js +10670 -0
- package/dist/meridian-indexer-ZIW363L7.js +21 -0
- package/dist/native-resolver-PN34UIVV.js +13 -0
- package/dist/node_modules/better-sqlite3/lib/database.js +90 -0
- package/dist/node_modules/better-sqlite3/lib/index.js +3 -0
- package/dist/node_modules/better-sqlite3/lib/methods/aggregate.js +43 -0
- package/dist/node_modules/better-sqlite3/lib/methods/backup.js +67 -0
- package/dist/node_modules/better-sqlite3/lib/methods/function.js +31 -0
- package/dist/node_modules/better-sqlite3/lib/methods/inspect.js +7 -0
- package/dist/node_modules/better-sqlite3/lib/methods/pragma.js +12 -0
- package/dist/node_modules/better-sqlite3/lib/methods/serialize.js +16 -0
- package/dist/node_modules/better-sqlite3/lib/methods/table.js +189 -0
- package/dist/node_modules/better-sqlite3/lib/methods/transaction.js +78 -0
- package/dist/node_modules/better-sqlite3/lib/methods/wrappers.js +54 -0
- package/dist/node_modules/better-sqlite3/lib/sqlite-error.js +20 -0
- package/dist/node_modules/better-sqlite3/lib/util.js +12 -0
- package/dist/node_modules/better-sqlite3/package.json +59 -0
- package/dist/ownership-ledger-E3WIEKPL.js +9 -0
- package/dist/package.json +3 -0
- package/dist/path-grants-XRRXHXG7.js +40 -0
- package/dist/paths-LVV5IMQN.js +49 -0
- package/dist/plan-fact-anchors-LMVQZCW5.js +13 -0
- package/dist/plan-store-ODLTTPFB.js +17 -0
- package/dist/plan-templates-5FPFBOL6.js +109 -0
- package/dist/plugin-loader-ZZKX7ZE6.js +22 -0
- package/dist/prefix-budget-62OV5HQC.js +13 -0
- package/dist/project-trust-5WS5MOMU.js +31 -0
- package/dist/provider-cli-ALDQXIT7.js +37 -0
- package/dist/providers-XQYTAVPK.js +11 -0
- package/dist/proxy-resolver-V2Y6534D.js +15 -0
- package/dist/recovery-cli-KPGY6FY3.js +106 -0
- package/dist/review-discipline-MNSYCST5.js +55 -0
- package/dist/review-router-BVQZW3MX.js +18 -0
- package/dist/seed-capsules/seed-capsule-attack-methodology.md +62 -0
- package/dist/seed-capsules/seed-capsule-changgeng.md +31 -0
- package/dist/seed-capsules/seed-capsule-design-aesthetics.md +38 -0
- package/dist/seed-capsules/seed-capsule-diagnostic-ladder.md +54 -0
- package/dist/seed-capsules/seed-capsule-fu.md +39 -0
- package/dist/seed-capsules/seed-capsule-huagai.md +73 -0
- package/dist/seed-capsules/seed-capsule-kaiyang.md +65 -0
- package/dist/seed-capsules/seed-capsule-knowledge-work.md +40 -0
- package/dist/seed-capsules/seed-capsule-tanlang.md +47 -0
- package/dist/seed-capsules/seed-capsule-tianfu.md +23 -0
- package/dist/seed-capsules/seed-capsule-tianliang.md +63 -0
- package/dist/seed-capsules/seed-capsule-tianquan.md +32 -0
- package/dist/seed-capsules/seed-capsule-tianxuan-ccr.md +22 -0
- package/dist/seed-capsules/seed-capsule-tianxuan.md +64 -0
- package/dist/seed-capsules/seed-capsule-yaoguang.md +87 -0
- package/dist/serve-X2R2UTLF.js +170 -0
- package/dist/serve-agent-KHSU6RVV.js +965 -0
- package/dist/session-persist-HUUC772H.js +37 -0
- package/dist/session-registry-6XUD5C4Y.js +15 -0
- package/dist/skill-loader-CK3RMO47.js +48 -0
- package/dist/skills/cli-harness-sop.md +207 -0
- package/dist/spawn-git-4HFU65AH.js +29 -0
- package/dist/sse-EYUTBWUK.js +512 -0
- package/dist/starflow-NOFBMPFU.js +30 -0
- package/dist/streamableHttp-V67CNJMO.js +447 -0
- package/dist/task-contract-6XHBNJQK.js +32 -0
- package/dist/task-ledger-3K4TJIER.js +9 -0
- package/dist/team-orchestrate-S6NHZP7X.js +64 -0
- package/dist/title-extract-QX4XGCYN.js +50 -0
- package/dist/todo-QDXGRZZP.js +31 -0
- package/dist/todo-store-V3TTO5AH.js +12 -0
- package/dist/tool-preset-LU5BYEDZ.js +23 -0
- package/dist/tool-tiers-GD742YBN.js +23 -0
- package/dist/turndown.es-OI55K6C3.js +17373 -0
- package/dist/verification-attribution-EKG4CNUV.js +13 -0
- package/dist/volatile-git-MBTST7RA.js +15 -0
- package/dist/wave-checkpoint-XLIIHXQA.js +30 -0
- package/dist/web-cli-DUG4I35O.js +233 -0
- package/dist/web-search-3IK542AA.js +34 -0
- package/dist/workers/cpu-worker.js +45 -0
- package/dist/workflow-runner-HX7UBAYG.js +297 -0
- package/dist/worktree-baseline-O5NCU6M2.js +9 -0
- package/package.json +122 -0
- package/scripts/check-mobile-env.mjs +76 -0
- package/scripts/fetch-native-sqlite.js +392 -0
- package/scripts/prune-installed-wasms.js +45 -0
- package/scripts/tree-sitter-wasm-keep.js +40 -0
|
@@ -0,0 +1,2014 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --expose-gc --max-old-space-size=4096
|
|
2
|
+
import { createRequire as __rivetCreateRequire } from 'node:module'; const require = __rivetCreateRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
validatePath
|
|
5
|
+
} from "./chunk-7RDOV3DF.js";
|
|
6
|
+
import {
|
|
7
|
+
canonicalPathKey,
|
|
8
|
+
relativePosix
|
|
9
|
+
} from "./chunk-GD4ZNZ7W.js";
|
|
10
|
+
import {
|
|
11
|
+
compactProviderStrategy,
|
|
12
|
+
pruneThresholds
|
|
13
|
+
} from "./chunk-TPI73L57.js";
|
|
14
|
+
import {
|
|
15
|
+
debugLog
|
|
16
|
+
} from "./chunk-CI4R6P2B.js";
|
|
17
|
+
import {
|
|
18
|
+
persistRawOutput
|
|
19
|
+
} from "./chunk-XU2YXCDL.js";
|
|
20
|
+
import {
|
|
21
|
+
init_esm_shims
|
|
22
|
+
} from "./chunk-5GGOGLSN.js";
|
|
23
|
+
|
|
24
|
+
// src/tools/read-file.ts
|
|
25
|
+
init_esm_shims();
|
|
26
|
+
import { existsSync as existsSync2 } from "fs";
|
|
27
|
+
import { stat as stat2, readFile as readFile3 } from "fs/promises";
|
|
28
|
+
import { extname as extname3, relative } from "path";
|
|
29
|
+
|
|
30
|
+
// src/tools/truncation.ts
|
|
31
|
+
init_esm_shims();
|
|
32
|
+
var TRUNCATION_NOTE = "... (truncated, use offset/limit for more specific ranges)";
|
|
33
|
+
function truncateContent(content, maxChars, keepHead, keepTail) {
|
|
34
|
+
if (content.length <= maxChars) return content;
|
|
35
|
+
const head = content.slice(0, keepHead);
|
|
36
|
+
const tail = content.slice(-keepTail);
|
|
37
|
+
return `${head}
|
|
38
|
+
${TRUNCATION_NOTE}
|
|
39
|
+
${tail}`;
|
|
40
|
+
}
|
|
41
|
+
function buildPartialView(content, filePath, maxChars, skeletonOf) {
|
|
42
|
+
const lines = content.split("\n");
|
|
43
|
+
const totalLines = lines.length;
|
|
44
|
+
const totalChars = content.length;
|
|
45
|
+
const HEADER_OVERHEAD = skeletonOf ? 480 : 300;
|
|
46
|
+
const budget = Math.max(0, maxChars - HEADER_OVERHEAD);
|
|
47
|
+
let keptLines = 0;
|
|
48
|
+
let keptChars = 0;
|
|
49
|
+
while (keptLines < totalLines && keptChars + (lines[keptLines]?.length ?? 0) + 1 <= budget) {
|
|
50
|
+
keptChars += lines[keptLines].length + 1;
|
|
51
|
+
keptLines++;
|
|
52
|
+
}
|
|
53
|
+
keptLines = Math.max(1, keptLines);
|
|
54
|
+
const firstPage = lines.slice(0, keptLines).join("\n");
|
|
55
|
+
const header = skeletonOf ? [
|
|
56
|
+
`\u2500\u2500 SKELETON view of ${filePath} (${skeletonOf.lines} lines, ${skeletonOf.chars} chars) \u2500\u2500`,
|
|
57
|
+
`This is NOT the file's text: it is a structural outline (${keptLines} of ${totalLines} outline lines).`,
|
|
58
|
+
`Prose, comments and code bodies have been REMOVED \u2014 an empty block here means content was dropped, not that the file is empty.`,
|
|
59
|
+
`To read the actual content: read_file(file_path="${filePath}", offset=1, limit=200), then page with offset.`
|
|
60
|
+
] : [
|
|
61
|
+
`\u2500\u2500 PARTIAL view of ${filePath} (${totalLines} lines, ${totalChars} chars) \u2500\u2500`,
|
|
62
|
+
`Showing lines 1-${keptLines} of ${totalLines}.`,
|
|
63
|
+
`To read more: read_file(file_path="${filePath}", offset=${keptLines + 1}, limit=200)`
|
|
64
|
+
];
|
|
65
|
+
return [
|
|
66
|
+
...header,
|
|
67
|
+
`To find specific code: use grep first, then read_file with offset/limit.`,
|
|
68
|
+
`For editing: use grep to locate the target line, then hash_edit with anchors \u2014 no full read needed.`,
|
|
69
|
+
"",
|
|
70
|
+
firstPage
|
|
71
|
+
].join("\n");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// src/tools/gitignore.ts
|
|
75
|
+
init_esm_shims();
|
|
76
|
+
import { existsSync } from "fs";
|
|
77
|
+
import { readFile } from "fs/promises";
|
|
78
|
+
import { resolve, join } from "path";
|
|
79
|
+
var DEFAULT_IGNORE = [
|
|
80
|
+
"node_modules",
|
|
81
|
+
".git",
|
|
82
|
+
".svn",
|
|
83
|
+
".hg",
|
|
84
|
+
"__pycache__",
|
|
85
|
+
".pytest_cache",
|
|
86
|
+
".mypy_cache",
|
|
87
|
+
".next",
|
|
88
|
+
".nuxt",
|
|
89
|
+
".cache",
|
|
90
|
+
".turbo",
|
|
91
|
+
"dist",
|
|
92
|
+
"build",
|
|
93
|
+
"out",
|
|
94
|
+
"target",
|
|
95
|
+
".env",
|
|
96
|
+
".env.local",
|
|
97
|
+
".env.production",
|
|
98
|
+
"*.pyc",
|
|
99
|
+
"*.pyo",
|
|
100
|
+
"*.so",
|
|
101
|
+
"*.dylib",
|
|
102
|
+
"*.dll",
|
|
103
|
+
"*.min.js",
|
|
104
|
+
"*.min.css",
|
|
105
|
+
"*.map",
|
|
106
|
+
"package-lock.json",
|
|
107
|
+
"yarn.lock",
|
|
108
|
+
"pnpm-lock.yaml",
|
|
109
|
+
".DS_Store",
|
|
110
|
+
"Thumbs.db"
|
|
111
|
+
];
|
|
112
|
+
var GitignoreFilter = class _GitignoreFilter {
|
|
113
|
+
patterns;
|
|
114
|
+
constructor(_cwd, patterns) {
|
|
115
|
+
this.patterns = patterns ?? [...DEFAULT_IGNORE];
|
|
116
|
+
}
|
|
117
|
+
static async create(cwd) {
|
|
118
|
+
const patterns = [...DEFAULT_IGNORE];
|
|
119
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
120
|
+
if (existsSync(gitignorePath)) {
|
|
121
|
+
try {
|
|
122
|
+
const content = await readFile(gitignorePath, "utf-8");
|
|
123
|
+
for (const line of content.split("\n")) {
|
|
124
|
+
const trimmed = line.trim();
|
|
125
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
126
|
+
patterns.push(trimmed);
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return new _GitignoreFilter(cwd, patterns);
|
|
132
|
+
}
|
|
133
|
+
isIgnored(cwd, filePath) {
|
|
134
|
+
const absPath = resolve(cwd, filePath);
|
|
135
|
+
const relPath = relativePosix(cwd, absPath);
|
|
136
|
+
if (relPath === ".." || relPath.startsWith("../")) return false;
|
|
137
|
+
for (const pattern of this.patterns) {
|
|
138
|
+
if (this.matchPattern(pattern, relPath)) return true;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
matchPattern(pattern, relPath) {
|
|
143
|
+
if (pattern.startsWith("!")) return false;
|
|
144
|
+
let cleanPattern = pattern;
|
|
145
|
+
const dirOnly = cleanPattern.endsWith("/");
|
|
146
|
+
if (dirOnly) cleanPattern = cleanPattern.replace(/\/+$/, "");
|
|
147
|
+
const anchored = cleanPattern.startsWith("/");
|
|
148
|
+
if (anchored) cleanPattern = cleanPattern.replace(/^\/+/, "");
|
|
149
|
+
if (anchored) return this.matchGlob(cleanPattern, relPath);
|
|
150
|
+
const segments = relPath.split("/");
|
|
151
|
+
for (let i = 0; i < segments.length; i++) {
|
|
152
|
+
const candidate = segments.slice(i).join("/");
|
|
153
|
+
if (this.matchGlob(cleanPattern, candidate)) return true;
|
|
154
|
+
if (dirOnly && i === 0 && segments.length > 1 && this.matchGlob(cleanPattern, segments[i])) return true;
|
|
155
|
+
}
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
matchGlob(pattern, str) {
|
|
159
|
+
if (pattern === str) return true;
|
|
160
|
+
if (pattern.includes("*")) {
|
|
161
|
+
const re = globToRegex(pattern);
|
|
162
|
+
return re.test(str);
|
|
163
|
+
}
|
|
164
|
+
if (str.startsWith(pattern + "/") || str === pattern) return true;
|
|
165
|
+
if (pattern.startsWith(".") && str.endsWith(pattern)) return true;
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
var BACKSLASH = String.fromCharCode(92);
|
|
170
|
+
var RE_SPECIAL = /* @__PURE__ */ new Set([
|
|
171
|
+
".",
|
|
172
|
+
"$",
|
|
173
|
+
"^",
|
|
174
|
+
"{",
|
|
175
|
+
"}",
|
|
176
|
+
"(",
|
|
177
|
+
")",
|
|
178
|
+
"|",
|
|
179
|
+
"[",
|
|
180
|
+
"]",
|
|
181
|
+
BACKSLASH
|
|
182
|
+
]);
|
|
183
|
+
function globToRegex(glob) {
|
|
184
|
+
let re = "";
|
|
185
|
+
for (let i = 0; i < glob.length; i++) {
|
|
186
|
+
const ch = glob[i];
|
|
187
|
+
if (ch === "*") {
|
|
188
|
+
if (glob[i + 1] === "*") {
|
|
189
|
+
i++;
|
|
190
|
+
if (glob[i + 1] === "/") {
|
|
191
|
+
i++;
|
|
192
|
+
re += "(?:.*/)?";
|
|
193
|
+
} else {
|
|
194
|
+
re += ".*";
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
re += "[^/]*";
|
|
198
|
+
}
|
|
199
|
+
} else if (ch === "?") {
|
|
200
|
+
re += "[^/]";
|
|
201
|
+
} else if (RE_SPECIAL.has(ch)) {
|
|
202
|
+
re += BACKSLASH + ch;
|
|
203
|
+
} else {
|
|
204
|
+
re += ch;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return new RegExp("^(?:" + re + ")(?:/.*)?$");
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/artifact/summarize.ts
|
|
211
|
+
init_esm_shims();
|
|
212
|
+
function summarizeFileContent(content, filePath) {
|
|
213
|
+
const ext = filePath.split(".").pop()?.toLowerCase() ?? "";
|
|
214
|
+
switch (ext) {
|
|
215
|
+
case "ts":
|
|
216
|
+
case "tsx":
|
|
217
|
+
case "js":
|
|
218
|
+
case "jsx":
|
|
219
|
+
return summarizeJsTs(content, filePath);
|
|
220
|
+
case "py":
|
|
221
|
+
return summarizePython(content, filePath);
|
|
222
|
+
case "rs":
|
|
223
|
+
return summarizeRust(content, filePath);
|
|
224
|
+
case "go":
|
|
225
|
+
return summarizeGo(content, filePath);
|
|
226
|
+
case "md":
|
|
227
|
+
case "mdx":
|
|
228
|
+
return summarizeMarkdown(content, filePath);
|
|
229
|
+
case "json":
|
|
230
|
+
return summarizeJson(content, filePath);
|
|
231
|
+
default:
|
|
232
|
+
return summarizeGeneric(content, filePath);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function summarizeJsTs(content, filePath) {
|
|
236
|
+
const lines = content.split("\n");
|
|
237
|
+
const sections = [];
|
|
238
|
+
const exports = [];
|
|
239
|
+
const functions = [];
|
|
240
|
+
const classes = [];
|
|
241
|
+
const imports = [];
|
|
242
|
+
for (let i = 0; i < lines.length; i++) {
|
|
243
|
+
const line = lines[i];
|
|
244
|
+
const importMatch = line.match(/^import\s+.*?\s+from\s+['"](.+?)['"]/) ?? line.match(/^import\s+['"](.+?)['"]/);
|
|
245
|
+
if (importMatch) {
|
|
246
|
+
imports.push(importMatch[1]);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const reExportMatch = line.match(/^export\s+\{[^}]*\}\s+from\s+['"](.+?)['"]/);
|
|
250
|
+
if (reExportMatch) {
|
|
251
|
+
imports.push(reExportMatch[1]);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const exportMatch = line.match(/^export\s+(?:default\s+)?(?:function|class|const|let|var)\s+(\w+)/);
|
|
255
|
+
if (exportMatch) {
|
|
256
|
+
exports.push(exportMatch[1]);
|
|
257
|
+
const end = findBlockEnd(lines, i);
|
|
258
|
+
sections.push({
|
|
259
|
+
name: `export:${exportMatch[1]}`,
|
|
260
|
+
lineStart: i + 1,
|
|
261
|
+
lineEnd: end + 1,
|
|
262
|
+
charCount: lines.slice(i, end + 1).join("\n").length
|
|
263
|
+
});
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
const bareExportMatch = line.match(/^export\s+\{([^}]+)\}/);
|
|
267
|
+
if (bareExportMatch) {
|
|
268
|
+
const names = bareExportMatch[1].split(",").map((n) => n.trim().split(/\s+as\s+/).pop().trim()).filter(Boolean);
|
|
269
|
+
exports.push(...names);
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const fnMatch = line.match(/^(?:export\s+)?(?:async\s+)?function\s+(\w+)/);
|
|
273
|
+
if (fnMatch && !exports.includes(fnMatch[1])) {
|
|
274
|
+
functions.push(fnMatch[1]);
|
|
275
|
+
}
|
|
276
|
+
const classMatch = line.match(/^(?:export\s+)?(?:default\s+)?class\s+(\w+)/);
|
|
277
|
+
if (classMatch && !exports.includes(classMatch[1])) {
|
|
278
|
+
classes.push(classMatch[1]);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (imports.length > 0) {
|
|
282
|
+
sections.unshift({
|
|
283
|
+
name: "imports",
|
|
284
|
+
lineStart: 1,
|
|
285
|
+
lineEnd: Math.min(imports.length, lines.length),
|
|
286
|
+
charCount: imports.join("\n").length
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const ext = filePath.split(".").pop() ?? "";
|
|
290
|
+
const parts = [`${ext} file, ${lines.length} lines.`];
|
|
291
|
+
if (exports.length > 0) parts.push(`Exports: ${exports.slice(0, 8).join(", ")}${exports.length > 8 ? ` (+${exports.length - 8})` : ""}`);
|
|
292
|
+
if (functions.length > 0) parts.push(`Functions: ${functions.slice(0, 5).join(", ")}`);
|
|
293
|
+
if (classes.length > 0) parts.push(`Classes: ${classes.join(", ")}`);
|
|
294
|
+
return { summary: parts.join(" "), sections };
|
|
295
|
+
}
|
|
296
|
+
function summarizePython(content, _filePath) {
|
|
297
|
+
const lines = content.split("\n");
|
|
298
|
+
const sections = [];
|
|
299
|
+
const classes = [];
|
|
300
|
+
const functions = [];
|
|
301
|
+
for (let i = 0; i < lines.length; i++) {
|
|
302
|
+
const line = lines[i];
|
|
303
|
+
const classMatch = line.match(/^class\s+(\w+)/);
|
|
304
|
+
if (classMatch) {
|
|
305
|
+
classes.push(classMatch[1]);
|
|
306
|
+
const end = findPythonBlockEnd(lines, i);
|
|
307
|
+
sections.push({
|
|
308
|
+
name: `class:${classMatch[1]}`,
|
|
309
|
+
lineStart: i + 1,
|
|
310
|
+
lineEnd: end + 1,
|
|
311
|
+
charCount: lines.slice(i, end + 1).join("\n").length
|
|
312
|
+
});
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
const fnMatch = line.match(/^(?:async\s+)?def\s+(\w+)/);
|
|
316
|
+
if (fnMatch) {
|
|
317
|
+
functions.push(fnMatch[1]);
|
|
318
|
+
const end = findPythonBlockEnd(lines, i);
|
|
319
|
+
sections.push({
|
|
320
|
+
name: `function:${fnMatch[1]}`,
|
|
321
|
+
lineStart: i + 1,
|
|
322
|
+
lineEnd: end + 1,
|
|
323
|
+
charCount: lines.slice(i, end + 1).join("\n").length
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const parts = [`py file, ${lines.length} lines.`];
|
|
328
|
+
if (classes.length > 0) parts.push(`Classes: ${classes.join(", ")}`);
|
|
329
|
+
if (functions.length > 0) parts.push(`Functions: ${functions.join(", ")}`);
|
|
330
|
+
return { summary: parts.join(" "), sections };
|
|
331
|
+
}
|
|
332
|
+
function summarizeRust(content, _filePath) {
|
|
333
|
+
const lines = content.split("\n");
|
|
334
|
+
const sections = [];
|
|
335
|
+
const fns = [];
|
|
336
|
+
const structs = [];
|
|
337
|
+
const enums = [];
|
|
338
|
+
const impls = [];
|
|
339
|
+
const traits = [];
|
|
340
|
+
for (let i = 0; i < lines.length; i++) {
|
|
341
|
+
const line = lines[i];
|
|
342
|
+
const fnMatch = line.match(/^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/);
|
|
343
|
+
if (fnMatch) {
|
|
344
|
+
fns.push(fnMatch[1]);
|
|
345
|
+
sections.push({
|
|
346
|
+
name: `fn:${fnMatch[1]}`,
|
|
347
|
+
lineStart: i + 1,
|
|
348
|
+
lineEnd: findBlockEnd(lines, i) + 1,
|
|
349
|
+
charCount: 0
|
|
350
|
+
});
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const structMatch = line.match(/^(?:pub\s+)?struct\s+(\w+)/);
|
|
354
|
+
if (structMatch) {
|
|
355
|
+
structs.push(structMatch[1]);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
const enumMatch = line.match(/^(?:pub\s+)?enum\s+(\w+)/);
|
|
359
|
+
if (enumMatch) {
|
|
360
|
+
enums.push(enumMatch[1]);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
const implMatch = line.match(/^impl(?:<[^>]*>)?\s+(?:\w+\s+for\s+)?(\w+)/);
|
|
364
|
+
if (implMatch) {
|
|
365
|
+
impls.push(implMatch[1]);
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
const traitMatch = line.match(/^(?:pub\s+)?trait\s+(\w+)/);
|
|
369
|
+
if (traitMatch) {
|
|
370
|
+
traits.push(traitMatch[1]);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const parts = [`rs file, ${lines.length} lines.`];
|
|
375
|
+
if (structs.length > 0) parts.push(`Structs: ${structs.join(", ")}`);
|
|
376
|
+
if (enums.length > 0) parts.push(`Enums: ${enums.join(", ")}`);
|
|
377
|
+
if (traits.length > 0) parts.push(`Traits: ${traits.join(", ")}`);
|
|
378
|
+
if (impls.length > 0) parts.push(`Impls: ${impls.join(", ")}`);
|
|
379
|
+
if (fns.length > 0) parts.push(`Fns: ${fns.slice(0, 8).join(", ")}`);
|
|
380
|
+
return { summary: parts.join(" "), sections };
|
|
381
|
+
}
|
|
382
|
+
function summarizeGo(content, _filePath) {
|
|
383
|
+
const lines = content.split("\n");
|
|
384
|
+
return {
|
|
385
|
+
summary: `go file, ${lines.length} lines. low-detail summary (no Go structural parser yet), consider read_section for details.`,
|
|
386
|
+
sections: []
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function summarizeMarkdown(content, _filePath) {
|
|
390
|
+
const lines = content.split("\n");
|
|
391
|
+
const sections = [];
|
|
392
|
+
const headings = [];
|
|
393
|
+
for (let i = 0; i < lines.length; i++) {
|
|
394
|
+
const line = lines[i];
|
|
395
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)/);
|
|
396
|
+
if (headingMatch) {
|
|
397
|
+
const title = headingMatch[2].trim();
|
|
398
|
+
headings.push(title);
|
|
399
|
+
const level = headingMatch[1].length;
|
|
400
|
+
const end = findMarkdownSectionEnd(lines, i, level);
|
|
401
|
+
sections.push({
|
|
402
|
+
name: `heading:${title}`,
|
|
403
|
+
lineStart: i + 1,
|
|
404
|
+
lineEnd: end + 1,
|
|
405
|
+
charCount: lines.slice(i, end + 1).join("\n").length
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const parts = [`md file, ${lines.length} lines.`];
|
|
410
|
+
if (headings.length > 0) parts.push(`Headings: ${headings.slice(0, 10).join(", ")}`);
|
|
411
|
+
return { summary: parts.join(" "), sections };
|
|
412
|
+
}
|
|
413
|
+
function summarizeJson(content, _filePath) {
|
|
414
|
+
const lines = content.split("\n");
|
|
415
|
+
const sections = [];
|
|
416
|
+
try {
|
|
417
|
+
const parsed = JSON.parse(content);
|
|
418
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
419
|
+
const topKeys = Object.keys(parsed);
|
|
420
|
+
const nestedKeys = [];
|
|
421
|
+
for (const key of topKeys) {
|
|
422
|
+
const val = parsed[key];
|
|
423
|
+
if (typeof val === "object" && val !== null) {
|
|
424
|
+
nestedKeys.push(key);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
for (const key of topKeys) {
|
|
428
|
+
const keyLineIdx = lines.findIndex((l) => l.includes(`"${key}"`));
|
|
429
|
+
if (keyLineIdx >= 0) {
|
|
430
|
+
sections.push({
|
|
431
|
+
name: `key:${key}`,
|
|
432
|
+
lineStart: keyLineIdx + 1,
|
|
433
|
+
lineEnd: keyLineIdx + 1,
|
|
434
|
+
charCount: 0
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const parts = [`json file, ${lines.length} lines.`];
|
|
439
|
+
parts.push(`Keys: ${topKeys.join(", ")}`);
|
|
440
|
+
if (nestedKeys.length > 0) {
|
|
441
|
+
parts.push(`Nested: ${nestedKeys.join(", ")}`);
|
|
442
|
+
}
|
|
443
|
+
return { summary: parts.join(" "), sections };
|
|
444
|
+
}
|
|
445
|
+
} catch {
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
summary: `json file, ${lines.length} lines.`,
|
|
449
|
+
sections
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
function summarizeGeneric(content, filePath) {
|
|
453
|
+
const lines = content.split("\n");
|
|
454
|
+
const ext = filePath.split(".").pop() ?? "";
|
|
455
|
+
return {
|
|
456
|
+
summary: `${ext} file, ${lines.length} lines. low-detail summary, consider read_section for details.`,
|
|
457
|
+
sections: []
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function findBlockEnd(lines, startIdx) {
|
|
461
|
+
let braceCount = 0;
|
|
462
|
+
let foundOpen = false;
|
|
463
|
+
for (let i = startIdx; i < lines.length; i++) {
|
|
464
|
+
const line = lines[i];
|
|
465
|
+
for (const ch of line) {
|
|
466
|
+
if (ch === "{") {
|
|
467
|
+
braceCount++;
|
|
468
|
+
foundOpen = true;
|
|
469
|
+
}
|
|
470
|
+
if (ch === "}") braceCount--;
|
|
471
|
+
}
|
|
472
|
+
if (foundOpen && braceCount <= 0) return i;
|
|
473
|
+
}
|
|
474
|
+
return Math.min(startIdx + 20, lines.length - 1);
|
|
475
|
+
}
|
|
476
|
+
function findPythonBlockEnd(lines, startIdx) {
|
|
477
|
+
const startIndent = lines[startIdx].search(/\S/);
|
|
478
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
479
|
+
const line = lines[i];
|
|
480
|
+
if (line.trim().length === 0) continue;
|
|
481
|
+
const indent = line.search(/\S/);
|
|
482
|
+
if (indent <= startIndent) return i - 1;
|
|
483
|
+
}
|
|
484
|
+
return lines.length - 1;
|
|
485
|
+
}
|
|
486
|
+
function findMarkdownSectionEnd(lines, startIdx, level) {
|
|
487
|
+
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
488
|
+
const headingMatch = lines[i].match(/^(#{1,6})\s+/);
|
|
489
|
+
if (headingMatch && headingMatch[1].length <= level) return i - 1;
|
|
490
|
+
}
|
|
491
|
+
return lines.length - 1;
|
|
492
|
+
}
|
|
493
|
+
function grepLineFile(line) {
|
|
494
|
+
const m = line.match(/^(.+?):\d+:/) ?? line.match(/^(.+?)-\d+-/);
|
|
495
|
+
return (m ? m[1] : line.split(":")[0] ?? "").trim();
|
|
496
|
+
}
|
|
497
|
+
function summarizeGrepResult(content, pattern) {
|
|
498
|
+
const lines = content.split(/\r?\n/).filter((l) => l.trim());
|
|
499
|
+
const files = new Set(lines.map(grepLineFile).filter(Boolean));
|
|
500
|
+
return {
|
|
501
|
+
summary: `grep "${pattern}": ${lines.length} matches in ${files.size} files. Files: ${[...files].slice(0, 5).join(", ")}${files.size > 5 ? ` (+${files.size - 5})` : ""}`,
|
|
502
|
+
sections: []
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function summarizeBashOutput(content, command, exitCode) {
|
|
506
|
+
const lines = content.split("\n");
|
|
507
|
+
const status = exitCode === 0 ? "success" : `failed (exit ${exitCode})`;
|
|
508
|
+
const testSummary = lines.find((l) => /tests?\s*(?:pass|passed|fail|failed)|total/i.test(l)) ?? lines.find((l) => /\d+\s+tests?\s+pass/i.test(l)) ?? lines.find((l) => /\d+\s+tests?\s+passed/i.test(l));
|
|
509
|
+
const errorLines = lines.filter((l) => /error|Error|FAIL/i.test(l)).slice(0, 3);
|
|
510
|
+
const parts = [`[${command.slice(0, 40)}] ${status}, ${lines.length} lines.`];
|
|
511
|
+
if (testSummary) parts.push(testSummary.trim());
|
|
512
|
+
if (exitCode === 0 && !testSummary) {
|
|
513
|
+
const lastLine = lines.filter((l) => l.trim()).pop();
|
|
514
|
+
if (lastLine) parts.push(lastLine.trim());
|
|
515
|
+
}
|
|
516
|
+
if (errorLines.length > 0 && exitCode !== 0) parts.push(`Errors: ${errorLines.map((l) => l.trim().slice(0, 60)).join("; ")}`);
|
|
517
|
+
return { summary: parts.join(" "), sections: [] };
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// src/tools/model-read-cap.ts
|
|
521
|
+
init_esm_shims();
|
|
522
|
+
var DEFAULT_MODEL_READ_CAP = {
|
|
523
|
+
maxChars: 8e3,
|
|
524
|
+
headChars: 4e3,
|
|
525
|
+
tailChars: 2e3
|
|
526
|
+
};
|
|
527
|
+
var ABSOLUTE_MAX_CHARS = 12e4;
|
|
528
|
+
function tokenFractionPerCall(contextWindow) {
|
|
529
|
+
if (contextWindow >= 5e5) return 0.05;
|
|
530
|
+
if (contextWindow >= 2e5) return 0.03;
|
|
531
|
+
return 0.02;
|
|
532
|
+
}
|
|
533
|
+
var CHARS_PER_TOKEN = 4;
|
|
534
|
+
var STRATEGY_MULTIPLIER = {
|
|
535
|
+
"cache-preserving": 1.3,
|
|
536
|
+
balanced: 1,
|
|
537
|
+
aggressive: 0.65
|
|
538
|
+
};
|
|
539
|
+
function computeModelReadCap(input = {}) {
|
|
540
|
+
const { contextWindow, providerProfile } = input;
|
|
541
|
+
if (!contextWindow || contextWindow <= 0) {
|
|
542
|
+
return DEFAULT_MODEL_READ_CAP;
|
|
543
|
+
}
|
|
544
|
+
const strategy = compactProviderStrategy(providerProfile);
|
|
545
|
+
const multiplier = STRATEGY_MULTIPLIER[strategy];
|
|
546
|
+
const fraction = tokenFractionPerCall(contextWindow);
|
|
547
|
+
const computed = Math.floor(contextWindow * fraction * CHARS_PER_TOKEN * multiplier);
|
|
548
|
+
const maxChars = Math.min(Math.max(computed, DEFAULT_MODEL_READ_CAP.maxChars), ABSOLUTE_MAX_CHARS);
|
|
549
|
+
return {
|
|
550
|
+
maxChars,
|
|
551
|
+
headChars: Math.floor(maxChars * 0.6),
|
|
552
|
+
tailChars: Math.floor(maxChars * 0.3)
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/tools/artifact-threshold.ts
|
|
557
|
+
init_esm_shims();
|
|
558
|
+
var TOOL_ARTIFACT_MULTIPLIERS = {
|
|
559
|
+
read_file: 2,
|
|
560
|
+
// B3 lowered from 5.0; on 1M window: 300K (B1 cap at 120K is tighter)
|
|
561
|
+
bash: 1,
|
|
562
|
+
// B3 lowered from 1.67; ~150K on 1M window (matches prune minChars)
|
|
563
|
+
grep: 0.67,
|
|
564
|
+
// search results are compact; wrap earlier at ~100K
|
|
565
|
+
run_tests: 1.5,
|
|
566
|
+
// B3 lowered from 3.33; ~225K on 1M window
|
|
567
|
+
diff: 1,
|
|
568
|
+
// B3 lowered from 2.0; ~150K on 1M window
|
|
569
|
+
glob: 0.5,
|
|
570
|
+
// file lists are very compact
|
|
571
|
+
repo_map: 0.5,
|
|
572
|
+
// file trees are compact
|
|
573
|
+
inspect_project: 0.5,
|
|
574
|
+
// project summaries are compact
|
|
575
|
+
web_fetch: 1,
|
|
576
|
+
// web content varies; default
|
|
577
|
+
read_section: 2
|
|
578
|
+
// B3 lowered from 5.0; same as read_file
|
|
579
|
+
};
|
|
580
|
+
var DEFAULT_MULTIPLIER = 1;
|
|
581
|
+
function getToolArtifactThreshold(toolName, contextWindow) {
|
|
582
|
+
const base = contextWindow != null && contextWindow > 0 ? pruneThresholds(contextWindow).minChars : 800;
|
|
583
|
+
const multiplier = TOOL_ARTIFACT_MULTIPLIERS[toolName] ?? DEFAULT_MULTIPLIER;
|
|
584
|
+
return Math.round(base * multiplier);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// src/tools/read-policy.ts
|
|
588
|
+
init_esm_shims();
|
|
589
|
+
var LOG_PREVIEW_GUARD_BYTES = 16 * 1024;
|
|
590
|
+
var DEFAULT_PREVIEW_LINES = 80;
|
|
591
|
+
var DEFAULT_MAX_RANGE_LINES = 200;
|
|
592
|
+
var SOURCE_SMALL_BYTES = 20 * 1024;
|
|
593
|
+
var SOURCE_LARGE_BYTES = 80 * 1024;
|
|
594
|
+
function classifyPath(filePath) {
|
|
595
|
+
const lower = filePath.toLowerCase();
|
|
596
|
+
if (/\.(?:jsonl|ndjson)(?:\.\d+)?$/.test(lower)) return "jsonl";
|
|
597
|
+
if (/\.(?:log|out|err|trace)(?:\.\d+)?$/.test(lower)) return "log";
|
|
598
|
+
if (/\.min\.(?:js|css)$/.test(lower)) return "minified";
|
|
599
|
+
if (/(?:^|\/)(?:dist|build|coverage|\.next)\//.test(lower)) return "generated";
|
|
600
|
+
if (/\.(?:ts|tsx|js|jsx|mjs|cjs|json|md|css|scss|html|yml|yaml)$/.test(lower)) return "source";
|
|
601
|
+
return "unknown";
|
|
602
|
+
}
|
|
603
|
+
function decideReadPolicy(input) {
|
|
604
|
+
const kind = classifyPath(input.filePath);
|
|
605
|
+
const base = { kind, previewLines: DEFAULT_PREVIEW_LINES, maxRangeLines: DEFAULT_MAX_RANGE_LINES };
|
|
606
|
+
if (input.hasExplicitRange) {
|
|
607
|
+
return { ...base, action: "full", reason: "explicit range requested" };
|
|
608
|
+
}
|
|
609
|
+
if ((kind === "log" || kind === "jsonl") && input.sizeBytes > LOG_PREVIEW_GUARD_BYTES) {
|
|
610
|
+
return { ...base, action: "preview", reason: "log-like file over preview guard" };
|
|
611
|
+
}
|
|
612
|
+
if (kind === "generated" || kind === "minified") {
|
|
613
|
+
return { ...base, action: "reject-with-range", reason: "generated or minified file requires an explicit range" };
|
|
614
|
+
}
|
|
615
|
+
if (kind === "source" || kind === "unknown") {
|
|
616
|
+
if (input.sizeBytes > SOURCE_LARGE_BYTES) {
|
|
617
|
+
return { ...base, action: "partial", reason: "large source file \u2014 returning first page with navigation hints" };
|
|
618
|
+
}
|
|
619
|
+
if (input.sizeBytes > SOURCE_SMALL_BYTES) {
|
|
620
|
+
return { ...base, action: "full-with-hint", reason: "medium source file \u2014 full read with editing hints" };
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return { ...base, action: "full", reason: "safe default read" };
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// src/compact/code-fold.ts
|
|
627
|
+
init_esm_shims();
|
|
628
|
+
import { extname } from "path";
|
|
629
|
+
var DEFAULT_MAX_LINES = 200;
|
|
630
|
+
var MIN_LINES_TO_FOLD = 50;
|
|
631
|
+
var MAX_SCAN_LINES = 3e3;
|
|
632
|
+
var TAIL_KEEP = 20;
|
|
633
|
+
function detectLanguage(filePath) {
|
|
634
|
+
const ext = extname(filePath).toLowerCase();
|
|
635
|
+
const map = {
|
|
636
|
+
".ts": "ts",
|
|
637
|
+
".tsx": "tsx",
|
|
638
|
+
".js": "js",
|
|
639
|
+
".jsx": "jsx",
|
|
640
|
+
".mjs": "js",
|
|
641
|
+
".cjs": "js",
|
|
642
|
+
".py": "py",
|
|
643
|
+
".pyi": "py",
|
|
644
|
+
".json": "json",
|
|
645
|
+
".md": "md",
|
|
646
|
+
".mdx": "md"
|
|
647
|
+
};
|
|
648
|
+
return map[ext] ?? "unknown";
|
|
649
|
+
}
|
|
650
|
+
function countNetBraces(line) {
|
|
651
|
+
let depth = 0;
|
|
652
|
+
let inString = null;
|
|
653
|
+
let escaped = false;
|
|
654
|
+
for (let i = 0; i < line.length; i++) {
|
|
655
|
+
const ch = line[i];
|
|
656
|
+
if (escaped) {
|
|
657
|
+
escaped = false;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (ch === "\\") {
|
|
661
|
+
escaped = true;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (inString) {
|
|
665
|
+
if (ch === inString) inString = null;
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
if (ch === '"' || ch === "'" || ch === "`") {
|
|
669
|
+
inString = ch;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
if (ch === "{") depth++;
|
|
673
|
+
else if (ch === "}") depth--;
|
|
674
|
+
}
|
|
675
|
+
return depth;
|
|
676
|
+
}
|
|
677
|
+
var FUNCTION_LIKE = /^\s*(export\s+)?(default\s+)?(async\s+)?(function\s*\*?\s+\w+|const\s+\w+\s*(?::\s*[^=]+)?=\s*(?:async\s*)?\(|class\s+\w+|get\s+\w+|set\s+\w+|constructor\s*\()/;
|
|
678
|
+
var STRUCTURAL_DECL = /^\s*(export\s+)?(abstract\s+)?(interface\s+\w+|type\s+\w+\s*=|enum\s+\w+)/;
|
|
679
|
+
var IMPORT_EXPORT = /^\s*(import\s|export\s(?:\{|\*|type|default))/;
|
|
680
|
+
function classifyLine(line) {
|
|
681
|
+
if (IMPORT_EXPORT.test(line)) return "import";
|
|
682
|
+
if (STRUCTURAL_DECL.test(line)) return "structural";
|
|
683
|
+
if (FUNCTION_LIKE.test(line)) return "signature";
|
|
684
|
+
return "other";
|
|
685
|
+
}
|
|
686
|
+
function truncateSig(line) {
|
|
687
|
+
const t = line.trim();
|
|
688
|
+
return t.length > 120 ? t.slice(0, 117) + "..." : t;
|
|
689
|
+
}
|
|
690
|
+
function foldTsLike(lines, originalLines, maxLines) {
|
|
691
|
+
const output = [];
|
|
692
|
+
const signatures = [];
|
|
693
|
+
const scanLimit = Math.min(lines.length, MAX_SCAN_LINES);
|
|
694
|
+
let braceDepth = 0;
|
|
695
|
+
let hadFold = false;
|
|
696
|
+
let i = 0;
|
|
697
|
+
while (i < scanLimit) {
|
|
698
|
+
const line = lines[i];
|
|
699
|
+
const cls = classifyLine(line);
|
|
700
|
+
const netBraces = countNetBraces(line);
|
|
701
|
+
if (cls === "import") {
|
|
702
|
+
output.push(line);
|
|
703
|
+
i++;
|
|
704
|
+
continue;
|
|
705
|
+
}
|
|
706
|
+
if (cls === "signature") {
|
|
707
|
+
signatures.push(truncateSig(line));
|
|
708
|
+
output.push(line);
|
|
709
|
+
const entryDepth = braceDepth;
|
|
710
|
+
let blockStartDepth = null;
|
|
711
|
+
if (netBraces > 0) {
|
|
712
|
+
blockStartDepth = braceDepth;
|
|
713
|
+
braceDepth += netBraces;
|
|
714
|
+
if (braceDepth <= entryDepth) blockStartDepth = null;
|
|
715
|
+
} else if (i + 1 < scanLimit) {
|
|
716
|
+
const nextLine = lines[i + 1];
|
|
717
|
+
const nextNet = countNetBraces(nextLine);
|
|
718
|
+
if (nextLine.trim().startsWith("{") && nextNet > 0) {
|
|
719
|
+
blockStartDepth = braceDepth;
|
|
720
|
+
braceDepth += nextNet;
|
|
721
|
+
i++;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (blockStartDepth !== null && braceDepth > blockStartDepth) {
|
|
725
|
+
hadFold = true;
|
|
726
|
+
while (i + 1 < scanLimit && braceDepth > blockStartDepth) {
|
|
727
|
+
i++;
|
|
728
|
+
braceDepth += countNetBraces(lines[i]);
|
|
729
|
+
}
|
|
730
|
+
braceDepth = Math.max(0, braceDepth);
|
|
731
|
+
output.push(" { \u2026 }");
|
|
732
|
+
}
|
|
733
|
+
i++;
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
if (cls === "structural") {
|
|
737
|
+
signatures.push(truncateSig(line));
|
|
738
|
+
output.push(line);
|
|
739
|
+
braceDepth += netBraces;
|
|
740
|
+
braceDepth = Math.max(0, braceDepth);
|
|
741
|
+
i++;
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
output.push(line);
|
|
745
|
+
braceDepth += netBraces;
|
|
746
|
+
braceDepth = Math.max(0, braceDepth);
|
|
747
|
+
i++;
|
|
748
|
+
}
|
|
749
|
+
if (scanLimit < originalLines) {
|
|
750
|
+
output.push(` \u2026 (${originalLines - scanLimit} more lines not scanned) \u2026`);
|
|
751
|
+
}
|
|
752
|
+
if (output.length > maxLines) {
|
|
753
|
+
const headCount = maxLines - TAIL_KEEP - 1;
|
|
754
|
+
const headPart = output.slice(0, headCount);
|
|
755
|
+
const tailPart = output.slice(-TAIL_KEEP);
|
|
756
|
+
const omitted = output.length - headCount - TAIL_KEEP;
|
|
757
|
+
output.length = 0;
|
|
758
|
+
output.push(...headPart, ` \u2026 (${omitted} lines omitted) \u2026`, ...tailPart);
|
|
759
|
+
}
|
|
760
|
+
const folded = output.join("\n");
|
|
761
|
+
return {
|
|
762
|
+
folded,
|
|
763
|
+
originalLines,
|
|
764
|
+
foldedLines: output.length,
|
|
765
|
+
signatures,
|
|
766
|
+
wasFolded: hadFold && output.length < originalLines
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
var PY_DEF = /^\s*(async\s+)?def\s+\w+/;
|
|
770
|
+
var PY_CLASS = /^\s*class\s+\w+/;
|
|
771
|
+
var PY_DECORATOR = /^\s*@/;
|
|
772
|
+
function indentOf(line) {
|
|
773
|
+
let n = 0;
|
|
774
|
+
for (const ch of line) {
|
|
775
|
+
if (ch === " ") n++;
|
|
776
|
+
else break;
|
|
777
|
+
}
|
|
778
|
+
return n;
|
|
779
|
+
}
|
|
780
|
+
function foldPython(lines, originalLines, maxLines) {
|
|
781
|
+
const output = [];
|
|
782
|
+
const signatures = [];
|
|
783
|
+
let hadFold = false;
|
|
784
|
+
let i = 0;
|
|
785
|
+
while (i < lines.length) {
|
|
786
|
+
const line = lines[i];
|
|
787
|
+
if (PY_DECORATOR.test(line)) {
|
|
788
|
+
output.push(line);
|
|
789
|
+
i++;
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
if (PY_DEF.test(line) || PY_CLASS.test(line)) {
|
|
793
|
+
signatures.push(truncateSig(line));
|
|
794
|
+
output.push(line);
|
|
795
|
+
const defIndent = indentOf(line);
|
|
796
|
+
let bodyStart = i + 1;
|
|
797
|
+
let bodyEnd = bodyStart;
|
|
798
|
+
while (bodyEnd < lines.length) {
|
|
799
|
+
const bodyLine = lines[bodyEnd];
|
|
800
|
+
if (bodyLine.trim() === "") {
|
|
801
|
+
bodyEnd++;
|
|
802
|
+
continue;
|
|
803
|
+
}
|
|
804
|
+
if (indentOf(bodyLine) <= defIndent) break;
|
|
805
|
+
bodyEnd++;
|
|
806
|
+
}
|
|
807
|
+
if (bodyEnd > bodyStart) {
|
|
808
|
+
hadFold = true;
|
|
809
|
+
output.push(" { \u2026 }");
|
|
810
|
+
i = bodyEnd;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
i++;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
output.push(line);
|
|
817
|
+
i++;
|
|
818
|
+
}
|
|
819
|
+
if (output.length > maxLines) {
|
|
820
|
+
const headCount = maxLines - TAIL_KEEP - 1;
|
|
821
|
+
const headPart = output.slice(0, headCount);
|
|
822
|
+
const tailPart = output.slice(-TAIL_KEEP);
|
|
823
|
+
const omitted = output.length - headCount - TAIL_KEEP;
|
|
824
|
+
output.length = 0;
|
|
825
|
+
output.push(...headPart, ` \u2026 (${omitted} lines omitted) \u2026`, ...tailPart);
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
folded: output.join("\n"),
|
|
829
|
+
originalLines,
|
|
830
|
+
foldedLines: output.length,
|
|
831
|
+
signatures,
|
|
832
|
+
wasFolded: hadFold && output.length < originalLines
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
function foldJson(content, originalLines) {
|
|
836
|
+
try {
|
|
837
|
+
const parsed = JSON.parse(content);
|
|
838
|
+
const summary = JSON.stringify(jsonSkeleton(parsed), null, 2);
|
|
839
|
+
const lines = summary.split("\n");
|
|
840
|
+
return {
|
|
841
|
+
folded: summary,
|
|
842
|
+
originalLines,
|
|
843
|
+
foldedLines: lines.length,
|
|
844
|
+
signatures: [],
|
|
845
|
+
wasFolded: lines.length < originalLines
|
|
846
|
+
};
|
|
847
|
+
} catch {
|
|
848
|
+
return { folded: content, originalLines, foldedLines: originalLines, signatures: [], wasFolded: false };
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
function jsonSkeleton(value, depth = 0) {
|
|
852
|
+
if (depth > 2) return "\u2026";
|
|
853
|
+
if (Array.isArray(value)) {
|
|
854
|
+
if (value.length === 0) return [];
|
|
855
|
+
return [`(${value.length} items)`, jsonSkeleton(value[0], depth + 1)];
|
|
856
|
+
}
|
|
857
|
+
if (value !== null && typeof value === "object") {
|
|
858
|
+
const result = {};
|
|
859
|
+
for (const key of Object.keys(value)) {
|
|
860
|
+
const v = value[key];
|
|
861
|
+
if (Array.isArray(v)) {
|
|
862
|
+
result[key] = v.length > 0 ? `[${v.length} items]` : [];
|
|
863
|
+
} else if (v !== null && typeof v === "object") {
|
|
864
|
+
result[key] = depth < 1 ? jsonSkeleton(v, depth + 1) : "{ \u2026 }";
|
|
865
|
+
} else {
|
|
866
|
+
result[key] = typeof v;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
return result;
|
|
870
|
+
}
|
|
871
|
+
return typeof value;
|
|
872
|
+
}
|
|
873
|
+
function foldMarkdown(lines, originalLines, maxLines) {
|
|
874
|
+
const output = [];
|
|
875
|
+
let hadFold = false;
|
|
876
|
+
for (const line of lines) {
|
|
877
|
+
if (/^#{1,6}\s/.test(line)) {
|
|
878
|
+
output.push(line);
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
if (output.length > 0 && /^#{1,6}\s/.test(output[output.length - 1])) {
|
|
882
|
+
output.push(line);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (line.trim().startsWith("```")) {
|
|
886
|
+
output.push(line);
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (line.trim() !== "") {
|
|
890
|
+
hadFold = true;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
if (!hadFold || output.length >= originalLines) {
|
|
894
|
+
return { folded: lines.join("\n"), originalLines, foldedLines: originalLines, signatures: [], wasFolded: false };
|
|
895
|
+
}
|
|
896
|
+
if (output.length > maxLines) {
|
|
897
|
+
const headCount = maxLines - TAIL_KEEP - 1;
|
|
898
|
+
const headPart = output.slice(0, headCount);
|
|
899
|
+
const tailPart = output.slice(-TAIL_KEEP);
|
|
900
|
+
const omitted = output.length - headCount - TAIL_KEEP;
|
|
901
|
+
output.length = 0;
|
|
902
|
+
output.push(...headPart, ` \u2026 (${omitted} lines omitted) \u2026`, ...tailPart);
|
|
903
|
+
}
|
|
904
|
+
return {
|
|
905
|
+
folded: output.join("\n"),
|
|
906
|
+
originalLines,
|
|
907
|
+
foldedLines: output.length,
|
|
908
|
+
signatures: [],
|
|
909
|
+
wasFolded: true
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
function foldCode(content, options) {
|
|
913
|
+
const filePath = options.filePath;
|
|
914
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
915
|
+
const lang = detectLanguage(filePath);
|
|
916
|
+
const lines = content.split("\n");
|
|
917
|
+
const originalLines = lines.length;
|
|
918
|
+
if (originalLines < MIN_LINES_TO_FOLD) {
|
|
919
|
+
return { folded: content, originalLines, foldedLines: originalLines, signatures: [], wasFolded: false };
|
|
920
|
+
}
|
|
921
|
+
switch (lang) {
|
|
922
|
+
case "unknown":
|
|
923
|
+
return { folded: content, originalLines, foldedLines: originalLines, signatures: [], wasFolded: false };
|
|
924
|
+
case "json":
|
|
925
|
+
return foldJson(content, originalLines);
|
|
926
|
+
case "md":
|
|
927
|
+
return foldMarkdown(lines, originalLines, maxLines);
|
|
928
|
+
case "py":
|
|
929
|
+
return foldPython(lines, originalLines, maxLines);
|
|
930
|
+
default:
|
|
931
|
+
return foldTsLike(lines, originalLines, maxLines);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// src/agent/prewarm-file.ts
|
|
936
|
+
init_esm_shims();
|
|
937
|
+
import { stat } from "fs/promises";
|
|
938
|
+
import { statSync } from "fs";
|
|
939
|
+
var MAX_PREWARM_BYTES = 1e5;
|
|
940
|
+
function canUsePrewarmForRead(input) {
|
|
941
|
+
return typeof input.file_path === "string" && input.offset === void 0 && input.limit === void 0;
|
|
942
|
+
}
|
|
943
|
+
async function buildPrewarmValue(cwd, filePath) {
|
|
944
|
+
try {
|
|
945
|
+
const payload = await readFilePayload(cwd, { filePath });
|
|
946
|
+
const fileStat = await stat(payload.canonicalPath);
|
|
947
|
+
if (fileStat.size > MAX_PREWARM_BYTES) return void 0;
|
|
948
|
+
return {
|
|
949
|
+
canonicalPath: payload.canonicalPath,
|
|
950
|
+
content: payload.rawContent,
|
|
951
|
+
uiContent: payload.uiContent,
|
|
952
|
+
mtimeMs: fileStat.mtimeMs,
|
|
953
|
+
sizeBytes: fileStat.size
|
|
954
|
+
};
|
|
955
|
+
} catch {
|
|
956
|
+
return void 0;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
var buildPrewarmValueAsync = buildPrewarmValue;
|
|
960
|
+
function consumePrewarm(cache, canonicalPath) {
|
|
961
|
+
const value = cache.get(canonicalPath);
|
|
962
|
+
if (!value) return null;
|
|
963
|
+
try {
|
|
964
|
+
const live = statSync(canonicalPath);
|
|
965
|
+
if (live.mtimeMs !== value.mtimeMs || live.size !== value.sizeBytes) {
|
|
966
|
+
cache.invalidate(canonicalPath);
|
|
967
|
+
return null;
|
|
968
|
+
}
|
|
969
|
+
return value;
|
|
970
|
+
} catch {
|
|
971
|
+
cache.invalidate(canonicalPath);
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
async function batchPrewarm(cwd, paths, cache, limit = 5) {
|
|
976
|
+
let count = 0;
|
|
977
|
+
for (const filePath of paths) {
|
|
978
|
+
if (count >= limit) break;
|
|
979
|
+
const value = await buildPrewarmValueAsync(cwd, filePath);
|
|
980
|
+
if (!value) continue;
|
|
981
|
+
if (cache.has(value.canonicalPath)) continue;
|
|
982
|
+
cache.set(value.canonicalPath, value);
|
|
983
|
+
count++;
|
|
984
|
+
await new Promise((resolve2) => setImmediate(resolve2));
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// src/tools/office-reader.ts
|
|
989
|
+
init_esm_shims();
|
|
990
|
+
import { execFile } from "child_process";
|
|
991
|
+
import { access, readFile as readFile2, unlink } from "fs/promises";
|
|
992
|
+
import { extname as extname2, basename } from "path";
|
|
993
|
+
import { tmpdir } from "os";
|
|
994
|
+
function execFileAsync(binary, args, opts) {
|
|
995
|
+
return new Promise((resolve2, reject) => {
|
|
996
|
+
execFile(binary, args, { timeout: opts?.timeout ?? 3e4, maxBuffer: 10 * 1024 * 1024, windowsHide: true }, (err, stdout, stderr) => {
|
|
997
|
+
if (err) reject(err);
|
|
998
|
+
else resolve2({ stdout, stderr });
|
|
999
|
+
});
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
var OFFICE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1003
|
+
".doc",
|
|
1004
|
+
".docx",
|
|
1005
|
+
".rtf",
|
|
1006
|
+
".odt"
|
|
1007
|
+
]);
|
|
1008
|
+
var cachedSofficeBinary = "soffice";
|
|
1009
|
+
async function detectEngine() {
|
|
1010
|
+
if (process.platform === "darwin") {
|
|
1011
|
+
try {
|
|
1012
|
+
await access("/usr/bin/textutil");
|
|
1013
|
+
return "textutil";
|
|
1014
|
+
} catch {
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
try {
|
|
1018
|
+
await execFileAsync("soffice", ["--version"], { timeout: 5e3 });
|
|
1019
|
+
cachedSofficeBinary = "soffice";
|
|
1020
|
+
return "soffice";
|
|
1021
|
+
} catch {
|
|
1022
|
+
}
|
|
1023
|
+
try {
|
|
1024
|
+
await execFileAsync("libreoffice", ["--version"], { timeout: 5e3 });
|
|
1025
|
+
cachedSofficeBinary = "libreoffice";
|
|
1026
|
+
return "soffice";
|
|
1027
|
+
} catch {
|
|
1028
|
+
}
|
|
1029
|
+
return null;
|
|
1030
|
+
}
|
|
1031
|
+
var cachedEngine;
|
|
1032
|
+
async function getEngine() {
|
|
1033
|
+
if (cachedEngine !== void 0) return cachedEngine;
|
|
1034
|
+
cachedEngine = await detectEngine();
|
|
1035
|
+
if (!cachedEngine) {
|
|
1036
|
+
try {
|
|
1037
|
+
await import("mammoth");
|
|
1038
|
+
cachedEngine = "mammoth";
|
|
1039
|
+
} catch {
|
|
1040
|
+
const installs = {
|
|
1041
|
+
darwin: "",
|
|
1042
|
+
// textutil is built-in
|
|
1043
|
+
linux: "sudo apt install libreoffice",
|
|
1044
|
+
win32: "winget install LibreOffice.LibreOffice"
|
|
1045
|
+
};
|
|
1046
|
+
throw new Error(
|
|
1047
|
+
`No Office document converter available. Install LibreOffice${installs[process.platform] ? ": " + installs[process.platform] : ""}. Or install mammoth for .docx only: npm install mammoth`
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return cachedEngine;
|
|
1052
|
+
}
|
|
1053
|
+
async function readOfficeFile(filePath) {
|
|
1054
|
+
const ext = extname2(filePath).toLowerCase();
|
|
1055
|
+
const engine = await getEngine();
|
|
1056
|
+
if (engine === "textutil") {
|
|
1057
|
+
return readWithTextutil(filePath, ext);
|
|
1058
|
+
}
|
|
1059
|
+
if (engine === "soffice") {
|
|
1060
|
+
return readWithSoffice(filePath, ext);
|
|
1061
|
+
}
|
|
1062
|
+
return readWithMammoth(filePath, ext);
|
|
1063
|
+
}
|
|
1064
|
+
async function readWithTextutil(filePath, ext) {
|
|
1065
|
+
const text = await execTextutil(filePath);
|
|
1066
|
+
return { text, engine: "textutil", sourceFormat: ext };
|
|
1067
|
+
}
|
|
1068
|
+
async function readWithSoffice(filePath, ext) {
|
|
1069
|
+
const text = await execSoffice(filePath);
|
|
1070
|
+
return { text, engine: "soffice", sourceFormat: ext };
|
|
1071
|
+
}
|
|
1072
|
+
async function readWithMammoth(filePath, ext) {
|
|
1073
|
+
if (ext !== ".docx") {
|
|
1074
|
+
throw new Error(
|
|
1075
|
+
`mammoth only supports .docx files (got ${ext}). Install LibreOffice for .doc/.rtf/.odt support: https://www.libreoffice.org/`
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
const text = await execMammoth(filePath);
|
|
1079
|
+
return { text, engine: "mammoth", sourceFormat: ext };
|
|
1080
|
+
}
|
|
1081
|
+
function execTextutil(filePath) {
|
|
1082
|
+
return new Promise((resolve2, reject) => {
|
|
1083
|
+
execFile("textutil", ["-convert", "txt", "-stdout", filePath], {
|
|
1084
|
+
timeout: 3e4,
|
|
1085
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
1086
|
+
// 10 MB
|
|
1087
|
+
windowsHide: true
|
|
1088
|
+
}, (err, stdout) => {
|
|
1089
|
+
if (err) reject(new Error(`textutil failed: ${err.message}`));
|
|
1090
|
+
else resolve2(stdout);
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
function execSoffice(filePath) {
|
|
1095
|
+
const name = basename(filePath);
|
|
1096
|
+
const outName = name.replace(/\.[^.]+$/, ".txt");
|
|
1097
|
+
const outPath = `${tmpdir()}/${outName}`;
|
|
1098
|
+
return new Promise((resolve2, reject) => {
|
|
1099
|
+
execFile(cachedSofficeBinary, ["--headless", "--convert-to", "txt", "--outdir", tmpdir(), filePath], {
|
|
1100
|
+
timeout: 6e4,
|
|
1101
|
+
windowsHide: true
|
|
1102
|
+
}, async (err) => {
|
|
1103
|
+
if (err) {
|
|
1104
|
+
reject(new Error(`${cachedSofficeBinary} failed: ${err.message}`));
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
try {
|
|
1108
|
+
const text = await readFile2(outPath, "utf-8");
|
|
1109
|
+
await unlink(outPath).catch(() => {
|
|
1110
|
+
});
|
|
1111
|
+
resolve2(text);
|
|
1112
|
+
} catch (e) {
|
|
1113
|
+
reject(new Error(`soffice output file not found: ${outPath}`));
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
async function execMammoth(filePath) {
|
|
1119
|
+
const mammoth = await import("mammoth");
|
|
1120
|
+
const result = await mammoth.extractRawText({ path: filePath });
|
|
1121
|
+
return result.value;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
// src/tools/focused-read.ts
|
|
1125
|
+
init_esm_shims();
|
|
1126
|
+
var DEFAULT_MAX_MATCHES = 8;
|
|
1127
|
+
var DEFAULT_CONTEXT_LINES = 2;
|
|
1128
|
+
var MAX_FOCUS_LENGTH = 240;
|
|
1129
|
+
var FOCUS_STOP_WORDS = /* @__PURE__ */ new Set([
|
|
1130
|
+
"a",
|
|
1131
|
+
"an",
|
|
1132
|
+
"and",
|
|
1133
|
+
"for",
|
|
1134
|
+
"from",
|
|
1135
|
+
"find",
|
|
1136
|
+
"file",
|
|
1137
|
+
"into",
|
|
1138
|
+
"look",
|
|
1139
|
+
"read",
|
|
1140
|
+
"the",
|
|
1141
|
+
"this",
|
|
1142
|
+
"with",
|
|
1143
|
+
"please",
|
|
1144
|
+
"show",
|
|
1145
|
+
"where",
|
|
1146
|
+
"what",
|
|
1147
|
+
"which",
|
|
1148
|
+
"code",
|
|
1149
|
+
"source",
|
|
1150
|
+
"implementation",
|
|
1151
|
+
"\u8BF7",
|
|
1152
|
+
"\u5E2E\u6211",
|
|
1153
|
+
"\u67E5\u627E",
|
|
1154
|
+
"\u8BFB\u53D6",
|
|
1155
|
+
"\u770B\u770B",
|
|
1156
|
+
"\u6587\u4EF6",
|
|
1157
|
+
"\u4EE3\u7801",
|
|
1158
|
+
"\u5B9E\u73B0",
|
|
1159
|
+
"\u76F8\u5173",
|
|
1160
|
+
"\u95EE\u9898",
|
|
1161
|
+
"\u4E00\u4E0B",
|
|
1162
|
+
"\u91CC\u9762"
|
|
1163
|
+
]);
|
|
1164
|
+
var STRUCTURAL_LINE = /^\s*(?:import\b|export\b|(?:async\s+)?function\b|class\b|interface\b|type\b|enum\b|const\b|let\b|var\b|def\b|struct\b|impl\b|trait\b|#{1,6}\s)/;
|
|
1165
|
+
function normalizeFocus(focus) {
|
|
1166
|
+
return focus.replace(/\s+/g, " ").trim().slice(0, MAX_FOCUS_LENGTH);
|
|
1167
|
+
}
|
|
1168
|
+
function tokenizeFocus(focus) {
|
|
1169
|
+
const fragments = normalizeFocus(focus).toLowerCase().match(/[a-z_$][a-z0-9_$-]{1,}|[\u4e00-\u9fff]+/g) ?? [];
|
|
1170
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
1171
|
+
for (const fragment of fragments) {
|
|
1172
|
+
if (/^[a-z_$]/.test(fragment)) {
|
|
1173
|
+
if (fragment.length >= 2 && !FOCUS_STOP_WORDS.has(fragment)) tokens.add(fragment);
|
|
1174
|
+
continue;
|
|
1175
|
+
}
|
|
1176
|
+
if (fragment.length === 1) {
|
|
1177
|
+
if (!FOCUS_STOP_WORDS.has(fragment)) tokens.add(fragment);
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
for (let i = 0; i < fragment.length - 1; i++) {
|
|
1181
|
+
const bigram = fragment.slice(i, i + 2);
|
|
1182
|
+
if (!FOCUS_STOP_WORDS.has(bigram)) tokens.add(bigram);
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
return [...tokens];
|
|
1186
|
+
}
|
|
1187
|
+
function scoreLine(line, focus, tokens) {
|
|
1188
|
+
const lower = line.toLowerCase();
|
|
1189
|
+
const normalizedFocus = normalizeFocus(focus).toLowerCase();
|
|
1190
|
+
let score = 0;
|
|
1191
|
+
if (normalizedFocus.length >= 4 && lower.includes(normalizedFocus)) score += 24;
|
|
1192
|
+
let matches = 0;
|
|
1193
|
+
for (const token of tokens) {
|
|
1194
|
+
if (!lower.includes(token)) continue;
|
|
1195
|
+
matches++;
|
|
1196
|
+
score += token.includes("_") || token.length >= 6 ? 7 : 3;
|
|
1197
|
+
}
|
|
1198
|
+
if (matches > 1) score += matches * 2;
|
|
1199
|
+
if (matches > 0 && STRUCTURAL_LINE.test(line)) score += 4;
|
|
1200
|
+
return score;
|
|
1201
|
+
}
|
|
1202
|
+
function mergeRanges(ranges) {
|
|
1203
|
+
const sorted = [...ranges].sort((a, b) => a.startLine - b.startLine);
|
|
1204
|
+
const merged = [];
|
|
1205
|
+
for (const range of sorted) {
|
|
1206
|
+
const previous = merged[merged.length - 1];
|
|
1207
|
+
if (!previous || range.startLine > previous.endLine + 1) {
|
|
1208
|
+
merged.push({ ...range });
|
|
1209
|
+
continue;
|
|
1210
|
+
}
|
|
1211
|
+
previous.endLine = Math.max(previous.endLine, range.endLine);
|
|
1212
|
+
previous.score = Math.max(previous.score, range.score);
|
|
1213
|
+
}
|
|
1214
|
+
return merged;
|
|
1215
|
+
}
|
|
1216
|
+
function structuralSkeleton(filePath, lines, content, maxChars) {
|
|
1217
|
+
const folded = foldCode(content, { filePath, maxLines: 100 });
|
|
1218
|
+
const structural = folded.wasFolded ? folded.folded : lines.filter((line) => STRUCTURAL_LINE.test(line)).slice(0, 100).join("\n");
|
|
1219
|
+
const body = structural.trim() || lines.slice(0, 40).join("\n");
|
|
1220
|
+
return body.slice(0, Math.max(600, maxChars));
|
|
1221
|
+
}
|
|
1222
|
+
function renderRanges(lines, ranges) {
|
|
1223
|
+
return ranges.map((range) => {
|
|
1224
|
+
const body = lines.slice(range.startLine, range.endLine + 1).map((line, offset) => `${String(range.startLine + offset + 1).padStart(5, " ")} | ${line}`).join("\n");
|
|
1225
|
+
return `--- L${range.startLine + 1}-L${range.endLine + 1} (relevance ${range.score}) ---
|
|
1226
|
+
${body}`;
|
|
1227
|
+
}).join("\n\n");
|
|
1228
|
+
}
|
|
1229
|
+
function renderFocusedContent(filePath, focus, lines, summary, ranges, maxChars) {
|
|
1230
|
+
const shownLines = ranges.reduce((sum, range) => sum + range.endLine - range.startLine + 1, 0);
|
|
1231
|
+
const header = [
|
|
1232
|
+
`[focused-read] ${filePath}`,
|
|
1233
|
+
`focus: ${normalizeFocus(focus)}`,
|
|
1234
|
+
`source: ${lines.length} lines, ${lines.join("\n").length} chars; showing ${shownLines} relevant lines`,
|
|
1235
|
+
summary ? `structural summary: ${summary}` : "",
|
|
1236
|
+
"Only the ranges below are selected evidence; this is not the complete file."
|
|
1237
|
+
].filter(Boolean).join("\n");
|
|
1238
|
+
const body = ranges.length > 0 ? renderRanges(lines, ranges) : `No direct focus match. Structural outline only:
|
|
1239
|
+
${structuralSkeleton(filePath, lines, lines.join("\n"), Math.max(800, maxChars - header.length))}`;
|
|
1240
|
+
const omitted = Math.max(0, lines.length - shownLines);
|
|
1241
|
+
const footer = `
|
|
1242
|
+
|
|
1243
|
+
[focused-read] omitted ${omitted} source lines; use read_file(offset, limit) for an exact range.`;
|
|
1244
|
+
const output = `${header}
|
|
1245
|
+
|
|
1246
|
+
${body}${footer}`;
|
|
1247
|
+
if (output.length <= maxChars) return output;
|
|
1248
|
+
return `${output.slice(0, Math.max(0, maxChars - 80))}
|
|
1249
|
+
...[focused-read output capped at ${maxChars} chars]`;
|
|
1250
|
+
}
|
|
1251
|
+
function buildFocusedReadView(options) {
|
|
1252
|
+
const focus = normalizeFocus(options.focus);
|
|
1253
|
+
const lines = options.content.split("\n");
|
|
1254
|
+
const tokens = tokenizeFocus(focus);
|
|
1255
|
+
const contextLines = Math.max(0, Math.min(8, Math.floor(options.contextLines ?? DEFAULT_CONTEXT_LINES)));
|
|
1256
|
+
const maxMatches = Math.max(1, Math.min(20, Math.floor(options.maxMatches ?? DEFAULT_MAX_MATCHES)));
|
|
1257
|
+
const maxChars = Math.max(800, Math.floor(options.maxChars));
|
|
1258
|
+
const summary = summarizeFileContent(options.content, options.filePath).summary.trim();
|
|
1259
|
+
const ranked = tokens.length === 0 ? [] : lines.map((line, index) => ({ line, index, score: scoreLine(line, focus, tokens) })).filter((entry) => entry.score > 0).sort((a, b) => b.score - a.score || a.index - b.index);
|
|
1260
|
+
const selected = ranked.slice(0, maxMatches).map((entry) => ({
|
|
1261
|
+
startLine: Math.max(0, entry.index - contextLines),
|
|
1262
|
+
endLine: Math.min(lines.length - 1, entry.index + contextLines),
|
|
1263
|
+
score: entry.score
|
|
1264
|
+
}));
|
|
1265
|
+
let ranges = mergeRanges(selected);
|
|
1266
|
+
while (ranges.length > 1 && renderRanges(lines, ranges).length > Math.max(400, maxChars - 600)) {
|
|
1267
|
+
const lowest = ranges.reduce((index, range, current) => range.score < ranges[index].score ? current : index, 0);
|
|
1268
|
+
ranges = ranges.filter((_, index) => index !== lowest);
|
|
1269
|
+
}
|
|
1270
|
+
const content = renderFocusedContent(options.filePath, focus, lines, summary, ranges, maxChars);
|
|
1271
|
+
const matchedLines = ranges.reduce((sum, range) => sum + range.endLine - range.startLine + 1, 0);
|
|
1272
|
+
return {
|
|
1273
|
+
content,
|
|
1274
|
+
ranges,
|
|
1275
|
+
matchedLines,
|
|
1276
|
+
omittedLines: Math.max(0, lines.length - matchedLines),
|
|
1277
|
+
matched: ranges.length > 0
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// src/tools/read-file.ts
|
|
1282
|
+
var gitignoreCache = /* @__PURE__ */ new Map();
|
|
1283
|
+
var GITIGNORE_CACHE_TTL = 6e4;
|
|
1284
|
+
var GITIGNORE_CACHE_MAX = 50;
|
|
1285
|
+
function trimGitignoreCache() {
|
|
1286
|
+
if (gitignoreCache.size <= GITIGNORE_CACHE_MAX) return;
|
|
1287
|
+
const now = Date.now();
|
|
1288
|
+
for (const [key, val] of gitignoreCache) {
|
|
1289
|
+
if (now - val.ts > GITIGNORE_CACHE_TTL) gitignoreCache.delete(key);
|
|
1290
|
+
}
|
|
1291
|
+
while (gitignoreCache.size > GITIGNORE_CACHE_MAX) {
|
|
1292
|
+
const [key] = gitignoreCache.keys();
|
|
1293
|
+
gitignoreCache.delete(key);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
var readHistory = /* @__PURE__ */ new Map();
|
|
1297
|
+
var READ_HISTORY_MAX = 500;
|
|
1298
|
+
var neighborHintProvider;
|
|
1299
|
+
function setNeighborHintProvider(provider) {
|
|
1300
|
+
neighborHintProvider = provider;
|
|
1301
|
+
}
|
|
1302
|
+
var fileReadHistory = /* @__PURE__ */ new Map();
|
|
1303
|
+
var FILE_READ_HISTORY_MAX = 200;
|
|
1304
|
+
var lastKnownFileState = /* @__PURE__ */ new Map();
|
|
1305
|
+
var LAST_KNOWN_MAX = 500;
|
|
1306
|
+
var editFailCount = /* @__PURE__ */ new Map();
|
|
1307
|
+
function incrementEditFailCount(canonicalPath) {
|
|
1308
|
+
const n = (editFailCount.get(canonicalPath) ?? 0) + 1;
|
|
1309
|
+
editFailCount.set(canonicalPath, n);
|
|
1310
|
+
return n;
|
|
1311
|
+
}
|
|
1312
|
+
function resetEditFailCount(canonicalPath) {
|
|
1313
|
+
editFailCount.delete(canonicalPath);
|
|
1314
|
+
}
|
|
1315
|
+
function trimLastKnown() {
|
|
1316
|
+
if (lastKnownFileState.size <= LAST_KNOWN_MAX) return;
|
|
1317
|
+
const drop = Math.ceil(lastKnownFileState.size * 0.2);
|
|
1318
|
+
let i = 0;
|
|
1319
|
+
for (const key of lastKnownFileState.keys()) {
|
|
1320
|
+
lastKnownFileState.delete(key);
|
|
1321
|
+
if (++i >= drop) break;
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
function isReadRefEnabled() {
|
|
1325
|
+
return process.env["RIVET_READ_REF"] !== "0";
|
|
1326
|
+
}
|
|
1327
|
+
var READ_REF_THRESHOLD = 2048;
|
|
1328
|
+
var readRefSavedBytes = 0;
|
|
1329
|
+
var readRefCount = 0;
|
|
1330
|
+
var sessionFileEdits = /* @__PURE__ */ new Set();
|
|
1331
|
+
function markSessionFileEdit(canonicalPath, sessionId) {
|
|
1332
|
+
sessionFileEdits.add(fileHistoryKey(sessionId, canonicalPath));
|
|
1333
|
+
}
|
|
1334
|
+
function wasFileEditedBySession(canonicalPath, sessionId) {
|
|
1335
|
+
return sessionFileEdits.has(fileHistoryKey(sessionId, canonicalPath));
|
|
1336
|
+
}
|
|
1337
|
+
function readHistoryKey(cwd, canonicalPath, offset, limit, sessionId) {
|
|
1338
|
+
return `${sessionId ?? ""}::${canonicalPathKey(cwd)}::${canonicalPathKey(canonicalPath)}::${offset}::${limit ?? "all"}`;
|
|
1339
|
+
}
|
|
1340
|
+
function fileHistoryKey(sessionId, canonicalPath) {
|
|
1341
|
+
return `${sessionId ?? ""}::${canonicalPathKey(canonicalPath)}`;
|
|
1342
|
+
}
|
|
1343
|
+
function trimReadHistory() {
|
|
1344
|
+
if (readHistory.size <= READ_HISTORY_MAX) return;
|
|
1345
|
+
const sorted = [...readHistory.entries()].sort((a, b) => a[1].recordedAt - b[1].recordedAt);
|
|
1346
|
+
const drop = Math.ceil(readHistory.size * 0.2);
|
|
1347
|
+
for (let i = 0; i < drop; i++) readHistory.delete(sorted[i][0]);
|
|
1348
|
+
}
|
|
1349
|
+
function trimFileReadHistory() {
|
|
1350
|
+
if (fileReadHistory.size <= FILE_READ_HISTORY_MAX) return;
|
|
1351
|
+
const sorted = [...fileReadHistory.entries()].sort((a, b) => a[1].recordedAt - b[1].recordedAt);
|
|
1352
|
+
const drop = Math.ceil(fileReadHistory.size * 0.2);
|
|
1353
|
+
for (let i = 0; i < drop; i++) fileReadHistory.delete(sorted[i][0]);
|
|
1354
|
+
}
|
|
1355
|
+
function isUnchangedRepeatRead(canonical, currentMtimeMs, currentSizeBytes, dedupKey, offset, limit, sessionId) {
|
|
1356
|
+
const priorSame = readHistory.get(dedupKey);
|
|
1357
|
+
if (priorSame && priorSame.mtimeMs === currentMtimeMs && priorSame.sizeBytes === currentSizeBytes) return true;
|
|
1358
|
+
const fullPrior = fileReadHistory.get(fileHistoryKey(sessionId, canonical));
|
|
1359
|
+
if (fullPrior && fullPrior.mtimeMs === currentMtimeMs && fullPrior.sizeBytes === currentSizeBytes && offset === 1 && !limit) return true;
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
function maybeAppendNeighborHint(cwd, canonicalPath, sessionId, content) {
|
|
1363
|
+
if (process.env["RIVET_NEIGHBOR_HINT"] !== "1") return content;
|
|
1364
|
+
if (!neighborHintProvider) return content;
|
|
1365
|
+
let neighbors;
|
|
1366
|
+
try {
|
|
1367
|
+
neighbors = neighborHintProvider(cwd, canonicalPath);
|
|
1368
|
+
} catch {
|
|
1369
|
+
return content;
|
|
1370
|
+
}
|
|
1371
|
+
const unread = neighbors.filter((abs) => !fileReadHistory.has(fileHistoryKey(sessionId, abs)));
|
|
1372
|
+
if (unread.length === 0) return content;
|
|
1373
|
+
const label = "\u7ED3\u6784\u90BB\u5C45: " + unread.slice(0, 3).map((abs) => relative(cwd, abs)).join(", ");
|
|
1374
|
+
return content + "\n\n" + label.slice(0, 100);
|
|
1375
|
+
}
|
|
1376
|
+
function getFileReadMtime(canonicalPath, sessionId) {
|
|
1377
|
+
const entry = lastKnownFileState.get(fileHistoryKey(sessionId, canonicalPath));
|
|
1378
|
+
return entry ? entry.mtimeMs : null;
|
|
1379
|
+
}
|
|
1380
|
+
function noteFileObserved(canonicalPath, mtimeMs, sizeBytes, sessionId) {
|
|
1381
|
+
lastKnownFileState.set(fileHistoryKey(sessionId, canonicalPath), { mtimeMs, sizeBytes });
|
|
1382
|
+
trimLastKnown();
|
|
1383
|
+
}
|
|
1384
|
+
function invalidateReadHistory(canonicalPath) {
|
|
1385
|
+
const keyPath = canonicalPathKey(canonicalPath);
|
|
1386
|
+
const suffix = `::${keyPath}`;
|
|
1387
|
+
for (const key of fileReadHistory.keys()) {
|
|
1388
|
+
if (key.endsWith(suffix)) fileReadHistory.delete(key);
|
|
1389
|
+
}
|
|
1390
|
+
const segment = `::${keyPath}::`;
|
|
1391
|
+
for (const key of readHistory.keys()) {
|
|
1392
|
+
if (key.includes(segment)) readHistory.delete(key);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
function invalidateSessionReadDedup(sessionId) {
|
|
1396
|
+
const prefix = `${sessionId ?? ""}::`;
|
|
1397
|
+
for (const key of readHistory.keys()) {
|
|
1398
|
+
if (key.startsWith(prefix)) readHistory.delete(key);
|
|
1399
|
+
}
|
|
1400
|
+
for (const key of fileReadHistory.keys()) {
|
|
1401
|
+
if (key.startsWith(prefix)) fileReadHistory.delete(key);
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
async function recordSuccessfulEdit(canonicalPath, sessionId) {
|
|
1405
|
+
invalidateReadHistory(canonicalPath);
|
|
1406
|
+
markSessionFileEdit(canonicalPath, sessionId);
|
|
1407
|
+
try {
|
|
1408
|
+
const s = await stat2(canonicalPath);
|
|
1409
|
+
noteFileObserved(canonicalPath, s.mtimeMs, s.size, sessionId);
|
|
1410
|
+
} catch {
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
function registerGrepFileAccess(canonicalPath, mtimeMs, sessionId) {
|
|
1414
|
+
const key = fileHistoryKey(sessionId, canonicalPath);
|
|
1415
|
+
if (lastKnownFileState.has(key)) return;
|
|
1416
|
+
lastKnownFileState.set(key, { mtimeMs, sizeBytes: -1 });
|
|
1417
|
+
trimLastKnown();
|
|
1418
|
+
}
|
|
1419
|
+
function getReadRefStats() {
|
|
1420
|
+
return { savedBytes: readRefSavedBytes, count: readRefCount };
|
|
1421
|
+
}
|
|
1422
|
+
async function sliceFromArtifact(store, artifactId, offset, limit) {
|
|
1423
|
+
const recovered = await store.readRaw(artifactId);
|
|
1424
|
+
if (!recovered) return null;
|
|
1425
|
+
const lines = recovered.split("\n");
|
|
1426
|
+
const start = Math.max(0, offset - 1);
|
|
1427
|
+
const end = limit ? start + limit : lines.length;
|
|
1428
|
+
return lines.slice(start, end).join("\n");
|
|
1429
|
+
}
|
|
1430
|
+
function getGitignoreFilter(cwd) {
|
|
1431
|
+
const cached = gitignoreCache.get(cwd);
|
|
1432
|
+
if (cached && Date.now() - cached.ts < GITIGNORE_CACHE_TTL) {
|
|
1433
|
+
return cached.filter;
|
|
1434
|
+
}
|
|
1435
|
+
const filterPromise = GitignoreFilter.create(cwd);
|
|
1436
|
+
gitignoreCache.set(cwd, { filter: filterPromise, ts: Date.now() });
|
|
1437
|
+
trimGitignoreCache();
|
|
1438
|
+
return filterPromise;
|
|
1439
|
+
}
|
|
1440
|
+
var MAX_TOOL_INPUT_BYTES = 100 * 1024;
|
|
1441
|
+
var MAX_FOCUS_SCAN_BYTES = 2 * 1024 * 1024;
|
|
1442
|
+
var LOG_PREVIEW_LINES = 80;
|
|
1443
|
+
function applyFoldThenPartial(content, filePath, cap) {
|
|
1444
|
+
const fold = foldCode(content, { filePath, maxLines: 200 });
|
|
1445
|
+
if (fold.wasFolded && fold.foldedLines < fold.originalLines * 0.7) {
|
|
1446
|
+
return buildPartialView(fold.folded, filePath, cap.maxChars, { lines: fold.originalLines, chars: content.length });
|
|
1447
|
+
}
|
|
1448
|
+
return buildPartialView(content, filePath, cap.maxChars);
|
|
1449
|
+
}
|
|
1450
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1451
|
+
".png",
|
|
1452
|
+
".jpg",
|
|
1453
|
+
".jpeg",
|
|
1454
|
+
".gif",
|
|
1455
|
+
".bmp",
|
|
1456
|
+
".ico",
|
|
1457
|
+
".webp",
|
|
1458
|
+
".svgz",
|
|
1459
|
+
".pdf",
|
|
1460
|
+
".exe",
|
|
1461
|
+
".dll",
|
|
1462
|
+
".so",
|
|
1463
|
+
".dylib",
|
|
1464
|
+
".wasm",
|
|
1465
|
+
".zip",
|
|
1466
|
+
".tar",
|
|
1467
|
+
".gz",
|
|
1468
|
+
".bz2",
|
|
1469
|
+
".xz",
|
|
1470
|
+
".7z",
|
|
1471
|
+
".rar",
|
|
1472
|
+
".mp3",
|
|
1473
|
+
".mp4",
|
|
1474
|
+
".avi",
|
|
1475
|
+
".mov",
|
|
1476
|
+
".wav",
|
|
1477
|
+
".ogg",
|
|
1478
|
+
".flac",
|
|
1479
|
+
".woff",
|
|
1480
|
+
".woff2",
|
|
1481
|
+
".ttf",
|
|
1482
|
+
".otf",
|
|
1483
|
+
".eot",
|
|
1484
|
+
".pyc",
|
|
1485
|
+
".class",
|
|
1486
|
+
".o",
|
|
1487
|
+
".obj",
|
|
1488
|
+
".db",
|
|
1489
|
+
".sqlite",
|
|
1490
|
+
".sqlite3"
|
|
1491
|
+
]);
|
|
1492
|
+
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Map([
|
|
1493
|
+
[".png", "image/png"],
|
|
1494
|
+
[".jpg", "image/jpeg"],
|
|
1495
|
+
[".jpeg", "image/jpeg"],
|
|
1496
|
+
[".gif", "image/gif"],
|
|
1497
|
+
[".webp", "image/webp"],
|
|
1498
|
+
[".bmp", "image/bmp"]
|
|
1499
|
+
]);
|
|
1500
|
+
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
1501
|
+
async function readImageFile(filePath, ext, sizeBytes) {
|
|
1502
|
+
if (sizeBytes > MAX_IMAGE_BYTES) {
|
|
1503
|
+
return {
|
|
1504
|
+
content: `Error: image too large (${(sizeBytes / 1048576).toFixed(1)}MB\uFF0C\u4E0A\u9650 10MB)\u3002\u8BF7\u5148\u7F29\u653E/\u88C1\u526A\uFF08\u5982 sips -Z 1600 ${filePath} \u6216 magick ${filePath} -resize 1600x\uFF09\uFF0C\u6216\u7528 tesseract OCR \u63D0\u53D6\u6587\u5B57\u3002`,
|
|
1505
|
+
isError: true
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
const buf = await readFile3(filePath);
|
|
1509
|
+
const mime = IMAGE_EXTENSIONS.get(ext);
|
|
1510
|
+
const dataUrl = `data:${mime};base64,${buf.toString("base64")}`;
|
|
1511
|
+
return {
|
|
1512
|
+
content: `read_file: ${filePath} \u662F\u56FE\u7247\uFF08${mime}\uFF0C${sizeBytes} bytes\uFF09\uFF0C\u5DF2\u4F5C\u4E3A\u89C6\u89C9\u9644\u4EF6\u9644\u4E0A\u2014\u2014\u8BF7\u76F4\u63A5\u67E5\u770B\u56FE\u7247\u5185\u5BB9\u4F5C\u7B54\uFF08\u89C6\u89C9\u6A21\u578B\u53EF\u89C1\uFF1B\u975E\u89C6\u89C9\u6A21\u578B\u8BE5\u9644\u4EF6\u4F1A\u88AB\u81EA\u52A8\u4E22\u5F03\uFF09\u3002`,
|
|
1513
|
+
images: [dataUrl]
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
function buildLogPreviewContent(filePath, content) {
|
|
1517
|
+
const lines = content.split("\n");
|
|
1518
|
+
const headCount = Math.min(LOG_PREVIEW_LINES, lines.length);
|
|
1519
|
+
const tailCount = Math.min(LOG_PREVIEW_LINES, Math.max(0, lines.length - headCount));
|
|
1520
|
+
const head = lines.slice(0, headCount);
|
|
1521
|
+
const tail = tailCount > 0 ? lines.slice(-tailCount) : [];
|
|
1522
|
+
const omitted = Math.max(0, lines.length - head.length - tail.length);
|
|
1523
|
+
const tailStart = tail.length > 0 ? lines.length - tail.length + 1 : 1;
|
|
1524
|
+
const parts = [
|
|
1525
|
+
`read_file: ${filePath} looks like a log/JSONL output file (${content.length} chars, ${lines.length} lines).`,
|
|
1526
|
+
`Full first reads of log files waste context; returning a bounded preview only.`,
|
|
1527
|
+
`Preview boundaries: head offset=1 limit=${head.length}${tail.length > 0 ? `; tail offset=${tailStart} limit=${tail.length}` : ""}.`,
|
|
1528
|
+
`Next step: use read_file(file_path=..., offset=<known line>, limit<=200) for a specific range; use grep on this file for keywords/timestamps before reading middle ranges. Do not scan the whole project for this log.`,
|
|
1529
|
+
"",
|
|
1530
|
+
`\u2500\u2500 head (L1-L${head.length}) \u2500\u2500`,
|
|
1531
|
+
...head
|
|
1532
|
+
];
|
|
1533
|
+
if (omitted > 0) {
|
|
1534
|
+
parts.push("", `... ${omitted} lines omitted ...`, "", `\u2500\u2500 tail (L${tailStart}-L${lines.length}) \u2500\u2500`, ...tail);
|
|
1535
|
+
}
|
|
1536
|
+
return parts.join("\n");
|
|
1537
|
+
}
|
|
1538
|
+
function buildFileUiOutput(raw, maxLines) {
|
|
1539
|
+
const lines = raw.split("\n");
|
|
1540
|
+
const totalLines = lines.length;
|
|
1541
|
+
if (totalLines <= maxLines) {
|
|
1542
|
+
return lines.map((l, i) => `${String(i + 1).padStart(4, " ")}\u2502 ${l}`).join("\n");
|
|
1543
|
+
}
|
|
1544
|
+
const headLines = Math.ceil(maxLines * 0.6);
|
|
1545
|
+
const tailLines = Math.floor(maxLines * 0.4);
|
|
1546
|
+
const omitted = totalLines - headLines - tailLines;
|
|
1547
|
+
const head = lines.slice(0, headLines).map((l, i) => `${String(i + 1).padStart(4, " ")}\u2502 ${l}`);
|
|
1548
|
+
const tail = lines.slice(-tailLines).map((l, i) => `${String(totalLines - tailLines + i + 1).padStart(4, " ")}\u2502 ${l}`);
|
|
1549
|
+
return [...head, ` ... ${omitted} lines omitted ...`, ...tail].join("\n");
|
|
1550
|
+
}
|
|
1551
|
+
function isRivetStatePath(cwd, filePath) {
|
|
1552
|
+
const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
1553
|
+
const base = `${norm(cwd)}/`;
|
|
1554
|
+
return norm(filePath).startsWith(`${base}.rivet/`) || norm(filePath).startsWith(`${base}docs/superpowers/`);
|
|
1555
|
+
}
|
|
1556
|
+
async function readFilePayload(cwd, options) {
|
|
1557
|
+
const filePath = validatePath(cwd, options.filePath);
|
|
1558
|
+
let fileStat;
|
|
1559
|
+
try {
|
|
1560
|
+
fileStat = await stat2(filePath);
|
|
1561
|
+
} catch {
|
|
1562
|
+
throw new Error(`File not found: ${filePath}`);
|
|
1563
|
+
}
|
|
1564
|
+
const filter = await getGitignoreFilter(cwd);
|
|
1565
|
+
if (filter.isIgnored(cwd, filePath) && !isRivetStatePath(cwd, filePath)) {
|
|
1566
|
+
throw new Error(`File is gitignored (node_modules, build artifacts, etc.): ${filePath}`);
|
|
1567
|
+
}
|
|
1568
|
+
const ext = extname3(filePath).toLowerCase();
|
|
1569
|
+
if (OFFICE_EXTENSIONS.has(ext)) {
|
|
1570
|
+
const result = await readOfficeFile(filePath);
|
|
1571
|
+
const officeHint = `
|
|
1572
|
+
|
|
1573
|
+
\u2500\u2500 Office document (${ext}, converted via ${result.engine}) \u2500\u2500`;
|
|
1574
|
+
const cap2 = options.modelCap ?? DEFAULT_MODEL_READ_CAP;
|
|
1575
|
+
return {
|
|
1576
|
+
canonicalPath: filePath,
|
|
1577
|
+
rawContent: result.text,
|
|
1578
|
+
modelContent: truncateContent(result.text, cap2.maxChars, cap2.headChars, cap2.tailChars) + officeHint,
|
|
1579
|
+
uiContent: result.text.slice(0, 200)
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
if (BINARY_EXTENSIONS.has(ext)) {
|
|
1583
|
+
throw new Error(`File is binary (${ext} format). read_file only reads text files. Use file_info to inspect metadata.`);
|
|
1584
|
+
}
|
|
1585
|
+
const fileSize = fileStat.size;
|
|
1586
|
+
const hasExplicitRange = options.offset !== void 0 || options.limit !== void 0;
|
|
1587
|
+
const focus = typeof options.focus === "string" ? options.focus.trim() : "";
|
|
1588
|
+
const hasFocus = focus.length > 0 && !hasExplicitRange;
|
|
1589
|
+
const policy = decideReadPolicy({ filePath, sizeBytes: fileSize, hasExplicitRange });
|
|
1590
|
+
if (hasFocus && fileSize > MAX_FOCUS_SCAN_BYTES) {
|
|
1591
|
+
throw new Error(
|
|
1592
|
+
`Focused read refuses files over ${(MAX_FOCUS_SCAN_BYTES / 1024 / 1024).toFixed(0)}MB. Use grep or an explicit offset/limit range first.`
|
|
1593
|
+
);
|
|
1594
|
+
}
|
|
1595
|
+
if (fileSize > MAX_TOOL_INPUT_BYTES && !hasExplicitRange && !hasFocus) {
|
|
1596
|
+
if (policy.action === "partial") {
|
|
1597
|
+
const content2 = options.prefetchedContent ?? await readFile3(filePath, "utf-8");
|
|
1598
|
+
const cap2 = options.modelCap ?? DEFAULT_MODEL_READ_CAP;
|
|
1599
|
+
const partialContent = applyFoldThenPartial(content2, filePath, cap2);
|
|
1600
|
+
return {
|
|
1601
|
+
canonicalPath: filePath,
|
|
1602
|
+
rawContent: content2,
|
|
1603
|
+
modelContent: partialContent,
|
|
1604
|
+
uiContent: buildFileUiOutput(content2, 80)
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
const sizeKB = (fileSize / 1024).toFixed(0);
|
|
1608
|
+
const estLines = Math.ceil(fileSize / 80);
|
|
1609
|
+
throw new Error(
|
|
1610
|
+
`File too large (${sizeKB}KB, ~${estLines} lines). Use offset and limit to read specific ranges.`
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
let content = options.prefetchedContent ?? await readFile3(filePath, "utf-8");
|
|
1614
|
+
const offset = options.offset ?? 1;
|
|
1615
|
+
const limit = options.limit;
|
|
1616
|
+
const cap = options.modelCap ?? DEFAULT_MODEL_READ_CAP;
|
|
1617
|
+
if (policy.action === "reject-with-range" && !hasExplicitRange) {
|
|
1618
|
+
throw new Error(`${policy.reason}. Use offset and limit to read a specific range.`);
|
|
1619
|
+
}
|
|
1620
|
+
if (hasFocus) {
|
|
1621
|
+
const focused = buildFocusedReadView({
|
|
1622
|
+
filePath,
|
|
1623
|
+
content,
|
|
1624
|
+
focus,
|
|
1625
|
+
maxChars: cap.maxChars,
|
|
1626
|
+
maxMatches: options.focusMaxMatches
|
|
1627
|
+
});
|
|
1628
|
+
return {
|
|
1629
|
+
canonicalPath: filePath,
|
|
1630
|
+
rawContent: content,
|
|
1631
|
+
modelContent: focused.content,
|
|
1632
|
+
uiContent: buildFileUiOutput(content, 80)
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
if (policy.action === "preview" && !hasExplicitRange) {
|
|
1636
|
+
const preview = buildLogPreviewContent(filePath, content);
|
|
1637
|
+
return {
|
|
1638
|
+
canonicalPath: filePath,
|
|
1639
|
+
rawContent: content,
|
|
1640
|
+
modelContent: truncateContent(preview, cap.maxChars, cap.headChars, cap.tailChars),
|
|
1641
|
+
uiContent: buildFileUiOutput(content, 80)
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
if (policy.action === "partial" && !hasExplicitRange) {
|
|
1645
|
+
const partialContent = applyFoldThenPartial(content, filePath, cap);
|
|
1646
|
+
return {
|
|
1647
|
+
canonicalPath: filePath,
|
|
1648
|
+
rawContent: content,
|
|
1649
|
+
modelContent: partialContent,
|
|
1650
|
+
uiContent: buildFileUiOutput(content, 80)
|
|
1651
|
+
};
|
|
1652
|
+
}
|
|
1653
|
+
if (offset > 1 || limit) {
|
|
1654
|
+
const lines = content.split("\n");
|
|
1655
|
+
const startIdx = offset - 1;
|
|
1656
|
+
if (startIdx >= lines.length) {
|
|
1657
|
+
return {
|
|
1658
|
+
canonicalPath: filePath,
|
|
1659
|
+
rawContent: `Error: offset ${offset} exceeds file length (${lines.length} lines)`,
|
|
1660
|
+
modelContent: `Error: offset ${offset} exceeds file length (${lines.length} lines). File has ${lines.length} lines. Re-read without offset or use a smaller offset value.`,
|
|
1661
|
+
uiContent: ""
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
if (offset < 1) {
|
|
1665
|
+
return {
|
|
1666
|
+
canonicalPath: filePath,
|
|
1667
|
+
rawContent: `Error: offset must be >= 1 (got ${offset})`,
|
|
1668
|
+
modelContent: `Error: offset must be >= 1 (got ${offset}). Lines are 1-based.`,
|
|
1669
|
+
uiContent: ""
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
const endIdx = limit ? startIdx + limit : void 0;
|
|
1673
|
+
content = lines.slice(startIdx, endIdx).join("\n");
|
|
1674
|
+
}
|
|
1675
|
+
let modelContent;
|
|
1676
|
+
if (options.preferFoldOnOverflow && !hasExplicitRange && content.length > cap.maxChars) {
|
|
1677
|
+
const folded = applyFoldThenPartial(content, filePath, cap);
|
|
1678
|
+
modelContent = folded.length <= Math.floor(cap.maxChars * 1.2) ? folded : truncateContent(content, cap.maxChars, cap.headChars, cap.tailChars);
|
|
1679
|
+
} else {
|
|
1680
|
+
modelContent = truncateContent(content, cap.maxChars, cap.headChars, cap.tailChars);
|
|
1681
|
+
}
|
|
1682
|
+
const hint = policy.action === "full-with-hint" ? `
|
|
1683
|
+
|
|
1684
|
+
\u2500\u2500 Note: this file is ${content.split("\n").length} lines. For editing, consider: grep to locate target \u2192 hash_edit with anchors. \u2500\u2500` : "";
|
|
1685
|
+
return {
|
|
1686
|
+
canonicalPath: filePath,
|
|
1687
|
+
rawContent: content,
|
|
1688
|
+
modelContent: modelContent + hint,
|
|
1689
|
+
uiContent: buildFileUiOutput(content, 50)
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
var READ_FILE_TOOL = {
|
|
1693
|
+
definition: {
|
|
1694
|
+
name: "read_file",
|
|
1695
|
+
description: `\u4ECE\u6587\u4EF6\u7CFB\u7EDF\u8BFB\u53D6\u6587\u4EF6\uFF0C\u652F\u6301\u53EF\u9009\u7684\u884C\u8303\u56F4\u3002
|
|
1696
|
+
|
|
1697
|
+
- \u7EA6 50,000 \u884C\u4EE5\u5185\u7684\u6587\u4EF6\u5B8C\u6574\u8FD4\u56DE\u2014\u2014\u4E0D\u8981\u81EA\u5DF1\u5207\u6210\u5C0F\u7247\u5206\u591A\u6B21\u8BFB
|
|
1698
|
+
- focus="..." \u7528\u4E8E\u6309\u5F53\u524D\u4EFB\u52A1\u63D0\u53D6\u5173\u952E\u4EE3\u7801\uFF1A\u8FD4\u56DE\u7ED3\u6784\u6458\u8981\u548C\u9AD8\u76F8\u5173\u884C\u6BB5\uFF0C\u907F\u514D\u628A\u65E0\u5173\u6B63\u6587\u585E\u8FDB\u4E0A\u4E0B\u6587
|
|
1699
|
+
- offset/limit \u53EA\u7528\u4E8E\u5DF2\u77E5\u5B50\u533A\u95F4\uFF08\u5982\u7B2C 800-900 \u884C\uFF09\uFF0C\u4E0D\u8981\u62FF\u5B83\u5F53\u957F\u6587\u4EF6\u7684\u7ED5\u884C\u624B\u6BB5
|
|
1700
|
+
- \u8D85\u8FC7\u7EA6 2000 \u884C\u7684\u6587\u4EF6\u8FD4\u56DE PARTIAL \u89C6\u56FE\u5E76\u9644\u5BFC\u822A\u63D0\u793A
|
|
1701
|
+
- \u5DF2\u8BFB\u6587\u4EF6\u7684\u5185\u5BB9\u4F1A\u9A7B\u7559\u4E0A\u4E0B\u6587\u3001\u53EF\u76F4\u63A5\u5F15\u7528\uFF1B\u4EC5\u5F53\u6587\u4EF6\u53EF\u80FD\u5DF2\u53D8\u5316\uFF08\u5916\u90E8\u4FEE\u6539\u3001\u5E76\u53D1\u5199\u5165\u3001\u6216\u4F60\u521A\u7F16\u8F91\u8FC7\uFF09\u6216\u6240\u9700\u5185\u5BB9\u4E0D\u5728\u5DF2\u8BFB\u8303\u56F4\u5185\u65F6\uFF0C\u91CD\u8BFB\u662F\u6070\u5F53\u7684
|
|
1702
|
+
- \u5C0F\u6587\u4EF6\uFF08\u5982\u6570\u767E\u884C\u5185\u7684\u65E5\u5FD7\u4E0E\u914D\u7F6E\uFF09\u4F18\u5148\u6574\u4F53\u8BFB\u53D6\u2014\u2014\u6574\u8BFB\u8986\u76D6\u5168\u90E8\u76F8\u5173\u7EC6\u8282\uFF0C\u5B9A\u5411\u641C\u7D22\u53EF\u80FD\u9057\u6F0F\u5173\u952E\u4E0A\u4E0B\u6587
|
|
1703
|
+
- file_paths \u4E00\u6B21\u8C03\u7528\u6700\u591A\u8BFB 5 \u4E2A\u6587\u4EF6
|
|
1704
|
+
- \u56FE\u7247\u6587\u4EF6\uFF08png / jpg / jpeg / gif / webp / bmp\uFF09\u4F1A\u4F5C\u4E3A\u89C6\u89C9\u9644\u4EF6\u8FD4\u56DE\u2014\u2014\u89C6\u89C9\u6A21\u578B\u53EF\u76F4\u63A5\u67E5\u770B\u56FE\u7247\u5185\u5BB9\u4F5C\u7B54\uFF08\u5982\u626B\u63CF\u4EF6\u3001\u622A\u56FE\u3001\u56FE\u8868\uFF09\uFF0C\u65E0\u9700\u5148 OCR\uFF1B\u975E\u89C6\u89C9\u6A21\u578B\u8BE5\u9644\u4EF6\u81EA\u52A8\u4E22\u5F03`,
|
|
1705
|
+
input_schema: {
|
|
1706
|
+
type: "object",
|
|
1707
|
+
properties: {
|
|
1708
|
+
file_path: { type: "string", description: "\u6587\u4EF6\u7684\u7EDD\u5BF9\u8DEF\u5F84" },
|
|
1709
|
+
file_paths: {
|
|
1710
|
+
type: "array",
|
|
1711
|
+
items: { type: "string" },
|
|
1712
|
+
description: "\u4E00\u6B21\u8C03\u7528\u8BFB\u53D6\u591A\u4E2A\u6587\u4EF6\u3002\u7528\u4E8E\u66FF\u4EE3\u91CD\u590D\u7684 read_file \u8C03\u7528\u3002\u6BCF\u4E2A\u6587\u4EF6\u5355\u72EC\u6210\u8282\u3002\u6700\u591A 5 \u4E2A\u6587\u4EF6\u3002"
|
|
1713
|
+
},
|
|
1714
|
+
offset: { type: "integer", description: "\u8D77\u59CB\u8BFB\u53D6\u884C\u53F7\uFF08\u4ECE 1 \u5F00\u59CB\uFF09" },
|
|
1715
|
+
limit: { type: "integer", description: "\u6700\u591A\u8BFB\u53D6\u7684\u884C\u6570" },
|
|
1716
|
+
focus: { type: "string", description: "\u4EFB\u52A1\u5173\u952E\u8BCD\u6216\u95EE\u9898\uFF1B\u53EA\u8FD4\u56DE\u7ED3\u6784\u6458\u8981\u548C\u76F8\u5173\u7247\u6BB5" },
|
|
1717
|
+
focus_max_matches: { type: "integer", description: "focus \u6700\u591A\u8FD4\u56DE\u7684\u7247\u6BB5\u6570\u91CF\uFF08\u9ED8\u8BA4 8\uFF09" }
|
|
1718
|
+
},
|
|
1719
|
+
required: ["file_path"]
|
|
1720
|
+
}
|
|
1721
|
+
},
|
|
1722
|
+
async execute(params) {
|
|
1723
|
+
const filePaths = params.input.file_paths;
|
|
1724
|
+
if (filePaths && filePaths.length > 0) {
|
|
1725
|
+
const focus2 = typeof params.input.focus === "string" ? params.input.focus : void 0;
|
|
1726
|
+
const focusMaxMatches = typeof params.input.focus_max_matches === "number" ? params.input.focus_max_matches : void 0;
|
|
1727
|
+
return await handleMultiRead(params, filePaths.slice(0, 5), focus2, focusMaxMatches);
|
|
1728
|
+
}
|
|
1729
|
+
let payload;
|
|
1730
|
+
const computedCap = params.readCapOverride ?? computeModelReadCap({
|
|
1731
|
+
contextWindow: params.contextWindow,
|
|
1732
|
+
providerProfile: params.providerProfile
|
|
1733
|
+
});
|
|
1734
|
+
const filePath = params.input.file_path;
|
|
1735
|
+
const offset = params.input.offset ?? 1;
|
|
1736
|
+
const limit = params.input.limit;
|
|
1737
|
+
const focus = typeof params.input.focus === "string" ? params.input.focus.trim() : "";
|
|
1738
|
+
const focusedRead = focus.length > 0 && params.input.offset === void 0 && params.input.limit === void 0;
|
|
1739
|
+
let dedupKey = null;
|
|
1740
|
+
let currentMtimeMs = null;
|
|
1741
|
+
let currentSizeBytes = null;
|
|
1742
|
+
let canonical = null;
|
|
1743
|
+
try {
|
|
1744
|
+
canonical = validatePath(params.cwd, filePath);
|
|
1745
|
+
if (existsSync2(canonical)) {
|
|
1746
|
+
const currentStat = await stat2(canonical);
|
|
1747
|
+
currentMtimeMs = currentStat.mtimeMs;
|
|
1748
|
+
currentSizeBytes = currentStat.size;
|
|
1749
|
+
if (!focusedRead) {
|
|
1750
|
+
dedupKey = readHistoryKey(params.cwd, canonical, offset, limit, params.sessionId);
|
|
1751
|
+
}
|
|
1752
|
+
const prior = focusedRead || !dedupKey ? void 0 : readHistory.get(dedupKey);
|
|
1753
|
+
if (!focusedRead && prior && prior.mtimeMs === currentMtimeMs && prior.sizeBytes === currentSizeBytes && prior.artifactId) {
|
|
1754
|
+
if (params.artifactStore) {
|
|
1755
|
+
const slice = await sliceFromArtifact(params.artifactStore, prior.artifactId, offset, limit);
|
|
1756
|
+
if (slice !== null) {
|
|
1757
|
+
debugLog(`[read-dedup] re-serve from artifact file=${canonical} offset=${offset} limit=${limit ?? "all"}`);
|
|
1758
|
+
noteFileObserved(canonical, currentMtimeMs, currentSizeBytes, params.sessionId);
|
|
1759
|
+
return { content: slice };
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
debugLog(`[read-dedup] artifact unreadable, falling through to normal read file=${canonical}`);
|
|
1763
|
+
}
|
|
1764
|
+
const fullEntry = focusedRead ? void 0 : fileReadHistory.get(fileHistoryKey(params.sessionId, canonical));
|
|
1765
|
+
if (!focusedRead && fullEntry && fullEntry.mtimeMs === currentMtimeMs && fullEntry.sizeBytes === currentSizeBytes && fullEntry.artifactId && (offset !== 1 || limit !== void 0)) {
|
|
1766
|
+
if (params.artifactStore) {
|
|
1767
|
+
const slice = await sliceFromArtifact(params.artifactStore, fullEntry.artifactId, offset, limit);
|
|
1768
|
+
if (slice !== null) {
|
|
1769
|
+
debugLog(`[read-dedup-file] re-serve slice from artifact file=${canonical} offset=${offset} limit=${limit ?? "all"}`);
|
|
1770
|
+
noteFileObserved(canonical, currentMtimeMs, currentSizeBytes, params.sessionId);
|
|
1771
|
+
return { content: slice };
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
debugLog(`[read-dedup-file] artifact unreadable, falling through file=${canonical}`);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
} catch {
|
|
1778
|
+
}
|
|
1779
|
+
const imageExt = canonical ? extname3(canonical).toLowerCase() : "";
|
|
1780
|
+
if (canonical && IMAGE_EXTENSIONS.has(imageExt)) {
|
|
1781
|
+
return await readImageFile(canonical, imageExt, currentSizeBytes ?? 0);
|
|
1782
|
+
}
|
|
1783
|
+
const unchangedRepeat = !focusedRead && canonical && currentMtimeMs !== null && currentSizeBytes !== null && dedupKey ? isUnchangedRepeatRead(canonical, currentMtimeMs, currentSizeBytes, dedupKey, offset, limit, params.sessionId) : false;
|
|
1784
|
+
let repeatWarning = null;
|
|
1785
|
+
if (unchangedRepeat) {
|
|
1786
|
+
const priorSame = readHistory.get(dedupKey);
|
|
1787
|
+
const fullPrior = fileReadHistory.get(fileHistoryKey(params.sessionId, canonical));
|
|
1788
|
+
if (priorSame && priorSame.mtimeMs === currentMtimeMs) {
|
|
1789
|
+
repeatWarning = `
|
|
1790
|
+
\u2500\u2500 read-dedup \u2500\u2500
|
|
1791
|
+
\u26A0 \u6B64\u6587\u4EF6\u672C\u8F6E\u5DF2\u8BFB\u53D6\u8FC7\u4E14\u672A\u53D8\u66F4 (${priorSame.modelBytes} bytes, ${priorSame.truncated ? "\u5DF2\u622A\u65AD" : "\u5B8C\u6574"})\u3002\u5185\u5BB9\u9644\u5728\u4E0B\u65B9\uFF1B\u540E\u7EED\u8BF7\u52FF\u91CD\u590D\u8BFB\u53D6\u672A\u53D8\u66F4\u7684\u6587\u4EF6\u3002
|
|
1792
|
+
\u2500\u2500 read-dedup \u2500\u2500`;
|
|
1793
|
+
} else if (fullPrior && fullPrior.mtimeMs === currentMtimeMs && offset === 1 && !limit) {
|
|
1794
|
+
repeatWarning = `
|
|
1795
|
+
\u2500\u2500 read-dedup \u2500\u2500
|
|
1796
|
+
\u26A0 \u6B64\u6587\u4EF6\u672C\u8F6E\u5DF2\u5B8C\u6574\u8BFB\u53D6\u8FC7\u4E14\u672A\u53D8\u66F4 (${fullPrior.totalLines} lines, ${fullPrior.modelBytes} bytes)\u3002\u5185\u5BB9\u9644\u5728\u4E0B\u65B9\uFF1B\u540E\u7EED\u8BF7\u52FF\u91CD\u590D\u8BFB\u53D6\u672A\u53D8\u66F4\u7684\u6587\u4EF6\u3002
|
|
1797
|
+
\u2500\u2500 read-dedup \u2500\u2500`;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
if (unchangedRepeat && isReadRefEnabled()) {
|
|
1801
|
+
const priorSame = readHistory.get(dedupKey);
|
|
1802
|
+
const fullPrior = fileReadHistory.get(fileHistoryKey(params.sessionId, canonical));
|
|
1803
|
+
const matchedSame = priorSame?.mtimeMs === currentMtimeMs ? priorSame : void 0;
|
|
1804
|
+
const matchedFull = fullPrior?.mtimeMs === currentMtimeMs ? fullPrior : void 0;
|
|
1805
|
+
const entryBytes = matchedSame ? matchedSame.modelBytes : matchedFull?.modelBytes ?? 0;
|
|
1806
|
+
const totalLines = matchedFull ? matchedFull.totalLines : 0;
|
|
1807
|
+
const refAlreadyServed = Math.max(matchedSame?.refServed ?? 0, matchedFull?.refServed ?? 0) >= 1;
|
|
1808
|
+
if (entryBytes > READ_REF_THRESHOLD && refAlreadyServed) {
|
|
1809
|
+
debugLog(`[read-ref-degrade] file=${canonical} ref did not help, re-serving full content`);
|
|
1810
|
+
repeatWarning = null;
|
|
1811
|
+
} else if (entryBytes > READ_REF_THRESHOLD) {
|
|
1812
|
+
const relPath = relative(params.cwd, canonical);
|
|
1813
|
+
const sizeHint = totalLines > 0 ? `${totalLines} \u884C\uFF0C${entryBytes} bytes` : `${entryBytes} bytes`;
|
|
1814
|
+
const ref = [
|
|
1815
|
+
`[read-ref] ${relPath} \u672C\u4F1A\u8BDD\u5DF2\u8BFB\u4E14\u672A\u53D8\uFF08${sizeHint}\uFF09\u3002`,
|
|
1816
|
+
`\u9700\u8981\u5177\u4F53\u533A\u6BB5\uFF1Aread_section(file_path="${relPath}", section="L{N}-L{M}")\u2014\u2014\u76F4\u63A5\u8BFB\u78C1\u76D8\uFF0C\u4E0D\u4F9D\u8D56\u4E0A\u6587\u3002`,
|
|
1817
|
+
`\u82E5\u4E0A\u6587\u7684 tool_result \u4ECD\u5B8C\u6574\u53EF\u89C1\uFF0C\u56DE\u770B\u5373\u53EF\uFF1B\u82E5\u5DF2\u88AB\u538B\u7F29\u4E3A\u5360\u4F4D\u7B26\uFF0C\u7528 read_section\u3002`
|
|
1818
|
+
].join("\n");
|
|
1819
|
+
if (matchedSame) matchedSame.refServed = (matchedSame.refServed ?? 0) + 1;
|
|
1820
|
+
if (matchedFull) matchedFull.refServed = (matchedFull.refServed ?? 0) + 1;
|
|
1821
|
+
if (params.readRefStats) {
|
|
1822
|
+
params.readRefStats.savedBytes += entryBytes;
|
|
1823
|
+
params.readRefStats.count++;
|
|
1824
|
+
} else {
|
|
1825
|
+
readRefSavedBytes += entryBytes;
|
|
1826
|
+
readRefCount++;
|
|
1827
|
+
}
|
|
1828
|
+
const totalSaved = params.readRefStats?.savedBytes ?? readRefSavedBytes;
|
|
1829
|
+
const totalCount = params.readRefStats?.count ?? readRefCount;
|
|
1830
|
+
debugLog(`[read-ref] file=${canonical} saved=${entryBytes} total-saved=${totalSaved} count=${totalCount}`);
|
|
1831
|
+
noteFileObserved(canonical, currentMtimeMs, currentSizeBytes, params.sessionId);
|
|
1832
|
+
return { content: ref };
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
try {
|
|
1836
|
+
let prefetchedContent;
|
|
1837
|
+
if (params.prewarmCache && canonical && canUsePrewarmForRead(params.input)) {
|
|
1838
|
+
const cached = consumePrewarm(params.prewarmCache, canonical);
|
|
1839
|
+
if (cached) {
|
|
1840
|
+
prefetchedContent = cached.content;
|
|
1841
|
+
debugLog(`[prewarm-hit] file=${canonical} cached=${cached.content.length} bytes`);
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
payload = await readFilePayload(params.cwd, {
|
|
1845
|
+
filePath,
|
|
1846
|
+
...params.input.offset !== void 0 ? { offset } : {},
|
|
1847
|
+
...params.input.limit !== void 0 ? { limit } : {},
|
|
1848
|
+
...focusedRead ? { focus, focusMaxMatches: typeof params.input.focus_max_matches === "number" ? params.input.focus_max_matches : void 0 } : {},
|
|
1849
|
+
modelCap: computedCap,
|
|
1850
|
+
prefetchedContent,
|
|
1851
|
+
preferFoldOnOverflow: params.readCapOverride !== void 0
|
|
1852
|
+
});
|
|
1853
|
+
} catch (err) {
|
|
1854
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1855
|
+
return { content: `Error: ${message}`, isError: true };
|
|
1856
|
+
}
|
|
1857
|
+
debugLog(`[read-cap] file=${payload.canonicalPath} raw=${payload.rawContent.length} model=${payload.modelContent.length} truncated=${payload.rawContent.length !== payload.modelContent.length} cap=${computedCap.maxChars} ctxWindow=${params.contextWindow ?? "undefined"}`);
|
|
1858
|
+
const rawPath = await persistRawOutput(params.toolUseId, payload.rawContent);
|
|
1859
|
+
if (canonical && currentMtimeMs !== null && currentSizeBytes !== null) {
|
|
1860
|
+
noteFileObserved(canonical, currentMtimeMs, currentSizeBytes, params.sessionId);
|
|
1861
|
+
}
|
|
1862
|
+
const recordDedup = (artifactId) => {
|
|
1863
|
+
if (!dedupKey || currentMtimeMs === null || currentSizeBytes === null) return;
|
|
1864
|
+
readHistory.set(dedupKey, {
|
|
1865
|
+
mtimeMs: currentMtimeMs,
|
|
1866
|
+
sizeBytes: currentSizeBytes,
|
|
1867
|
+
rawBytes: payload.rawContent.length,
|
|
1868
|
+
modelBytes: payload.modelContent.length,
|
|
1869
|
+
truncated: payload.rawContent.length !== payload.modelContent.length,
|
|
1870
|
+
recordedAt: Date.now(),
|
|
1871
|
+
artifactId
|
|
1872
|
+
});
|
|
1873
|
+
trimReadHistory();
|
|
1874
|
+
};
|
|
1875
|
+
const recordFileDedup = (artifactId) => {
|
|
1876
|
+
if (!canonical || currentMtimeMs === null || currentSizeBytes === null) return;
|
|
1877
|
+
if (offset !== 1 || limit !== void 0) return;
|
|
1878
|
+
fileReadHistory.set(fileHistoryKey(params.sessionId, canonical), {
|
|
1879
|
+
mtimeMs: currentMtimeMs,
|
|
1880
|
+
sizeBytes: currentSizeBytes,
|
|
1881
|
+
totalLines: payload.rawContent.split("\n").length,
|
|
1882
|
+
rawBytes: payload.rawContent.length,
|
|
1883
|
+
modelBytes: payload.modelContent.length,
|
|
1884
|
+
artifactId,
|
|
1885
|
+
recordedAt: Date.now()
|
|
1886
|
+
});
|
|
1887
|
+
trimFileReadHistory();
|
|
1888
|
+
};
|
|
1889
|
+
if (params.artifactStore) {
|
|
1890
|
+
const artifactThreshold = getToolArtifactThreshold("read_file", params.contextWindow);
|
|
1891
|
+
const wrapInArtifact = payload.rawContent.length >= artifactThreshold;
|
|
1892
|
+
if (!wrapInArtifact) {
|
|
1893
|
+
debugLog(`[artifact-skip] tool=read_file file=${payload.canonicalPath} raw=${payload.rawContent.length} threshold=${artifactThreshold}`);
|
|
1894
|
+
recordDedup();
|
|
1895
|
+
recordFileDedup();
|
|
1896
|
+
return {
|
|
1897
|
+
content: repeatWarning ? repeatWarning + "\n" + payload.modelContent : payload.modelContent,
|
|
1898
|
+
uiContent: payload.uiContent,
|
|
1899
|
+
rawPath
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
debugLog(`[artifact-wrap] tool=read_file file=${payload.canonicalPath} raw=${payload.rawContent.length} threshold=${artifactThreshold}`);
|
|
1903
|
+
const { summary, sections } = summarizeFileContent(payload.rawContent, payload.canonicalPath);
|
|
1904
|
+
const artifactId = await params.artifactStore.save({
|
|
1905
|
+
tool: "read_file",
|
|
1906
|
+
target: payload.canonicalPath,
|
|
1907
|
+
rawContent: payload.rawContent,
|
|
1908
|
+
summary,
|
|
1909
|
+
sections
|
|
1910
|
+
});
|
|
1911
|
+
recordDedup(artifactId);
|
|
1912
|
+
recordFileDedup(artifactId);
|
|
1913
|
+
const summaryBlock = summary.trim() ? `
|
|
1914
|
+
|
|
1915
|
+
\u2500\u2500 Structural outline \u2500\u2500
|
|
1916
|
+
${summary.trim()}` : "";
|
|
1917
|
+
const hinted = maybeAppendNeighborHint(params.cwd, payload.canonicalPath, params.sessionId, payload.modelContent + summaryBlock);
|
|
1918
|
+
const baseContent = hinted + `
|
|
1919
|
+
[artifact:${artifactId}]`;
|
|
1920
|
+
return {
|
|
1921
|
+
content: repeatWarning ? repeatWarning + "\n" + baseContent : baseContent,
|
|
1922
|
+
rawContent: payload.modelContent,
|
|
1923
|
+
uiContent: payload.uiContent,
|
|
1924
|
+
rawPath
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
recordDedup();
|
|
1928
|
+
recordFileDedup();
|
|
1929
|
+
const plainContent = repeatWarning ? repeatWarning + "\n" + payload.modelContent : payload.modelContent;
|
|
1930
|
+
return {
|
|
1931
|
+
content: maybeAppendNeighborHint(params.cwd, payload.canonicalPath, params.sessionId, plainContent),
|
|
1932
|
+
uiContent: payload.uiContent,
|
|
1933
|
+
rawPath
|
|
1934
|
+
};
|
|
1935
|
+
},
|
|
1936
|
+
requiresApproval: () => false,
|
|
1937
|
+
isConcurrencySafe: () => true,
|
|
1938
|
+
isEnabled: () => true
|
|
1939
|
+
};
|
|
1940
|
+
async function handleMultiRead(params, paths, focus, focusMaxMatches) {
|
|
1941
|
+
const computedCap = params.readCapOverride ?? computeModelReadCap({
|
|
1942
|
+
contextWindow: params.contextWindow,
|
|
1943
|
+
providerProfile: params.providerProfile
|
|
1944
|
+
});
|
|
1945
|
+
const perFileCap = {
|
|
1946
|
+
maxChars: Math.floor(computedCap.maxChars / paths.length),
|
|
1947
|
+
headChars: Math.floor(computedCap.headChars / paths.length),
|
|
1948
|
+
tailChars: Math.floor(computedCap.tailChars / paths.length)
|
|
1949
|
+
};
|
|
1950
|
+
const sections = [];
|
|
1951
|
+
let totalBytes = 0;
|
|
1952
|
+
let errors = 0;
|
|
1953
|
+
for (const rawPath of paths) {
|
|
1954
|
+
const trimmed = rawPath.trim();
|
|
1955
|
+
if (!trimmed) continue;
|
|
1956
|
+
try {
|
|
1957
|
+
const payload = await readFilePayload(params.cwd, {
|
|
1958
|
+
filePath: trimmed,
|
|
1959
|
+
...focus ? { focus, focusMaxMatches } : {},
|
|
1960
|
+
modelCap: perFileCap,
|
|
1961
|
+
preferFoldOnOverflow: params.readCapOverride !== void 0
|
|
1962
|
+
});
|
|
1963
|
+
const relPath = relative(params.cwd, payload.canonicalPath).replaceAll("\\", "/");
|
|
1964
|
+
sections.push(`\u2500\u2500 ${relPath} \u2500\u2500
|
|
1965
|
+
${payload.modelContent}`);
|
|
1966
|
+
totalBytes += payload.rawContent.length;
|
|
1967
|
+
const currentStat = await stat2(payload.canonicalPath);
|
|
1968
|
+
fileReadHistory.set(fileHistoryKey(params.sessionId, payload.canonicalPath), {
|
|
1969
|
+
mtimeMs: currentStat.mtimeMs,
|
|
1970
|
+
sizeBytes: currentStat.size,
|
|
1971
|
+
totalLines: payload.rawContent.split("\n").length,
|
|
1972
|
+
rawBytes: payload.rawContent.length,
|
|
1973
|
+
modelBytes: payload.modelContent.length,
|
|
1974
|
+
recordedAt: Date.now()
|
|
1975
|
+
});
|
|
1976
|
+
noteFileObserved(payload.canonicalPath, currentStat.mtimeMs, currentStat.size, params.sessionId);
|
|
1977
|
+
} catch (err) {
|
|
1978
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1979
|
+
const display = trimmed.startsWith(params.cwd) ? relative(params.cwd, trimmed) : trimmed;
|
|
1980
|
+
sections.push(`\u2500\u2500 ${display} \u2500\u2500
|
|
1981
|
+
Error: ${msg}`);
|
|
1982
|
+
errors++;
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
trimFileReadHistory();
|
|
1986
|
+
const content = sections.join("\n\n");
|
|
1987
|
+
return {
|
|
1988
|
+
content,
|
|
1989
|
+
uiContent: `Read ${paths.length - errors}/${paths.length} files (${(totalBytes / 1024).toFixed(1)} KB total)`
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1993
|
+
export {
|
|
1994
|
+
truncateContent,
|
|
1995
|
+
GitignoreFilter,
|
|
1996
|
+
summarizeGrepResult,
|
|
1997
|
+
summarizeBashOutput,
|
|
1998
|
+
computeModelReadCap,
|
|
1999
|
+
getToolArtifactThreshold,
|
|
2000
|
+
buildPrewarmValue,
|
|
2001
|
+
batchPrewarm,
|
|
2002
|
+
setNeighborHintProvider,
|
|
2003
|
+
incrementEditFailCount,
|
|
2004
|
+
resetEditFailCount,
|
|
2005
|
+
wasFileEditedBySession,
|
|
2006
|
+
getFileReadMtime,
|
|
2007
|
+
noteFileObserved,
|
|
2008
|
+
invalidateReadHistory,
|
|
2009
|
+
invalidateSessionReadDedup,
|
|
2010
|
+
recordSuccessfulEdit,
|
|
2011
|
+
registerGrepFileAccess,
|
|
2012
|
+
getReadRefStats,
|
|
2013
|
+
READ_FILE_TOOL
|
|
2014
|
+
};
|