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.
Files changed (255) hide show
  1. package/LICENSE +189 -0
  2. package/README.en.md +1066 -0
  3. package/README.ja.md +1214 -0
  4. package/README.ko.md +1216 -0
  5. package/README.md +309 -0
  6. package/completions/rivet.bash +118 -0
  7. package/completions/rivet.fish +110 -0
  8. package/completions/rivet.ps1 +230 -0
  9. package/completions/rivet.zsh +187 -0
  10. package/dist/account-OAEKORZE.js +47 -0
  11. package/dist/account-login-V6FOLHJR.js +65 -0
  12. package/dist/account-status-KHNPPAFL.js +23 -0
  13. package/dist/agent/worker-process/child.js +407 -0
  14. package/dist/browser-cli-FLNY5ZHI.js +19 -0
  15. package/dist/browser-readiness-LWVGSBD5.js +12 -0
  16. package/dist/build-backends-5KXKR2S4.js +13 -0
  17. package/dist/build-options-66Y4ZE7C.js +9 -0
  18. package/dist/bundled-skills/brainstorming/SKILL.md +139 -0
  19. package/dist/bundled-skills/visual-acceptance/SKILL.md +64 -0
  20. package/dist/chain-7MAMFXZ3.js +10 -0
  21. package/dist/checkpoint-MND4DUIX.js +33 -0
  22. package/dist/chunk-2E6CK2RC.js +392 -0
  23. package/dist/chunk-2EBWGAXD.js +51 -0
  24. package/dist/chunk-2HDP7RMR.js +119 -0
  25. package/dist/chunk-2HKD2UFM.js +1000 -0
  26. package/dist/chunk-2VYRLULP.js +51 -0
  27. package/dist/chunk-2XN7PM4N.js +125 -0
  28. package/dist/chunk-35H7GVNR.js +66 -0
  29. package/dist/chunk-3C3R7JXY.js +77 -0
  30. package/dist/chunk-3VGD2FXB.js +118 -0
  31. package/dist/chunk-424AP7FM.js +534 -0
  32. package/dist/chunk-4J5TZGOE.js +3707 -0
  33. package/dist/chunk-4QJ3Y5JM.js +898 -0
  34. package/dist/chunk-4U2ET7ZY.js +1326 -0
  35. package/dist/chunk-4XPFLICE.js +876 -0
  36. package/dist/chunk-5B2OCU5P.js +31707 -0
  37. package/dist/chunk-5GGOGLSN.js +73 -0
  38. package/dist/chunk-5JRZPLI6.js +251 -0
  39. package/dist/chunk-5TQ3JYBU.js +397 -0
  40. package/dist/chunk-66JPZSGX.js +55 -0
  41. package/dist/chunk-6HSVXHCZ.js +764 -0
  42. package/dist/chunk-6S5FPUS7.js +21 -0
  43. package/dist/chunk-722LQO64.js +131 -0
  44. package/dist/chunk-73NJ7IT7.js +354 -0
  45. package/dist/chunk-7FW3I7WC.js +95 -0
  46. package/dist/chunk-7KUCBNLB.js +173 -0
  47. package/dist/chunk-7RDOV3DF.js +201 -0
  48. package/dist/chunk-A5MNH4ER.js +79 -0
  49. package/dist/chunk-ADIVZ2D5.js +15 -0
  50. package/dist/chunk-AE3P3F6W.js +366 -0
  51. package/dist/chunk-AERM3VGP.js +280 -0
  52. package/dist/chunk-AEWHC4WM.js +246 -0
  53. package/dist/chunk-AOVQ7VIF.js +169 -0
  54. package/dist/chunk-AOYHP3WI.js +12411 -0
  55. package/dist/chunk-B3YEBABJ.js +7160 -0
  56. package/dist/chunk-BCQXEZPJ.js +154 -0
  57. package/dist/chunk-BKTQAYO2.js +418 -0
  58. package/dist/chunk-BSECIPAQ.js +80 -0
  59. package/dist/chunk-BUIMGNDD.js +52 -0
  60. package/dist/chunk-BXHV3GSB.js +10320 -0
  61. package/dist/chunk-C6ZVZCPP.js +139 -0
  62. package/dist/chunk-CEZNQVWM.js +82 -0
  63. package/dist/chunk-CGFZGR7G.js +1492 -0
  64. package/dist/chunk-CGOADM2X.js +337 -0
  65. package/dist/chunk-CI4R6P2B.js +24 -0
  66. package/dist/chunk-D2TCGRSX.js +73 -0
  67. package/dist/chunk-D4PTYFOG.js +154 -0
  68. package/dist/chunk-D54ZWG4T.js +717 -0
  69. package/dist/chunk-DEBI37QB.js +145 -0
  70. package/dist/chunk-DWROHPXP.js +1051 -0
  71. package/dist/chunk-DZ6UOJ5D.js +92 -0
  72. package/dist/chunk-EBJTVRQN.js +81 -0
  73. package/dist/chunk-EGEI2RQA.js +1167 -0
  74. package/dist/chunk-EHHUASO5.js +1305 -0
  75. package/dist/chunk-EPKTIOED.js +41 -0
  76. package/dist/chunk-ER5JW2QZ.js +5681 -0
  77. package/dist/chunk-ETHFACAB.js +315 -0
  78. package/dist/chunk-EXUE5W2F.js +112 -0
  79. package/dist/chunk-EXXICUIW.js +73 -0
  80. package/dist/chunk-FFQSZJOO.js +169 -0
  81. package/dist/chunk-FHFKPGDO.js +85 -0
  82. package/dist/chunk-FW3QKMFX.js +158 -0
  83. package/dist/chunk-GD4ZNZ7W.js +30 -0
  84. package/dist/chunk-GHVMWF2I.js +581 -0
  85. package/dist/chunk-GMKAXSYQ.js +297 -0
  86. package/dist/chunk-HBVF6BWO.js +325 -0
  87. package/dist/chunk-HV4YGVMS.js +379 -0
  88. package/dist/chunk-ILBCKAV6.js +89 -0
  89. package/dist/chunk-J4U4AW46.js +440 -0
  90. package/dist/chunk-J6ESSM47.js +1791 -0
  91. package/dist/chunk-JBZHIC6T.js +2309 -0
  92. package/dist/chunk-JCFWOULR.js +245 -0
  93. package/dist/chunk-JRF5L6OD.js +131 -0
  94. package/dist/chunk-JSS75KCG.js +2014 -0
  95. package/dist/chunk-JSXWIWWJ.js +225 -0
  96. package/dist/chunk-JYV7I2QH.js +98 -0
  97. package/dist/chunk-KIOXTQQL.js +1596 -0
  98. package/dist/chunk-KWVTXT5B.js +89 -0
  99. package/dist/chunk-KX22RD6R.js +33 -0
  100. package/dist/chunk-LA6IPTGH.js +709 -0
  101. package/dist/chunk-LH5VCGLB.js +1796 -0
  102. package/dist/chunk-LQQ6XJ2Y.js +269 -0
  103. package/dist/chunk-MJQ55WZZ.js +65 -0
  104. package/dist/chunk-ML3UKMH3.js +40 -0
  105. package/dist/chunk-MOZPLHSU.js +5526 -0
  106. package/dist/chunk-NGEWJ4DF.js +89 -0
  107. package/dist/chunk-NH6VIOAR.js +211 -0
  108. package/dist/chunk-O5VZQKCB.js +26106 -0
  109. package/dist/chunk-OHSV42QB.js +748 -0
  110. package/dist/chunk-OO3TAHO5.js +4070 -0
  111. package/dist/chunk-OTNHSAGN.js +38282 -0
  112. package/dist/chunk-OUWAC7AQ.js +442 -0
  113. package/dist/chunk-OXMIV4WE.js +270 -0
  114. package/dist/chunk-P5KJURUR.js +229 -0
  115. package/dist/chunk-P6GJOVO6.js +307 -0
  116. package/dist/chunk-QARAGUXX.js +23 -0
  117. package/dist/chunk-R43HJCDX.js +2711 -0
  118. package/dist/chunk-RAXBAWRN.js +210 -0
  119. package/dist/chunk-RK3JHWPT.js +39 -0
  120. package/dist/chunk-RWA23GMM.js +24 -0
  121. package/dist/chunk-S4K4L6IM.js +179 -0
  122. package/dist/chunk-SAXK6LVF.js +124 -0
  123. package/dist/chunk-SEPGEJNK.js +1607 -0
  124. package/dist/chunk-SUJERP5I.js +285 -0
  125. package/dist/chunk-T3ED6BY2.js +913 -0
  126. package/dist/chunk-THKDVSEC.js +90 -0
  127. package/dist/chunk-TI4GSEL5.js +52 -0
  128. package/dist/chunk-TPI73L57.js +164 -0
  129. package/dist/chunk-TVHW77C7.js +338 -0
  130. package/dist/chunk-TWNHOSDT.js +1073 -0
  131. package/dist/chunk-UFMN76QD.js +347 -0
  132. package/dist/chunk-UHGLFRZE.js +205 -0
  133. package/dist/chunk-UTZYPKBF.js +15231 -0
  134. package/dist/chunk-UXNLELW6.js +25 -0
  135. package/dist/chunk-UXXS53EA.js +166 -0
  136. package/dist/chunk-V5DHFGI7.js +171 -0
  137. package/dist/chunk-V5YIPMHV.js +2353 -0
  138. package/dist/chunk-WMUWEO2C.js +754 -0
  139. package/dist/chunk-WOE3D7JM.js +945 -0
  140. package/dist/chunk-X2IK5LVR.js +2592 -0
  141. package/dist/chunk-XU2YXCDL.js +459 -0
  142. package/dist/chunk-YJG5HIX5.js +203 -0
  143. package/dist/chunk-Z3QE7TZM.js +72 -0
  144. package/dist/chunk-Z5Z3JHJY.js +64 -0
  145. package/dist/chunk-ZDIFBJFT.js +168 -0
  146. package/dist/chunk-ZGGCEXDC.js +176 -0
  147. package/dist/chunk-ZMZCZNKU.js +263 -0
  148. package/dist/chunk-ZNC5T65N.js +17 -0
  149. package/dist/chunk-ZO3I3A7O.js +308 -0
  150. package/dist/chunk-ZQKXBUB7.js +217 -0
  151. package/dist/chunk-ZTRINFM2.js +78 -0
  152. package/dist/chunk-ZU7JZCVP.js +193 -0
  153. package/dist/chunk-ZZAWNALS.js +350 -0
  154. package/dist/cli/entry.js +54 -0
  155. package/dist/connector-L5QQTR6Y.js +26 -0
  156. package/dist/context-62DC6EC3.js +31 -0
  157. package/dist/council-convene-NJWPNBDU.js +46 -0
  158. package/dist/create-agent-config-ODSQWNVC.js +55 -0
  159. package/dist/default-registry-2PU3BTAU.js +68 -0
  160. package/dist/deliver-task-KONV4C4M.js +57 -0
  161. package/dist/delivery-gate-v2-F2YVMVGK.js +27 -0
  162. package/dist/domain-knowledge-store-DDZ3F4J7.js +11 -0
  163. package/dist/fetch-core-ZPPYDSZH.js +14 -0
  164. package/dist/galaxy-K72BBDQN.js +91 -0
  165. package/dist/goal-criteria-SCALAU2D.js +141 -0
  166. package/dist/goal-persist-XWY2GDQX.js +19 -0
  167. package/dist/goal-tracker-FXMJ2GVK.js +11 -0
  168. package/dist/headless-LSB6I7PT.js +162 -0
  169. package/dist/headless-coordinator-VDPDMVUT.js +90 -0
  170. package/dist/inject-U55SMB6A.js +39 -0
  171. package/dist/login-flow-CD4ZGXOC.js +59 -0
  172. package/dist/logs-cli-SQVYFMMN.js +283 -0
  173. package/dist/loop-ULZYBDA3.js +76 -0
  174. package/dist/main.js +4064 -0
  175. package/dist/manager-ROZBCWPA.js +171 -0
  176. package/dist/manager-Z7XFFUOM.js +10670 -0
  177. package/dist/meridian-indexer-ZIW363L7.js +21 -0
  178. package/dist/native-resolver-PN34UIVV.js +13 -0
  179. package/dist/node_modules/better-sqlite3/lib/database.js +90 -0
  180. package/dist/node_modules/better-sqlite3/lib/index.js +3 -0
  181. package/dist/node_modules/better-sqlite3/lib/methods/aggregate.js +43 -0
  182. package/dist/node_modules/better-sqlite3/lib/methods/backup.js +67 -0
  183. package/dist/node_modules/better-sqlite3/lib/methods/function.js +31 -0
  184. package/dist/node_modules/better-sqlite3/lib/methods/inspect.js +7 -0
  185. package/dist/node_modules/better-sqlite3/lib/methods/pragma.js +12 -0
  186. package/dist/node_modules/better-sqlite3/lib/methods/serialize.js +16 -0
  187. package/dist/node_modules/better-sqlite3/lib/methods/table.js +189 -0
  188. package/dist/node_modules/better-sqlite3/lib/methods/transaction.js +78 -0
  189. package/dist/node_modules/better-sqlite3/lib/methods/wrappers.js +54 -0
  190. package/dist/node_modules/better-sqlite3/lib/sqlite-error.js +20 -0
  191. package/dist/node_modules/better-sqlite3/lib/util.js +12 -0
  192. package/dist/node_modules/better-sqlite3/package.json +59 -0
  193. package/dist/ownership-ledger-E3WIEKPL.js +9 -0
  194. package/dist/package.json +3 -0
  195. package/dist/path-grants-XRRXHXG7.js +40 -0
  196. package/dist/paths-LVV5IMQN.js +49 -0
  197. package/dist/plan-fact-anchors-LMVQZCW5.js +13 -0
  198. package/dist/plan-store-ODLTTPFB.js +17 -0
  199. package/dist/plan-templates-5FPFBOL6.js +109 -0
  200. package/dist/plugin-loader-ZZKX7ZE6.js +22 -0
  201. package/dist/prefix-budget-62OV5HQC.js +13 -0
  202. package/dist/project-trust-5WS5MOMU.js +31 -0
  203. package/dist/provider-cli-ALDQXIT7.js +37 -0
  204. package/dist/providers-XQYTAVPK.js +11 -0
  205. package/dist/proxy-resolver-V2Y6534D.js +15 -0
  206. package/dist/recovery-cli-KPGY6FY3.js +106 -0
  207. package/dist/review-discipline-MNSYCST5.js +55 -0
  208. package/dist/review-router-BVQZW3MX.js +18 -0
  209. package/dist/seed-capsules/seed-capsule-attack-methodology.md +62 -0
  210. package/dist/seed-capsules/seed-capsule-changgeng.md +31 -0
  211. package/dist/seed-capsules/seed-capsule-design-aesthetics.md +38 -0
  212. package/dist/seed-capsules/seed-capsule-diagnostic-ladder.md +54 -0
  213. package/dist/seed-capsules/seed-capsule-fu.md +39 -0
  214. package/dist/seed-capsules/seed-capsule-huagai.md +73 -0
  215. package/dist/seed-capsules/seed-capsule-kaiyang.md +65 -0
  216. package/dist/seed-capsules/seed-capsule-knowledge-work.md +40 -0
  217. package/dist/seed-capsules/seed-capsule-tanlang.md +47 -0
  218. package/dist/seed-capsules/seed-capsule-tianfu.md +23 -0
  219. package/dist/seed-capsules/seed-capsule-tianliang.md +63 -0
  220. package/dist/seed-capsules/seed-capsule-tianquan.md +32 -0
  221. package/dist/seed-capsules/seed-capsule-tianxuan-ccr.md +22 -0
  222. package/dist/seed-capsules/seed-capsule-tianxuan.md +64 -0
  223. package/dist/seed-capsules/seed-capsule-yaoguang.md +87 -0
  224. package/dist/serve-X2R2UTLF.js +170 -0
  225. package/dist/serve-agent-KHSU6RVV.js +965 -0
  226. package/dist/session-persist-HUUC772H.js +37 -0
  227. package/dist/session-registry-6XUD5C4Y.js +15 -0
  228. package/dist/skill-loader-CK3RMO47.js +48 -0
  229. package/dist/skills/cli-harness-sop.md +207 -0
  230. package/dist/spawn-git-4HFU65AH.js +29 -0
  231. package/dist/sse-EYUTBWUK.js +512 -0
  232. package/dist/starflow-NOFBMPFU.js +30 -0
  233. package/dist/streamableHttp-V67CNJMO.js +447 -0
  234. package/dist/task-contract-6XHBNJQK.js +32 -0
  235. package/dist/task-ledger-3K4TJIER.js +9 -0
  236. package/dist/team-orchestrate-S6NHZP7X.js +64 -0
  237. package/dist/title-extract-QX4XGCYN.js +50 -0
  238. package/dist/todo-QDXGRZZP.js +31 -0
  239. package/dist/todo-store-V3TTO5AH.js +12 -0
  240. package/dist/tool-preset-LU5BYEDZ.js +23 -0
  241. package/dist/tool-tiers-GD742YBN.js +23 -0
  242. package/dist/turndown.es-OI55K6C3.js +17373 -0
  243. package/dist/verification-attribution-EKG4CNUV.js +13 -0
  244. package/dist/volatile-git-MBTST7RA.js +15 -0
  245. package/dist/wave-checkpoint-XLIIHXQA.js +30 -0
  246. package/dist/web-cli-DUG4I35O.js +233 -0
  247. package/dist/web-search-3IK542AA.js +34 -0
  248. package/dist/workers/cpu-worker.js +45 -0
  249. package/dist/workflow-runner-HX7UBAYG.js +297 -0
  250. package/dist/worktree-baseline-O5NCU6M2.js +9 -0
  251. package/package.json +122 -0
  252. package/scripts/check-mobile-env.mjs +76 -0
  253. package/scripts/fetch-native-sqlite.js +392 -0
  254. package/scripts/prune-installed-wasms.js +45 -0
  255. 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
+ };