@nghyane/arcane 0.1.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 (738) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/README.md +12 -0
  3. package/examples/README.md +21 -0
  4. package/examples/custom-tools/README.md +109 -0
  5. package/examples/custom-tools/hello/index.ts +20 -0
  6. package/examples/custom-tools/todo/index.ts +206 -0
  7. package/examples/extensions/README.md +143 -0
  8. package/examples/extensions/api-demo.ts +89 -0
  9. package/examples/extensions/chalk-logger.ts +25 -0
  10. package/examples/extensions/hello.ts +32 -0
  11. package/examples/extensions/pirate.ts +43 -0
  12. package/examples/extensions/plan-mode.ts +550 -0
  13. package/examples/extensions/reload-runtime.ts +37 -0
  14. package/examples/extensions/todo.ts +296 -0
  15. package/examples/extensions/tools.ts +144 -0
  16. package/examples/extensions/with-deps/index.ts +35 -0
  17. package/examples/extensions/with-deps/package-lock.json +31 -0
  18. package/examples/extensions/with-deps/package.json +16 -0
  19. package/examples/hooks/README.md +56 -0
  20. package/examples/hooks/auto-commit-on-exit.ts +48 -0
  21. package/examples/hooks/confirm-destructive.ts +58 -0
  22. package/examples/hooks/custom-compaction.ts +116 -0
  23. package/examples/hooks/dirty-repo-guard.ts +51 -0
  24. package/examples/hooks/file-trigger.ts +40 -0
  25. package/examples/hooks/git-checkpoint.ts +52 -0
  26. package/examples/hooks/handoff.ts +150 -0
  27. package/examples/hooks/permission-gate.ts +33 -0
  28. package/examples/hooks/protected-paths.ts +29 -0
  29. package/examples/hooks/qna.ts +119 -0
  30. package/examples/hooks/status-line.ts +39 -0
  31. package/examples/sdk/01-minimal.ts +21 -0
  32. package/examples/sdk/02-custom-model.ts +49 -0
  33. package/examples/sdk/03-custom-prompt.ts +43 -0
  34. package/examples/sdk/04-skills.ts +43 -0
  35. package/examples/sdk/06-extensions.ts +80 -0
  36. package/examples/sdk/06-hooks.ts +61 -0
  37. package/examples/sdk/07-context-files.ts +35 -0
  38. package/examples/sdk/08-prompt-templates.ts +36 -0
  39. package/examples/sdk/08-slash-commands.ts +41 -0
  40. package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
  41. package/examples/sdk/11-sessions.ts +47 -0
  42. package/examples/sdk/README.md +150 -0
  43. package/package.json +464 -0
  44. package/scripts/format-prompts.ts +184 -0
  45. package/scripts/generate-docs-index.ts +40 -0
  46. package/scripts/generate-template.ts +32 -0
  47. package/src/bun-imports.d.ts +22 -0
  48. package/src/capability/context-file.ts +39 -0
  49. package/src/capability/extension-module.ts +33 -0
  50. package/src/capability/extension.ts +47 -0
  51. package/src/capability/fs.ts +89 -0
  52. package/src/capability/hook.ts +39 -0
  53. package/src/capability/index.ts +432 -0
  54. package/src/capability/instruction.ts +36 -0
  55. package/src/capability/mcp.ts +60 -0
  56. package/src/capability/prompt.ts +34 -0
  57. package/src/capability/rule.ts +223 -0
  58. package/src/capability/settings.ts +34 -0
  59. package/src/capability/skill.ts +48 -0
  60. package/src/capability/slash-command.ts +39 -0
  61. package/src/capability/ssh.ts +41 -0
  62. package/src/capability/system-prompt.ts +34 -0
  63. package/src/capability/tool.ts +37 -0
  64. package/src/capability/types.ts +156 -0
  65. package/src/cli/args.ts +259 -0
  66. package/src/cli/config-cli.ts +357 -0
  67. package/src/cli/file-processor.ts +124 -0
  68. package/src/cli/grep-cli.ts +152 -0
  69. package/src/cli/jupyter-cli.ts +106 -0
  70. package/src/cli/list-models.ts +103 -0
  71. package/src/cli/plugin-cli.ts +661 -0
  72. package/src/cli/session-picker.ts +42 -0
  73. package/src/cli/setup-cli.ts +376 -0
  74. package/src/cli/shell-cli.ts +174 -0
  75. package/src/cli/ssh-cli.ts +179 -0
  76. package/src/cli/stats-cli.ts +197 -0
  77. package/src/cli/update-cli.ts +286 -0
  78. package/src/cli/web-search-cli.ts +143 -0
  79. package/src/cli.ts +65 -0
  80. package/src/commands/commit.ts +36 -0
  81. package/src/commands/config.ts +51 -0
  82. package/src/commands/grep.ts +41 -0
  83. package/src/commands/jupyter.ts +32 -0
  84. package/src/commands/launch.ts +139 -0
  85. package/src/commands/plugin.ts +70 -0
  86. package/src/commands/setup.ts +42 -0
  87. package/src/commands/shell.ts +29 -0
  88. package/src/commands/ssh.ts +60 -0
  89. package/src/commands/stats.ts +29 -0
  90. package/src/commands/update.ts +21 -0
  91. package/src/commands/web-search.ts +42 -0
  92. package/src/commit/agentic/agent.ts +311 -0
  93. package/src/commit/agentic/fallback.ts +96 -0
  94. package/src/commit/agentic/index.ts +359 -0
  95. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  96. package/src/commit/agentic/prompts/session-user.md +25 -0
  97. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  98. package/src/commit/agentic/prompts/system.md +38 -0
  99. package/src/commit/agentic/state.ts +69 -0
  100. package/src/commit/agentic/tools/analyze-file.ts +118 -0
  101. package/src/commit/agentic/tools/git-file-diff.ts +194 -0
  102. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  103. package/src/commit/agentic/tools/git-overview.ts +84 -0
  104. package/src/commit/agentic/tools/index.ts +56 -0
  105. package/src/commit/agentic/tools/propose-changelog.ts +128 -0
  106. package/src/commit/agentic/tools/propose-commit.ts +154 -0
  107. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  108. package/src/commit/agentic/tools/split-commit.ts +280 -0
  109. package/src/commit/agentic/topo-sort.ts +44 -0
  110. package/src/commit/agentic/trivial.ts +51 -0
  111. package/src/commit/agentic/validation.ts +200 -0
  112. package/src/commit/analysis/conventional.ts +165 -0
  113. package/src/commit/analysis/index.ts +4 -0
  114. package/src/commit/analysis/scope.ts +242 -0
  115. package/src/commit/analysis/summary.ts +112 -0
  116. package/src/commit/analysis/validation.ts +66 -0
  117. package/src/commit/changelog/detect.ts +37 -0
  118. package/src/commit/changelog/generate.ts +110 -0
  119. package/src/commit/changelog/index.ts +234 -0
  120. package/src/commit/changelog/parse.ts +44 -0
  121. package/src/commit/cli.ts +93 -0
  122. package/src/commit/git/diff.ts +148 -0
  123. package/src/commit/git/errors.ts +9 -0
  124. package/src/commit/git/index.ts +211 -0
  125. package/src/commit/git/operations.ts +54 -0
  126. package/src/commit/index.ts +5 -0
  127. package/src/commit/map-reduce/index.ts +64 -0
  128. package/src/commit/map-reduce/map-phase.ts +178 -0
  129. package/src/commit/map-reduce/reduce-phase.ts +145 -0
  130. package/src/commit/map-reduce/utils.ts +9 -0
  131. package/src/commit/message.ts +11 -0
  132. package/src/commit/model-selection.ts +69 -0
  133. package/src/commit/pipeline.ts +243 -0
  134. package/src/commit/prompts/analysis-system.md +148 -0
  135. package/src/commit/prompts/analysis-user.md +38 -0
  136. package/src/commit/prompts/changelog-system.md +50 -0
  137. package/src/commit/prompts/changelog-user.md +18 -0
  138. package/src/commit/prompts/file-observer-system.md +24 -0
  139. package/src/commit/prompts/file-observer-user.md +8 -0
  140. package/src/commit/prompts/reduce-system.md +50 -0
  141. package/src/commit/prompts/reduce-user.md +17 -0
  142. package/src/commit/prompts/summary-retry.md +3 -0
  143. package/src/commit/prompts/summary-system.md +38 -0
  144. package/src/commit/prompts/summary-user.md +13 -0
  145. package/src/commit/prompts/types-description.md +2 -0
  146. package/src/commit/types.ts +109 -0
  147. package/src/commit/utils/exclusions.ts +42 -0
  148. package/src/config/file-lock.ts +121 -0
  149. package/src/config/keybindings.ts +280 -0
  150. package/src/config/model-registry.ts +1140 -0
  151. package/src/config/model-resolver.ts +812 -0
  152. package/src/config/prompt-templates.ts +526 -0
  153. package/src/config/resolve-config-value.ts +92 -0
  154. package/src/config/settings-schema.ts +1236 -0
  155. package/src/config/settings.ts +706 -0
  156. package/src/config.ts +414 -0
  157. package/src/cursor.ts +239 -0
  158. package/src/debug/index.ts +431 -0
  159. package/src/debug/log-formatting.ts +60 -0
  160. package/src/debug/log-viewer.ts +903 -0
  161. package/src/debug/profiler.ts +158 -0
  162. package/src/debug/report-bundle.ts +366 -0
  163. package/src/debug/system-info.ts +112 -0
  164. package/src/discovery/agents-md.ts +68 -0
  165. package/src/discovery/agents.ts +199 -0
  166. package/src/discovery/builtin.ts +815 -0
  167. package/src/discovery/claude-plugins.ts +205 -0
  168. package/src/discovery/claude.ts +506 -0
  169. package/src/discovery/cline.ts +83 -0
  170. package/src/discovery/codex.ts +532 -0
  171. package/src/discovery/cursor.ts +218 -0
  172. package/src/discovery/gemini.ts +395 -0
  173. package/src/discovery/github.ts +117 -0
  174. package/src/discovery/helpers.ts +698 -0
  175. package/src/discovery/index.ts +89 -0
  176. package/src/discovery/mcp-json.ts +156 -0
  177. package/src/discovery/opencode.ts +394 -0
  178. package/src/discovery/ssh.ts +160 -0
  179. package/src/discovery/vscode.ts +103 -0
  180. package/src/discovery/windsurf.ts +145 -0
  181. package/src/exa/company.ts +57 -0
  182. package/src/exa/index.ts +62 -0
  183. package/src/exa/linkedin.ts +57 -0
  184. package/src/exa/mcp-client.ts +289 -0
  185. package/src/exa/render.ts +244 -0
  186. package/src/exa/researcher.ts +89 -0
  187. package/src/exa/search.ts +330 -0
  188. package/src/exa/types.ts +166 -0
  189. package/src/exa/websets.ts +247 -0
  190. package/src/exec/bash-executor.ts +184 -0
  191. package/src/exec/exec.ts +53 -0
  192. package/src/export/custom-share.ts +65 -0
  193. package/src/export/html/index.ts +162 -0
  194. package/src/export/html/template.css +889 -0
  195. package/src/export/html/template.generated.ts +2 -0
  196. package/src/export/html/template.html +45 -0
  197. package/src/export/html/template.js +1329 -0
  198. package/src/export/html/template.macro.ts +24 -0
  199. package/src/export/html/vendor/highlight.min.js +1213 -0
  200. package/src/export/html/vendor/marked.min.js +6 -0
  201. package/src/export/ttsr.ts +434 -0
  202. package/src/extensibility/custom-commands/bundled/review/index.ts +433 -0
  203. package/src/extensibility/custom-commands/index.ts +15 -0
  204. package/src/extensibility/custom-commands/loader.ts +231 -0
  205. package/src/extensibility/custom-commands/types.ts +111 -0
  206. package/src/extensibility/custom-tools/index.ts +22 -0
  207. package/src/extensibility/custom-tools/loader.ts +235 -0
  208. package/src/extensibility/custom-tools/types.ts +226 -0
  209. package/src/extensibility/custom-tools/wrapper.ts +45 -0
  210. package/src/extensibility/extensions/index.ts +136 -0
  211. package/src/extensibility/extensions/loader.ts +520 -0
  212. package/src/extensibility/extensions/runner.ts +774 -0
  213. package/src/extensibility/extensions/types.ts +1293 -0
  214. package/src/extensibility/extensions/wrapper.ts +188 -0
  215. package/src/extensibility/hooks/index.ts +16 -0
  216. package/src/extensibility/hooks/loader.ts +273 -0
  217. package/src/extensibility/hooks/runner.ts +441 -0
  218. package/src/extensibility/hooks/tool-wrapper.ts +106 -0
  219. package/src/extensibility/hooks/types.ts +817 -0
  220. package/src/extensibility/plugins/doctor.ts +65 -0
  221. package/src/extensibility/plugins/git-url.ts +281 -0
  222. package/src/extensibility/plugins/index.ts +33 -0
  223. package/src/extensibility/plugins/installer.ts +192 -0
  224. package/src/extensibility/plugins/loader.ts +338 -0
  225. package/src/extensibility/plugins/manager.ts +716 -0
  226. package/src/extensibility/plugins/parser.ts +105 -0
  227. package/src/extensibility/plugins/types.ts +190 -0
  228. package/src/extensibility/skills.ts +385 -0
  229. package/src/extensibility/slash-commands.ts +287 -0
  230. package/src/extensibility/tool-proxy.ts +25 -0
  231. package/src/index.ts +275 -0
  232. package/src/internal-urls/agent-protocol.ts +136 -0
  233. package/src/internal-urls/artifact-protocol.ts +97 -0
  234. package/src/internal-urls/docs-index.generated.ts +54 -0
  235. package/src/internal-urls/docs-protocol.ts +84 -0
  236. package/src/internal-urls/index.ts +31 -0
  237. package/src/internal-urls/json-query.ts +126 -0
  238. package/src/internal-urls/memory-protocol.ts +133 -0
  239. package/src/internal-urls/router.ts +70 -0
  240. package/src/internal-urls/rule-protocol.ts +55 -0
  241. package/src/internal-urls/skill-protocol.ts +111 -0
  242. package/src/internal-urls/types.ts +52 -0
  243. package/src/ipy/executor.ts +556 -0
  244. package/src/ipy/gateway-coordinator.ts +426 -0
  245. package/src/ipy/kernel.ts +892 -0
  246. package/src/ipy/modules.ts +109 -0
  247. package/src/ipy/prelude.py +831 -0
  248. package/src/ipy/prelude.ts +3 -0
  249. package/src/ipy/runtime.ts +222 -0
  250. package/src/lsp/client.ts +867 -0
  251. package/src/lsp/clients/biome-client.ts +202 -0
  252. package/src/lsp/clients/index.ts +50 -0
  253. package/src/lsp/clients/lsp-linter-client.ts +93 -0
  254. package/src/lsp/clients/swiftlint-client.ts +120 -0
  255. package/src/lsp/config.ts +397 -0
  256. package/src/lsp/defaults.json +464 -0
  257. package/src/lsp/edits.ts +109 -0
  258. package/src/lsp/index.ts +1268 -0
  259. package/src/lsp/lspmux.ts +250 -0
  260. package/src/lsp/render.ts +689 -0
  261. package/src/lsp/types.ts +414 -0
  262. package/src/lsp/utils.ts +549 -0
  263. package/src/main.ts +773 -0
  264. package/src/mcp/client.ts +239 -0
  265. package/src/mcp/config-writer.ts +215 -0
  266. package/src/mcp/config.ts +363 -0
  267. package/src/mcp/index.ts +55 -0
  268. package/src/mcp/json-rpc.ts +84 -0
  269. package/src/mcp/loader.ts +124 -0
  270. package/src/mcp/manager.ts +490 -0
  271. package/src/mcp/oauth-discovery.ts +274 -0
  272. package/src/mcp/oauth-flow.ts +229 -0
  273. package/src/mcp/render.ts +123 -0
  274. package/src/mcp/tool-bridge.ts +372 -0
  275. package/src/mcp/tool-cache.ts +121 -0
  276. package/src/mcp/transports/http.ts +332 -0
  277. package/src/mcp/transports/index.ts +6 -0
  278. package/src/mcp/transports/stdio.ts +281 -0
  279. package/src/mcp/types.ts +248 -0
  280. package/src/memories/index.ts +1099 -0
  281. package/src/memories/storage.ts +563 -0
  282. package/src/modes/components/agent-dashboard.ts +1130 -0
  283. package/src/modes/components/assistant-message.ts +144 -0
  284. package/src/modes/components/bash-execution.ts +218 -0
  285. package/src/modes/components/bordered-loader.ts +41 -0
  286. package/src/modes/components/branch-summary-message.ts +45 -0
  287. package/src/modes/components/codemode-group.ts +369 -0
  288. package/src/modes/components/compaction-summary-message.ts +51 -0
  289. package/src/modes/components/countdown-timer.ts +46 -0
  290. package/src/modes/components/custom-editor.ts +181 -0
  291. package/src/modes/components/custom-message.ts +91 -0
  292. package/src/modes/components/diff.ts +186 -0
  293. package/src/modes/components/dynamic-border.ts +25 -0
  294. package/src/modes/components/extensions/extension-dashboard.ts +325 -0
  295. package/src/modes/components/extensions/extension-list.ts +484 -0
  296. package/src/modes/components/extensions/index.ts +9 -0
  297. package/src/modes/components/extensions/inspector-panel.ts +321 -0
  298. package/src/modes/components/extensions/state-manager.ts +586 -0
  299. package/src/modes/components/extensions/types.ts +191 -0
  300. package/src/modes/components/footer.ts +315 -0
  301. package/src/modes/components/history-search.ts +157 -0
  302. package/src/modes/components/hook-editor.ts +101 -0
  303. package/src/modes/components/hook-input.ts +72 -0
  304. package/src/modes/components/hook-message.ts +100 -0
  305. package/src/modes/components/hook-selector.ts +155 -0
  306. package/src/modes/components/index.ts +41 -0
  307. package/src/modes/components/keybinding-hints.ts +65 -0
  308. package/src/modes/components/login-dialog.ts +164 -0
  309. package/src/modes/components/mcp-add-wizard.ts +1295 -0
  310. package/src/modes/components/model-selector.ts +625 -0
  311. package/src/modes/components/oauth-selector.ts +210 -0
  312. package/src/modes/components/plugin-settings.ts +477 -0
  313. package/src/modes/components/python-execution.ts +196 -0
  314. package/src/modes/components/queue-mode-selector.ts +56 -0
  315. package/src/modes/components/read-tool-group.ts +119 -0
  316. package/src/modes/components/session-selector.ts +242 -0
  317. package/src/modes/components/settings-defs.ts +340 -0
  318. package/src/modes/components/settings-selector.ts +529 -0
  319. package/src/modes/components/show-images-selector.ts +45 -0
  320. package/src/modes/components/skill-message.ts +90 -0
  321. package/src/modes/components/status-line/index.ts +4 -0
  322. package/src/modes/components/status-line/presets.ts +94 -0
  323. package/src/modes/components/status-line/segments.ts +352 -0
  324. package/src/modes/components/status-line/separators.ts +55 -0
  325. package/src/modes/components/status-line/types.ts +75 -0
  326. package/src/modes/components/status-line-segment-editor.ts +354 -0
  327. package/src/modes/components/status-line.ts +421 -0
  328. package/src/modes/components/theme-selector.ts +63 -0
  329. package/src/modes/components/thinking-selector.ts +64 -0
  330. package/src/modes/components/todo-display.ts +115 -0
  331. package/src/modes/components/todo-reminder.ts +40 -0
  332. package/src/modes/components/tool-execution.ts +703 -0
  333. package/src/modes/components/tree-selector.ts +904 -0
  334. package/src/modes/components/ttsr-notification.ts +80 -0
  335. package/src/modes/components/user-message-selector.ts +146 -0
  336. package/src/modes/components/user-message.ts +22 -0
  337. package/src/modes/components/visual-truncate.ts +63 -0
  338. package/src/modes/components/welcome.ts +247 -0
  339. package/src/modes/controllers/command-controller.ts +1120 -0
  340. package/src/modes/controllers/event-controller.ts +479 -0
  341. package/src/modes/controllers/extension-ui-controller.ts +778 -0
  342. package/src/modes/controllers/input-controller.ts +671 -0
  343. package/src/modes/controllers/mcp-command-controller.ts +1315 -0
  344. package/src/modes/controllers/selector-controller.ts +712 -0
  345. package/src/modes/controllers/ssh-command-controller.ts +452 -0
  346. package/src/modes/index.ts +15 -0
  347. package/src/modes/interactive-mode.ts +1027 -0
  348. package/src/modes/print-mode.ts +191 -0
  349. package/src/modes/rpc/rpc-client.ts +583 -0
  350. package/src/modes/rpc/rpc-mode.ts +700 -0
  351. package/src/modes/rpc/rpc-types.ts +236 -0
  352. package/src/modes/theme/dark.json +95 -0
  353. package/src/modes/theme/defaults/alabaster.json +93 -0
  354. package/src/modes/theme/defaults/amethyst.json +96 -0
  355. package/src/modes/theme/defaults/anthracite.json +93 -0
  356. package/src/modes/theme/defaults/basalt.json +91 -0
  357. package/src/modes/theme/defaults/birch.json +95 -0
  358. package/src/modes/theme/defaults/dark-abyss.json +91 -0
  359. package/src/modes/theme/defaults/dark-arctic.json +104 -0
  360. package/src/modes/theme/defaults/dark-aurora.json +95 -0
  361. package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
  362. package/src/modes/theme/defaults/dark-cavern.json +91 -0
  363. package/src/modes/theme/defaults/dark-copper.json +95 -0
  364. package/src/modes/theme/defaults/dark-cosmos.json +90 -0
  365. package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
  366. package/src/modes/theme/defaults/dark-dracula.json +98 -0
  367. package/src/modes/theme/defaults/dark-eclipse.json +91 -0
  368. package/src/modes/theme/defaults/dark-ember.json +95 -0
  369. package/src/modes/theme/defaults/dark-equinox.json +90 -0
  370. package/src/modes/theme/defaults/dark-forest.json +96 -0
  371. package/src/modes/theme/defaults/dark-github.json +105 -0
  372. package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
  373. package/src/modes/theme/defaults/dark-lavender.json +95 -0
  374. package/src/modes/theme/defaults/dark-lunar.json +89 -0
  375. package/src/modes/theme/defaults/dark-midnight.json +95 -0
  376. package/src/modes/theme/defaults/dark-monochrome.json +94 -0
  377. package/src/modes/theme/defaults/dark-monokai.json +98 -0
  378. package/src/modes/theme/defaults/dark-nebula.json +90 -0
  379. package/src/modes/theme/defaults/dark-nord.json +97 -0
  380. package/src/modes/theme/defaults/dark-ocean.json +101 -0
  381. package/src/modes/theme/defaults/dark-one.json +100 -0
  382. package/src/modes/theme/defaults/dark-rainforest.json +91 -0
  383. package/src/modes/theme/defaults/dark-reef.json +91 -0
  384. package/src/modes/theme/defaults/dark-retro.json +92 -0
  385. package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
  386. package/src/modes/theme/defaults/dark-sakura.json +95 -0
  387. package/src/modes/theme/defaults/dark-slate.json +95 -0
  388. package/src/modes/theme/defaults/dark-solarized.json +97 -0
  389. package/src/modes/theme/defaults/dark-solstice.json +90 -0
  390. package/src/modes/theme/defaults/dark-starfall.json +91 -0
  391. package/src/modes/theme/defaults/dark-sunset.json +99 -0
  392. package/src/modes/theme/defaults/dark-swamp.json +90 -0
  393. package/src/modes/theme/defaults/dark-synthwave.json +103 -0
  394. package/src/modes/theme/defaults/dark-taiga.json +91 -0
  395. package/src/modes/theme/defaults/dark-terminal.json +95 -0
  396. package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
  397. package/src/modes/theme/defaults/dark-tundra.json +91 -0
  398. package/src/modes/theme/defaults/dark-twilight.json +91 -0
  399. package/src/modes/theme/defaults/dark-volcanic.json +91 -0
  400. package/src/modes/theme/defaults/graphite.json +92 -0
  401. package/src/modes/theme/defaults/index.ts +195 -0
  402. package/src/modes/theme/defaults/light-arctic.json +107 -0
  403. package/src/modes/theme/defaults/light-aurora-day.json +91 -0
  404. package/src/modes/theme/defaults/light-canyon.json +91 -0
  405. package/src/modes/theme/defaults/light-catppuccin.json +106 -0
  406. package/src/modes/theme/defaults/light-cirrus.json +90 -0
  407. package/src/modes/theme/defaults/light-coral.json +95 -0
  408. package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
  409. package/src/modes/theme/defaults/light-dawn.json +90 -0
  410. package/src/modes/theme/defaults/light-dunes.json +91 -0
  411. package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
  412. package/src/modes/theme/defaults/light-forest.json +100 -0
  413. package/src/modes/theme/defaults/light-frost.json +95 -0
  414. package/src/modes/theme/defaults/light-github.json +115 -0
  415. package/src/modes/theme/defaults/light-glacier.json +91 -0
  416. package/src/modes/theme/defaults/light-gruvbox.json +108 -0
  417. package/src/modes/theme/defaults/light-haze.json +90 -0
  418. package/src/modes/theme/defaults/light-honeycomb.json +95 -0
  419. package/src/modes/theme/defaults/light-lagoon.json +91 -0
  420. package/src/modes/theme/defaults/light-lavender.json +95 -0
  421. package/src/modes/theme/defaults/light-meadow.json +91 -0
  422. package/src/modes/theme/defaults/light-mint.json +95 -0
  423. package/src/modes/theme/defaults/light-monochrome.json +101 -0
  424. package/src/modes/theme/defaults/light-ocean.json +99 -0
  425. package/src/modes/theme/defaults/light-one.json +99 -0
  426. package/src/modes/theme/defaults/light-opal.json +91 -0
  427. package/src/modes/theme/defaults/light-orchard.json +91 -0
  428. package/src/modes/theme/defaults/light-paper.json +95 -0
  429. package/src/modes/theme/defaults/light-prism.json +90 -0
  430. package/src/modes/theme/defaults/light-retro.json +98 -0
  431. package/src/modes/theme/defaults/light-sand.json +95 -0
  432. package/src/modes/theme/defaults/light-savanna.json +91 -0
  433. package/src/modes/theme/defaults/light-solarized.json +102 -0
  434. package/src/modes/theme/defaults/light-soleil.json +90 -0
  435. package/src/modes/theme/defaults/light-sunset.json +99 -0
  436. package/src/modes/theme/defaults/light-synthwave.json +98 -0
  437. package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
  438. package/src/modes/theme/defaults/light-wetland.json +91 -0
  439. package/src/modes/theme/defaults/light-zenith.json +89 -0
  440. package/src/modes/theme/defaults/limestone.json +94 -0
  441. package/src/modes/theme/defaults/mahogany.json +97 -0
  442. package/src/modes/theme/defaults/marble.json +93 -0
  443. package/src/modes/theme/defaults/obsidian.json +91 -0
  444. package/src/modes/theme/defaults/onyx.json +91 -0
  445. package/src/modes/theme/defaults/pearl.json +93 -0
  446. package/src/modes/theme/defaults/porcelain.json +91 -0
  447. package/src/modes/theme/defaults/quartz.json +96 -0
  448. package/src/modes/theme/defaults/sandstone.json +95 -0
  449. package/src/modes/theme/defaults/titanium.json +90 -0
  450. package/src/modes/theme/light.json +93 -0
  451. package/src/modes/theme/mermaid-cache.ts +111 -0
  452. package/src/modes/theme/theme-schema.json +429 -0
  453. package/src/modes/theme/theme.ts +2333 -0
  454. package/src/modes/types.ts +216 -0
  455. package/src/modes/utils/ui-helpers.ts +529 -0
  456. package/src/patch/applicator.ts +1482 -0
  457. package/src/patch/diff.ts +425 -0
  458. package/src/patch/fuzzy.ts +784 -0
  459. package/src/patch/hashline.ts +972 -0
  460. package/src/patch/index.ts +964 -0
  461. package/src/patch/normalize.ts +397 -0
  462. package/src/patch/normative.ts +72 -0
  463. package/src/patch/parser.ts +532 -0
  464. package/src/patch/shared.ts +400 -0
  465. package/src/patch/types.ts +292 -0
  466. package/src/priority.json +35 -0
  467. package/src/prompts/agents/explore.md +48 -0
  468. package/src/prompts/agents/frontmatter.md +9 -0
  469. package/src/prompts/agents/init.md +36 -0
  470. package/src/prompts/agents/librarian.md +53 -0
  471. package/src/prompts/agents/oracle.md +51 -0
  472. package/src/prompts/agents/reviewer.md +70 -0
  473. package/src/prompts/agents/task.md +14 -0
  474. package/src/prompts/compaction/branch-summary-context.md +5 -0
  475. package/src/prompts/compaction/branch-summary-preamble.md +2 -0
  476. package/src/prompts/compaction/branch-summary.md +30 -0
  477. package/src/prompts/compaction/compaction-short-summary.md +9 -0
  478. package/src/prompts/compaction/compaction-summary-context.md +5 -0
  479. package/src/prompts/compaction/compaction-summary.md +38 -0
  480. package/src/prompts/compaction/compaction-turn-prefix.md +17 -0
  481. package/src/prompts/compaction/compaction-update-summary.md +45 -0
  482. package/src/prompts/memories/consolidation.md +30 -0
  483. package/src/prompts/memories/read_path.md +11 -0
  484. package/src/prompts/memories/stage_one_input.md +6 -0
  485. package/src/prompts/memories/stage_one_system.md +21 -0
  486. package/src/prompts/review-request.md +64 -0
  487. package/src/prompts/system/agent-creation-architect.md +65 -0
  488. package/src/prompts/system/agent-creation-user.md +6 -0
  489. package/src/prompts/system/custom-system-prompt.md +68 -0
  490. package/src/prompts/system/file-operations.md +10 -0
  491. package/src/prompts/system/subagent-submit-reminder.md +11 -0
  492. package/src/prompts/system/subagent-system-prompt.md +31 -0
  493. package/src/prompts/system/subagent-user-prompt.md +8 -0
  494. package/src/prompts/system/summarization-system.md +3 -0
  495. package/src/prompts/system/system-prompt.md +300 -0
  496. package/src/prompts/system/title-system.md +2 -0
  497. package/src/prompts/system/ttsr-interrupt.md +7 -0
  498. package/src/prompts/system/web-search.md +28 -0
  499. package/src/prompts/tools/ask.md +44 -0
  500. package/src/prompts/tools/bash.md +24 -0
  501. package/src/prompts/tools/browser.md +33 -0
  502. package/src/prompts/tools/calculator.md +12 -0
  503. package/src/prompts/tools/explore.md +29 -0
  504. package/src/prompts/tools/fetch.md +16 -0
  505. package/src/prompts/tools/find.md +18 -0
  506. package/src/prompts/tools/gemini-image.md +23 -0
  507. package/src/prompts/tools/grep.md +28 -0
  508. package/src/prompts/tools/hashline.md +232 -0
  509. package/src/prompts/tools/librarian.md +24 -0
  510. package/src/prompts/tools/lsp.md +28 -0
  511. package/src/prompts/tools/oracle.md +26 -0
  512. package/src/prompts/tools/patch.md +74 -0
  513. package/src/prompts/tools/python.md +66 -0
  514. package/src/prompts/tools/read.md +36 -0
  515. package/src/prompts/tools/replace.md +38 -0
  516. package/src/prompts/tools/reviewer.md +41 -0
  517. package/src/prompts/tools/ssh.md +51 -0
  518. package/src/prompts/tools/task-summary.md +28 -0
  519. package/src/prompts/tools/task.md +275 -0
  520. package/src/prompts/tools/todo-write.md +65 -0
  521. package/src/prompts/tools/undo-edit.md +7 -0
  522. package/src/prompts/tools/web-search.md +19 -0
  523. package/src/prompts/tools/write.md +18 -0
  524. package/src/sdk.ts +1287 -0
  525. package/src/secrets/index.ts +116 -0
  526. package/src/secrets/obfuscator.ts +269 -0
  527. package/src/secrets/regex.ts +21 -0
  528. package/src/session/agent-session.ts +4669 -0
  529. package/src/session/agent-storage.ts +621 -0
  530. package/src/session/artifacts.ts +132 -0
  531. package/src/session/auth-storage.ts +1433 -0
  532. package/src/session/blob-store.ts +103 -0
  533. package/src/session/compaction/branch-summarization.ts +315 -0
  534. package/src/session/compaction/compaction.ts +864 -0
  535. package/src/session/compaction/index.ts +7 -0
  536. package/src/session/compaction/pruning.ts +91 -0
  537. package/src/session/compaction/utils.ts +171 -0
  538. package/src/session/history-storage.ts +170 -0
  539. package/src/session/messages.ts +317 -0
  540. package/src/session/session-manager.ts +2276 -0
  541. package/src/session/session-storage.ts +342 -0
  542. package/src/session/streaming-output.ts +565 -0
  543. package/src/slash-commands/builtin-registry.ts +439 -0
  544. package/src/ssh/config-writer.ts +183 -0
  545. package/src/ssh/connection-manager.ts +444 -0
  546. package/src/ssh/ssh-executor.ts +127 -0
  547. package/src/ssh/sshfs-mount.ts +135 -0
  548. package/src/stt/downloader.ts +71 -0
  549. package/src/stt/index.ts +3 -0
  550. package/src/stt/recorder.ts +351 -0
  551. package/src/stt/setup.ts +52 -0
  552. package/src/stt/stt-controller.ts +160 -0
  553. package/src/stt/transcribe.py +70 -0
  554. package/src/stt/transcriber.ts +91 -0
  555. package/src/system-prompt.ts +685 -0
  556. package/src/task/agents.ts +155 -0
  557. package/src/task/batch.ts +102 -0
  558. package/src/task/commands.ts +134 -0
  559. package/src/task/discovery.ts +126 -0
  560. package/src/task/executor.ts +908 -0
  561. package/src/task/index.ts +223 -0
  562. package/src/task/output-manager.ts +107 -0
  563. package/src/task/parallel.ts +84 -0
  564. package/src/task/render.ts +326 -0
  565. package/src/task/subprocess-tool-registry.ts +88 -0
  566. package/src/task/template.ts +32 -0
  567. package/src/task/types.ts +144 -0
  568. package/src/tools/ask.ts +523 -0
  569. package/src/tools/bash-interactive.ts +419 -0
  570. package/src/tools/bash-interceptor.ts +105 -0
  571. package/src/tools/bash-normalize.ts +107 -0
  572. package/src/tools/bash-skill-urls.ts +177 -0
  573. package/src/tools/bash.ts +347 -0
  574. package/src/tools/browser.ts +1374 -0
  575. package/src/tools/calculator.ts +537 -0
  576. package/src/tools/context.ts +39 -0
  577. package/src/tools/explore.ts +23 -0
  578. package/src/tools/fetch.ts +1091 -0
  579. package/src/tools/find.ts +540 -0
  580. package/src/tools/fs-cache-invalidation.ts +28 -0
  581. package/src/tools/gemini-image.ts +907 -0
  582. package/src/tools/grep.ts +489 -0
  583. package/src/tools/index.ts +337 -0
  584. package/src/tools/json-tree.ts +231 -0
  585. package/src/tools/jtd-to-json-schema.ts +247 -0
  586. package/src/tools/jtd-to-typescript.ts +198 -0
  587. package/src/tools/librarian.ts +33 -0
  588. package/src/tools/list-limit.ts +40 -0
  589. package/src/tools/notebook.ts +287 -0
  590. package/src/tools/oracle.ts +40 -0
  591. package/src/tools/output-meta.ts +459 -0
  592. package/src/tools/output-utils.ts +63 -0
  593. package/src/tools/path-utils.ts +116 -0
  594. package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
  595. package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
  596. package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
  597. package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
  598. package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
  599. package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
  600. package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
  601. package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
  602. package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
  603. package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
  604. package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
  605. package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
  606. package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
  607. package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
  608. package/src/tools/python.ts +1118 -0
  609. package/src/tools/read.ts +1193 -0
  610. package/src/tools/render-utils.ts +680 -0
  611. package/src/tools/renderers.ts +60 -0
  612. package/src/tools/reviewer-tool.ts +41 -0
  613. package/src/tools/ssh.ts +326 -0
  614. package/src/tools/subagent-tool.ts +169 -0
  615. package/src/tools/submit-result.ts +152 -0
  616. package/src/tools/todo-write.ts +255 -0
  617. package/src/tools/tool-errors.ts +92 -0
  618. package/src/tools/tool-result.ts +86 -0
  619. package/src/tools/undo-edit.ts +145 -0
  620. package/src/tools/undo-history.ts +22 -0
  621. package/src/tools/write.ts +274 -0
  622. package/src/tui/code-cell.ts +108 -0
  623. package/src/tui/file-list.ts +47 -0
  624. package/src/tui/index.ts +11 -0
  625. package/src/tui/output-block.ts +144 -0
  626. package/src/tui/status-line.ts +39 -0
  627. package/src/tui/tree-list.ts +53 -0
  628. package/src/tui/types.ts +16 -0
  629. package/src/tui/utils.ts +116 -0
  630. package/src/utils/changelog.ts +98 -0
  631. package/src/utils/event-bus.ts +33 -0
  632. package/src/utils/external-editor.ts +59 -0
  633. package/src/utils/file-display-mode.ts +36 -0
  634. package/src/utils/file-mentions.ts +384 -0
  635. package/src/utils/frontmatter.ts +101 -0
  636. package/src/utils/fuzzy.ts +108 -0
  637. package/src/utils/ignore-files.ts +119 -0
  638. package/src/utils/image-convert.ts +27 -0
  639. package/src/utils/image-resize.ts +236 -0
  640. package/src/utils/mime.ts +30 -0
  641. package/src/utils/open.ts +20 -0
  642. package/src/utils/shell-snapshot.ts +199 -0
  643. package/src/utils/timings.ts +26 -0
  644. package/src/utils/title-generator.ts +167 -0
  645. package/src/utils/tools-manager.ts +362 -0
  646. package/src/web/scrapers/artifacthub.ts +215 -0
  647. package/src/web/scrapers/arxiv.ts +88 -0
  648. package/src/web/scrapers/aur.ts +175 -0
  649. package/src/web/scrapers/biorxiv.ts +141 -0
  650. package/src/web/scrapers/bluesky.ts +284 -0
  651. package/src/web/scrapers/brew.ts +177 -0
  652. package/src/web/scrapers/cheatsh.ts +78 -0
  653. package/src/web/scrapers/chocolatey.ts +158 -0
  654. package/src/web/scrapers/choosealicense.ts +110 -0
  655. package/src/web/scrapers/cisa-kev.ts +100 -0
  656. package/src/web/scrapers/clojars.ts +180 -0
  657. package/src/web/scrapers/coingecko.ts +184 -0
  658. package/src/web/scrapers/crates-io.ts +128 -0
  659. package/src/web/scrapers/crossref.ts +149 -0
  660. package/src/web/scrapers/devto.ts +177 -0
  661. package/src/web/scrapers/discogs.ts +307 -0
  662. package/src/web/scrapers/discourse.ts +221 -0
  663. package/src/web/scrapers/dockerhub.ts +160 -0
  664. package/src/web/scrapers/fdroid.ts +158 -0
  665. package/src/web/scrapers/firefox-addons.ts +214 -0
  666. package/src/web/scrapers/flathub.ts +239 -0
  667. package/src/web/scrapers/github-gist.ts +68 -0
  668. package/src/web/scrapers/github.ts +490 -0
  669. package/src/web/scrapers/gitlab.ts +456 -0
  670. package/src/web/scrapers/go-pkg.ts +275 -0
  671. package/src/web/scrapers/hackage.ts +94 -0
  672. package/src/web/scrapers/hackernews.ts +208 -0
  673. package/src/web/scrapers/hex.ts +121 -0
  674. package/src/web/scrapers/huggingface.ts +385 -0
  675. package/src/web/scrapers/iacr.ts +86 -0
  676. package/src/web/scrapers/index.ts +249 -0
  677. package/src/web/scrapers/jetbrains-marketplace.ts +169 -0
  678. package/src/web/scrapers/lemmy.ts +220 -0
  679. package/src/web/scrapers/lobsters.ts +186 -0
  680. package/src/web/scrapers/mastodon.ts +310 -0
  681. package/src/web/scrapers/maven.ts +152 -0
  682. package/src/web/scrapers/mdn.ts +172 -0
  683. package/src/web/scrapers/metacpan.ts +253 -0
  684. package/src/web/scrapers/musicbrainz.ts +272 -0
  685. package/src/web/scrapers/npm.ts +114 -0
  686. package/src/web/scrapers/nuget.ts +205 -0
  687. package/src/web/scrapers/nvd.ts +243 -0
  688. package/src/web/scrapers/ollama.ts +265 -0
  689. package/src/web/scrapers/open-vsx.ts +119 -0
  690. package/src/web/scrapers/opencorporates.ts +275 -0
  691. package/src/web/scrapers/openlibrary.ts +319 -0
  692. package/src/web/scrapers/orcid.ts +298 -0
  693. package/src/web/scrapers/osv.ts +192 -0
  694. package/src/web/scrapers/packagist.ts +174 -0
  695. package/src/web/scrapers/pub-dev.ts +185 -0
  696. package/src/web/scrapers/pubmed.ts +177 -0
  697. package/src/web/scrapers/pypi.ts +129 -0
  698. package/src/web/scrapers/rawg.ts +124 -0
  699. package/src/web/scrapers/readthedocs.ts +125 -0
  700. package/src/web/scrapers/reddit.ts +104 -0
  701. package/src/web/scrapers/repology.ts +262 -0
  702. package/src/web/scrapers/rfc.ts +209 -0
  703. package/src/web/scrapers/rubygems.ts +117 -0
  704. package/src/web/scrapers/searchcode.ts +217 -0
  705. package/src/web/scrapers/sec-edgar.ts +274 -0
  706. package/src/web/scrapers/semantic-scholar.ts +190 -0
  707. package/src/web/scrapers/snapcraft.ts +200 -0
  708. package/src/web/scrapers/sourcegraph.ts +373 -0
  709. package/src/web/scrapers/spdx.ts +121 -0
  710. package/src/web/scrapers/spotify.ts +217 -0
  711. package/src/web/scrapers/stackoverflow.ts +124 -0
  712. package/src/web/scrapers/terraform.ts +304 -0
  713. package/src/web/scrapers/tldr.ts +51 -0
  714. package/src/web/scrapers/twitter.ts +97 -0
  715. package/src/web/scrapers/types.ts +200 -0
  716. package/src/web/scrapers/utils.ts +142 -0
  717. package/src/web/scrapers/vimeo.ts +152 -0
  718. package/src/web/scrapers/vscode-marketplace.ts +195 -0
  719. package/src/web/scrapers/w3c.ts +163 -0
  720. package/src/web/scrapers/wikidata.ts +357 -0
  721. package/src/web/scrapers/wikipedia.ts +95 -0
  722. package/src/web/scrapers/youtube.ts +312 -0
  723. package/src/web/search/auth.ts +178 -0
  724. package/src/web/search/index.ts +598 -0
  725. package/src/web/search/provider.ts +77 -0
  726. package/src/web/search/providers/anthropic.ts +284 -0
  727. package/src/web/search/providers/base.ts +22 -0
  728. package/src/web/search/providers/brave.ts +165 -0
  729. package/src/web/search/providers/codex.ts +377 -0
  730. package/src/web/search/providers/exa.ts +158 -0
  731. package/src/web/search/providers/gemini.ts +437 -0
  732. package/src/web/search/providers/jina.ts +99 -0
  733. package/src/web/search/providers/kimi.ts +196 -0
  734. package/src/web/search/providers/perplexity.ts +546 -0
  735. package/src/web/search/providers/synthetic.ts +136 -0
  736. package/src/web/search/providers/zai.ts +352 -0
  737. package/src/web/search/render.ts +299 -0
  738. package/src/web/search/types.ts +437 -0
@@ -0,0 +1,1374 @@
1
+ import * as os from "node:os";
2
+ import * as path from "node:path";
3
+ import { Readability } from "@mozilla/readability";
4
+ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@nghyane/arcane-agent";
5
+ import { StringEnum } from "@nghyane/arcane-ai";
6
+ import { logger, Snowflake, untilAborted } from "@nghyane/arcane-utils";
7
+ import { getPuppeteerDir } from "@nghyane/arcane-utils/dirs";
8
+ import { type Static, Type } from "@sinclair/typebox";
9
+ import { type HTMLElement, parseHTML } from "linkedom";
10
+ import type {
11
+ Browser,
12
+ CDPSession,
13
+ ElementHandle,
14
+ KeyInput,
15
+ Page,
16
+ default as Puppeteer,
17
+ SerializedAXNode,
18
+ } from "puppeteer";
19
+ import { renderPromptTemplate } from "../config/prompt-templates";
20
+ import browserDescription from "../prompts/tools/browser.md" with { type: "text" };
21
+ import type { ToolSession } from "../sdk";
22
+ import { formatDimensionNote, resizeImage } from "../utils/image-resize";
23
+ import { htmlToBasicMarkdown } from "../web/scrapers/types";
24
+ import type { OutputMeta } from "./output-meta";
25
+ import stealthTamperingScript from "./puppeteer/00_stealth_tampering.txt" with { type: "text" };
26
+ import stealthActivityScript from "./puppeteer/01_stealth_activity.txt" with { type: "text" };
27
+ import stealthHairlineScript from "./puppeteer/02_stealth_hairline.txt" with { type: "text" };
28
+ import stealthBotdScript from "./puppeteer/03_stealth_botd.txt" with { type: "text" };
29
+ import stealthIframeScript from "./puppeteer/04_stealth_iframe.txt" with { type: "text" };
30
+ import stealthWebglScript from "./puppeteer/05_stealth_webgl.txt" with { type: "text" };
31
+ import stealthScreenScript from "./puppeteer/06_stealth_screen.txt" with { type: "text" };
32
+ import stealthFontsScript from "./puppeteer/07_stealth_fonts.txt" with { type: "text" };
33
+ import stealthAudioScript from "./puppeteer/08_stealth_audio.txt" with { type: "text" };
34
+ import stealthLocaleScript from "./puppeteer/09_stealth_locale.txt" with { type: "text" };
35
+ import stealthPluginsScript from "./puppeteer/10_stealth_plugins.txt" with { type: "text" };
36
+ import stealthHardwareScript from "./puppeteer/11_stealth_hardware.txt" with { type: "text" };
37
+ import stealthCodecsScript from "./puppeteer/12_stealth_codecs.txt" with { type: "text" };
38
+ import stealthWorkerScript from "./puppeteer/13_stealth_worker.txt" with { type: "text" };
39
+ import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
40
+ import { toolResult } from "./tool-result";
41
+
42
+ /**
43
+ * Lazy-import puppeteer from a safe CWD so cosmiconfig doesn't choke
44
+ * on malformed package.json files in the user's project tree.
45
+ */
46
+ let puppeteerModule: typeof Puppeteer | undefined;
47
+ async function loadPuppeteer(): Promise<typeof Puppeteer> {
48
+ if (puppeteerModule) return puppeteerModule;
49
+ const prev = process.cwd();
50
+ const safeDir = getPuppeteerDir();
51
+ await Bun.write(path.join(safeDir, "package.json"), "{}");
52
+ try {
53
+ process.chdir(safeDir);
54
+ puppeteerModule = (await import("puppeteer")).default;
55
+ return puppeteerModule;
56
+ } finally {
57
+ process.chdir(prev);
58
+ }
59
+ }
60
+
61
+ const DEFAULT_TIMEOUT_SECONDS = 30;
62
+ const MAX_TIMEOUT_SECONDS = 120;
63
+ const DEFAULT_VIEWPORT = { width: 1365, height: 768, deviceScaleFactor: 1.25 };
64
+ const STEALTH_IGNORE_DEFAULT_ARGS = [
65
+ "--disable-extensions",
66
+ "--disable-default-apps",
67
+ "--disable-component-extensions-with-background-pages",
68
+ ];
69
+ const STEALTH_ACCEPT_LANGUAGE = "en-US,en";
70
+ const PUPPETEER_SOURCE_URL_SUFFIX = "//# sourceURL=__puppeteer_evaluation_script__";
71
+ const INTERACTIVE_AX_ROLES = new Set([
72
+ "button",
73
+ "link",
74
+ "textbox",
75
+ "combobox",
76
+ "listbox",
77
+ "option",
78
+ "checkbox",
79
+ "radio",
80
+ "switch",
81
+ "tab",
82
+ "menuitem",
83
+ "menuitemcheckbox",
84
+ "menuitemradio",
85
+ "slider",
86
+ "spinbutton",
87
+ "searchbox",
88
+ "treeitem",
89
+ ]);
90
+
91
+ declare global {
92
+ interface Element extends HTMLElement {}
93
+
94
+ function getCarcutedStyle(element: Element): Record<string, unknown>;
95
+ var innerWidth: number;
96
+ var innerHeight: number;
97
+ var document: {
98
+ elementFromPoint(x: number, y: number): Element | null;
99
+ };
100
+ }
101
+
102
+ const LEGACY_SELECTOR_PREFIXES = ["p-aria/", "p-text/", "p-xpath/", "p-pierce/"] as const;
103
+
104
+ function normalizeSelector(selector: string): string {
105
+ if (!selector) return selector;
106
+ if (selector.startsWith("p-") && !LEGACY_SELECTOR_PREFIXES.some(prefix => selector.startsWith(prefix))) {
107
+ throw new ToolError(
108
+ `Unsupported selector prefix. Use CSS or puppeteer query handlers (aria/, text/, xpath/, pierce/). Got: ${selector}`,
109
+ );
110
+ }
111
+ if (selector.startsWith("p-text/")) {
112
+ return `text/${selector.slice("p-text/".length)}`;
113
+ }
114
+ if (selector.startsWith("p-xpath/")) {
115
+ return `xpath/${selector.slice("p-xpath/".length)}`;
116
+ }
117
+ if (selector.startsWith("p-pierce/")) {
118
+ return `pierce/${selector.slice("p-pierce/".length)}`;
119
+ }
120
+ if (selector.startsWith("p-aria/")) {
121
+ const rest = selector.slice("p-aria/".length);
122
+ // Playwright-style: p-aria/[name="Sign in"] → aria/Sign in
123
+ const nameMatch = rest.match(/\[\s*name\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\]]+))\s*\]/);
124
+ const name = nameMatch?.[1] ?? nameMatch?.[2] ?? nameMatch?.[3];
125
+ if (name) return `aria/${name.trim()}`;
126
+ return `aria/${rest}`;
127
+ }
128
+ return selector;
129
+ }
130
+
131
+ type ActionabilityResult = { ok: true; x: number; y: number } | { ok: false; reason: string };
132
+
133
+ async function resolveActionableQueryHandlerClickTarget(handles: ElementHandle[]): Promise<ElementHandle | null> {
134
+ const candidates: Array<{
135
+ handle: ElementHandle;
136
+ rect: { x: number; y: number; w: number; h: number };
137
+ ownedProxy?: ElementHandle;
138
+ }> = [];
139
+
140
+ for (const handle of handles) {
141
+ let clickable: ElementHandle = handle;
142
+ let clickableProxy: ElementHandle | null = null;
143
+ try {
144
+ const proxy = await handle.evaluateHandle(el => {
145
+ const target =
146
+ (el as Element).closest(
147
+ 'a,button,[role="button"],[role="link"],input[type="button"],input[type="submit"]',
148
+ ) ?? el;
149
+ return target;
150
+ });
151
+ const nodeHandle = proxy.asElement();
152
+ clickableProxy = nodeHandle ? (nodeHandle as unknown as ElementHandle) : null;
153
+ if (clickableProxy) {
154
+ clickable = clickableProxy;
155
+ }
156
+ } catch {
157
+ // ignore
158
+ }
159
+
160
+ try {
161
+ const intersecting = await clickable.isIntersectingViewport();
162
+ if (!intersecting) continue;
163
+ const rect = (await clickable.evaluate(el => {
164
+ const r = (el as Element).getBoundingClientRect();
165
+ return { x: r.left, y: r.top, w: r.width, h: r.height };
166
+ })) as { x: number; y: number; w: number; h: number };
167
+ if (rect.w < 1 || rect.h < 1) continue;
168
+ candidates.push({ handle: clickable, rect, ownedProxy: clickableProxy ?? undefined });
169
+ } catch {
170
+ // ignore
171
+ } finally {
172
+ if (clickableProxy && clickableProxy !== handle && clickable !== clickableProxy) {
173
+ try {
174
+ await clickableProxy.dispose();
175
+ } catch {}
176
+ }
177
+ }
178
+ }
179
+
180
+ if (!candidates.length) return null;
181
+
182
+ // Prefer top-most visible element (nav/header usually wins), tie-break by left-most.
183
+ candidates.sort((a, b) => a.rect.y - b.rect.y || a.rect.x - b.rect.x);
184
+ const winner = candidates[0]?.handle ?? null;
185
+ // Dispose owned proxies for non-winning candidates
186
+ for (let i = 1; i < candidates.length; i++) {
187
+ const c = candidates[i]!;
188
+ if (c.ownedProxy) {
189
+ try {
190
+ await c.ownedProxy.dispose();
191
+ } catch {}
192
+ }
193
+ }
194
+ return winner;
195
+ }
196
+
197
+ async function isClickActionable(handle: ElementHandle): Promise<ActionabilityResult> {
198
+ return (await handle.evaluate(el => {
199
+ const element = el as HTMLElement;
200
+ const style = globalThis.getCarcutedStyle(element);
201
+ if (style.display === "none") return { ok: false as const, reason: "display:none" };
202
+ if (style.visibility === "hidden") return { ok: false as const, reason: "visibility:hidden" };
203
+ if (style.pointerEvents === "none") return { ok: false as const, reason: "pointer-events:none" };
204
+ if (Number(style.opacity) === 0) return { ok: false as const, reason: "opacity:0" };
205
+
206
+ const r = element.getBoundingClientRect();
207
+ if (r.width < 1 || r.height < 1) return { ok: false as const, reason: "zero-size" };
208
+
209
+ const vw = globalThis.innerWidth;
210
+ const vh = globalThis.innerHeight;
211
+ const left = Math.max(0, Math.min(vw, r.left));
212
+ const right = Math.max(0, Math.min(vw, r.right));
213
+ const top = Math.max(0, Math.min(vh, r.top));
214
+ const bottom = Math.max(0, Math.min(vh, r.bottom));
215
+ if (right - left < 1 || bottom - top < 1) return { ok: false as const, reason: "off-viewport" };
216
+
217
+ const x = Math.floor((left + right) / 2);
218
+ const y = Math.floor((top + bottom) / 2);
219
+ const topEl = globalThis.document.elementFromPoint(x, y);
220
+ if (!topEl) return { ok: false as const, reason: "elementFromPoint-null" };
221
+ if (topEl === element || element.contains(topEl) || (topEl as Element).contains(element)) {
222
+ return { ok: true as const, x, y };
223
+ }
224
+ return { ok: false as const, reason: "obscured" };
225
+ })) as ActionabilityResult;
226
+ }
227
+
228
+ async function clickQueryHandlerText(
229
+ page: Page,
230
+ selector: string,
231
+ timeoutMs: number,
232
+ signal?: AbortSignal,
233
+ ): Promise<void> {
234
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
235
+ const clickSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
236
+ const start = Date.now();
237
+ let lastSeen = 0;
238
+ let lastReason: string | null = null;
239
+
240
+ while (Date.now() - start < timeoutMs) {
241
+ throwIfAborted(clickSignal);
242
+ const handles = (await untilAborted(clickSignal, () => page.$$(selector))) as ElementHandle[];
243
+ try {
244
+ lastSeen = handles.length;
245
+ const target = await resolveActionableQueryHandlerClickTarget(handles);
246
+ if (!target) {
247
+ lastReason = handles.length ? "no-visible-candidate" : "no-matches";
248
+ await Bun.sleep(100);
249
+ continue;
250
+ }
251
+ const actionability = await isClickActionable(target);
252
+ if (!actionability.ok) {
253
+ lastReason = actionability.reason;
254
+ await Bun.sleep(100);
255
+ continue;
256
+ }
257
+
258
+ try {
259
+ await untilAborted(clickSignal, () => target.click());
260
+ return;
261
+ } catch (err) {
262
+ lastReason = err instanceof Error ? err.message : String(err);
263
+ await Bun.sleep(100);
264
+ }
265
+ } finally {
266
+ await Promise.all(
267
+ handles.map(async h => {
268
+ try {
269
+ await h.dispose();
270
+ } catch {}
271
+ }),
272
+ );
273
+ }
274
+ }
275
+
276
+ throw new ToolError(
277
+ `Timed out clicking ${selector} (seen ${lastSeen} matches; last reason: ${lastReason ?? "unknown"}). ` +
278
+ "If there are multiple matching elements, use observe+click_id or a more specific selector.",
279
+ );
280
+ }
281
+
282
+ /**
283
+ * Stealth init scripts for Puppeteer.
284
+ */
285
+
286
+ type PuppeteerCdpClient = {
287
+ send: (method: string, params?: Record<string, unknown>) => Promise<unknown>;
288
+ };
289
+
290
+ type UserAgentOverride = {
291
+ userAgent: string;
292
+ platform: string;
293
+ acceptLanguage: string;
294
+ userAgentMetadata: {
295
+ brands: Array<{ brand: string; version: string }>;
296
+ fullVersion: string;
297
+ platform: string;
298
+ platformVersion: string;
299
+ architecture: string;
300
+ model: string;
301
+ mobile: boolean;
302
+ };
303
+ };
304
+
305
+ function resolvePageClient(page: Page): PuppeteerCdpClient | null {
306
+ const pageWithClient = page as Page & {
307
+ _client?: (() => PuppeteerCdpClient) | PuppeteerCdpClient;
308
+ };
309
+ if (!pageWithClient._client) return null;
310
+ return typeof pageWithClient._client === "function" ? pageWithClient._client() : pageWithClient._client;
311
+ }
312
+
313
+ const puppeteerGetArgsSchema = Type.Array(
314
+ Type.Object({
315
+ selector: Type.String({
316
+ description:
317
+ "Selector for the target element (CSS, or puppeteer query handler like aria/, text/, xpath/, pierce/; also accepts legacy p- prefixes)",
318
+ }),
319
+ attribute: Type.Optional(Type.String({ description: "Attribute name (get_attribute)" })),
320
+ }),
321
+ { description: "Batch arguments for get_* actions", minItems: 1 },
322
+ );
323
+
324
+ const browserSchema = Type.Object({
325
+ action: StringEnum(
326
+ [
327
+ "open",
328
+ "goto",
329
+ "observe",
330
+ "click",
331
+ "click_id",
332
+ "type",
333
+ "type_id",
334
+ "fill",
335
+ "fill_id",
336
+ "press",
337
+ "scroll",
338
+ "drag",
339
+ "wait_for_selector",
340
+ "evaluate",
341
+ "get_text",
342
+ "get_html",
343
+ "get_attribute",
344
+ "extract_readable",
345
+ "screenshot",
346
+ "close",
347
+ ],
348
+ { description: "Action to perform" },
349
+ ),
350
+ url: Type.Optional(Type.String({ description: "URL to navigate to (goto)" })),
351
+ selector: Type.Optional(
352
+ Type.String({
353
+ description:
354
+ "Selector for the target element (CSS, or puppeteer query handler like aria/, text/, xpath/, pierce/; also accepts legacy p- prefixes)",
355
+ }),
356
+ ),
357
+ element_id: Type.Optional(Type.Number({ description: "Element ID from observe" })),
358
+ include_all: Type.Optional(Type.Boolean({ description: "Include non-interactive nodes in observe" })),
359
+ viewport_only: Type.Optional(Type.Boolean({ description: "Limit observe output to elements in the viewport" })),
360
+ args: Type.Optional(puppeteerGetArgsSchema),
361
+ script: Type.Optional(Type.String({ description: "JavaScript to evaluate (evaluate)" })),
362
+ text: Type.Optional(Type.String({ description: "Text to type (type)" })),
363
+ value: Type.Optional(Type.String({ description: "Value to set (fill)" })),
364
+ attribute: Type.Optional(Type.String({ description: "Attribute name to read (get_attribute)" })),
365
+ key: Type.Optional(Type.String({ description: "Keyboard key to press (press)" })),
366
+ timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 30)" })),
367
+ wait_until: Type.Optional(
368
+ StringEnum(["load", "domcontentloaded", "networkidle0", "networkidle2"], {
369
+ description: "Navigation wait condition (goto)",
370
+ }),
371
+ ),
372
+ full_page: Type.Optional(Type.Boolean({ description: "Capture full page screenshot (screenshot)" })),
373
+ format: Type.Optional(
374
+ StringEnum(["text", "markdown"], {
375
+ description: "Output format for extract_readable (text/markdown)",
376
+ }),
377
+ ),
378
+ path: Type.Optional(Type.String({ description: "Optional path to save screenshot (relative to cwd)" })),
379
+ viewport: Type.Optional(
380
+ Type.Object({
381
+ width: Type.Number({ description: "Viewport width in pixels" }),
382
+ height: Type.Number({ description: "Viewport height in pixels" }),
383
+ deviceScaleFactor: Type.Optional(Type.Number({ description: "Device scale factor" })),
384
+ }),
385
+ ),
386
+ delta_x: Type.Optional(Type.Number({ description: "Scroll delta X (scroll)" })),
387
+ delta_y: Type.Optional(Type.Number({ description: "Scroll delta Y (scroll)" })),
388
+ from_selector: Type.Optional(
389
+ Type.String({
390
+ description:
391
+ "Drag start selector (CSS, or puppeteer query handler like aria/, text/, xpath/, pierce/; also accepts legacy p- prefixes)",
392
+ }),
393
+ ),
394
+ to_selector: Type.Optional(
395
+ Type.String({
396
+ description:
397
+ "Drag end selector (CSS, or puppeteer query handler like aria/, text/, xpath/, pierce/; also accepts legacy p- prefixes)",
398
+ }),
399
+ ),
400
+ });
401
+
402
+ /** Input schema for the Puppeteer tool. */
403
+ export type BrowserParams = Static<typeof browserSchema>;
404
+
405
+ /** Details describing a Puppeteer tool execution result. */
406
+ export interface BrowserToolDetails {
407
+ action: BrowserParams["action"];
408
+ url?: string;
409
+ selector?: string;
410
+ elementId?: number;
411
+ result?: string | string[];
412
+ screenshotPath?: string;
413
+ mimeType?: string;
414
+ bytes?: number;
415
+ viewport?: { width: number; height: number; deviceScaleFactor?: number };
416
+ observation?: Observation;
417
+ readable?: ReadableResult;
418
+ meta?: OutputMeta;
419
+ }
420
+
421
+ export interface ObservationEntry {
422
+ id: number;
423
+ role: string;
424
+ name?: string;
425
+ value?: string | number;
426
+ description?: string;
427
+ keyshortcuts?: string;
428
+ states: string[];
429
+ }
430
+
431
+ export interface Observation {
432
+ url: string;
433
+ title?: string;
434
+ viewport: { width: number; height: number; deviceScaleFactor?: number };
435
+ scroll: {
436
+ x: number;
437
+ y: number;
438
+ width: number;
439
+ height: number;
440
+ scrollWidth: number;
441
+ scrollHeight: number;
442
+ };
443
+ elements: ObservationEntry[];
444
+ }
445
+
446
+ export interface ReadableResult {
447
+ url: string;
448
+ title?: string;
449
+ byline?: string;
450
+ excerpt?: string;
451
+ contentLength: number;
452
+ text?: string;
453
+ markdown?: string;
454
+ }
455
+
456
+ function clampTimeout(timeoutSeconds?: number): number {
457
+ if (timeoutSeconds === undefined) return DEFAULT_TIMEOUT_SECONDS;
458
+ return Math.min(Math.max(timeoutSeconds, 1), MAX_TIMEOUT_SECONDS);
459
+ }
460
+
461
+ function ensureParam<T>(value: T | undefined, name: string, action: string): T {
462
+ if (value === undefined || value === null || value === "") {
463
+ throw new ToolError(`Missing required parameter '${name}' for action '${action}'.`);
464
+ }
465
+ return value;
466
+ }
467
+
468
+ function formatEvaluateResult(value: unknown): string {
469
+ if (typeof value === "string") return value;
470
+ if (value === undefined) return "undefined";
471
+ try {
472
+ const serialized = JSON.stringify(value, null, 2);
473
+ return serialized ?? "undefined";
474
+ } catch {
475
+ return String(value);
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Puppeteer tool for headless browser automation.
481
+ */
482
+ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolDetails> {
483
+ readonly name = "puppeteer";
484
+ readonly label = "Puppeteer";
485
+ readonly description: string;
486
+ readonly parameters = browserSchema;
487
+ #browser: Browser | null = null;
488
+ #page: Page | null = null;
489
+ #currentHeadless: boolean | null = null;
490
+ #browserSession: CDPSession | null = null;
491
+ #userAgentOverride: UserAgentOverride | null = null;
492
+ #elementIdCounter = 0;
493
+ readonly #elementCache = new Map<number, ElementHandle>();
494
+ readonly #patchedClients = new WeakSet<object>();
495
+
496
+ constructor(private readonly session: ToolSession) {
497
+ this.description = renderPromptTemplate(browserDescription, {});
498
+ }
499
+
500
+ async #closeBrowser(): Promise<void> {
501
+ await this.#clearElementCache();
502
+ if (this.#page && !this.#page.isClosed()) {
503
+ await this.#page.close();
504
+ }
505
+ this.#page = null;
506
+ if (this.#browser?.connected) {
507
+ await this.#browser.close();
508
+ }
509
+ this.#browser = null;
510
+ this.#browserSession = null;
511
+ this.#userAgentOverride = null;
512
+ }
513
+
514
+ async #resetBrowser(params?: BrowserParams): Promise<Page> {
515
+ await this.#closeBrowser();
516
+ this.#currentHeadless = this.session.settings.get("browser.headless");
517
+ const initialViewport = params?.viewport ?? DEFAULT_VIEWPORT;
518
+ const puppeteer = await loadPuppeteer();
519
+ this.#browser = await puppeteer.launch({
520
+ headless: this.#currentHeadless,
521
+ defaultViewport: this.#currentHeadless ? initialViewport : null,
522
+ args: [
523
+ "--no-sandbox",
524
+ "--disable-setuid-sandbox",
525
+ "--disable-blink-features=AutomationControlled",
526
+ `--window-size=${initialViewport.width},${initialViewport.height}`,
527
+ ],
528
+ ignoreDefaultArgs: [...STEALTH_IGNORE_DEFAULT_ARGS],
529
+ });
530
+ this.#page = await this.#browser.newPage();
531
+ await this.#applyStealthPatches(this.#page);
532
+ if (this.#currentHeadless || params?.viewport) {
533
+ await this.#applyViewport(this.#page, params?.viewport);
534
+ }
535
+ return this.#page;
536
+ }
537
+
538
+ async #ensurePage(params?: BrowserParams): Promise<Page> {
539
+ const desiredHeadless = this.session.settings.get("browser.headless");
540
+ if (this.#currentHeadless !== null && this.#currentHeadless !== desiredHeadless) {
541
+ return this.#resetBrowser(params);
542
+ }
543
+ if (this.#page && !this.#page.isClosed()) {
544
+ return this.#page;
545
+ }
546
+ if (!this.#browser || !this.#browser.isConnected()) {
547
+ return this.#resetBrowser(params);
548
+ }
549
+ this.#page = await this.#browser.newPage();
550
+ await this.#applyStealthPatches(this.#page);
551
+ if (this.#currentHeadless || params?.viewport) {
552
+ await this.#applyViewport(this.#page, params?.viewport);
553
+ }
554
+ return this.#page;
555
+ }
556
+
557
+ async #applyViewport(page: Page, viewport?: BrowserParams["viewport"]): Promise<void> {
558
+ const target = viewport ?? DEFAULT_VIEWPORT;
559
+ await page.setViewport(target);
560
+ }
561
+
562
+ async #clearElementCache(): Promise<void> {
563
+ if (this.#elementCache.size === 0) {
564
+ this.#elementIdCounter = 0;
565
+ return;
566
+ }
567
+ const handles = Array.from(this.#elementCache.values());
568
+ this.#elementCache.clear();
569
+ this.#elementIdCounter = 0;
570
+ await Promise.all(
571
+ handles.map(async handle => {
572
+ try {
573
+ await handle.dispose();
574
+ } catch {
575
+ return;
576
+ }
577
+ }),
578
+ );
579
+ }
580
+
581
+ async #resolveCachedHandle(id: number): Promise<ElementHandle> {
582
+ const handle = this.#elementCache.get(id);
583
+ if (!handle) {
584
+ throw new ToolError(`Unknown element_id ${id}. Run observe to refresh the element list.`);
585
+ }
586
+ try {
587
+ const isConnected = (await handle.evaluate(el => el.isConnected)) as boolean;
588
+ if (!isConnected) {
589
+ await this.#clearElementCache();
590
+ throw new ToolError(`Element_id ${id} is stale. Run observe again.`);
591
+ }
592
+ } catch {
593
+ await this.#clearElementCache();
594
+ throw new ToolError(`Element_id ${id} is stale. Run observe again.`);
595
+ }
596
+ return handle;
597
+ }
598
+
599
+ #isInteractiveNode(node: SerializedAXNode): boolean {
600
+ if (INTERACTIVE_AX_ROLES.has(node.role)) return true;
601
+ return (
602
+ node.checked !== undefined ||
603
+ node.pressed !== undefined ||
604
+ node.selected !== undefined ||
605
+ node.expanded !== undefined ||
606
+ node.focused === true
607
+ );
608
+ }
609
+
610
+ async #collectObservationEntries(
611
+ node: SerializedAXNode,
612
+ entries: ObservationEntry[],
613
+ options: { viewportOnly: boolean; includeAll: boolean },
614
+ ): Promise<void> {
615
+ if (options.includeAll || this.#isInteractiveNode(node)) {
616
+ const handle = await node.elementHandle();
617
+ if (handle) {
618
+ let inViewport = true;
619
+ if (options.viewportOnly) {
620
+ try {
621
+ inViewport = await handle.isIntersectingViewport();
622
+ } catch {
623
+ inViewport = false;
624
+ }
625
+ }
626
+ if (inViewport) {
627
+ const id = ++this.#elementIdCounter;
628
+ const states: string[] = [];
629
+ if (node.disabled) states.push("disabled");
630
+ if (node.checked !== undefined) states.push(`checked=${String(node.checked)}`);
631
+ if (node.pressed !== undefined) states.push(`pressed=${String(node.pressed)}`);
632
+ if (node.selected !== undefined) states.push(`selected=${String(node.selected)}`);
633
+ if (node.expanded !== undefined) states.push(`expanded=${String(node.expanded)}`);
634
+ if (node.required) states.push("required");
635
+ if (node.readonly) states.push("readonly");
636
+ if (node.multiselectable) states.push("multiselectable");
637
+ if (node.multiline) states.push("multiline");
638
+ if (node.modal) states.push("modal");
639
+ if (node.focused) states.push("focused");
640
+ this.#elementCache.set(id, handle);
641
+ entries.push({
642
+ id,
643
+ role: node.role,
644
+ name: node.name,
645
+ value: node.value,
646
+ description: node.description,
647
+ keyshortcuts: node.keyshortcuts,
648
+ states,
649
+ });
650
+ } else {
651
+ await handle.dispose();
652
+ }
653
+ }
654
+ }
655
+ for (const child of node.children ?? []) {
656
+ await this.#collectObservationEntries(child, entries, options);
657
+ }
658
+ }
659
+
660
+ #formatObservation(observation: Observation): string {
661
+ const viewport = `${observation.viewport.width}x${observation.viewport.height}`;
662
+ const scroll = `x=${observation.scroll.x} y=${observation.scroll.y} viewport=${observation.scroll.width}x${observation.scroll.height} doc=${observation.scroll.scrollWidth}x${observation.scroll.scrollHeight}`;
663
+ const lines = [
664
+ `URL: ${observation.url}`,
665
+ observation.title ? `Title: ${observation.title}` : "Title:",
666
+ `Viewport: ${viewport}`,
667
+ `Scroll: ${scroll}`,
668
+ "Elements:",
669
+ ];
670
+ for (const entry of observation.elements) {
671
+ const name = entry.name ? ` "${entry.name}"` : "";
672
+ const value = entry.value !== undefined ? ` value=${JSON.stringify(entry.value)}` : "";
673
+ const description = entry.description ? ` desc=${JSON.stringify(entry.description)}` : "";
674
+ const shortcuts = entry.keyshortcuts ? ` shortcuts=${JSON.stringify(entry.keyshortcuts)}` : "";
675
+ const state = entry.states.length ? ` (${entry.states.join(", ")})` : "";
676
+ lines.push(`${entry.id}. ${entry.role}${name}${value}${description}${shortcuts}${state}`);
677
+ }
678
+ return lines.join("\n");
679
+ }
680
+
681
+ /**
682
+ * Restart the browser to apply changes like headless mode.
683
+ */
684
+ async restartForModeChange(): Promise<void> {
685
+ await this.#resetBrowser();
686
+ }
687
+
688
+ async #applyStealthPatches(page: Page): Promise<void> {
689
+ this.#patchSourceUrl(page);
690
+ await this.#applyUserAgentOverride(page);
691
+ await this.#injectStealthScripts(page);
692
+ }
693
+
694
+ async #applyUserAgentOverride(page: Page): Promise<void> {
695
+ const client = resolvePageClient(page);
696
+ if (!client) return;
697
+ const override = await this.#resolveUserAgentOverride(page);
698
+ await this.#sendUserAgentOverride(client, override);
699
+ await this.#configureUserAgentTargets(override);
700
+ }
701
+
702
+ async #resolveUserAgentOverride(page: Page): Promise<UserAgentOverride> {
703
+ if (this.#userAgentOverride) return this.#userAgentOverride;
704
+ const rawUserAgent = await page.browser().userAgent();
705
+ let userAgent = rawUserAgent.replace("HeadlessChrome/", "Chrome/");
706
+ if (userAgent.includes("Linux") && !userAgent.includes("Android")) {
707
+ userAgent = userAgent.replace(/\(([^)]+)\)/, "(Windows NT 10.0; Win64; x64)");
708
+ }
709
+
710
+ const uaVersionMatch = userAgent.match(/Chrome\/([\d|.]+)/);
711
+ const fallbackVersionMatch = uaVersionMatch ?? (await page.browser().version()).match(/\/([\d|.]+)/);
712
+ const uaVersion = fallbackVersionMatch?.[1] ?? "0";
713
+ const majorVersion = Number.parseInt(uaVersion.split(".")[0] ?? "0", 10) || 0;
714
+ const isAndroid = userAgent.includes("Android");
715
+ const platform = userAgent.includes("Mac OS X")
716
+ ? "MacIntel"
717
+ : isAndroid
718
+ ? "Android"
719
+ : userAgent.includes("Linux")
720
+ ? "Linux"
721
+ : "Win32";
722
+ const platformFull = userAgent.includes("Mac OS X")
723
+ ? "Mac OS X"
724
+ : isAndroid
725
+ ? "Android"
726
+ : userAgent.includes("Linux")
727
+ ? "Linux"
728
+ : "Windows";
729
+ const platformVersion = userAgent.includes("Mac OS X ")
730
+ ? (userAgent.match(/Mac OS X ([^)]+)/)?.[1] ?? "")
731
+ : userAgent.includes("Android ")
732
+ ? (userAgent.match(/Android ([^;]+)/)?.[1] ?? "")
733
+ : userAgent.includes("Windows ")
734
+ ? (userAgent.match(/Windows .*?([\d|.]+);?/)?.[1] ?? "")
735
+ : "";
736
+ const architecture = isAndroid ? "" : "x86";
737
+ const model = isAndroid ? (userAgent.match(/Android.*?;\s([^)]+)/)?.[1] ?? "") : "";
738
+
739
+ const brandOrders = [
740
+ [0, 1, 2],
741
+ [0, 2, 1],
742
+ [1, 0, 2],
743
+ [1, 2, 0],
744
+ [2, 0, 1],
745
+ [2, 1, 0],
746
+ ];
747
+ const order = brandOrders[majorVersion % brandOrders.length] ?? brandOrders[0];
748
+ const escapedChars = [" ", " ", ";"];
749
+ const greaseyBrand = `${escapedChars[order[0]]}Not${escapedChars[order[1]]}A${escapedChars[order[2]]}Brand`;
750
+ const brands: { brand: string; version: string }[] = [];
751
+ brands[order[0]] = { brand: greaseyBrand, version: "99" };
752
+ brands[order[1]] = { brand: "Chromium", version: String(majorVersion) };
753
+ brands[order[2]] = { brand: "Google Chrome", version: String(majorVersion) };
754
+
755
+ this.#userAgentOverride = {
756
+ userAgent,
757
+ platform,
758
+ acceptLanguage: STEALTH_ACCEPT_LANGUAGE,
759
+ userAgentMetadata: {
760
+ brands,
761
+ fullVersion: uaVersion,
762
+ platform: platformFull,
763
+ platformVersion,
764
+ architecture,
765
+ model,
766
+ mobile: isAndroid,
767
+ },
768
+ };
769
+ return this.#userAgentOverride;
770
+ }
771
+
772
+ async #configureUserAgentTargets(override: UserAgentOverride): Promise<void> {
773
+ if (!this.#browser) return;
774
+ if (!this.#browserSession) {
775
+ this.#browserSession = await this.#browser.target().createCDPSession();
776
+ await this.#browserSession.send("Target.setAutoAttach", {
777
+ autoAttach: true,
778
+ waitForDebuggerOnStart: false,
779
+ flatten: true,
780
+ });
781
+ this.#browserSession.on("Target.attachedToTarget", async (event: { sessionId: string }) => {
782
+ const connection = this.#browserSession?.connection();
783
+ const session = connection?.session(event.sessionId);
784
+ if (!session || !this.#userAgentOverride) return;
785
+ await this.#sendUserAgentOverride(this.#wrapSession(session), this.#userAgentOverride);
786
+ });
787
+ }
788
+
789
+ const targets = this.#browser.targets();
790
+ await Promise.all(
791
+ targets.map(async target => {
792
+ const session = await target.createCDPSession();
793
+ await this.#sendUserAgentOverride(this.#wrapSession(session), override);
794
+ }),
795
+ );
796
+ }
797
+
798
+ #wrapSession(session: CDPSession): PuppeteerCdpClient {
799
+ return {
800
+ send: async (method, params) => session.send(method as never, params as never),
801
+ };
802
+ }
803
+
804
+ async #sendUserAgentOverride(client: PuppeteerCdpClient, override: UserAgentOverride): Promise<void> {
805
+ try {
806
+ await client.send("Network.enable");
807
+ } catch {}
808
+ try {
809
+ await client.send("Network.setUserAgentOverride", override);
810
+ } catch (error) {
811
+ logger.debug("Failed to apply Network user agent override", {
812
+ error: error instanceof Error ? error.message : String(error),
813
+ });
814
+ }
815
+ try {
816
+ await client.send("Emulation.setUserAgentOverride", override);
817
+ } catch (error) {
818
+ logger.debug("Failed to apply Emulation user agent override", {
819
+ error: error instanceof Error ? error.message : String(error),
820
+ });
821
+ }
822
+ }
823
+
824
+ #patchSourceUrl(page: Page): void {
825
+ const client = resolvePageClient(page);
826
+ if (!client) return;
827
+ const clientKey = client as object;
828
+ if (this.#patchedClients.has(clientKey)) return;
829
+ this.#patchedClients.add(clientKey);
830
+ const originalSend = client.send.bind(client);
831
+ client.send = async (method: string, params?: Record<string, unknown>) => {
832
+ const next = async (payload?: Record<string, unknown>) => {
833
+ try {
834
+ return await originalSend(method, payload);
835
+ } catch (error) {
836
+ if (
837
+ error instanceof Error &&
838
+ error.message.includes(
839
+ "Protocol error (Network.getResponseBody): No resource with given identifier found",
840
+ )
841
+ ) {
842
+ return undefined;
843
+ }
844
+ throw error;
845
+ }
846
+ };
847
+ if (!method || !params) {
848
+ return next(params);
849
+ }
850
+ const key =
851
+ method === "Runtime.evaluate"
852
+ ? "expression"
853
+ : method === "Runtime.callFunctionOn"
854
+ ? "functionDeclaration"
855
+ : null;
856
+ if (!key) {
857
+ return next(params);
858
+ }
859
+ const value = params[key];
860
+ if (typeof value !== "string" || !value.includes(PUPPETEER_SOURCE_URL_SUFFIX)) {
861
+ return next(params);
862
+ }
863
+ const patchedParams = { ...params, [key]: value.replace(PUPPETEER_SOURCE_URL_SUFFIX, "") };
864
+ return next(patchedParams);
865
+ };
866
+ }
867
+
868
+ /** Injects stealth scripts that cover common puppeteer detection surfaces. */
869
+ async #injectStealthScripts(page: Page): Promise<void> {
870
+ const scripts = [
871
+ stealthTamperingScript,
872
+ stealthActivityScript,
873
+ stealthHairlineScript,
874
+ stealthBotdScript,
875
+ stealthIframeScript,
876
+ stealthWebglScript,
877
+ stealthScreenScript,
878
+ stealthFontsScript,
879
+ stealthAudioScript,
880
+ stealthLocaleScript,
881
+ stealthPluginsScript,
882
+ stealthHardwareScript,
883
+ stealthCodecsScript,
884
+ stealthWorkerScript,
885
+ ];
886
+
887
+ const joint = scripts
888
+ .map(
889
+ script => `
890
+ try {
891
+ ${script};
892
+ } catch (e) {}
893
+ `,
894
+ )
895
+ .join(";\n");
896
+
897
+ await page.evaluateOnNewDocument(`(() => {
898
+ // Native function cache - captured before any tampering
899
+ const iframe = document.createElement("iframe");
900
+ iframe.style.display = "none";
901
+ document.head.appendChild(iframe);
902
+ const nativeWindow = iframe.contentWindow;
903
+ if (!nativeWindow) return;
904
+
905
+ // Cache pristine native functions
906
+ const Function_toString = nativeWindow.Function.prototype.toString;
907
+ const Object_getOwnPropertyDescriptor = nativeWindow.Object.getOwnPropertyDescriptor;
908
+ const Object_getOwnPropertyDescriptors = nativeWindow.Object.getOwnPropertyDescriptors;
909
+ const Object_getPrototypeOf = nativeWindow.Object.getPrototypeOf;
910
+ const Object_defineProperty = nativeWindow.Object.defineProperty;
911
+ const Object_getOwnPropertyDescriptorOriginal = nativeWindow.Object.getOwnPropertyDescriptor;
912
+ const Object_create = nativeWindow.Object.create;
913
+ const Object_keys = nativeWindow.Object.keys;
914
+ const Object_getOwnPropertyNames = nativeWindow.Object.getOwnPropertyNames;
915
+ const Object_entries = nativeWindow.Object.entries;
916
+ const Object_setPrototypeOf = nativeWindow.Object.setPrototypeOf;
917
+ const Object_assign = nativeWindow.Object.assign;
918
+ const Window_setTimeout = nativeWindow.setTimeout;
919
+ const Math_random = nativeWindow.Math.random;
920
+ const Math_floor = nativeWindow.Math.floor;
921
+ const Math_max = nativeWindow.Math.max;
922
+ const Math_min = nativeWindow.Math.min;
923
+ const Window_Event = nativeWindow.Event;
924
+ const Promise_resolve = nativeWindow.Promise.resolve.bind(nativeWindow.Promise);
925
+ const Window_Blob = nativeWindow.Blob;
926
+ const Window_Proxy = nativeWindow.Proxy;
927
+ const Intl_DateTimeFormat = nativeWindow.Intl.DateTimeFormat;
928
+ const Date_constructor = nativeWindow.Date;
929
+
930
+
931
+ ${joint}
932
+
933
+ document.head.removeChild(iframe);})();`);
934
+ }
935
+
936
+ async execute(
937
+ _toolCallId: string,
938
+ params: BrowserParams,
939
+ signal?: AbortSignal,
940
+ _onUpdate?: AgentToolUpdateCallback<BrowserToolDetails>,
941
+ _ctx?: AgentToolContext,
942
+ ): Promise<AgentToolResult<BrowserToolDetails>> {
943
+ try {
944
+ throwIfAborted(signal);
945
+ const timeoutSeconds = clampTimeout(params.timeout);
946
+ const timeoutMs = timeoutSeconds * 1000;
947
+ const details: BrowserToolDetails = { action: params.action };
948
+
949
+ switch (params.action) {
950
+ case "open": {
951
+ const page = await untilAborted(signal, () => this.#resetBrowser(params));
952
+ const viewport = page.viewport();
953
+ details.viewport = viewport ?? DEFAULT_VIEWPORT;
954
+ return toolResult(details).text("Opened headless browser session").done();
955
+ }
956
+ case "close": {
957
+ await untilAborted(signal, () => this.#closeBrowser());
958
+ return toolResult(details).text("Closed headless browser session").done();
959
+ }
960
+ case "goto": {
961
+ const url = ensureParam(params.url, "url", params.action);
962
+ details.url = url;
963
+ const page = await this.#ensurePage(params);
964
+ const waitUntil = params.wait_until ?? "networkidle2";
965
+ await this.#clearElementCache();
966
+ await untilAborted(signal, () => page.goto(url, { waitUntil, timeout: timeoutMs }));
967
+ const finalUrl = page.url();
968
+ const title = (await untilAborted(signal, () => page.title())) as string;
969
+ details.url = finalUrl;
970
+ details.result = title;
971
+ return toolResult(details)
972
+ .text(`Navigated to ${finalUrl}${title ? `\nTitle: ${title}` : ""}`)
973
+ .done();
974
+ }
975
+ case "observe": {
976
+ const page = await this.#ensurePage(params);
977
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
978
+ const observeSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
979
+ await this.#clearElementCache();
980
+ const snapshot = (await untilAborted(observeSignal, () =>
981
+ page.accessibility.snapshot({ interestingOnly: !(params.include_all ?? false) }),
982
+ )) as SerializedAXNode | null;
983
+ if (!snapshot) {
984
+ throw new ToolError("Accessibility snapshot unavailable");
985
+ }
986
+ const entries: ObservationEntry[] = [];
987
+ await this.#collectObservationEntries(snapshot, entries, {
988
+ viewportOnly: params.viewport_only ?? false,
989
+ includeAll: params.include_all ?? false,
990
+ });
991
+ const scroll = (await untilAborted(observeSignal, () =>
992
+ page.evaluate(() => {
993
+ const win = globalThis as unknown as {
994
+ scrollX: number;
995
+ scrollY: number;
996
+ innerWidth: number;
997
+ innerHeight: number;
998
+ document: { documentElement: { scrollWidth: number; scrollHeight: number } };
999
+ };
1000
+ const doc = win.document.documentElement;
1001
+ return {
1002
+ x: win.scrollX,
1003
+ y: win.scrollY,
1004
+ width: win.innerWidth,
1005
+ height: win.innerHeight,
1006
+ scrollWidth: doc.scrollWidth,
1007
+ scrollHeight: doc.scrollHeight,
1008
+ };
1009
+ }),
1010
+ )) as Observation["scroll"];
1011
+ const url = page.url();
1012
+ const title = (await untilAborted(observeSignal, () => page.title())) as string;
1013
+ const viewport = page.viewport() ?? DEFAULT_VIEWPORT;
1014
+ const observation: Observation = {
1015
+ url,
1016
+ title,
1017
+ viewport,
1018
+ scroll,
1019
+ elements: entries,
1020
+ };
1021
+ details.url = url;
1022
+ details.viewport = viewport;
1023
+ details.observation = observation;
1024
+ details.result = `${entries.length} elements`;
1025
+ return toolResult(details).text(this.#formatObservation(observation)).done();
1026
+ }
1027
+ case "click": {
1028
+ const selector = ensureParam(params.selector, "selector", params.action);
1029
+ details.selector = selector;
1030
+ const page = await this.#ensurePage(params);
1031
+ const resolvedSelector = normalizeSelector(selector);
1032
+ if (resolvedSelector.startsWith("text/")) {
1033
+ await clickQueryHandlerText(page, resolvedSelector, timeoutMs, signal);
1034
+ } else {
1035
+ const locator = page.locator(resolvedSelector).setTimeout(timeoutMs);
1036
+ await untilAborted(signal, () => locator.click());
1037
+ }
1038
+ return toolResult(details).text(`Clicked ${selector}`).done();
1039
+ }
1040
+ case "click_id": {
1041
+ const elementId = ensureParam(params.element_id, "element_id", params.action);
1042
+ details.elementId = elementId;
1043
+ const handle = await this.#resolveCachedHandle(elementId);
1044
+ try {
1045
+ await untilAborted(signal, () => handle.click());
1046
+ } catch {
1047
+ await this.#clearElementCache();
1048
+ throw new ToolError(`Element_id ${elementId} is stale. Run observe again.`);
1049
+ }
1050
+ return toolResult(details).text(`Clicked element ${elementId}`).done();
1051
+ }
1052
+ case "type": {
1053
+ const selector = ensureParam(params.selector, "selector", params.action);
1054
+ const text = ensureParam(params.text, "text", params.action);
1055
+ details.selector = selector;
1056
+ const page = await this.#ensurePage(params);
1057
+ const resolvedSelector = normalizeSelector(selector);
1058
+ const locator = page.locator(resolvedSelector).setTimeout(timeoutMs);
1059
+ const handle = (await untilAborted(signal, () => locator.waitHandle())) as ElementHandle;
1060
+ await untilAborted(signal, () => handle.type(text, { delay: 0 }));
1061
+ await handle.dispose();
1062
+ return toolResult(details).text(`Typed into ${selector}`).done();
1063
+ }
1064
+ case "type_id": {
1065
+ const elementId = ensureParam(params.element_id, "element_id", params.action);
1066
+ const text = ensureParam(params.text, "text", params.action);
1067
+ details.elementId = elementId;
1068
+ const page = await this.#ensurePage(params);
1069
+ const handle = await this.#resolveCachedHandle(elementId);
1070
+ try {
1071
+ await untilAborted(signal, () => handle.focus());
1072
+ await untilAborted(signal, () => page.keyboard.type(text, { delay: 0 }));
1073
+ } catch {
1074
+ await this.#clearElementCache();
1075
+ throw new ToolError(`Element_id ${elementId} is stale. Run observe again.`);
1076
+ }
1077
+ return toolResult(details).text(`Typed into element ${elementId}`).done();
1078
+ }
1079
+ case "fill": {
1080
+ const selector = ensureParam(params.selector, "selector", params.action);
1081
+ const value = ensureParam(params.value, "value", params.action);
1082
+ details.selector = selector;
1083
+ const page = await this.#ensurePage(params);
1084
+ const resolvedSelector = normalizeSelector(selector);
1085
+ const locator = page.locator(resolvedSelector).setTimeout(timeoutMs);
1086
+ await untilAborted(signal, () => locator.fill(value));
1087
+ return toolResult(details).text(`Filled ${selector}`).done();
1088
+ }
1089
+ case "fill_id": {
1090
+ const elementId = ensureParam(params.element_id, "element_id", params.action);
1091
+ const value = ensureParam(params.value, "value", params.action);
1092
+ details.elementId = elementId;
1093
+ const handle = await this.#resolveCachedHandle(elementId);
1094
+ try {
1095
+ await untilAborted(signal, () =>
1096
+ handle.evaluate((el, inputValue) => {
1097
+ const element = el as { value?: string; dispatchEvent: (event: Event) => boolean };
1098
+ if (!("value" in element)) {
1099
+ throw new Error("Target element is not a form input");
1100
+ }
1101
+ element.value = String(inputValue);
1102
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1103
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1104
+ }, value),
1105
+ );
1106
+ } catch {
1107
+ await this.#clearElementCache();
1108
+ throw new ToolError(`Element_id ${elementId} is stale. Run observe again.`);
1109
+ }
1110
+ return toolResult(details).text(`Filled element ${elementId}`).done();
1111
+ }
1112
+ case "press": {
1113
+ const key = ensureParam(params.key, "key", params.action) as KeyInput;
1114
+ const page = await this.#ensurePage(params);
1115
+ if (params.selector) {
1116
+ const resolvedSelector = normalizeSelector(params.selector as string);
1117
+ await untilAborted(signal, () => page.focus(resolvedSelector));
1118
+ }
1119
+ await untilAborted(signal, () => page.keyboard.press(key));
1120
+ return toolResult(details).text(`Pressed ${key}`).done();
1121
+ }
1122
+ case "scroll": {
1123
+ const deltaY = ensureParam(params.delta_y, "delta_y", params.action);
1124
+ const deltaX = params.delta_x ?? 0;
1125
+ const page = await this.#ensurePage(params);
1126
+ await untilAborted(signal, () => page.mouse.wheel({ deltaX, deltaY }));
1127
+ return toolResult(details).text(`Scrolled by ${deltaX}, ${deltaY}`).done();
1128
+ }
1129
+ case "drag": {
1130
+ const fromSelector = ensureParam(params.from_selector, "from_selector", params.action);
1131
+ const toSelector = ensureParam(params.to_selector, "to_selector", params.action);
1132
+ const page = await this.#ensurePage(params);
1133
+ const resolvedFromSelector = normalizeSelector(fromSelector);
1134
+ const resolvedToSelector = normalizeSelector(toSelector);
1135
+ const fromHandle = (await untilAborted(signal, () =>
1136
+ page.$(resolvedFromSelector),
1137
+ )) as ElementHandle | null;
1138
+ const toHandle = (await untilAborted(signal, () => page.$(resolvedToSelector))) as ElementHandle | null;
1139
+ if (!fromHandle || !toHandle) {
1140
+ throw new ToolError("Drag selectors did not resolve to elements");
1141
+ }
1142
+ const fromBox = (await untilAborted(signal, () => fromHandle.boundingBox())) as {
1143
+ x: number;
1144
+ y: number;
1145
+ width: number;
1146
+ height: number;
1147
+ } | null;
1148
+ const toBox = (await untilAborted(signal, () => toHandle.boundingBox())) as {
1149
+ x: number;
1150
+ y: number;
1151
+ width: number;
1152
+ height: number;
1153
+ } | null;
1154
+ await fromHandle.dispose();
1155
+ await toHandle.dispose();
1156
+ if (!fromBox || !toBox) {
1157
+ throw new ToolError("Drag elements are not visible");
1158
+ }
1159
+ const startX = fromBox.x + fromBox.width / 2;
1160
+ const startY = fromBox.y + fromBox.height / 2;
1161
+ const endX = toBox.x + toBox.width / 2;
1162
+ const endY = toBox.y + toBox.height / 2;
1163
+ await untilAborted(signal, () => page.mouse.move(startX, startY));
1164
+ await untilAborted(signal, () => page.mouse.down());
1165
+ await untilAborted(signal, () => page.mouse.move(endX, endY, { steps: 12 }));
1166
+ await untilAborted(signal, () => page.mouse.up());
1167
+ return toolResult(details).text(`Dragged from ${fromSelector} to ${toSelector}`).done();
1168
+ }
1169
+ case "wait_for_selector": {
1170
+ const selector = ensureParam(params.selector, "selector", params.action);
1171
+ details.selector = selector;
1172
+ const page = await this.#ensurePage(params);
1173
+ const resolvedSelector = normalizeSelector(selector);
1174
+ const locator = page.locator(resolvedSelector).setTimeout(timeoutMs);
1175
+ await untilAborted(signal, () => locator.wait());
1176
+ return toolResult(details).text(`Selector ready: ${selector}`).done();
1177
+ }
1178
+ case "evaluate": {
1179
+ const script = ensureParam(params.script, "script", params.action);
1180
+ const page = await this.#ensurePage(params);
1181
+ const value = (await untilAborted(signal, () =>
1182
+ page.evaluate(async (source: string) => {
1183
+ try {
1184
+ return await new Function(`return (async () => (${source}))();`)();
1185
+ } catch {
1186
+ return await new Function(`return (async () => { ${source} })();`)();
1187
+ }
1188
+ }, script),
1189
+ )) as unknown;
1190
+ const output = formatEvaluateResult(value);
1191
+ details.result = output;
1192
+ return toolResult(details).text(output).done();
1193
+ }
1194
+ case "get_text": {
1195
+ const page = await this.#ensurePage(params);
1196
+ if (params.args?.length) {
1197
+ const values = (await Promise.all(
1198
+ params.args.map((arg, index) => {
1199
+ const selector = ensureParam(arg.selector, `args[${index}].selector`, params.action);
1200
+ const resolvedSelector = normalizeSelector(selector);
1201
+ return untilAborted(signal, () =>
1202
+ page.$eval(resolvedSelector, (el: Element) => (el as HTMLElement).innerText),
1203
+ );
1204
+ }),
1205
+ )) as string[];
1206
+ details.result = values;
1207
+ return toolResult(details)
1208
+ .text(JSON.stringify(values, null, 2))
1209
+ .done();
1210
+ }
1211
+ const selector = ensureParam(params.selector, "selector", params.action);
1212
+ details.selector = selector;
1213
+ const resolvedSelector = normalizeSelector(selector);
1214
+ const value = (await untilAborted(signal, () =>
1215
+ page.$eval(resolvedSelector, (el: Element) => (el as HTMLElement).innerText),
1216
+ )) as string;
1217
+ details.result = value;
1218
+ return toolResult(details).text(value).done();
1219
+ }
1220
+ case "get_html": {
1221
+ const page = await this.#ensurePage(params);
1222
+ if (params.args?.length) {
1223
+ const values = (await Promise.all(
1224
+ params.args.map((arg, index) => {
1225
+ const selector = ensureParam(arg.selector, `args[${index}].selector`, params.action);
1226
+ const resolvedSelector = normalizeSelector(selector);
1227
+ return untilAborted(signal, () =>
1228
+ page.$eval(resolvedSelector, (el: Element) => (el as HTMLElement).innerHTML),
1229
+ );
1230
+ }),
1231
+ )) as string[];
1232
+ details.result = values;
1233
+ return toolResult(details)
1234
+ .text(JSON.stringify(values, null, 2))
1235
+ .done();
1236
+ }
1237
+ const selector = ensureParam(params.selector, "selector", params.action);
1238
+ details.selector = selector;
1239
+ const resolvedSelector = normalizeSelector(selector);
1240
+ const value = (await untilAborted(signal, () =>
1241
+ page.$eval(resolvedSelector, (el: Element) => (el as HTMLElement).innerHTML),
1242
+ )) as string;
1243
+ details.result = value;
1244
+ return toolResult(details).text(value).done();
1245
+ }
1246
+ case "get_attribute": {
1247
+ const page = await this.#ensurePage(params);
1248
+ if (params.args?.length) {
1249
+ const values = (await Promise.all(
1250
+ params.args.map((arg, index) => {
1251
+ const selector = ensureParam(arg.selector, `args[${index}].selector`, params.action);
1252
+ const attribute = ensureParam(arg.attribute, `args[${index}].attribute`, params.action);
1253
+ const resolvedSelector = normalizeSelector(selector);
1254
+ return untilAborted(signal, () =>
1255
+ page.$eval(
1256
+ resolvedSelector,
1257
+ (el: Element, attr: string) => (el as HTMLElement).getAttribute(String(attr)),
1258
+ attribute,
1259
+ ),
1260
+ );
1261
+ }),
1262
+ )) as string[];
1263
+ details.result = values;
1264
+ return toolResult(details)
1265
+ .text(JSON.stringify(values, null, 2))
1266
+ .done();
1267
+ }
1268
+ const selector = ensureParam(params.selector, "selector", params.action);
1269
+ const attribute = ensureParam(params.attribute, "attribute", params.action);
1270
+ details.selector = selector;
1271
+ const resolvedSelector = normalizeSelector(selector);
1272
+ const value = (await untilAborted(signal, () =>
1273
+ page.$eval(
1274
+ resolvedSelector,
1275
+ (el: { getAttribute: (name: string) => string | null }, attr: string) =>
1276
+ el.getAttribute(String(attr)),
1277
+ attribute,
1278
+ ),
1279
+ )) as string | null;
1280
+ const output = value ?? "";
1281
+ details.result = output;
1282
+ return toolResult(details).text(output).done();
1283
+ }
1284
+ case "extract_readable": {
1285
+ const page = await this.#ensurePage(params);
1286
+ const format = params.format ?? "markdown";
1287
+ const html = (await untilAborted(signal, () => page.content())) as string;
1288
+ const url = page.url();
1289
+ const { document } = parseHTML(html);
1290
+ const reader = new Readability(document);
1291
+ const article = reader.parse();
1292
+ if (!article) {
1293
+ throw new ToolError("Readable content not found");
1294
+ }
1295
+ const markdown = format === "markdown" ? htmlToBasicMarkdown(article.content ?? "") : undefined;
1296
+ const text = format === "text" ? (article.textContent ?? "") : undefined;
1297
+ const readable: ReadableResult = {
1298
+ url,
1299
+ title: article.title ?? undefined,
1300
+ byline: article.byline ?? undefined,
1301
+ excerpt: article.excerpt ?? undefined,
1302
+ contentLength: article.length ?? article.textContent?.length ?? 0,
1303
+ text,
1304
+ markdown,
1305
+ };
1306
+ details.url = url;
1307
+ details.readable = readable;
1308
+ details.result = format === "markdown" ? (markdown ?? "") : (text ?? "");
1309
+ return toolResult(details)
1310
+ .text(JSON.stringify(readable, null, 2))
1311
+ .done();
1312
+ }
1313
+ case "screenshot": {
1314
+ const page = await this.#ensurePage(params);
1315
+ const fullPage = params.selector ? false : (params.full_page ?? false);
1316
+ let buffer: Buffer;
1317
+
1318
+ if (params.selector) {
1319
+ const resolvedSelector = normalizeSelector(params.selector as string);
1320
+ const handle = (await untilAborted(signal, () => page.$(resolvedSelector))) as ElementHandle | null;
1321
+ if (!handle) {
1322
+ throw new ToolError("Screenshot selector did not resolve to an element");
1323
+ }
1324
+ buffer = (await untilAborted(signal, () => handle.screenshot({ type: "png" }))) as Buffer;
1325
+ await handle.dispose();
1326
+ details.selector = params.selector;
1327
+ } else {
1328
+ buffer = (await untilAborted(signal, () => page.screenshot({ type: "png", fullPage }))) as Buffer;
1329
+ }
1330
+
1331
+ // Compress for API content (same as pasted images)
1332
+ // NOTE: screenshots can be deceptively large (especially PNG) even at modest resolutions,
1333
+ // and tool results are immediately embedded in the next LLM request.
1334
+ // Use a tighter budget than the global per-image limit to avoid 413 request_too_large.
1335
+ const resized = await resizeImage(
1336
+ { type: "image", data: buffer.toBase64(), mimeType: "image/png" },
1337
+ { maxBytes: 0.75 * 1024 * 1024 },
1338
+ );
1339
+ const dimensionNote = formatDimensionNote(resized);
1340
+ const tempFile = path.join(os.tmpdir(), `arc-sshots-${Snowflake.next()}.png`);
1341
+ await Bun.write(tempFile, resized.buffer);
1342
+ details.screenshotPath = tempFile;
1343
+ details.mimeType = resized.mimeType;
1344
+ details.bytes = resized.buffer.length;
1345
+
1346
+ // Show both raw bytes (saved to disk) and compressed bytes (sent to model).
1347
+ const lines = [
1348
+ "Screenshot captured",
1349
+ `Format: ${resized.mimeType} (${(resized.buffer.length / 1024).toFixed(2)} KB)`,
1350
+ `Dimensions: ${resized.width}x${resized.height}`,
1351
+ ];
1352
+ if (dimensionNote) {
1353
+ lines.push(dimensionNote);
1354
+ }
1355
+
1356
+ return toolResult(details)
1357
+ .content([
1358
+ { type: "text", text: lines.join("\n") },
1359
+ { type: "image", data: resized.data, mimeType: resized.mimeType },
1360
+ ])
1361
+ .done();
1362
+ }
1363
+ default:
1364
+ throw new ToolError(`Unsupported action: ${params.action}`);
1365
+ }
1366
+ } catch (error) {
1367
+ if (error instanceof ToolAbortError) throw error;
1368
+ if (error instanceof Error && error.name === "AbortError") {
1369
+ throw new ToolAbortError();
1370
+ }
1371
+ throw error;
1372
+ }
1373
+ }
1374
+ }