@bastani/atomic 0.9.2-alpha.1 → 0.9.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (455) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/README.md +2 -2
  3. package/dist/builtin/cursor/CHANGELOG.md +6 -0
  4. package/dist/builtin/cursor/package.json +2 -2
  5. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  6. package/dist/builtin/intercom/package.json +1 -1
  7. package/dist/builtin/mcp/CHANGELOG.md +12 -0
  8. package/dist/builtin/mcp/direct-tools.ts +4 -2
  9. package/dist/builtin/mcp/package.json +1 -1
  10. package/dist/builtin/mcp/proxy-call.ts +3 -1
  11. package/dist/builtin/mcp/utils.ts +18 -7
  12. package/dist/builtin/subagents/CHANGELOG.md +17 -0
  13. package/dist/builtin/subagents/README.md +6 -6
  14. package/dist/builtin/subagents/agents/code-simplifier.md +7 -6
  15. package/dist/builtin/subagents/agents/codebase-analyzer.md +5 -4
  16. package/dist/builtin/subagents/agents/codebase-locator.md +3 -3
  17. package/dist/builtin/subagents/agents/codebase-online-researcher.md +10 -10
  18. package/dist/builtin/subagents/agents/codebase-pattern-finder.md +4 -4
  19. package/dist/builtin/subagents/agents/codebase-research-analyzer.md +3 -3
  20. package/dist/builtin/subagents/agents/codebase-research-locator.md +4 -4
  21. package/dist/builtin/subagents/agents/debugger.md +5 -5
  22. package/dist/builtin/subagents/agents/worker.md +56 -0
  23. package/dist/builtin/subagents/package.json +1 -1
  24. package/dist/builtin/subagents/skills/subagent/SKILL.md +11 -11
  25. package/dist/builtin/subagents/src/agents/agent-loaders.ts +3 -5
  26. package/dist/builtin/subagents/src/agents/agent-management-helpers.ts +3 -3
  27. package/dist/builtin/subagents/src/extension/schemas.ts +2 -2
  28. package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -3
  29. package/dist/builtin/subagents/src/runs/shared/mcp-direct-tool-allowlist.ts +1 -1
  30. package/dist/builtin/subagents/src/runs/shared/nested-render.ts +2 -2
  31. package/dist/builtin/subagents/src/runs/shared/pi-args.ts +2 -1
  32. package/dist/builtin/subagents/src/shared/types-depth.ts +5 -5
  33. package/dist/builtin/subagents/src/shared/types-runtime.ts +2 -1
  34. package/dist/builtin/subagents/src/tui/render-event-formatting.ts +2 -2
  35. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  36. package/dist/builtin/web-access/package.json +1 -1
  37. package/dist/builtin/workflows/CHANGELOG.md +21 -0
  38. package/dist/builtin/workflows/README.md +2 -2
  39. package/dist/builtin/workflows/builtin/goal-artifacts.ts +11 -6
  40. package/dist/builtin/workflows/builtin/goal-ledger.ts +33 -1
  41. package/dist/builtin/workflows/builtin/goal-prompts.ts +23 -28
  42. package/dist/builtin/workflows/builtin/goal-reducer.ts +2 -2
  43. package/dist/builtin/workflows/builtin/goal-reports.ts +2 -5
  44. package/dist/builtin/workflows/builtin/goal-review.ts +1 -1
  45. package/dist/builtin/workflows/builtin/goal-runner.ts +10 -17
  46. package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +3 -3
  47. package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +1 -3
  48. package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +1 -1
  49. package/dist/builtin/workflows/builtin/ralph-core.ts +7 -17
  50. package/dist/builtin/workflows/builtin/ralph-runner.ts +11 -18
  51. package/dist/builtin/workflows/builtin/shared-prompts.ts +1 -1
  52. package/dist/builtin/workflows/package.json +1 -1
  53. package/dist/builtin/workflows/src/extension/config-loader.ts +35 -15
  54. package/dist/builtin/workflows/src/extension/discovery.ts +20 -8
  55. package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +1 -2
  56. package/dist/builtin/workflows/src/extension/wiring.ts +1 -1
  57. package/dist/builtin/workflows/src/tui/dispatch-confirm.ts +11 -10
  58. package/dist/cli/args.d.ts.map +1 -1
  59. package/dist/cli/args.js +9 -9
  60. package/dist/cli/args.js.map +1 -1
  61. package/dist/config-self-update.d.ts.map +1 -1
  62. package/dist/config-self-update.js +3 -4
  63. package/dist/config-self-update.js.map +1 -1
  64. package/dist/config.d.ts.map +1 -1
  65. package/dist/config.js +4 -5
  66. package/dist/config.js.map +1 -1
  67. package/dist/core/agent-session-bash.d.ts +1 -0
  68. package/dist/core/agent-session-bash.d.ts.map +1 -1
  69. package/dist/core/agent-session-bash.js +1 -0
  70. package/dist/core/agent-session-bash.js.map +1 -1
  71. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  72. package/dist/core/agent-session-tool-registry.js +23 -0
  73. package/dist/core/agent-session-tool-registry.js.map +1 -1
  74. package/dist/core/bash-executor.d.ts +2 -0
  75. package/dist/core/bash-executor.d.ts.map +1 -1
  76. package/dist/core/bash-executor.js +1 -0
  77. package/dist/core/bash-executor.js.map +1 -1
  78. package/dist/core/compaction/compaction.d.ts +29 -0
  79. package/dist/core/compaction/compaction.d.ts.map +1 -1
  80. package/dist/core/compaction/compaction.js +36 -1
  81. package/dist/core/compaction/compaction.js.map +1 -1
  82. package/dist/core/compaction/context-compaction-metrics.d.ts +14 -2
  83. package/dist/core/compaction/context-compaction-metrics.d.ts.map +1 -1
  84. package/dist/core/compaction/context-compaction-metrics.js +50 -1
  85. package/dist/core/compaction/context-compaction-metrics.js.map +1 -1
  86. package/dist/core/compaction/context-compaction-prompt.d.ts.map +1 -1
  87. package/dist/core/compaction/context-compaction-prompt.js +2 -0
  88. package/dist/core/compaction/context-compaction-prompt.js.map +1 -1
  89. package/dist/core/compaction/context-compaction-runner.d.ts.map +1 -1
  90. package/dist/core/compaction/context-compaction-runner.js +1 -1
  91. package/dist/core/compaction/context-compaction-runner.js.map +1 -1
  92. package/dist/core/compaction/context-deletion-application.d.ts.map +1 -1
  93. package/dist/core/compaction/context-deletion-application.js +5 -5
  94. package/dist/core/compaction/context-deletion-application.js.map +1 -1
  95. package/dist/core/compaction/context-deletion-targets.d.ts +2 -0
  96. package/dist/core/compaction/context-deletion-targets.d.ts.map +1 -1
  97. package/dist/core/compaction/context-deletion-targets.js +23 -3
  98. package/dist/core/compaction/context-deletion-targets.js.map +1 -1
  99. package/dist/core/compaction/context-deletion-tool-definitions.d.ts +6 -0
  100. package/dist/core/compaction/context-deletion-tool-definitions.d.ts.map +1 -1
  101. package/dist/core/compaction/context-deletion-tool-definitions.js.map +1 -1
  102. package/dist/core/compaction/context-deletion-tools.d.ts.map +1 -1
  103. package/dist/core/compaction/context-deletion-tools.js +18 -10
  104. package/dist/core/compaction/context-deletion-tools.js.map +1 -1
  105. package/dist/core/compaction/context-transcript-analysis.d.ts.map +1 -1
  106. package/dist/core/compaction/context-transcript-analysis.js +2 -4
  107. package/dist/core/compaction/context-transcript-analysis.js.map +1 -1
  108. package/dist/core/copilot-gemini-tool-arguments.d.ts.map +1 -1
  109. package/dist/core/copilot-gemini-tool-arguments.js +2 -60
  110. package/dist/core/copilot-gemini-tool-arguments.js.map +1 -1
  111. package/dist/core/extensions/context-types.d.ts +2 -0
  112. package/dist/core/extensions/context-types.d.ts.map +1 -1
  113. package/dist/core/extensions/context-types.js.map +1 -1
  114. package/dist/core/extensions/index.d.ts +2 -2
  115. package/dist/core/extensions/index.d.ts.map +1 -1
  116. package/dist/core/extensions/index.js +1 -1
  117. package/dist/core/extensions/index.js.map +1 -1
  118. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  119. package/dist/core/extensions/loader-virtual-modules.js +11 -3
  120. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  121. package/dist/core/extensions/runner-context.d.ts.map +1 -1
  122. package/dist/core/extensions/runner-context.js +11 -0
  123. package/dist/core/extensions/runner-context.js.map +1 -1
  124. package/dist/core/extensions/tool-events.d.ts +13 -13
  125. package/dist/core/extensions/tool-events.d.ts.map +1 -1
  126. package/dist/core/extensions/tool-events.js +3 -3
  127. package/dist/core/extensions/tool-events.js.map +1 -1
  128. package/dist/core/extensions/types.d.ts +1 -1
  129. package/dist/core/extensions/types.d.ts.map +1 -1
  130. package/dist/core/extensions/types.js +1 -1
  131. package/dist/core/extensions/types.js.map +1 -1
  132. package/dist/core/flattened-tool-arguments.d.ts +18 -0
  133. package/dist/core/flattened-tool-arguments.d.ts.map +1 -1
  134. package/dist/core/flattened-tool-arguments.js +104 -0
  135. package/dist/core/flattened-tool-arguments.js.map +1 -1
  136. package/dist/core/sdk-exports.d.ts +1 -1
  137. package/dist/core/sdk-exports.d.ts.map +1 -1
  138. package/dist/core/sdk-exports.js +1 -1
  139. package/dist/core/sdk-exports.js.map +1 -1
  140. package/dist/core/sdk-types.d.ts +2 -2
  141. package/dist/core/sdk-types.d.ts.map +1 -1
  142. package/dist/core/sdk-types.js.map +1 -1
  143. package/dist/core/settings-manager-basic-accessors.d.ts +4 -0
  144. package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
  145. package/dist/core/settings-manager-basic-accessors.js +18 -0
  146. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  147. package/dist/core/settings-manager-resource-accessors.d.ts +4 -0
  148. package/dist/core/settings-manager-resource-accessors.d.ts.map +1 -1
  149. package/dist/core/settings-manager-resource-accessors.js +15 -0
  150. package/dist/core/settings-manager-resource-accessors.js.map +1 -1
  151. package/dist/core/settings-types.d.ts +11 -0
  152. package/dist/core/settings-types.d.ts.map +1 -1
  153. package/dist/core/settings-types.js.map +1 -1
  154. package/dist/core/system-prompt.d.ts +1 -1
  155. package/dist/core/system-prompt.d.ts.map +1 -1
  156. package/dist/core/system-prompt.js +3 -2
  157. package/dist/core/system-prompt.js.map +1 -1
  158. package/dist/core/tools/artifact-protocol.d.ts +11 -0
  159. package/dist/core/tools/artifact-protocol.d.ts.map +1 -0
  160. package/dist/core/tools/artifact-protocol.js +76 -0
  161. package/dist/core/tools/artifact-protocol.js.map +1 -0
  162. package/dist/core/tools/artifacts.d.ts +18 -0
  163. package/dist/core/tools/artifacts.d.ts.map +1 -0
  164. package/dist/core/tools/artifacts.js +90 -0
  165. package/dist/core/tools/artifacts.js.map +1 -0
  166. package/dist/core/tools/bash-async-jobs.d.ts +20 -0
  167. package/dist/core/tools/bash-async-jobs.d.ts.map +1 -0
  168. package/dist/core/tools/bash-async-jobs.js +59 -0
  169. package/dist/core/tools/bash-async-jobs.js.map +1 -0
  170. package/dist/core/tools/bash-async-output.d.ts +10 -0
  171. package/dist/core/tools/bash-async-output.d.ts.map +1 -0
  172. package/dist/core/tools/bash-async-output.js +80 -0
  173. package/dist/core/tools/bash-async-output.js.map +1 -0
  174. package/dist/core/tools/bash-interceptor.d.ts +10 -0
  175. package/dist/core/tools/bash-interceptor.d.ts.map +1 -0
  176. package/dist/core/tools/bash-interceptor.js +39 -0
  177. package/dist/core/tools/bash-interceptor.js.map +1 -0
  178. package/dist/core/tools/bash-leading-cd.d.ts +7 -0
  179. package/dist/core/tools/bash-leading-cd.d.ts.map +1 -0
  180. package/dist/core/tools/bash-leading-cd.js +59 -0
  181. package/dist/core/tools/bash-leading-cd.js.map +1 -0
  182. package/dist/core/tools/bash-pty-native.d.ts +14 -0
  183. package/dist/core/tools/bash-pty-native.d.ts.map +1 -0
  184. package/dist/core/tools/bash-pty-native.js +71 -0
  185. package/dist/core/tools/bash-pty-native.js.map +1 -0
  186. package/dist/core/tools/bash.d.ts +28 -17
  187. package/dist/core/tools/bash.d.ts.map +1 -1
  188. package/dist/core/tools/bash.js +152 -35
  189. package/dist/core/tools/bash.js.map +1 -1
  190. package/dist/core/tools/block-resolver.d.ts +16 -0
  191. package/dist/core/tools/block-resolver.d.ts.map +1 -0
  192. package/dist/core/tools/block-resolver.js +74 -0
  193. package/dist/core/tools/block-resolver.js.map +1 -0
  194. package/dist/core/tools/conflict-registry.d.ts +16 -0
  195. package/dist/core/tools/conflict-registry.d.ts.map +1 -0
  196. package/dist/core/tools/conflict-registry.js +44 -0
  197. package/dist/core/tools/conflict-registry.js.map +1 -0
  198. package/dist/core/tools/directory-tree.d.ts +13 -0
  199. package/dist/core/tools/directory-tree.d.ts.map +1 -0
  200. package/dist/core/tools/directory-tree.js +81 -0
  201. package/dist/core/tools/directory-tree.js.map +1 -0
  202. package/dist/core/tools/edit.d.ts +4 -29
  203. package/dist/core/tools/edit.d.ts.map +1 -1
  204. package/dist/core/tools/edit.js +136 -228
  205. package/dist/core/tools/edit.js.map +1 -1
  206. package/dist/core/tools/fetch-url.d.ts +74 -0
  207. package/dist/core/tools/fetch-url.d.ts.map +1 -0
  208. package/dist/core/tools/fetch-url.js +518 -0
  209. package/dist/core/tools/fetch-url.js.map +1 -0
  210. package/dist/core/tools/find.d.ts +27 -9
  211. package/dist/core/tools/find.d.ts.map +1 -1
  212. package/dist/core/tools/find.js +400 -176
  213. package/dist/core/tools/find.js.map +1 -1
  214. package/dist/core/tools/glob-path-utils.d.ts +8 -0
  215. package/dist/core/tools/glob-path-utils.d.ts.map +1 -0
  216. package/dist/core/tools/glob-path-utils.js +26 -0
  217. package/dist/core/tools/glob-path-utils.js.map +1 -0
  218. package/dist/core/tools/grep.d.ts +12 -0
  219. package/dist/core/tools/grep.d.ts.map +1 -1
  220. package/dist/core/tools/grep.js +141 -17
  221. package/dist/core/tools/grep.js.map +1 -1
  222. package/dist/core/tools/hashline-engine/apply.d.ts +11 -0
  223. package/dist/core/tools/hashline-engine/apply.d.ts.map +1 -0
  224. package/dist/core/tools/hashline-engine/apply.js +752 -0
  225. package/dist/core/tools/hashline-engine/apply.js.map +1 -0
  226. package/dist/core/tools/hashline-engine/block.d.ts +40 -0
  227. package/dist/core/tools/hashline-engine/block.d.ts.map +1 -0
  228. package/dist/core/tools/hashline-engine/block.js +117 -0
  229. package/dist/core/tools/hashline-engine/block.js.map +1 -0
  230. package/dist/core/tools/hashline-engine/diff-preview.d.ts +15 -0
  231. package/dist/core/tools/hashline-engine/diff-preview.d.ts.map +1 -0
  232. package/dist/core/tools/hashline-engine/diff-preview.js +98 -0
  233. package/dist/core/tools/hashline-engine/diff-preview.js.map +1 -0
  234. package/dist/core/tools/hashline-engine/format.d.ts +71 -0
  235. package/dist/core/tools/hashline-engine/format.d.ts.map +1 -0
  236. package/dist/core/tools/hashline-engine/format.js +178 -0
  237. package/dist/core/tools/hashline-engine/format.js.map +1 -0
  238. package/dist/core/tools/hashline-engine/fs.d.ts +81 -0
  239. package/dist/core/tools/hashline-engine/fs.d.ts.map +1 -0
  240. package/dist/core/tools/hashline-engine/fs.js +143 -0
  241. package/dist/core/tools/hashline-engine/fs.js.map +1 -0
  242. package/dist/core/tools/hashline-engine/index.d.ts +18 -0
  243. package/dist/core/tools/hashline-engine/index.d.ts.map +1 -0
  244. package/dist/core/tools/hashline-engine/index.js +20 -0
  245. package/dist/core/tools/hashline-engine/index.js.map +1 -0
  246. package/dist/core/tools/hashline-engine/input.d.ts +101 -0
  247. package/dist/core/tools/hashline-engine/input.d.ts.map +1 -0
  248. package/dist/core/tools/hashline-engine/input.js +398 -0
  249. package/dist/core/tools/hashline-engine/input.js.map +1 -0
  250. package/dist/core/tools/hashline-engine/messages.d.ts +99 -0
  251. package/dist/core/tools/hashline-engine/messages.d.ts.map +1 -0
  252. package/dist/core/tools/hashline-engine/messages.js +144 -0
  253. package/dist/core/tools/hashline-engine/messages.js.map +1 -0
  254. package/dist/core/tools/hashline-engine/mismatch.d.ts +45 -0
  255. package/dist/core/tools/hashline-engine/mismatch.d.ts.map +1 -0
  256. package/dist/core/tools/hashline-engine/mismatch.js +90 -0
  257. package/dist/core/tools/hashline-engine/mismatch.js.map +1 -0
  258. package/dist/core/tools/hashline-engine/normalize.d.ts +21 -0
  259. package/dist/core/tools/hashline-engine/normalize.d.ts.map +1 -0
  260. package/dist/core/tools/hashline-engine/normalize.js +33 -0
  261. package/dist/core/tools/hashline-engine/normalize.js.map +1 -0
  262. package/dist/core/tools/hashline-engine/parser.d.ts +24 -0
  263. package/dist/core/tools/hashline-engine/parser.d.ts.map +1 -0
  264. package/dist/core/tools/hashline-engine/parser.js +381 -0
  265. package/dist/core/tools/hashline-engine/parser.js.map +1 -0
  266. package/dist/core/tools/hashline-engine/patcher.d.ts +118 -0
  267. package/dist/core/tools/hashline-engine/patcher.d.ts.map +1 -0
  268. package/dist/core/tools/hashline-engine/patcher.js +341 -0
  269. package/dist/core/tools/hashline-engine/patcher.js.map +1 -0
  270. package/dist/core/tools/hashline-engine/prefixes.d.ts +43 -0
  271. package/dist/core/tools/hashline-engine/prefixes.d.ts.map +1 -0
  272. package/dist/core/tools/hashline-engine/prefixes.js +135 -0
  273. package/dist/core/tools/hashline-engine/prefixes.js.map +1 -0
  274. package/dist/core/tools/hashline-engine/recovery.d.ts +41 -0
  275. package/dist/core/tools/hashline-engine/recovery.d.ts.map +1 -0
  276. package/dist/core/tools/hashline-engine/recovery.js +168 -0
  277. package/dist/core/tools/hashline-engine/recovery.js.map +1 -0
  278. package/dist/core/tools/hashline-engine/snapshots.d.ts +65 -0
  279. package/dist/core/tools/hashline-engine/snapshots.d.ts.map +1 -0
  280. package/dist/core/tools/hashline-engine/snapshots.js +108 -0
  281. package/dist/core/tools/hashline-engine/snapshots.js.map +1 -0
  282. package/dist/core/tools/hashline-engine/stream.d.ts +3 -0
  283. package/dist/core/tools/hashline-engine/stream.d.ts.map +1 -0
  284. package/dist/core/tools/hashline-engine/stream.js +111 -0
  285. package/dist/core/tools/hashline-engine/stream.js.map +1 -0
  286. package/dist/core/tools/hashline-engine/tokenizer.d.ts +69 -0
  287. package/dist/core/tools/hashline-engine/tokenizer.d.ts.map +1 -0
  288. package/dist/core/tools/hashline-engine/tokenizer.js +430 -0
  289. package/dist/core/tools/hashline-engine/tokenizer.js.map +1 -0
  290. package/dist/core/tools/hashline-engine/types.d.ts +166 -0
  291. package/dist/core/tools/hashline-engine/types.d.ts.map +1 -0
  292. package/dist/core/tools/hashline-engine/types.js +9 -0
  293. package/dist/core/tools/hashline-engine/types.js.map +1 -0
  294. package/dist/core/tools/hashline.d.ts +29 -0
  295. package/dist/core/tools/hashline.d.ts.map +1 -0
  296. package/dist/core/tools/hashline.js +110 -0
  297. package/dist/core/tools/hashline.js.map +1 -0
  298. package/dist/core/tools/index.d.ts +6 -4
  299. package/dist/core/tools/index.d.ts.map +1 -1
  300. package/dist/core/tools/index.js +52 -35
  301. package/dist/core/tools/index.js.map +1 -1
  302. package/dist/core/tools/notebook.d.ts +38 -0
  303. package/dist/core/tools/notebook.d.ts.map +1 -0
  304. package/dist/core/tools/notebook.js +125 -0
  305. package/dist/core/tools/notebook.js.map +1 -0
  306. package/dist/core/tools/read-document-extract.d.ts +9 -0
  307. package/dist/core/tools/read-document-extract.d.ts.map +1 -0
  308. package/dist/core/tools/read-document-extract.js +212 -0
  309. package/dist/core/tools/read-document-extract.js.map +1 -0
  310. package/dist/core/tools/read-selectors.d.ts +24 -0
  311. package/dist/core/tools/read-selectors.d.ts.map +1 -0
  312. package/dist/core/tools/read-selectors.js +277 -0
  313. package/dist/core/tools/read-selectors.js.map +1 -0
  314. package/dist/core/tools/read-url.d.ts +37 -0
  315. package/dist/core/tools/read-url.d.ts.map +1 -0
  316. package/dist/core/tools/read-url.js +39 -0
  317. package/dist/core/tools/read-url.js.map +1 -0
  318. package/dist/core/tools/read.d.ts +11 -11
  319. package/dist/core/tools/read.d.ts.map +1 -1
  320. package/dist/core/tools/read.js +224 -94
  321. package/dist/core/tools/read.js.map +1 -1
  322. package/dist/core/tools/resource-selectors.d.ts +44 -0
  323. package/dist/core/tools/resource-selectors.d.ts.map +1 -0
  324. package/dist/core/tools/resource-selectors.js +808 -0
  325. package/dist/core/tools/resource-selectors.js.map +1 -0
  326. package/dist/core/tools/search-details.d.ts +26 -0
  327. package/dist/core/tools/search-details.d.ts.map +1 -0
  328. package/dist/core/tools/search-details.js +24 -0
  329. package/dist/core/tools/search-details.js.map +1 -0
  330. package/dist/core/tools/search-line-ranges.d.ts +11 -0
  331. package/dist/core/tools/search-line-ranges.d.ts.map +1 -0
  332. package/dist/core/tools/search-line-ranges.js +65 -0
  333. package/dist/core/tools/search-line-ranges.js.map +1 -0
  334. package/dist/core/tools/search-native.d.ts +97 -0
  335. package/dist/core/tools/search-native.d.ts.map +1 -0
  336. package/dist/core/tools/search-native.js +27 -0
  337. package/dist/core/tools/search-native.js.map +1 -0
  338. package/dist/core/tools/search.d.ts +24 -0
  339. package/dist/core/tools/search.d.ts.map +1 -0
  340. package/dist/core/tools/search.js +573 -0
  341. package/dist/core/tools/search.js.map +1 -0
  342. package/dist/core/tools/truncate.d.ts +4 -4
  343. package/dist/core/tools/truncate.d.ts.map +1 -1
  344. package/dist/core/tools/truncate.js +3 -3
  345. package/dist/core/tools/truncate.js.map +1 -1
  346. package/dist/core/tools/url-ip-guards.d.ts +4 -0
  347. package/dist/core/tools/url-ip-guards.d.ts.map +1 -0
  348. package/dist/core/tools/url-ip-guards.js +126 -0
  349. package/dist/core/tools/url-ip-guards.js.map +1 -0
  350. package/dist/core/tools/write.d.ts +12 -2
  351. package/dist/core/tools/write.d.ts.map +1 -1
  352. package/dist/core/tools/write.js +166 -14
  353. package/dist/core/tools/write.js.map +1 -1
  354. package/dist/core/trust-manager.d.ts.map +1 -1
  355. package/dist/core/trust-manager.js +2 -3
  356. package/dist/core/trust-manager.js.map +1 -1
  357. package/dist/index-extensions.d.ts +2 -2
  358. package/dist/index-extensions.d.ts.map +1 -1
  359. package/dist/index-extensions.js +1 -1
  360. package/dist/index-extensions.js.map +1 -1
  361. package/dist/index.d.ts +3 -3
  362. package/dist/index.d.ts.map +1 -1
  363. package/dist/index.js +3 -3
  364. package/dist/index.js.map +1 -1
  365. package/dist/modes/interactive/components/custom-editor.d.ts +1 -0
  366. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  367. package/dist/modes/interactive/components/custom-editor.js +9 -2
  368. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  369. package/dist/modes/interactive/components/settings-selector-handlers.d.ts.map +1 -1
  370. package/dist/modes/interactive/components/settings-selector-handlers.js +3 -0
  371. package/dist/modes/interactive/components/settings-selector-handlers.js.map +1 -1
  372. package/dist/modes/interactive/components/settings-selector-items.d.ts.map +1 -1
  373. package/dist/modes/interactive/components/settings-selector-items.js +7 -0
  374. package/dist/modes/interactive/components/settings-selector-items.js.map +1 -1
  375. package/dist/modes/interactive/components/settings-selector-types.d.ts +2 -0
  376. package/dist/modes/interactive/components/settings-selector-types.d.ts.map +1 -1
  377. package/dist/modes/interactive/components/settings-selector-types.js.map +1 -1
  378. package/dist/modes/interactive/components/tree-selector-content.d.ts.map +1 -1
  379. package/dist/modes/interactive/components/tree-selector-content.js +0 -5
  380. package/dist/modes/interactive/components/tree-selector-content.js.map +1 -1
  381. package/dist/modes/interactive/interactive-auth-login.d.ts.map +1 -1
  382. package/dist/modes/interactive/interactive-auth-login.js +1 -0
  383. package/dist/modes/interactive/interactive-auth-login.js.map +1 -1
  384. package/dist/modes/interactive/interactive-autocomplete.d.ts.map +1 -1
  385. package/dist/modes/interactive/interactive-autocomplete.js +80 -2
  386. package/dist/modes/interactive/interactive-autocomplete.js.map +1 -1
  387. package/dist/modes/interactive/interactive-hotkeys-debug.d.ts.map +1 -1
  388. package/dist/modes/interactive/interactive-hotkeys-debug.js +3 -0
  389. package/dist/modes/interactive/interactive-hotkeys-debug.js.map +1 -1
  390. package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
  391. package/dist/modes/interactive/interactive-input-handling.js +51 -0
  392. package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
  393. package/dist/modes/interactive/interactive-mode-base.d.ts +5 -0
  394. package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
  395. package/dist/modes/interactive/interactive-mode-base.js +5 -0
  396. package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
  397. package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
  398. package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
  399. package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
  400. package/dist/modes/interactive/interactive-mode-surface.d.ts +12 -0
  401. package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
  402. package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
  403. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  404. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  405. package/dist/modes/interactive/interactive-mode.js +1 -0
  406. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  407. package/dist/modes/interactive/interactive-model-routing.d.ts.map +1 -1
  408. package/dist/modes/interactive/interactive-model-routing.js +4 -1
  409. package/dist/modes/interactive/interactive-model-routing.js.map +1 -1
  410. package/dist/modes/interactive/interactive-onboarding.d.ts +11 -0
  411. package/dist/modes/interactive/interactive-onboarding.d.ts.map +1 -0
  412. package/dist/modes/interactive/interactive-onboarding.js +220 -0
  413. package/dist/modes/interactive/interactive-onboarding.js.map +1 -0
  414. package/dist/modes/interactive/interactive-selectors.d.ts.map +1 -1
  415. package/dist/modes/interactive/interactive-selectors.js +4 -0
  416. package/dist/modes/interactive/interactive-selectors.js.map +1 -1
  417. package/dist/modes/interactive/interactive-session-routing.d.ts.map +1 -1
  418. package/dist/modes/interactive/interactive-session-routing.js +6 -0
  419. package/dist/modes/interactive/interactive-session-routing.js.map +1 -1
  420. package/dist/modes/interactive/interactive-slash-commands.d.ts.map +1 -1
  421. package/dist/modes/interactive/interactive-slash-commands.js +9 -4
  422. package/dist/modes/interactive/interactive-slash-commands.js.map +1 -1
  423. package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
  424. package/dist/modes/interactive/interactive-startup.js +28 -0
  425. package/dist/modes/interactive/interactive-startup.js.map +1 -1
  426. package/dist/utils/child-process.d.ts.map +1 -1
  427. package/dist/utils/child-process.js +21 -1
  428. package/dist/utils/child-process.js.map +1 -1
  429. package/dist/utils/markit.d.ts +8 -0
  430. package/dist/utils/markit.d.ts.map +1 -0
  431. package/dist/utils/markit.js +53 -0
  432. package/dist/utils/markit.js.map +1 -0
  433. package/dist/utils/paths.d.ts +2 -1
  434. package/dist/utils/paths.d.ts.map +1 -1
  435. package/dist/utils/paths.js +14 -1
  436. package/dist/utils/paths.js.map +1 -1
  437. package/docs/compaction.md +16 -1
  438. package/docs/containerization.md +1 -1
  439. package/docs/docs.json +1 -0
  440. package/docs/extensions.md +25 -36
  441. package/docs/quickstart.md +11 -6
  442. package/docs/sdk.md +5 -5
  443. package/docs/settings.md +7 -0
  444. package/docs/subagents.md +3 -2
  445. package/docs/tools.md +49 -0
  446. package/docs/usage.md +3 -3
  447. package/docs/workflows.md +7 -5
  448. package/examples/extensions/subagent/README.md +5 -5
  449. package/examples/extensions/subagent/agents/planner.md +1 -1
  450. package/examples/extensions/subagent/agents/reviewer.md +1 -1
  451. package/examples/extensions/subagent/agents/scout.md +2 -2
  452. package/examples/extensions/subagent/display.ts +3 -3
  453. package/examples/sdk/05-tools.ts +3 -3
  454. package/examples/sdk/README.md +1 -1
  455. package/package.json +3 -2
@@ -0,0 +1,74 @@
1
+ import type { TruncationResult } from "./truncate.ts";
2
+ export declare const FETCH_DEFAULT_MAX_LINES = 300;
3
+ export declare const READ_URL_CACHE_MAX_ENTRIES = 100;
4
+ export interface LineRange {
5
+ start: number;
6
+ end?: number;
7
+ }
8
+ export interface ParsedReadUrlTarget {
9
+ url: string;
10
+ raw: boolean;
11
+ offset?: number;
12
+ limit?: number;
13
+ ranges?: readonly LineRange[];
14
+ }
15
+ export interface ReadUrlToolDetails {
16
+ url: string;
17
+ finalUrl: string;
18
+ contentType: string;
19
+ method: string;
20
+ truncated: boolean;
21
+ notes: string[];
22
+ meta?: {
23
+ artifactId?: string;
24
+ truncation?: TruncationResult;
25
+ };
26
+ }
27
+ export interface LoadedPage {
28
+ ok: boolean;
29
+ status: number;
30
+ content: string;
31
+ contentType: string;
32
+ finalUrl: string;
33
+ }
34
+ export declare function repairCollapsedScheme(url: string): string;
35
+ export declare function isReadableUrlPath(readPath: string): boolean;
36
+ export declare function parseReadUrlTarget(readPath: string): ParsedReadUrlTarget | null;
37
+ export declare function getReadUrlCacheKey(scope: string, requestedUrl: string, raw: boolean): string;
38
+ export declare function loadPage(url: string, timeoutMs: number, signal?: AbortSignal, accept?: string): Promise<LoadedPage>;
39
+ export interface RenderedUrl {
40
+ content: string;
41
+ finalUrl: string;
42
+ contentType: string;
43
+ method: string;
44
+ notes: string[];
45
+ truncated: boolean;
46
+ }
47
+ export declare function renderUrl(url: string, raw: boolean, signal?: AbortSignal): Promise<RenderedUrl>;
48
+ export declare function buildUrlReadOutput(result: RenderedUrl): string;
49
+ export interface ExecuteReadUrlResult {
50
+ content: string;
51
+ details: ReadUrlToolDetails;
52
+ artifactId?: string;
53
+ }
54
+ export interface LoadedReadUrl {
55
+ output: string;
56
+ details: ReadUrlToolDetails;
57
+ artifactId?: string;
58
+ raw: boolean;
59
+ }
60
+ /**
61
+ * Resolve a URL to its full rendered (or raw) output, using the session-scoped
62
+ * cache and persisting the rendered body as a `read` artifact. Returns the
63
+ * complete untruncated output so callers can apply their own line selection.
64
+ */
65
+ export declare function loadReadUrlOutput(scope: string, params: {
66
+ path: string;
67
+ raw?: boolean;
68
+ }, artifactsDir: string | undefined, signal?: AbortSignal): Promise<LoadedReadUrl>;
69
+ export declare function executeReadUrl(scope: string, params: {
70
+ path: string;
71
+ raw?: boolean;
72
+ }, artifactsDir: string | undefined, signal?: AbortSignal): Promise<ExecuteReadUrlResult>;
73
+ export declare function resetReadUrlCache(): void;
74
+ //# sourceMappingURL=fetch-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-url.d.ts","sourceRoot":"","sources":["../../../src/core/tools/fetch-url.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAM9C,MAAM,WAAW,SAAS;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE;AAE1D,MAAM,WAAW,mBAAmB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,IAAI,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,gBAAgB,CAAA;KAAE,CAAC;CAC9D;AAQD,MAAM,WAAW,UAAU;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CACjB;AASD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAG3D;AAkDD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAoB/E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,MAAM,CAE5F;AAwFD,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAqCzH;AA6ED,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,SAAS,EAAE,OAAO,CAAC;CACnB;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAoCrG;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAG9D;AAOD,MAAM,WAAW,oBAAoB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,OAAO,CAAC;CACb;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAgB9K;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA+BlL;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC","sourcesContent":["/// <reference path=\"../../utils/turndown.d.ts\" />\n/**\n * URL fetch / cache / artifact / markdown-discovery pipeline, mirrored from\n * oh-my-pi's `packages/coding-agent/src/tools/fetch.ts` at 15b5c1397fc.\n *\n * Responsibilities:\n * - parseReadUrlTarget: URL + line selectors (:raw, :N, :A-B, :A+B, :A-B,C-D),\n * collapsed-scheme repair, host-port protection.\n * - session-scoped LRU cache keyed by `<scope>::<raw|rendered>::<normalizedUrl>`,\n * cached under both requested and final (redirected) URLs.\n * - renderUrl: markdown discovery (alternate <link>, .md suffix, content\n * negotiation, llms.txt/llms.md endpoints) + native HTML-to-markdown, with\n * quality gating.\n * - artifact persistence of the rendered output, with artifactId surfaced in\n * truncation metadata when the visible output is head-truncated.\n */\nimport { lookup } from \"node:dns/promises\";\nimport { ipFamily, isPrivateIpAddress, normalizeIpLiteralHost } from \"./url-ip-guards.ts\";\nimport { LRUCache } from \"lru-cache\";\nimport TurndownService from \"turndown\";\nimport { Agent, request as undiciRequest, type Dispatcher } from \"undici\";\nimport { getArtifactManager } from \"./artifacts.ts\";\nimport { extractDocumentMarkdown, isDocumentPath } from \"./read-document-extract.ts\";\nimport type { TruncationResult } from \"./truncate.ts\";\n\nexport const FETCH_DEFAULT_MAX_LINES = 300;\nexport const READ_URL_CACHE_MAX_ENTRIES = 100;\nconst REMOTE_FETCH_TIMEOUT_MS = 10_000;\nconst MAX_URL_REDIRECTS = 5;\nconst MAX_URL_RESPONSE_BYTES = 10 * 1024 * 1024;\nconst MIN_CONTENT_LENGTH = 100;\n\nexport interface LineRange { start: number; end?: number }\n\nexport interface ParsedReadUrlTarget {\n\turl: string;\n\traw: boolean;\n\toffset?: number;\n\tlimit?: number;\n\tranges?: readonly LineRange[];\n}\n\nexport interface ReadUrlToolDetails {\n\turl: string;\n\tfinalUrl: string;\n\tcontentType: string;\n\tmethod: string;\n\ttruncated: boolean;\n\tnotes: string[];\n\tmeta?: { artifactId?: string; truncation?: TruncationResult };\n}\n\ninterface ReadUrlCacheEntry {\n\tartifactId?: string;\n\tdetails: ReadUrlToolDetails;\n\toutput: string;\n}\n\nexport interface LoadedPage {\n\tok: boolean;\n\tstatus: number;\n\tcontent: string;\n\tcontentType: string;\n\tfinalUrl: string;\n}\n\nconst readUrlCache = new LRUCache<string, ReadUrlCacheEntry>({ max: READ_URL_CACHE_MAX_ENTRIES });\nlet turndown: TurndownService | undefined;\nfunction getTurndown(): TurndownService {\n\tif (!turndown) turndown = new TurndownService({ headingStyle: \"atx\", codeBlockStyle: \"fenced\", bulletListMarker: \"-\" });\n\treturn turndown;\n}\n\nexport function repairCollapsedScheme(url: string): string {\n\treturn url.replace(/^(https?):\\/([^/])/i, \"$1://$2\");\n}\n\nexport function isReadableUrlPath(readPath: string): boolean {\n\tconst repaired = repairCollapsedScheme(readPath);\n\treturn /^https?:\\/\\//i.test(repaired) || /^www\\./i.test(readPath);\n}\n\nfunction normalizeUrlForCache(url: string): string {\n\tconst repaired = repairCollapsedScheme(url);\n\tif (/^[a-z][a-z0-9+.-]*:\\/\\//i.test(repaired)) return repaired;\n\treturn /^https?:\\/\\//i.test(repaired) ? repaired : `https://${repaired}`;\n}\nconst RANGE_TOKEN_RE = /^(raw|\\d+(?:\\+(?:\\d+)|(?:-|\\.\\.)\\d+)?)$/i;\nconst MULTI_RANGE_RE = /^\\d+(?:(?:-|\\.\\.|,|\\+)\\d+)*$/;\n\nfunction isUrlSelectorToken(token: string): boolean {\n\treturn /^raw$/i.test(token) || RANGE_TOKEN_RE.test(token) || MULTI_RANGE_RE.test(token);\n}\n\nfunction parseSingleRange(token: string): LineRange | undefined {\n\tconst plus = token.match(/^(\\d+)\\+(\\d+)$/);\n\tif (plus) {\n\t\tconst start = Number.parseInt(plus[1]!, 10), count = Number.parseInt(plus[2]!, 10);\n\t\treturn start < 1 || count < 1 ? undefined : { start, end: start + count - 1 };\n\t}\n\tconst range = token.match(/^(\\d+)(?:-|\\.\\.)(\\d+)$/);\n\tif (range) { const start = Number.parseInt(range[1]!, 10); const end = Number.parseInt(range[2]!, 10); return start < 1 || end < start ? undefined : { start, end }; }\n\tconst single = token.match(/^(\\d+)$/);\n\tif (single) { const start = Number.parseInt(single[1]!, 10); return start < 1 ? undefined : { start }; }\n\treturn undefined;\n}\n\nfunction parseRangeGroup(token: string): LineRange[] | undefined {\n\tconst groups = token.split(\",\");\n\tconst ranges: LineRange[] = [];\n\tfor (const group of groups) { const r = parseSingleRange(group); if (!r) return undefined; ranges.push(r); }\n\treturn ranges;\n}\n\nfunction tryExtractEmbeddedUrlSelector(readPath: string): { path: string; sels: string[] } | null {\n\tlet basePath = readPath;\n\tconst sels: string[] = [];\n\tfor (;;) {\n\t\tconst lastColonIndex = basePath.lastIndexOf(\":\");\n\t\tif (lastColonIndex <= 0) break;\n\t\tconst candidate = basePath.slice(lastColonIndex + 1);\n\t\tconst remainder = basePath.slice(0, lastColonIndex);\n\t\tif (!isReadableUrlPath(remainder) || !isUrlSelectorToken(candidate)) break;\n\t\ttry { new URL(remainder.startsWith(\"http://\") || remainder.startsWith(\"https://\") ? remainder : `https://${remainder}`); } catch { break; }\n\t\tsels.unshift(candidate);\n\t\tbasePath = remainder;\n\t}\n\treturn sels.length === 0 ? null : { path: basePath, sels };\n}\n\nexport function parseReadUrlTarget(readPath: string): ParsedReadUrlTarget | null {\n\tconst repaired = repairCollapsedScheme(readPath);\n\tconst embedded = tryExtractEmbeddedUrlSelector(repaired);\n\tconst urlPath = embedded?.path ?? repaired;\n\tif (!isReadableUrlPath(urlPath)) return null;\n\tlet raw = false;\n\tlet ranges: LineRange[] | undefined;\n\tfor (const sel of embedded?.sels ?? []) {\n\t\tif (/^raw$/i.test(sel)) { raw = true; continue; }\n\t\tif (ranges !== undefined) throw new Error(\"URL selector has multiple range groups; combine them with commas (e.g. `:5-10,20-30`).\");\n\t\tconst parsed = parseRangeGroup(sel);\n\t\tif (!parsed) throw new Error(`Invalid URL line selector: ${sel}`);\n\t\tranges = parsed;\n\t}\n\tif (!ranges || ranges.length === 0) return { url: urlPath, raw };\n\tif (ranges.length === 1) {\n\t\tconst r = ranges[0]!;\n\t\treturn { url: urlPath, raw, offset: r.start, limit: r.end !== undefined ? r.end - r.start + 1 : undefined };\n\t}\n\treturn { url: urlPath, raw, ranges };\n}\n\nexport function getReadUrlCacheKey(scope: string, requestedUrl: string, raw: boolean): string {\n\treturn `${scope}::${raw ? \"raw\" : \"rendered\"}::${normalizeUrlForCache(requestedUrl)}`;\n}\n\nfunction looksLikeHtml(text: string): boolean {\n\treturn /<html[\\s>]/i.test(text) || (/<head[\\s>]/i.test(text) && /<body[\\s>]/i.test(text));\n}\n\nfunction documentExtensionFromContentType(contentType: string): string | undefined {\n\tif (/pdf/i.test(contentType)) return \".pdf\";\n\tif (/msword/i.test(contentType)) return \".doc\";\n\tif (/wordprocessingml|officedocument\\.word/i.test(contentType)) return \".docx\";\n\tif (/presentationml|officedocument\\.presentation/i.test(contentType)) return \".pptx\";\n\tif (/ms-powerpoint|vnd\\.ms-powerpoint/i.test(contentType)) return \".ppt\";\n\tif (/spreadsheetml|officedocument\\.spreadsheet/i.test(contentType)) return \".xlsx\";\n\tif (/ms-excel|vnd\\.ms-excel/i.test(contentType)) return \".xls\";\n\tif (/epub/i.test(contentType)) return \".epub\";\n\tif (/\\bjson\\b|ipynb|jupyter/i.test(contentType)) return \".ipynb\";\n\tif (/rtf/i.test(contentType)) return \".rtf\";\n\treturn undefined;\n}\n\ninterface SafeFetchAddress { address: string; family: 4 | 6; /** True only when the address came from DNS resolution and must be pinned to prevent rebinding at connect time. */ pinned: boolean }\ntype LookupCallback = (error: NodeJS.ErrnoException | null, address: string | SafeFetchAddress[], family?: number) => void;\n\nfunction createPinnedDispatcher(pinned: SafeFetchAddress): Agent {\n\treturn new Agent({ connect: { lookup: (_hostname: string, options: { all?: boolean }, callback: LookupCallback) => { if (options.all) callback(null, [pinned]); else callback(null, pinned.address, pinned.family); } } });\n}\n\nasync function closePinnedDispatcher(dispatcher: Agent | undefined): Promise<void> {\n\tconst close = (dispatcher as { close?: () => Promise<void> | void } | undefined)?.close;\n\tif (typeof close === \"function\") await close.call(dispatcher);\n}\n/**\n * Fetch a URL through undici with the pinned dispatcher so the validated\n * address is the one actually connected to. Unlike `globalThis.fetch`, which\n * silently ignores the undici `dispatcher` option under Bun's compiled binary\n * (reopening a DNS-rebinding TOCTOU window for hostname targets), undici's own\n * client honors the dispatcher's custom `lookup` regardless of host runtime.\n * Adapts the undici response to the WHATWG-Response shape loadPage consumes.\n */\nasync function pinnedFetch(url: string, dispatcher: Dispatcher, options: { signal: AbortSignal; headers?: Record<string, string> }): Promise<Response> {\n\tconst result = await undiciRequest(url, { method: \"GET\", dispatcher, signal: options.signal, headers: options.headers });\n\tconst body = result.body;\n\tconst status = result.statusCode;\n\tconst headers = new Headers();\n\tfor (const [key, value] of Object.entries(result.headers as Record<string, string | string[] | undefined>)) {\n\t\tif (value === undefined) continue;\n\t\tconst values = Array.isArray(value) ? value : [value];\n\t\tfor (const entry of values) headers.append(key, entry);\n\t}\n\tconst stream = new ReadableStream({\n\t\tstart(controller) { body.on(\"data\", (chunk: Buffer) => controller.enqueue(new Uint8Array(chunk))); body.on(\"end\", () => controller.close()); body.on(\"error\", (error: unknown) => controller.error(error)); },\n\t\tcancel() { body.destroy(); },\n\t});\n\treturn new Response(stream, { status, headers });\n}\n\n\nasync function assertSafeFetchUrl(url: string): Promise<SafeFetchAddress | undefined> {\n\tconst parsed = new URL(url);\n\tif (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") throw new Error(`Unsupported URL protocol: ${parsed.protocol}`);\n\tif (process.env.ATOMIC_ALLOW_PRIVATE_URL_READS === \"1\") return undefined;\n\tconst hostname = parsed.hostname.replace(/^\\[|\\]$/g, \"\").replace(/\\.$/, \"\").toLowerCase();\n\tif (hostname === \"localhost\" || hostname.endsWith(\".localhost\") || hostname === \"metadata.google.internal\") throw new Error(`Refusing to fetch private or metadata URL: ${url}`);\n\tconst literalAddress = normalizeIpLiteralHost(hostname);\n\tif (literalAddress) { if (isPrivateIpAddress(literalAddress)) throw new Error(`Refusing to fetch private or metadata URL: ${url}`); return { address: literalAddress, family: ipFamily(literalAddress), pinned: false }; }\n\tconst addresses = await lookup(hostname, { all: true }).catch((error: unknown) => { throw new Error(`Could not resolve URL host ${hostname}: ${error instanceof Error ? error.message : String(error)}`); });\n\tif (addresses.length === 0) throw new Error(`Could not resolve URL host ${hostname}: no addresses returned`);\n\tfor (const address of addresses) if (isPrivateIpAddress(address.address)) throw new Error(`Refusing to fetch private or metadata URL: ${url}`);\n\tconst first = addresses[0]!;\n\treturn { address: first.address, family: first.family === 6 ? 6 : 4, pinned: true };\n}\n\nasync function readResponseTextWithLimit(response: Response): Promise<string> {\n\tif (!response.body) { const buffer = Buffer.from(await response.arrayBuffer()); if (buffer.length > MAX_URL_RESPONSE_BYTES) throw new Error(`URL response exceeds ${MAX_URL_RESPONSE_BYTES} byte limit`); return buffer.toString(\"utf8\"); }\n\tconst reader = response.body.getReader();\n\tconst decoder = new TextDecoder();\n\tconst chunks: string[] = [];\n\tlet totalBytes = 0;\n\tfor (;;) {\n\t\tconst { done, value } = await reader.read();\n\t\tif (done) break;\n\t\ttotalBytes += value.byteLength;\n\t\tif (totalBytes > MAX_URL_RESPONSE_BYTES) { await reader.cancel(); throw new Error(`URL response exceeds ${MAX_URL_RESPONSE_BYTES} byte limit`); }\n\t\tchunks.push(decoder.decode(value, { stream: true }));\n\t}\n\tchunks.push(decoder.decode());\n\treturn chunks.join(\"\");\n}\nexport async function loadPage(url: string, timeoutMs: number, signal?: AbortSignal, accept?: string): Promise<LoadedPage> {\n\turl = normalizeUrlForCache(url);\n\tconst controller = new AbortController();\n\tconst timer = setTimeout(() => controller.abort(), Math.min(timeoutMs, REMOTE_FETCH_TIMEOUT_MS));\n\tconst onParentAbort = () => controller.abort();\n\tsignal?.addEventListener(\"abort\", onParentAbort, { once: true });\n\ttry {\n\t\tlet currentUrl = url;\n\t\tfor (let redirects = 0; redirects <= MAX_URL_REDIRECTS; redirects++) {\n\t\t\tconst pinned = await assertSafeFetchUrl(currentUrl);\n\t\t\t// Only a DNS-resolved hostname needs pinning: a literal-IP host is\n\t\t\t// validated directly and cannot be re-resolved at connect time, so\n\t\t\t// global fetch is sufficient and stays redirect/mock-friendly.\n\t\t\tconst dispatcher = pinned?.pinned ? createPinnedDispatcher(pinned) : undefined;\n\t\t\ttry {\n\t\t\t\tconst headers = accept ? { Accept: accept } : undefined;\n\t\t\t\t// Route pinned (hostname) fetches through undici directly so the\n\t\t\t\t// dispatcher's custom lookup is honored even under Bun's compiled\n\t\t\t\t// fetch, which silently ignores the `dispatcher` option.\n\t\t\t\tconst response = dispatcher\n\t\t\t\t\t? await pinnedFetch(currentUrl, dispatcher, { signal: controller.signal, headers })\n\t\t\t\t\t: await fetch(currentUrl, { signal: controller.signal, headers, redirect: \"manual\" });\n\t\t\t\tif (response.status >= 300 && response.status < 400 && response.headers.get(\"location\")) { await response.body?.cancel().catch(() => {}); currentUrl = new URL(response.headers.get(\"location\")!, currentUrl).href; continue; }\n\t\t\t\tconst contentLength = Number.parseInt(response.headers.get(\"content-length\") ?? \"0\", 10);\n\t\t\t\tif (contentLength > MAX_URL_RESPONSE_BYTES) throw new Error(`URL response exceeds ${MAX_URL_RESPONSE_BYTES} byte limit: ${currentUrl}`);\n\t\t\t\tconst contentType = response.headers.get(\"content-type\") ?? \"\";\n\t\t\t\tconst content = await readResponseTextWithLimit(response);\n\t\t\t\treturn { ok: response.ok, status: response.status, content, contentType, finalUrl: response.url || currentUrl };\n\t\t\t} finally {\n\t\t\t\tawait closePinnedDispatcher(dispatcher).catch(() => undefined);\n\t\t\t}\n\t\t}\n\t\tthrow new Error(`Too many redirects while fetching URL: ${url}`);\n\t} finally {\n\t\tclearTimeout(timer);\n\t\tsignal?.removeEventListener(\"abort\", onParentAbort);\n\t}\n}\n\nfunction isLowQuality(content: string): boolean {\n\tconst lower = content.toLowerCase();\n\tif (/(enable javascript|javascript required|turn on javascript|please enable javascript|browser not supported)/.test(lower) && content.trim().length < 2000) return true;\n\tconst nonblank = content.split(\"\\n\").filter((line) => line.trim().length > 0);\n\tif (nonblank.length > 10 && nonblank.filter((line) => line.trim().length < 40).length / nonblank.length > 0.7) return true;\n\treturn false;\n}\n\nfunction buildLlmEndpointCandidates(url: string): string[] {\n\tconst parsed = (() => { try { return new URL(url); } catch { return undefined; } })();\n\tif (!parsed) return [];\n\tconst origin = parsed.origin;\n\tif (parsed.pathname === \"/\" || parsed.pathname === \"\") return [`${origin}/.well-known/llms.txt`, `${origin}/llms.txt`, `${origin}/llms.md`];\n\tconst trimmed = parsed.pathname.replace(/\\/+$/, \"\");\n\tconst segments = trimmed.split(\"/\").filter(Boolean);\n\tconst candidates: string[] = [];\n\tconst depth = parsed.pathname.endsWith(\"/\") ? segments.length : Math.max(segments.length - 1, 1);\n\tfor (let scope = Math.min(depth, segments.length); scope >= 1; scope--) {\n\t\tconst base = `${origin}/${segments.slice(0, scope).join(\"/\")}`;\n\t\tcandidates.push(`${base}/llms.txt`, `${base}/llms.md`);\n\t}\n\tcandidates.push(`${origin}/llms.txt`, `${origin}/llms.md`);\n\treturn [...new Set(candidates)];\n}\n\nasync function tryLlmEndpoints(url: string, signal?: AbortSignal): Promise<{ content: string; endpoint: string } | null> {\n\tfor (const endpoint of buildLlmEndpointCandidates(url)) {\n\t\ttry {\n\t\t\tconst page = await loadPage(endpoint, 5000, signal);\n\t\t\tif (page.ok && page.content.trim().length > MIN_CONTENT_LENGTH && !looksLikeHtml(page.content)) return { content: page.content, endpoint };\n\t\t} catch {}\n\t}\n\treturn null;\n}\n\nfunction parseAlternateMarkdownLink(html: string, pageUrl: string): string | undefined {\n\tconst headEnd = html.toLowerCase().indexOf(\"</head>\");\n\tconst head = html.slice(0, headEnd >= 0 ? headEnd + 7 : html.length);\n\tconst links = [...head.matchAll(/<link\\b[^>]*>/gi)].map((m) => m[0] ?? \"\");\n\tfor (const tag of links) {\n\t\tconst rel = /rel=[\"']?([^\"'>]+)[\"']?/i.exec(tag)?.[1] ?? \"\";\n\t\tconst type = /type=[\"']?([^\"'>]+)[\"']?/i.exec(tag)?.[1] ?? \"\";\n\t\tconst href = /href=[\"']?([^\"'>]+)[\"']?/i.exec(tag)?.[1] ?? \"\";\n\t\tif (!/alternate/i.test(rel) || !href) continue;\n\t\tif (/RecentChanges|Special:|\\/feed\\/|action=feed/i.test(href)) continue;\n\t\tif (type.includes(\"markdown\") || href.endsWith(\".md\")) {\n\t\t\ttry { return new URL(href, pageUrl).href; } catch {}\n\t\t}\n\t}\n\treturn undefined;\n}\n\nasync function tryMdSuffix(url: string, signal?: AbortSignal): Promise<string | null> {\n\tconst candidates: string[] = [];\n\tif (/\\/$/.test(url)) candidates.push(`${url}index.html.md`);\n\telse if (/\\.\\w+\\/?$/.test(url)) candidates.push(`${url}.md`);\n\telse candidates.push(`${url}.md`, `${url}/index.html.md`);\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\tconst page = await loadPage(candidate, 5000, signal);\n\t\t\tif (page.ok && page.content.trim().length > MIN_CONTENT_LENGTH && !looksLikeHtml(page.content)) return page.content;\n\t\t} catch {}\n\t}\n\treturn null;\n}\n\nasync function tryContentNegotiation(url: string, signal?: AbortSignal): Promise<string | null> {\n\ttry {\n\t\tconst page = await loadPage(url, 5000, signal, \"text/markdown, text/plain;q=0.9, text/html;q=0.8\");\n\t\tconst mime = page.contentType.toLowerCase();\n\t\tif (page.ok && page.content.trim().length > MIN_CONTENT_LENGTH && !looksLikeHtml(page.content) && (mime.includes(\"markdown\") || mime.includes(\"text/plain\"))) return page.content;\n\t} catch {}\n\treturn null;\n}\n\nexport interface RenderedUrl {\n\tcontent: string;\n\tfinalUrl: string;\n\tcontentType: string;\n\tmethod: string;\n\tnotes: string[];\n\ttruncated: boolean;\n}\n\nexport async function renderUrl(url: string, raw: boolean, signal?: AbortSignal): Promise<RenderedUrl> {\n\tconst notes: string[] = [];\n\tlet method = \"text\";\n\tlet contentType = \"\";\n\tlet finalUrl: string;\n\tlet body = \"\";\n\tconst page = await loadPage(url, REMOTE_FETCH_TIMEOUT_MS, signal);\n\tfinalUrl = page.finalUrl;\n\tcontentType = page.contentType;\n\tif (!page.ok) return { content: page.content || `(request failed with status ${page.status})`, finalUrl, contentType, method: \"failed\", notes, truncated: false };\n\tif (raw) return { content: page.content, finalUrl, contentType, method: \"raw\", notes, truncated: false };\n\tconst buffer = Buffer.from(page.content, \"utf8\");\n\tconst docExt = documentExtensionFromContentType(contentType) ?? (isDocumentPath(finalUrl) ? (finalUrl.match(/\\.\\w+(?:$|[?#])/)?.[0] ?? \"\") : \"\");\n\tif (docExt) {\n\t\ttry { body = await extractDocumentMarkdown(buffer, `${finalUrl}${docExt}`); method = \"document\"; }\n\t\tcatch (error) { body = `[Cannot read ${docExt} file: ${error instanceof Error ? error.message : \"conversion failed\"}]`; method = \"document-error\"; }\n\t\treturn { content: body, finalUrl, contentType, method, notes, truncated: false };\n\t}\n\tconst isHtml = /html/i.test(contentType) || looksLikeHtml(page.content);\n\tif (isHtml) {\n\t\tconst altMd = parseAlternateMarkdownLink(page.content, finalUrl);\n\t\tif (altMd) { try { const alt = await loadPage(altMd, 5000, signal); if (alt.ok && alt.content.trim().length > MIN_CONTENT_LENGTH && !looksLikeHtml(alt.content)) { return { content: alt.content, finalUrl: alt.finalUrl, contentType: alt.contentType, method: \"alternate-markdown\", notes, truncated: false }; } } catch {} notes.push(\"alternate markdown link unusable\"); }\n\t\tconst md = await tryMdSuffix(finalUrl, signal); if (md) return { content: md, finalUrl, contentType: \"text/markdown\", method: \"md-suffix\", notes, truncated: false };\n\t\tconst neg = await tryContentNegotiation(finalUrl, signal); if (neg) return { content: neg, finalUrl, contentType: \"text/markdown\", method: \"content-negotiation\", notes, truncated: false };\n\t\tlet rendered = getTurndown().turndown(page.content).trim();\n\t\tmethod = \"native\";\n\t\tif (rendered.length <= MIN_CONTENT_LENGTH || isLowQuality(rendered)) {\n\t\t\tconst llms = await tryLlmEndpoints(finalUrl, signal);\n\t\t\tif (llms) { return { content: llms.content, finalUrl, contentType: \"text/plain\", method: `llms-txt (${llms.endpoint})`, notes, truncated: false }; }\n\t\t\tnotes.push(\"rendered content low quality; no markdown source discovered\");\n\t\t}\n\t\tbody = rendered;\n\t} else {\n\t\tbody = page.content;\n\t}\n\treturn { content: body, finalUrl, contentType, method, notes, truncated: false };\n}\n\nexport function buildUrlReadOutput(result: RenderedUrl): string {\n\tconst header = [`URL: ${result.finalUrl}`, `Content-Type: ${result.contentType}`, `Method: ${result.method}`, ...(result.notes.length ? [`Notes: ${result.notes.join(\"; \")}`] : [])].join(\"\\n\");\n\treturn `${header}\\n\\n---\\n\\n${result.content}`;\n}\n\nfunction persistReadUrlArtifact(artifactsDir: string | undefined, output: string): string | undefined {\n\tif (!artifactsDir) return undefined;\n\treturn getArtifactManager(artifactsDir).save(output, \"read\");\n}\n\nexport interface ExecuteReadUrlResult {\n\tcontent: string;\n\tdetails: ReadUrlToolDetails;\n\tartifactId?: string;\n}\n\nexport interface LoadedReadUrl {\n\toutput: string;\n\tdetails: ReadUrlToolDetails;\n\tartifactId?: string;\n\traw: boolean;\n}\n\n/**\n * Resolve a URL to its full rendered (or raw) output, using the session-scoped\n * cache and persisting the rendered body as a `read` artifact. Returns the\n * complete untruncated output so callers can apply their own line selection.\n */\nexport async function loadReadUrlOutput(scope: string, params: { path: string; raw?: boolean }, artifactsDir: string | undefined, signal?: AbortSignal): Promise<LoadedReadUrl> {\n\tconst parsed = parseReadUrlTarget(params.path);\n\tif (!parsed) throw new Error(`Not a readable URL: ${params.path}`);\n\tconst raw = params.raw ?? parsed.raw;\n\tconst key = getReadUrlCacheKey(scope, parsed.url, raw);\n\tlet entry = readUrlCache.get(key);\n\tif (!entry) {\n\t\tconst rendered = await renderUrl(parsed.url, raw, signal);\n\t\tconst output = raw ? rendered.content : buildUrlReadOutput(rendered);\n\t\tconst artifactId = persistReadUrlArtifact(artifactsDir, output);\n\t\tconst details: ReadUrlToolDetails = { url: parsed.url, finalUrl: rendered.finalUrl, contentType: rendered.contentType, method: rendered.method, truncated: rendered.truncated, notes: rendered.notes, meta: artifactId ? { artifactId } : undefined };\n\t\tentry = { artifactId, details, output };\n\t\treadUrlCache.set(key, entry);\n\t\treadUrlCache.set(getReadUrlCacheKey(scope, rendered.finalUrl, raw), entry);\n\t}\n\treturn { output: entry.output, details: entry.details, artifactId: entry.artifactId, raw };\n}\n\nexport async function executeReadUrl(scope: string, params: { path: string; raw?: boolean }, artifactsDir: string | undefined, signal?: AbortSignal): Promise<ExecuteReadUrlResult> {\n\tconst loaded = await loadReadUrlOutput(scope, params, artifactsDir, signal);\n\tconst entry = { output: loaded.output, details: loaded.details, artifactId: loaded.artifactId };\n\tconst fetchDefaultBytes = 50 * 1024;\n\tconst lines = entry.output.split(\"\\n\");\n\tconst visibleLines = lines.slice(0, FETCH_DEFAULT_MAX_LINES);\n\tlet content = visibleLines.join(\"\\n\");\n\tconst bytesBeforeByteClamp = Buffer.byteLength(content, \"utf8\");\n\tconst byteTruncated = bytesBeforeByteClamp > fetchDefaultBytes;\n\tconst lineTruncated = lines.length > FETCH_DEFAULT_MAX_LINES;\n\tif (byteTruncated) content = content.slice(0, fetchDefaultBytes);\n\tconst truncated = lineTruncated || byteTruncated;\n\tlet artifactId = entry.artifactId;\n\tif (truncated && !artifactId) artifactId = persistReadUrlArtifact(artifactsDir, entry.output);\n\tconst outputLines = content.length === 0 ? 0 : content.split(\"\\n\").length;\n\tconst truncation: TruncationResult | undefined = truncated ? {\n\t\tcontent,\n\t\ttruncated: true,\n\t\ttruncatedBy: byteTruncated ? \"bytes\" : \"lines\",\n\t\ttotalLines: lines.length,\n\t\ttotalBytes: Buffer.byteLength(entry.output, \"utf8\"),\n\t\toutputLines,\n\t\toutputBytes: Buffer.byteLength(content, \"utf8\"),\n\t\tlastLinePartial: byteTruncated,\n\t\tfirstLineExceedsLimit: false,\n\t\tmaxLines: FETCH_DEFAULT_MAX_LINES,\n\t\tmaxBytes: fetchDefaultBytes,\n\t} : undefined;\n\tconst details: ReadUrlToolDetails = { ...entry.details, truncated, meta: { ...(entry.details.meta ?? {}), ...(artifactId ? { artifactId } : {}), ...(truncation ? { truncation } : {}) } };\n\tif (truncated) content += `\\n\\n[Showing first ${visibleLines.length} of ${lines.length} lines.${artifactId ? ` Full output: artifact://${artifactId}` : \"\"}]`;\n\treturn { content, details, artifactId };\n}\n\nexport function resetReadUrlCache(): void {\n\treadUrlCache.clear();\n}\n"]}
@@ -0,0 +1,518 @@
1
+ /// <reference path="../../utils/turndown.d.ts" />
2
+ /**
3
+ * URL fetch / cache / artifact / markdown-discovery pipeline, mirrored from
4
+ * oh-my-pi's `packages/coding-agent/src/tools/fetch.ts` at 15b5c1397fc.
5
+ *
6
+ * Responsibilities:
7
+ * - parseReadUrlTarget: URL + line selectors (:raw, :N, :A-B, :A+B, :A-B,C-D),
8
+ * collapsed-scheme repair, host-port protection.
9
+ * - session-scoped LRU cache keyed by `<scope>::<raw|rendered>::<normalizedUrl>`,
10
+ * cached under both requested and final (redirected) URLs.
11
+ * - renderUrl: markdown discovery (alternate <link>, .md suffix, content
12
+ * negotiation, llms.txt/llms.md endpoints) + native HTML-to-markdown, with
13
+ * quality gating.
14
+ * - artifact persistence of the rendered output, with artifactId surfaced in
15
+ * truncation metadata when the visible output is head-truncated.
16
+ */
17
+ import { lookup } from "node:dns/promises";
18
+ import { ipFamily, isPrivateIpAddress, normalizeIpLiteralHost } from "./url-ip-guards.js";
19
+ import { LRUCache } from "lru-cache";
20
+ import TurndownService from "turndown";
21
+ import { Agent, request as undiciRequest } from "undici";
22
+ import { getArtifactManager } from "./artifacts.js";
23
+ import { extractDocumentMarkdown, isDocumentPath } from "./read-document-extract.js";
24
+ export const FETCH_DEFAULT_MAX_LINES = 300;
25
+ export const READ_URL_CACHE_MAX_ENTRIES = 100;
26
+ const REMOTE_FETCH_TIMEOUT_MS = 10_000;
27
+ const MAX_URL_REDIRECTS = 5;
28
+ const MAX_URL_RESPONSE_BYTES = 10 * 1024 * 1024;
29
+ const MIN_CONTENT_LENGTH = 100;
30
+ const readUrlCache = new LRUCache({ max: READ_URL_CACHE_MAX_ENTRIES });
31
+ let turndown;
32
+ function getTurndown() {
33
+ if (!turndown)
34
+ turndown = new TurndownService({ headingStyle: "atx", codeBlockStyle: "fenced", bulletListMarker: "-" });
35
+ return turndown;
36
+ }
37
+ export function repairCollapsedScheme(url) {
38
+ return url.replace(/^(https?):\/([^/])/i, "$1://$2");
39
+ }
40
+ export function isReadableUrlPath(readPath) {
41
+ const repaired = repairCollapsedScheme(readPath);
42
+ return /^https?:\/\//i.test(repaired) || /^www\./i.test(readPath);
43
+ }
44
+ function normalizeUrlForCache(url) {
45
+ const repaired = repairCollapsedScheme(url);
46
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(repaired))
47
+ return repaired;
48
+ return /^https?:\/\//i.test(repaired) ? repaired : `https://${repaired}`;
49
+ }
50
+ const RANGE_TOKEN_RE = /^(raw|\d+(?:\+(?:\d+)|(?:-|\.\.)\d+)?)$/i;
51
+ const MULTI_RANGE_RE = /^\d+(?:(?:-|\.\.|,|\+)\d+)*$/;
52
+ function isUrlSelectorToken(token) {
53
+ return /^raw$/i.test(token) || RANGE_TOKEN_RE.test(token) || MULTI_RANGE_RE.test(token);
54
+ }
55
+ function parseSingleRange(token) {
56
+ const plus = token.match(/^(\d+)\+(\d+)$/);
57
+ if (plus) {
58
+ const start = Number.parseInt(plus[1], 10), count = Number.parseInt(plus[2], 10);
59
+ return start < 1 || count < 1 ? undefined : { start, end: start + count - 1 };
60
+ }
61
+ const range = token.match(/^(\d+)(?:-|\.\.)(\d+)$/);
62
+ if (range) {
63
+ const start = Number.parseInt(range[1], 10);
64
+ const end = Number.parseInt(range[2], 10);
65
+ return start < 1 || end < start ? undefined : { start, end };
66
+ }
67
+ const single = token.match(/^(\d+)$/);
68
+ if (single) {
69
+ const start = Number.parseInt(single[1], 10);
70
+ return start < 1 ? undefined : { start };
71
+ }
72
+ return undefined;
73
+ }
74
+ function parseRangeGroup(token) {
75
+ const groups = token.split(",");
76
+ const ranges = [];
77
+ for (const group of groups) {
78
+ const r = parseSingleRange(group);
79
+ if (!r)
80
+ return undefined;
81
+ ranges.push(r);
82
+ }
83
+ return ranges;
84
+ }
85
+ function tryExtractEmbeddedUrlSelector(readPath) {
86
+ let basePath = readPath;
87
+ const sels = [];
88
+ for (;;) {
89
+ const lastColonIndex = basePath.lastIndexOf(":");
90
+ if (lastColonIndex <= 0)
91
+ break;
92
+ const candidate = basePath.slice(lastColonIndex + 1);
93
+ const remainder = basePath.slice(0, lastColonIndex);
94
+ if (!isReadableUrlPath(remainder) || !isUrlSelectorToken(candidate))
95
+ break;
96
+ try {
97
+ new URL(remainder.startsWith("http://") || remainder.startsWith("https://") ? remainder : `https://${remainder}`);
98
+ }
99
+ catch {
100
+ break;
101
+ }
102
+ sels.unshift(candidate);
103
+ basePath = remainder;
104
+ }
105
+ return sels.length === 0 ? null : { path: basePath, sels };
106
+ }
107
+ export function parseReadUrlTarget(readPath) {
108
+ const repaired = repairCollapsedScheme(readPath);
109
+ const embedded = tryExtractEmbeddedUrlSelector(repaired);
110
+ const urlPath = embedded?.path ?? repaired;
111
+ if (!isReadableUrlPath(urlPath))
112
+ return null;
113
+ let raw = false;
114
+ let ranges;
115
+ for (const sel of embedded?.sels ?? []) {
116
+ if (/^raw$/i.test(sel)) {
117
+ raw = true;
118
+ continue;
119
+ }
120
+ if (ranges !== undefined)
121
+ throw new Error("URL selector has multiple range groups; combine them with commas (e.g. `:5-10,20-30`).");
122
+ const parsed = parseRangeGroup(sel);
123
+ if (!parsed)
124
+ throw new Error(`Invalid URL line selector: ${sel}`);
125
+ ranges = parsed;
126
+ }
127
+ if (!ranges || ranges.length === 0)
128
+ return { url: urlPath, raw };
129
+ if (ranges.length === 1) {
130
+ const r = ranges[0];
131
+ return { url: urlPath, raw, offset: r.start, limit: r.end !== undefined ? r.end - r.start + 1 : undefined };
132
+ }
133
+ return { url: urlPath, raw, ranges };
134
+ }
135
+ export function getReadUrlCacheKey(scope, requestedUrl, raw) {
136
+ return `${scope}::${raw ? "raw" : "rendered"}::${normalizeUrlForCache(requestedUrl)}`;
137
+ }
138
+ function looksLikeHtml(text) {
139
+ return /<html[\s>]/i.test(text) || (/<head[\s>]/i.test(text) && /<body[\s>]/i.test(text));
140
+ }
141
+ function documentExtensionFromContentType(contentType) {
142
+ if (/pdf/i.test(contentType))
143
+ return ".pdf";
144
+ if (/msword/i.test(contentType))
145
+ return ".doc";
146
+ if (/wordprocessingml|officedocument\.word/i.test(contentType))
147
+ return ".docx";
148
+ if (/presentationml|officedocument\.presentation/i.test(contentType))
149
+ return ".pptx";
150
+ if (/ms-powerpoint|vnd\.ms-powerpoint/i.test(contentType))
151
+ return ".ppt";
152
+ if (/spreadsheetml|officedocument\.spreadsheet/i.test(contentType))
153
+ return ".xlsx";
154
+ if (/ms-excel|vnd\.ms-excel/i.test(contentType))
155
+ return ".xls";
156
+ if (/epub/i.test(contentType))
157
+ return ".epub";
158
+ if (/\bjson\b|ipynb|jupyter/i.test(contentType))
159
+ return ".ipynb";
160
+ if (/rtf/i.test(contentType))
161
+ return ".rtf";
162
+ return undefined;
163
+ }
164
+ function createPinnedDispatcher(pinned) {
165
+ return new Agent({ connect: { lookup: (_hostname, options, callback) => { if (options.all)
166
+ callback(null, [pinned]);
167
+ else
168
+ callback(null, pinned.address, pinned.family); } } });
169
+ }
170
+ async function closePinnedDispatcher(dispatcher) {
171
+ const close = dispatcher?.close;
172
+ if (typeof close === "function")
173
+ await close.call(dispatcher);
174
+ }
175
+ /**
176
+ * Fetch a URL through undici with the pinned dispatcher so the validated
177
+ * address is the one actually connected to. Unlike `globalThis.fetch`, which
178
+ * silently ignores the undici `dispatcher` option under Bun's compiled binary
179
+ * (reopening a DNS-rebinding TOCTOU window for hostname targets), undici's own
180
+ * client honors the dispatcher's custom `lookup` regardless of host runtime.
181
+ * Adapts the undici response to the WHATWG-Response shape loadPage consumes.
182
+ */
183
+ async function pinnedFetch(url, dispatcher, options) {
184
+ const result = await undiciRequest(url, { method: "GET", dispatcher, signal: options.signal, headers: options.headers });
185
+ const body = result.body;
186
+ const status = result.statusCode;
187
+ const headers = new Headers();
188
+ for (const [key, value] of Object.entries(result.headers)) {
189
+ if (value === undefined)
190
+ continue;
191
+ const values = Array.isArray(value) ? value : [value];
192
+ for (const entry of values)
193
+ headers.append(key, entry);
194
+ }
195
+ const stream = new ReadableStream({
196
+ start(controller) { body.on("data", (chunk) => controller.enqueue(new Uint8Array(chunk))); body.on("end", () => controller.close()); body.on("error", (error) => controller.error(error)); },
197
+ cancel() { body.destroy(); },
198
+ });
199
+ return new Response(stream, { status, headers });
200
+ }
201
+ async function assertSafeFetchUrl(url) {
202
+ const parsed = new URL(url);
203
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
204
+ throw new Error(`Unsupported URL protocol: ${parsed.protocol}`);
205
+ if (process.env.ATOMIC_ALLOW_PRIVATE_URL_READS === "1")
206
+ return undefined;
207
+ const hostname = parsed.hostname.replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
208
+ if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "metadata.google.internal")
209
+ throw new Error(`Refusing to fetch private or metadata URL: ${url}`);
210
+ const literalAddress = normalizeIpLiteralHost(hostname);
211
+ if (literalAddress) {
212
+ if (isPrivateIpAddress(literalAddress))
213
+ throw new Error(`Refusing to fetch private or metadata URL: ${url}`);
214
+ return { address: literalAddress, family: ipFamily(literalAddress), pinned: false };
215
+ }
216
+ const addresses = await lookup(hostname, { all: true }).catch((error) => { throw new Error(`Could not resolve URL host ${hostname}: ${error instanceof Error ? error.message : String(error)}`); });
217
+ if (addresses.length === 0)
218
+ throw new Error(`Could not resolve URL host ${hostname}: no addresses returned`);
219
+ for (const address of addresses)
220
+ if (isPrivateIpAddress(address.address))
221
+ throw new Error(`Refusing to fetch private or metadata URL: ${url}`);
222
+ const first = addresses[0];
223
+ return { address: first.address, family: first.family === 6 ? 6 : 4, pinned: true };
224
+ }
225
+ async function readResponseTextWithLimit(response) {
226
+ if (!response.body) {
227
+ const buffer = Buffer.from(await response.arrayBuffer());
228
+ if (buffer.length > MAX_URL_RESPONSE_BYTES)
229
+ throw new Error(`URL response exceeds ${MAX_URL_RESPONSE_BYTES} byte limit`);
230
+ return buffer.toString("utf8");
231
+ }
232
+ const reader = response.body.getReader();
233
+ const decoder = new TextDecoder();
234
+ const chunks = [];
235
+ let totalBytes = 0;
236
+ for (;;) {
237
+ const { done, value } = await reader.read();
238
+ if (done)
239
+ break;
240
+ totalBytes += value.byteLength;
241
+ if (totalBytes > MAX_URL_RESPONSE_BYTES) {
242
+ await reader.cancel();
243
+ throw new Error(`URL response exceeds ${MAX_URL_RESPONSE_BYTES} byte limit`);
244
+ }
245
+ chunks.push(decoder.decode(value, { stream: true }));
246
+ }
247
+ chunks.push(decoder.decode());
248
+ return chunks.join("");
249
+ }
250
+ export async function loadPage(url, timeoutMs, signal, accept) {
251
+ url = normalizeUrlForCache(url);
252
+ const controller = new AbortController();
253
+ const timer = setTimeout(() => controller.abort(), Math.min(timeoutMs, REMOTE_FETCH_TIMEOUT_MS));
254
+ const onParentAbort = () => controller.abort();
255
+ signal?.addEventListener("abort", onParentAbort, { once: true });
256
+ try {
257
+ let currentUrl = url;
258
+ for (let redirects = 0; redirects <= MAX_URL_REDIRECTS; redirects++) {
259
+ const pinned = await assertSafeFetchUrl(currentUrl);
260
+ // Only a DNS-resolved hostname needs pinning: a literal-IP host is
261
+ // validated directly and cannot be re-resolved at connect time, so
262
+ // global fetch is sufficient and stays redirect/mock-friendly.
263
+ const dispatcher = pinned?.pinned ? createPinnedDispatcher(pinned) : undefined;
264
+ try {
265
+ const headers = accept ? { Accept: accept } : undefined;
266
+ // Route pinned (hostname) fetches through undici directly so the
267
+ // dispatcher's custom lookup is honored even under Bun's compiled
268
+ // fetch, which silently ignores the `dispatcher` option.
269
+ const response = dispatcher
270
+ ? await pinnedFetch(currentUrl, dispatcher, { signal: controller.signal, headers })
271
+ : await fetch(currentUrl, { signal: controller.signal, headers, redirect: "manual" });
272
+ if (response.status >= 300 && response.status < 400 && response.headers.get("location")) {
273
+ await response.body?.cancel().catch(() => { });
274
+ currentUrl = new URL(response.headers.get("location"), currentUrl).href;
275
+ continue;
276
+ }
277
+ const contentLength = Number.parseInt(response.headers.get("content-length") ?? "0", 10);
278
+ if (contentLength > MAX_URL_RESPONSE_BYTES)
279
+ throw new Error(`URL response exceeds ${MAX_URL_RESPONSE_BYTES} byte limit: ${currentUrl}`);
280
+ const contentType = response.headers.get("content-type") ?? "";
281
+ const content = await readResponseTextWithLimit(response);
282
+ return { ok: response.ok, status: response.status, content, contentType, finalUrl: response.url || currentUrl };
283
+ }
284
+ finally {
285
+ await closePinnedDispatcher(dispatcher).catch(() => undefined);
286
+ }
287
+ }
288
+ throw new Error(`Too many redirects while fetching URL: ${url}`);
289
+ }
290
+ finally {
291
+ clearTimeout(timer);
292
+ signal?.removeEventListener("abort", onParentAbort);
293
+ }
294
+ }
295
+ function isLowQuality(content) {
296
+ const lower = content.toLowerCase();
297
+ if (/(enable javascript|javascript required|turn on javascript|please enable javascript|browser not supported)/.test(lower) && content.trim().length < 2000)
298
+ return true;
299
+ const nonblank = content.split("\n").filter((line) => line.trim().length > 0);
300
+ if (nonblank.length > 10 && nonblank.filter((line) => line.trim().length < 40).length / nonblank.length > 0.7)
301
+ return true;
302
+ return false;
303
+ }
304
+ function buildLlmEndpointCandidates(url) {
305
+ const parsed = (() => { try {
306
+ return new URL(url);
307
+ }
308
+ catch {
309
+ return undefined;
310
+ } })();
311
+ if (!parsed)
312
+ return [];
313
+ const origin = parsed.origin;
314
+ if (parsed.pathname === "/" || parsed.pathname === "")
315
+ return [`${origin}/.well-known/llms.txt`, `${origin}/llms.txt`, `${origin}/llms.md`];
316
+ const trimmed = parsed.pathname.replace(/\/+$/, "");
317
+ const segments = trimmed.split("/").filter(Boolean);
318
+ const candidates = [];
319
+ const depth = parsed.pathname.endsWith("/") ? segments.length : Math.max(segments.length - 1, 1);
320
+ for (let scope = Math.min(depth, segments.length); scope >= 1; scope--) {
321
+ const base = `${origin}/${segments.slice(0, scope).join("/")}`;
322
+ candidates.push(`${base}/llms.txt`, `${base}/llms.md`);
323
+ }
324
+ candidates.push(`${origin}/llms.txt`, `${origin}/llms.md`);
325
+ return [...new Set(candidates)];
326
+ }
327
+ async function tryLlmEndpoints(url, signal) {
328
+ for (const endpoint of buildLlmEndpointCandidates(url)) {
329
+ try {
330
+ const page = await loadPage(endpoint, 5000, signal);
331
+ if (page.ok && page.content.trim().length > MIN_CONTENT_LENGTH && !looksLikeHtml(page.content))
332
+ return { content: page.content, endpoint };
333
+ }
334
+ catch { }
335
+ }
336
+ return null;
337
+ }
338
+ function parseAlternateMarkdownLink(html, pageUrl) {
339
+ const headEnd = html.toLowerCase().indexOf("</head>");
340
+ const head = html.slice(0, headEnd >= 0 ? headEnd + 7 : html.length);
341
+ const links = [...head.matchAll(/<link\b[^>]*>/gi)].map((m) => m[0] ?? "");
342
+ for (const tag of links) {
343
+ const rel = /rel=["']?([^"'>]+)["']?/i.exec(tag)?.[1] ?? "";
344
+ const type = /type=["']?([^"'>]+)["']?/i.exec(tag)?.[1] ?? "";
345
+ const href = /href=["']?([^"'>]+)["']?/i.exec(tag)?.[1] ?? "";
346
+ if (!/alternate/i.test(rel) || !href)
347
+ continue;
348
+ if (/RecentChanges|Special:|\/feed\/|action=feed/i.test(href))
349
+ continue;
350
+ if (type.includes("markdown") || href.endsWith(".md")) {
351
+ try {
352
+ return new URL(href, pageUrl).href;
353
+ }
354
+ catch { }
355
+ }
356
+ }
357
+ return undefined;
358
+ }
359
+ async function tryMdSuffix(url, signal) {
360
+ const candidates = [];
361
+ if (/\/$/.test(url))
362
+ candidates.push(`${url}index.html.md`);
363
+ else if (/\.\w+\/?$/.test(url))
364
+ candidates.push(`${url}.md`);
365
+ else
366
+ candidates.push(`${url}.md`, `${url}/index.html.md`);
367
+ for (const candidate of candidates) {
368
+ try {
369
+ const page = await loadPage(candidate, 5000, signal);
370
+ if (page.ok && page.content.trim().length > MIN_CONTENT_LENGTH && !looksLikeHtml(page.content))
371
+ return page.content;
372
+ }
373
+ catch { }
374
+ }
375
+ return null;
376
+ }
377
+ async function tryContentNegotiation(url, signal) {
378
+ try {
379
+ const page = await loadPage(url, 5000, signal, "text/markdown, text/plain;q=0.9, text/html;q=0.8");
380
+ const mime = page.contentType.toLowerCase();
381
+ if (page.ok && page.content.trim().length > MIN_CONTENT_LENGTH && !looksLikeHtml(page.content) && (mime.includes("markdown") || mime.includes("text/plain")))
382
+ return page.content;
383
+ }
384
+ catch { }
385
+ return null;
386
+ }
387
+ export async function renderUrl(url, raw, signal) {
388
+ const notes = [];
389
+ let method = "text";
390
+ let contentType = "";
391
+ let finalUrl;
392
+ let body = "";
393
+ const page = await loadPage(url, REMOTE_FETCH_TIMEOUT_MS, signal);
394
+ finalUrl = page.finalUrl;
395
+ contentType = page.contentType;
396
+ if (!page.ok)
397
+ return { content: page.content || `(request failed with status ${page.status})`, finalUrl, contentType, method: "failed", notes, truncated: false };
398
+ if (raw)
399
+ return { content: page.content, finalUrl, contentType, method: "raw", notes, truncated: false };
400
+ const buffer = Buffer.from(page.content, "utf8");
401
+ const docExt = documentExtensionFromContentType(contentType) ?? (isDocumentPath(finalUrl) ? (finalUrl.match(/\.\w+(?:$|[?#])/)?.[0] ?? "") : "");
402
+ if (docExt) {
403
+ try {
404
+ body = await extractDocumentMarkdown(buffer, `${finalUrl}${docExt}`);
405
+ method = "document";
406
+ }
407
+ catch (error) {
408
+ body = `[Cannot read ${docExt} file: ${error instanceof Error ? error.message : "conversion failed"}]`;
409
+ method = "document-error";
410
+ }
411
+ return { content: body, finalUrl, contentType, method, notes, truncated: false };
412
+ }
413
+ const isHtml = /html/i.test(contentType) || looksLikeHtml(page.content);
414
+ if (isHtml) {
415
+ const altMd = parseAlternateMarkdownLink(page.content, finalUrl);
416
+ if (altMd) {
417
+ try {
418
+ const alt = await loadPage(altMd, 5000, signal);
419
+ if (alt.ok && alt.content.trim().length > MIN_CONTENT_LENGTH && !looksLikeHtml(alt.content)) {
420
+ return { content: alt.content, finalUrl: alt.finalUrl, contentType: alt.contentType, method: "alternate-markdown", notes, truncated: false };
421
+ }
422
+ }
423
+ catch { }
424
+ notes.push("alternate markdown link unusable");
425
+ }
426
+ const md = await tryMdSuffix(finalUrl, signal);
427
+ if (md)
428
+ return { content: md, finalUrl, contentType: "text/markdown", method: "md-suffix", notes, truncated: false };
429
+ const neg = await tryContentNegotiation(finalUrl, signal);
430
+ if (neg)
431
+ return { content: neg, finalUrl, contentType: "text/markdown", method: "content-negotiation", notes, truncated: false };
432
+ let rendered = getTurndown().turndown(page.content).trim();
433
+ method = "native";
434
+ if (rendered.length <= MIN_CONTENT_LENGTH || isLowQuality(rendered)) {
435
+ const llms = await tryLlmEndpoints(finalUrl, signal);
436
+ if (llms) {
437
+ return { content: llms.content, finalUrl, contentType: "text/plain", method: `llms-txt (${llms.endpoint})`, notes, truncated: false };
438
+ }
439
+ notes.push("rendered content low quality; no markdown source discovered");
440
+ }
441
+ body = rendered;
442
+ }
443
+ else {
444
+ body = page.content;
445
+ }
446
+ return { content: body, finalUrl, contentType, method, notes, truncated: false };
447
+ }
448
+ export function buildUrlReadOutput(result) {
449
+ const header = [`URL: ${result.finalUrl}`, `Content-Type: ${result.contentType}`, `Method: ${result.method}`, ...(result.notes.length ? [`Notes: ${result.notes.join("; ")}`] : [])].join("\n");
450
+ return `${header}\n\n---\n\n${result.content}`;
451
+ }
452
+ function persistReadUrlArtifact(artifactsDir, output) {
453
+ if (!artifactsDir)
454
+ return undefined;
455
+ return getArtifactManager(artifactsDir).save(output, "read");
456
+ }
457
+ /**
458
+ * Resolve a URL to its full rendered (or raw) output, using the session-scoped
459
+ * cache and persisting the rendered body as a `read` artifact. Returns the
460
+ * complete untruncated output so callers can apply their own line selection.
461
+ */
462
+ export async function loadReadUrlOutput(scope, params, artifactsDir, signal) {
463
+ const parsed = parseReadUrlTarget(params.path);
464
+ if (!parsed)
465
+ throw new Error(`Not a readable URL: ${params.path}`);
466
+ const raw = params.raw ?? parsed.raw;
467
+ const key = getReadUrlCacheKey(scope, parsed.url, raw);
468
+ let entry = readUrlCache.get(key);
469
+ if (!entry) {
470
+ const rendered = await renderUrl(parsed.url, raw, signal);
471
+ const output = raw ? rendered.content : buildUrlReadOutput(rendered);
472
+ const artifactId = persistReadUrlArtifact(artifactsDir, output);
473
+ const details = { url: parsed.url, finalUrl: rendered.finalUrl, contentType: rendered.contentType, method: rendered.method, truncated: rendered.truncated, notes: rendered.notes, meta: artifactId ? { artifactId } : undefined };
474
+ entry = { artifactId, details, output };
475
+ readUrlCache.set(key, entry);
476
+ readUrlCache.set(getReadUrlCacheKey(scope, rendered.finalUrl, raw), entry);
477
+ }
478
+ return { output: entry.output, details: entry.details, artifactId: entry.artifactId, raw };
479
+ }
480
+ export async function executeReadUrl(scope, params, artifactsDir, signal) {
481
+ const loaded = await loadReadUrlOutput(scope, params, artifactsDir, signal);
482
+ const entry = { output: loaded.output, details: loaded.details, artifactId: loaded.artifactId };
483
+ const fetchDefaultBytes = 50 * 1024;
484
+ const lines = entry.output.split("\n");
485
+ const visibleLines = lines.slice(0, FETCH_DEFAULT_MAX_LINES);
486
+ let content = visibleLines.join("\n");
487
+ const bytesBeforeByteClamp = Buffer.byteLength(content, "utf8");
488
+ const byteTruncated = bytesBeforeByteClamp > fetchDefaultBytes;
489
+ const lineTruncated = lines.length > FETCH_DEFAULT_MAX_LINES;
490
+ if (byteTruncated)
491
+ content = content.slice(0, fetchDefaultBytes);
492
+ const truncated = lineTruncated || byteTruncated;
493
+ let artifactId = entry.artifactId;
494
+ if (truncated && !artifactId)
495
+ artifactId = persistReadUrlArtifact(artifactsDir, entry.output);
496
+ const outputLines = content.length === 0 ? 0 : content.split("\n").length;
497
+ const truncation = truncated ? {
498
+ content,
499
+ truncated: true,
500
+ truncatedBy: byteTruncated ? "bytes" : "lines",
501
+ totalLines: lines.length,
502
+ totalBytes: Buffer.byteLength(entry.output, "utf8"),
503
+ outputLines,
504
+ outputBytes: Buffer.byteLength(content, "utf8"),
505
+ lastLinePartial: byteTruncated,
506
+ firstLineExceedsLimit: false,
507
+ maxLines: FETCH_DEFAULT_MAX_LINES,
508
+ maxBytes: fetchDefaultBytes,
509
+ } : undefined;
510
+ const details = { ...entry.details, truncated, meta: { ...(entry.details.meta ?? {}), ...(artifactId ? { artifactId } : {}), ...(truncation ? { truncation } : {}) } };
511
+ if (truncated)
512
+ content += `\n\n[Showing first ${visibleLines.length} of ${lines.length} lines.${artifactId ? ` Full output: artifact://${artifactId}` : ""}]`;
513
+ return { content, details, artifactId };
514
+ }
515
+ export function resetReadUrlCache() {
516
+ readUrlCache.clear();
517
+ }
518
+ //# sourceMappingURL=fetch-url.js.map