retestkit 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/.claude/commands/openspec/apply.md +23 -0
  2. package/.claude/commands/openspec/archive.md +27 -0
  3. package/.claude/commands/openspec/proposal.md +28 -0
  4. package/.gemini/commands/openspec/apply.toml +21 -0
  5. package/.gemini/commands/openspec/archive.toml +25 -0
  6. package/.gemini/commands/openspec/proposal.toml +26 -0
  7. package/.github/prompts/openspec-apply.prompt.md +22 -0
  8. package/.github/prompts/openspec-archive.prompt.md +26 -0
  9. package/.github/prompts/openspec-proposal.prompt.md +27 -0
  10. package/.github/workflows/release.yml +33 -0
  11. package/.kilocode/workflows/openspec-apply.md +17 -0
  12. package/.kilocode/workflows/openspec-archive.md +21 -0
  13. package/.kilocode/workflows/openspec-proposal.md +22 -0
  14. package/.mcp.json +23 -0
  15. package/.opencode/command/openspec-apply.md +25 -0
  16. package/.opencode/command/openspec-archive.md +28 -0
  17. package/.opencode/command/openspec-proposal.md +30 -0
  18. package/.roo/commands/openspec-apply.md +20 -0
  19. package/.roo/commands/openspec-archive.md +24 -0
  20. package/.roo/commands/openspec-proposal.md +25 -0
  21. package/.vscode/mcp.json +23 -0
  22. package/AGENTS.md +18 -0
  23. package/CLAUDE.md +18 -0
  24. package/LICENSE +65 -0
  25. package/README.md +303 -0
  26. package/dist/config.d.ts +4 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +27 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/elicitation/index.d.ts +17 -0
  31. package/dist/elicitation/index.d.ts.map +1 -0
  32. package/dist/elicitation/index.js +118 -0
  33. package/dist/elicitation/index.js.map +1 -0
  34. package/dist/elicitation/types.d.ts +35 -0
  35. package/dist/elicitation/types.d.ts.map +1 -0
  36. package/dist/elicitation/types.js +39 -0
  37. package/dist/elicitation/types.js.map +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +76 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/lifecycle/index.d.ts +31 -0
  43. package/dist/lifecycle/index.d.ts.map +1 -0
  44. package/dist/lifecycle/index.js +61 -0
  45. package/dist/lifecycle/index.js.map +1 -0
  46. package/dist/logger.d.ts +21 -0
  47. package/dist/logger.d.ts.map +1 -0
  48. package/dist/logger.js +182 -0
  49. package/dist/logger.js.map +1 -0
  50. package/dist/playwright-client/index.d.ts +29 -0
  51. package/dist/playwright-client/index.d.ts.map +1 -0
  52. package/dist/playwright-client/index.js +288 -0
  53. package/dist/playwright-client/index.js.map +1 -0
  54. package/dist/playwright-client/types.d.ts +44 -0
  55. package/dist/playwright-client/types.d.ts.map +1 -0
  56. package/dist/playwright-client/types.js +49 -0
  57. package/dist/playwright-client/types.js.map +1 -0
  58. package/dist/progress/index.d.ts +39 -0
  59. package/dist/progress/index.d.ts.map +1 -0
  60. package/dist/progress/index.js +106 -0
  61. package/dist/progress/index.js.map +1 -0
  62. package/dist/progress/types.d.ts +24 -0
  63. package/dist/progress/types.d.ts.map +1 -0
  64. package/dist/progress/types.js +2 -0
  65. package/dist/progress/types.js.map +1 -0
  66. package/dist/prompts/index.d.ts +19 -0
  67. package/dist/prompts/index.d.ts.map +1 -0
  68. package/dist/prompts/index.js +207 -0
  69. package/dist/prompts/index.js.map +1 -0
  70. package/dist/prompts/loader.d.ts +20 -0
  71. package/dist/prompts/loader.d.ts.map +1 -0
  72. package/dist/prompts/loader.js +47 -0
  73. package/dist/prompts/loader.js.map +1 -0
  74. package/dist/resources/index.d.ts +27 -0
  75. package/dist/resources/index.d.ts.map +1 -0
  76. package/dist/resources/index.js +186 -0
  77. package/dist/resources/index.js.map +1 -0
  78. package/dist/resources/subscriptions.d.ts +10 -0
  79. package/dist/resources/subscriptions.d.ts.map +1 -0
  80. package/dist/resources/subscriptions.js +23 -0
  81. package/dist/resources/subscriptions.js.map +1 -0
  82. package/dist/sampling/index.d.ts +11 -0
  83. package/dist/sampling/index.d.ts.map +1 -0
  84. package/dist/sampling/index.js +201 -0
  85. package/dist/sampling/index.js.map +1 -0
  86. package/dist/sampling/prompts.d.ts +56 -0
  87. package/dist/sampling/prompts.d.ts.map +1 -0
  88. package/dist/sampling/prompts.js +124 -0
  89. package/dist/sampling/prompts.js.map +1 -0
  90. package/dist/sampling/types.d.ts +57 -0
  91. package/dist/sampling/types.d.ts.map +1 -0
  92. package/dist/sampling/types.js +2 -0
  93. package/dist/sampling/types.js.map +1 -0
  94. package/dist/schemas/config.d.ts +40 -0
  95. package/dist/schemas/config.d.ts.map +1 -0
  96. package/dist/schemas/config.js +30 -0
  97. package/dist/schemas/config.js.map +1 -0
  98. package/dist/security/index.d.ts +38 -0
  99. package/dist/security/index.d.ts.map +1 -0
  100. package/dist/security/index.js +281 -0
  101. package/dist/security/index.js.map +1 -0
  102. package/dist/server.d.ts +9 -0
  103. package/dist/server.d.ts.map +1 -0
  104. package/dist/server.js +142 -0
  105. package/dist/server.js.map +1 -0
  106. package/dist/test-utils/index.d.ts +6 -0
  107. package/dist/test-utils/index.d.ts.map +1 -0
  108. package/dist/test-utils/index.js +6 -0
  109. package/dist/test-utils/index.js.map +1 -0
  110. package/dist/test-utils/mock-context.d.ts +64 -0
  111. package/dist/test-utils/mock-context.d.ts.map +1 -0
  112. package/dist/test-utils/mock-context.js +347 -0
  113. package/dist/test-utils/mock-context.js.map +1 -0
  114. package/dist/test-utils/mock-playwright-client.d.ts +62 -0
  115. package/dist/test-utils/mock-playwright-client.d.ts.map +1 -0
  116. package/dist/test-utils/mock-playwright-client.js +315 -0
  117. package/dist/test-utils/mock-playwright-client.js.map +1 -0
  118. package/dist/tools/index.d.ts +4 -0
  119. package/dist/tools/index.d.ts.map +1 -0
  120. package/dist/tools/index.js +8 -0
  121. package/dist/tools/index.js.map +1 -0
  122. package/dist/tools/webtest/crawl.d.ts +46 -0
  123. package/dist/tools/webtest/crawl.d.ts.map +1 -0
  124. package/dist/tools/webtest/crawl.js +678 -0
  125. package/dist/tools/webtest/crawl.js.map +1 -0
  126. package/dist/tools/webtest/discover-features.d.ts +30 -0
  127. package/dist/tools/webtest/discover-features.d.ts.map +1 -0
  128. package/dist/tools/webtest/discover-features.js +343 -0
  129. package/dist/tools/webtest/discover-features.js.map +1 -0
  130. package/dist/tools/webtest/discover-flows.d.ts +29 -0
  131. package/dist/tools/webtest/discover-flows.d.ts.map +1 -0
  132. package/dist/tools/webtest/discover-flows.js +341 -0
  133. package/dist/tools/webtest/discover-flows.js.map +1 -0
  134. package/dist/tools/webtest/generate-tests.d.ts +54 -0
  135. package/dist/tools/webtest/generate-tests.d.ts.map +1 -0
  136. package/dist/tools/webtest/generate-tests.js +364 -0
  137. package/dist/tools/webtest/generate-tests.js.map +1 -0
  138. package/dist/tools/webtest/index.d.ts +8 -0
  139. package/dist/tools/webtest/index.d.ts.map +1 -0
  140. package/dist/tools/webtest/index.js +8 -0
  141. package/dist/tools/webtest/index.js.map +1 -0
  142. package/dist/tools/webtest/run-test-case.d.ts +28 -0
  143. package/dist/tools/webtest/run-test-case.d.ts.map +1 -0
  144. package/dist/tools/webtest/run-test-case.js +420 -0
  145. package/dist/tools/webtest/run-test-case.js.map +1 -0
  146. package/dist/tools/webtest/schemas.d.ts +175 -0
  147. package/dist/tools/webtest/schemas.d.ts.map +1 -0
  148. package/dist/tools/webtest/schemas.js +156 -0
  149. package/dist/tools/webtest/schemas.js.map +1 -0
  150. package/dist/tools/webtest/start-analysis.d.ts +16 -0
  151. package/dist/tools/webtest/start-analysis.d.ts.map +1 -0
  152. package/dist/tools/webtest/start-analysis.js +137 -0
  153. package/dist/tools/webtest/start-analysis.js.map +1 -0
  154. package/dist/transports/http.d.ts +8 -0
  155. package/dist/transports/http.d.ts.map +1 -0
  156. package/dist/transports/http.js +9 -0
  157. package/dist/transports/http.js.map +1 -0
  158. package/dist/transports/index.d.ts +14 -0
  159. package/dist/transports/index.d.ts.map +1 -0
  160. package/dist/transports/index.js +20 -0
  161. package/dist/transports/index.js.map +1 -0
  162. package/dist/transports/stdio.d.ts +4 -0
  163. package/dist/transports/stdio.d.ts.map +1 -0
  164. package/dist/transports/stdio.js +6 -0
  165. package/dist/transports/stdio.js.map +1 -0
  166. package/dist/types/capabilities.d.ts +18 -0
  167. package/dist/types/capabilities.d.ts.map +1 -0
  168. package/dist/types/capabilities.js +35 -0
  169. package/dist/types/capabilities.js.map +1 -0
  170. package/dist/types/context.d.ts +20 -0
  171. package/dist/types/context.d.ts.map +1 -0
  172. package/dist/types/context.js +2 -0
  173. package/dist/types/context.js.map +1 -0
  174. package/dist/types/tool.d.ts +10 -0
  175. package/dist/types/tool.d.ts.map +1 -0
  176. package/dist/types/tool.js +2 -0
  177. package/dist/types/tool.js.map +1 -0
  178. package/dist/workspace/index.d.ts +99 -0
  179. package/dist/workspace/index.d.ts.map +1 -0
  180. package/dist/workspace/index.js +648 -0
  181. package/dist/workspace/index.js.map +1 -0
  182. package/dist/workspace/markdown.d.ts +50 -0
  183. package/dist/workspace/markdown.d.ts.map +1 -0
  184. package/dist/workspace/markdown.js +210 -0
  185. package/dist/workspace/markdown.js.map +1 -0
  186. package/dist/workspace/types.d.ts +173 -0
  187. package/dist/workspace/types.d.ts.map +1 -0
  188. package/dist/workspace/types.js +2 -0
  189. package/dist/workspace/types.js.map +1 -0
  190. package/openspec/AGENTS.md +456 -0
  191. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/proposal.md +33 -0
  192. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-resources/spec.md +27 -0
  193. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/specs/webtest-tools/spec.md +304 -0
  194. package/openspec/changes/archive/2025-12-18-add-hybrid-artifact-paths/tasks.md +43 -0
  195. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/design.md +209 -0
  196. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/proposal.md +41 -0
  197. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/specs/mcp-server-core/spec.md +183 -0
  198. package/openspec/changes/archive/2025-12-18-add-mcp-server-foundation/tasks.md +112 -0
  199. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/design.md +333 -0
  200. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/proposal.md +66 -0
  201. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/mcp-server-core/spec.md +129 -0
  202. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-lifecycle/spec.md +138 -0
  203. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-logging/spec.md +211 -0
  204. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-prompts/spec.md +157 -0
  205. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-resources/spec.md +213 -0
  206. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-sampling/spec.md +257 -0
  207. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/specs/webtest-tools/spec.md +501 -0
  208. package/openspec/changes/archive/2025-12-18-add-webtest-orchestrator/tasks.md +264 -0
  209. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/proposal.md +24 -0
  210. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/specs/webtest-tools/spec.md +80 -0
  211. package/openspec/changes/archive/2025-12-18-allow-analysis-of-incomplete-crawls/tasks.md +8 -0
  212. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/design.md +90 -0
  213. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/proposal.md +28 -0
  214. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/specs/webtest-sampling/spec.md +90 -0
  215. package/openspec/changes/archive/2025-12-18-fix-crawl-loop-stability/tasks.md +33 -0
  216. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/design.md +558 -0
  217. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/proposal.md +119 -0
  218. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-resources/spec.md +109 -0
  219. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/specs/webtest-tools/spec.md +121 -0
  220. package/openspec/changes/archive/2025-12-18-use-markdown-artifacts/tasks.md +133 -0
  221. package/openspec/changes/extract-prompts-to-markdown/design.md +86 -0
  222. package/openspec/changes/extract-prompts-to-markdown/proposal.md +50 -0
  223. package/openspec/changes/extract-prompts-to-markdown/specs/webtest-prompts/spec.md +74 -0
  224. package/openspec/changes/extract-prompts-to-markdown/tasks.md +40 -0
  225. package/openspec/changes/refactor-webtest-naming/design.md +95 -0
  226. package/openspec/changes/refactor-webtest-naming/proposal.md +66 -0
  227. package/openspec/changes/refactor-webtest-naming/specs/webtest-prompts/spec.md +79 -0
  228. package/openspec/changes/refactor-webtest-naming/specs/webtest-resources/spec.md +80 -0
  229. package/openspec/changes/refactor-webtest-naming/specs/webtest-sampling/spec.md +122 -0
  230. package/openspec/changes/refactor-webtest-naming/specs/webtest-tools/spec.md +113 -0
  231. package/openspec/changes/refactor-webtest-naming/tasks.md +119 -0
  232. package/openspec/changes/rename-package-to-retest/proposal.md +52 -0
  233. package/openspec/changes/rename-package-to-retest/specs/mcp-server-core/spec.md +53 -0
  234. package/openspec/changes/rename-package-to-retest/specs/retest-lifecycle/spec.md +68 -0
  235. package/openspec/changes/rename-package-to-retest/specs/retest-logging/spec.md +35 -0
  236. package/openspec/changes/rename-package-to-retest/specs/retest-prompts/spec.md +159 -0
  237. package/openspec/changes/rename-package-to-retest/specs/retest-resources/spec.md +251 -0
  238. package/openspec/changes/rename-package-to-retest/specs/retest-sampling/spec.md +99 -0
  239. package/openspec/changes/rename-package-to-retest/specs/retest-tools/spec.md +295 -0
  240. package/openspec/changes/rename-package-to-retest/tasks.md +71 -0
  241. package/openspec/project.md +31 -0
  242. package/openspec/specs/mcp-server-core/spec.md +178 -0
  243. package/openspec/specs/webtest-lifecycle/spec.md +136 -0
  244. package/openspec/specs/webtest-logging/spec.md +209 -0
  245. package/openspec/specs/webtest-prompts/spec.md +155 -0
  246. package/openspec/specs/webtest-resources/spec.md +248 -0
  247. package/openspec/specs/webtest-sampling/spec.md +344 -0
  248. package/openspec/specs/webtest-tools/spec.md +282 -0
  249. package/package.json +54 -0
  250. package/release.config.js +9 -0
  251. package/src/config.test.ts +96 -0
  252. package/src/config.ts +32 -0
  253. package/src/elicitation/index.test.ts +399 -0
  254. package/src/elicitation/index.ts +171 -0
  255. package/src/elicitation/types.ts +68 -0
  256. package/src/index.ts +83 -0
  257. package/src/lifecycle/index.test.ts +260 -0
  258. package/src/lifecycle/index.ts +101 -0
  259. package/src/logger.redaction.test.ts +322 -0
  260. package/src/logger.test.ts +123 -0
  261. package/src/logger.ts +229 -0
  262. package/src/playwright-client/index.ts +392 -0
  263. package/src/playwright-client/types.ts +99 -0
  264. package/src/progress/index.test.ts +327 -0
  265. package/src/progress/index.ts +170 -0
  266. package/src/progress/types.ts +25 -0
  267. package/src/prompts/index.test.ts +451 -0
  268. package/src/prompts/index.ts +246 -0
  269. package/src/prompts/loader.test.ts +100 -0
  270. package/src/prompts/loader.ts +59 -0
  271. package/src/prompts/templates/mcp/webtest-crawl.md +7 -0
  272. package/src/prompts/templates/mcp/webtest-discover-flows.md +11 -0
  273. package/src/prompts/templates/mcp/webtest-discover.md +12 -0
  274. package/src/prompts/templates/mcp/webtest-full-workflow.md +12 -0
  275. package/src/prompts/templates/mcp/webtest-generate-tests.md +11 -0
  276. package/src/prompts/templates/mcp/webtest-run-test.md +11 -0
  277. package/src/prompts/templates/mcp/webtest-start.md +8 -0
  278. package/src/prompts/templates/sampling/crawl-action.md +35 -0
  279. package/src/prompts/templates/sampling/feature-discovery.md +27 -0
  280. package/src/prompts/templates/sampling/flow-discovery.md +29 -0
  281. package/src/prompts/templates/sampling/page-content-wrapper.md +5 -0
  282. package/src/prompts/templates/sampling/system-prefix.md +12 -0
  283. package/src/prompts/templates/sampling/test-evaluation.md +17 -0
  284. package/src/prompts/templates/sampling/test-generation.md +31 -0
  285. package/src/resources/index.ts +250 -0
  286. package/src/resources/subscriptions.ts +37 -0
  287. package/src/sampling/index.test.ts +414 -0
  288. package/src/sampling/index.ts +286 -0
  289. package/src/sampling/prompts.ts +194 -0
  290. package/src/sampling/types.ts +60 -0
  291. package/src/schemas/config.ts +39 -0
  292. package/src/security/index.test.ts +441 -0
  293. package/src/security/index.ts +361 -0
  294. package/src/security/security-scenarios.test.ts +468 -0
  295. package/src/server.ts +211 -0
  296. package/src/test-utils/index.ts +6 -0
  297. package/src/test-utils/mock-context.ts +426 -0
  298. package/src/test-utils/mock-playwright-client.ts +422 -0
  299. package/src/tools/index.ts +11 -0
  300. package/src/tools/webtest/crawl.test.ts +834 -0
  301. package/src/tools/webtest/crawl.ts +901 -0
  302. package/src/tools/webtest/discover-features.ts +412 -0
  303. package/src/tools/webtest/discover-flows.ts +408 -0
  304. package/src/tools/webtest/generate-tests.test.ts +532 -0
  305. package/src/tools/webtest/generate-tests.ts +425 -0
  306. package/src/tools/webtest/index.ts +7 -0
  307. package/src/tools/webtest/integration.test.ts +536 -0
  308. package/src/tools/webtest/run-test-case.test.ts +659 -0
  309. package/src/tools/webtest/run-test-case.ts +508 -0
  310. package/src/tools/webtest/schemas.ts +201 -0
  311. package/src/tools/webtest/start-analysis.test.ts +151 -0
  312. package/src/tools/webtest/start-analysis.ts +158 -0
  313. package/src/transports/http.ts +19 -0
  314. package/src/transports/index.ts +30 -0
  315. package/src/transports/stdio.ts +7 -0
  316. package/src/types/capabilities.test.ts +193 -0
  317. package/src/types/capabilities.ts +50 -0
  318. package/src/types/context.ts +21 -0
  319. package/src/types/tool.ts +11 -0
  320. package/src/workspace/index.ts +945 -0
  321. package/src/workspace/markdown.ts +272 -0
  322. package/src/workspace/types.ts +186 -0
  323. package/tests/integration/server.test.ts +89 -0
  324. package/tests/integration/tools.test.ts +99 -0
  325. package/tsconfig.json +20 -0
  326. package/vitest.config.ts +9 -0
  327. package/vitest.integration.config.ts +10 -0
@@ -0,0 +1,678 @@
1
+ import { z } from "zod";
2
+ import { join } from "node:path";
3
+ import { CancellationError, calculateBudgetStatus } from "../../progress/index.js";
4
+ import { buildCrawlActionPrompt } from "../../sampling/prompts.js";
5
+ import { CrawlActionSchema, AnalysisIdSchema } from "./schemas.js";
6
+ import { createDomSignature } from "../../security/index.js";
7
+ export const crawlInputSchema = z.object({
8
+ analysisId: AnalysisIdSchema,
9
+ goal: z.string().min(1, "Goal is required").describe("What you want to discover or test (e.g., 'Find all navigation paths', 'Explore the checkout flow')"),
10
+ strategy: z
11
+ .enum(["breadth_first", "depth_first", "goal_directed"])
12
+ .default("goal_directed")
13
+ .describe("Crawling strategy"),
14
+ limits: z
15
+ .object({
16
+ maxSteps: z.number().int().min(1).max(1000).optional(),
17
+ maxMinutes: z.number().int().min(1).max(180).optional(),
18
+ maxPages: z.number().int().min(1).max(100).optional(),
19
+ })
20
+ .optional()
21
+ .describe("Override workspace limits for this crawl"),
22
+ artifacts: z
23
+ .object({
24
+ captureScreenshots: z.boolean().default(true),
25
+ captureSnapshots: z.boolean().default(true),
26
+ captureDom: z.boolean().default(true),
27
+ })
28
+ .optional()
29
+ .describe("What artifacts to capture"),
30
+ resume: z
31
+ .boolean()
32
+ .default(false)
33
+ .describe("Resume from the last checkpoint if available"),
34
+ manualNextActions: z
35
+ .array(z.object({
36
+ tool: z.string(),
37
+ args: z.record(z.string(), z.any()),
38
+ }))
39
+ .optional()
40
+ .describe("Manual actions to execute (for fallback mode when sampling unavailable)"),
41
+ });
42
+ export function createCrawlTool(getContext) {
43
+ return {
44
+ name: "webtest_crawl_app",
45
+ description: `Perform goal-directed exploration of a web application.
46
+
47
+ This tool crawls the target application using AI-powered decision making:
48
+ - Navigates pages based on the specified goal
49
+ - Captures screenshots, snapshots, and DOM at each step
50
+ - Detects and handles obstacles (cookie banners, modals)
51
+ - Supports checkpointing for resume after interruption
52
+ - Prevents infinite loops through multiple detection mechanisms
53
+
54
+ Progress is reported throughout, and the operation can be cancelled.
55
+ If sampling is unavailable, returns a prompt for manual execution.`,
56
+ inputSchema: crawlInputSchema,
57
+ async handler(input) {
58
+ const ctx = getContext();
59
+ const { logger, config, workspaceManager, playwrightClient, samplingClient, elicitationClient, cancellationRegistry, progressEmitter, securityValidator, resourceManager, } = ctx;
60
+ const requestId = `crawl-${input.analysisId}-${Date.now()}`;
61
+ const crawlLogger = logger.withCorrelation({
62
+ analysisId: input.analysisId,
63
+ requestId,
64
+ });
65
+ crawlLogger.info("Starting crawl", { goal: input.goal, strategy: input.strategy });
66
+ // Register for cancellation
67
+ cancellationRegistry.register(requestId);
68
+ try {
69
+ // Validate workspace exists
70
+ if (!(await workspaceManager.workspaceExists(input.analysisId))) {
71
+ return {
72
+ content: [
73
+ {
74
+ type: "text",
75
+ text: `Error: Analysis workspace "${input.analysisId}" not found. Run webtest_init first.`,
76
+ },
77
+ ],
78
+ isError: true,
79
+ };
80
+ }
81
+ const workspace = await workspaceManager.readWorkspaceIndex(input.analysisId);
82
+ const allowedDomains = [workspace.domain];
83
+ // Determine limits
84
+ const limits = {
85
+ maxSteps: input.limits?.maxSteps ?? workspace.limits.maxSteps,
86
+ maxMinutes: input.limits?.maxMinutes ?? workspace.limits.maxMinutes,
87
+ maxPages: input.limits?.maxPages ?? workspace.limits.maxPages,
88
+ };
89
+ // Create crawl entry
90
+ const { crawlId, crawlPath } = await workspaceManager.createCrawl(input.analysisId, { goal: input.goal, strategy: input.strategy, limits });
91
+ const iterationLogger = crawlLogger.withCorrelation({ crawlId });
92
+ iterationLogger.info("Crawl created", { crawlId, limits });
93
+ // Notify about new resource
94
+ await resourceManager.notifyListChanged();
95
+ // Initialize state
96
+ let state;
97
+ const startTime = Date.now();
98
+ // Check for resume
99
+ if (input.resume) {
100
+ const checkpoint = await workspaceManager.loadCheckpoint(input.analysisId, crawlId);
101
+ if (checkpoint && checkpoint.canResume) {
102
+ iterationLogger.info("Resuming from checkpoint", {
103
+ step: checkpoint.step,
104
+ hasLoopDetection: !!checkpoint.loopDetection,
105
+ });
106
+ // Restore loop detection state if available, otherwise start fresh
107
+ const loopDetection = checkpoint.loopDetection
108
+ ? {
109
+ domSignatures: new Map(checkpoint.loopDetection.domSignatures),
110
+ urlVisits: new Map(checkpoint.loopDetection.urlVisits),
111
+ recentActions: [...checkpoint.loopDetection.recentActions],
112
+ }
113
+ : {
114
+ domSignatures: new Map(),
115
+ urlVisits: new Map(),
116
+ recentActions: [],
117
+ };
118
+ if (!checkpoint.loopDetection) {
119
+ iterationLogger.warn("Resuming without loop detection history - context may be limited");
120
+ }
121
+ state = {
122
+ step: checkpoint.step,
123
+ currentUrl: checkpoint.currentUrl,
124
+ startUrl: checkpoint.startUrl || workspace.url,
125
+ visitedUrls: new Set(checkpoint.visitedUrls),
126
+ actionHistory: [],
127
+ goalProgress: checkpoint.goalProgress,
128
+ loopDetection,
129
+ };
130
+ }
131
+ else {
132
+ state = createInitialState(workspace.url);
133
+ }
134
+ }
135
+ else {
136
+ state = createInitialState(workspace.url);
137
+ }
138
+ // Ensure Playwright is connected
139
+ if (!playwrightClient.isConnected()) {
140
+ await playwrightClient.connect();
141
+ }
142
+ // Check current browser URL before navigating
143
+ // This prevents unnecessary navigation that would disrupt an existing browser session
144
+ let shouldNavigate = true;
145
+ try {
146
+ const currentSnapshot = await playwrightClient.snapshot();
147
+ const currentBrowserUrl = currentSnapshot.url;
148
+ // Skip navigation if browser is already on ANY page within the allowed domain
149
+ // This preserves the current browser state (e.g., logged in, items in cart)
150
+ if (currentBrowserUrl && currentBrowserUrl !== "about:blank") {
151
+ const currentDomain = new URL(currentBrowserUrl).hostname;
152
+ const targetDomain = new URL(state.startUrl).hostname;
153
+ if (currentDomain === targetDomain) {
154
+ // Browser is already on the target domain - don't navigate at all
155
+ // This preserves login state, cart contents, etc.
156
+ iterationLogger.info("Browser already on target domain, skipping navigation", {
157
+ currentBrowserUrl,
158
+ startUrl: state.startUrl,
159
+ });
160
+ shouldNavigate = false;
161
+ state.currentUrl = currentBrowserUrl;
162
+ }
163
+ }
164
+ }
165
+ catch (e) {
166
+ // If we can't get current URL, proceed with navigation
167
+ iterationLogger.debug("Could not check current browser URL, will navigate", {
168
+ error: e instanceof Error ? e.message : "Unknown error",
169
+ });
170
+ }
171
+ if (shouldNavigate) {
172
+ // Navigate to starting URL
173
+ await playwrightClient.navigate(state.currentUrl);
174
+ iterationLogger.info("Navigated to starting URL", { url: state.currentUrl });
175
+ }
176
+ // Main crawl loop
177
+ let goalComplete = false;
178
+ let blocked = false;
179
+ let blockedReason;
180
+ while (!goalComplete && !blocked) {
181
+ // Check cancellation
182
+ cancellationRegistry.checkCancelled(requestId);
183
+ // Check budget
184
+ const budget = calculateBudgetStatus({
185
+ stepsUsed: state.step,
186
+ maxSteps: limits.maxSteps,
187
+ startTime,
188
+ maxMinutes: limits.maxMinutes,
189
+ pagesVisited: state.visitedUrls.size,
190
+ maxPages: limits.maxPages,
191
+ });
192
+ if (budget.isExhausted) {
193
+ iterationLogger.info("Budget exhausted", { reason: budget.exhaustedReason });
194
+ break;
195
+ }
196
+ // Emit progress
197
+ progressEmitter.emit({
198
+ progressToken: requestId,
199
+ progress: state.step,
200
+ total: limits.maxSteps,
201
+ message: `Step ${state.step}: ${state.goalProgress || "Exploring..."}`,
202
+ budgetStatus: budget,
203
+ });
204
+ // Capture current state
205
+ const snapshot = await playwrightClient.snapshot();
206
+ const screenshot = await playwrightClient.screenshot();
207
+ state.currentUrl = snapshot.url;
208
+ // Track URL visits for loop detection
209
+ const urlVisits = state.loopDetection.urlVisits.get(state.currentUrl) || 0;
210
+ state.loopDetection.urlVisits.set(state.currentUrl, urlVisits + 1);
211
+ // Check for URL cycle
212
+ if (urlVisits >= 3) {
213
+ iterationLogger.warn("URL cycle detected", {
214
+ url: state.currentUrl,
215
+ visits: urlVisits + 1,
216
+ });
217
+ }
218
+ // Check DOM signature for same-state loop (include URL path for semantic differentiation)
219
+ let urlPath;
220
+ try {
221
+ urlPath = new URL(state.currentUrl).pathname;
222
+ }
223
+ catch {
224
+ // If URL parsing fails, try to extract path from the string or skip it
225
+ const pathMatch = state.currentUrl.match(/^(?:https?:\/\/[^\/]+)?(\/.*)$/);
226
+ urlPath = pathMatch?.[1];
227
+ }
228
+ const domSignature = createDomSignature(snapshot.content, urlPath);
229
+ const sigRepeats = state.loopDetection.domSignatures.get(domSignature) || 0;
230
+ state.loopDetection.domSignatures.set(domSignature, sigRepeats + 1);
231
+ if (sigRepeats >= 3) {
232
+ iterationLogger.warn("Same-state loop detected", {
233
+ signature: domSignature,
234
+ repeats: sigRepeats + 1,
235
+ });
236
+ }
237
+ // Save page artifacts
238
+ const artifacts = input.artifacts ?? {
239
+ captureScreenshots: true,
240
+ captureSnapshots: true,
241
+ captureDom: true,
242
+ };
243
+ if (artifacts.captureScreenshots ||
244
+ artifacts.captureSnapshots ||
245
+ artifacts.captureDom) {
246
+ await workspaceManager.savePage(input.analysisId, crawlId, {
247
+ url: state.currentUrl,
248
+ title: snapshot.title,
249
+ snapshot: JSON.stringify(snapshot),
250
+ screenshot,
251
+ dom: snapshot.content,
252
+ });
253
+ state.visitedUrls.add(state.currentUrl);
254
+ await resourceManager.notifyListChanged();
255
+ }
256
+ // Check for injection attempts in page content
257
+ const injectionCheck = securityValidator.detectInjectionAttempt(snapshot.content);
258
+ if (injectionCheck.detected) {
259
+ iterationLogger.warn("Injection attempt detected in page content", {
260
+ type: injectionCheck.type,
261
+ evidence: injectionCheck.evidence,
262
+ });
263
+ // Don't act on it, but include warning in sampling prompt
264
+ }
265
+ // Handle manual mode (no sampling)
266
+ if (input.manualNextActions && input.manualNextActions.length > 0) {
267
+ const action = input.manualNextActions.shift();
268
+ // Validate action
269
+ const actionValidation = securityValidator.validateAction(action, allowedDomains);
270
+ if (!actionValidation.valid) {
271
+ return {
272
+ content: [
273
+ {
274
+ type: "text",
275
+ text: `Security error: ${actionValidation.reason}`,
276
+ },
277
+ ],
278
+ isError: true,
279
+ };
280
+ }
281
+ await executeAction(playwrightClient, action, iterationLogger);
282
+ state.step++;
283
+ continue;
284
+ }
285
+ // Calculate budget percentage for flow progress
286
+ const budgetUsedPercent = Math.round((state.step / limits.maxSteps) * 100);
287
+ // Build common prompt parameters
288
+ const promptParams = {
289
+ goal: input.goal,
290
+ currentUrl: state.currentUrl,
291
+ pageSnapshot: snapshot.content.slice(0, 10000),
292
+ actionHistory: state.actionHistory,
293
+ allowedDomains,
294
+ startUrl: state.startUrl,
295
+ flowProgress: {
296
+ currentStep: state.step,
297
+ totalSteps: limits.maxSteps,
298
+ budgetUsedPercent,
299
+ previousGoalProgress: state.goalProgress,
300
+ },
301
+ loopState: {
302
+ domSignatureRepeats: sigRepeats,
303
+ urlVisitCount: Object.fromEntries(state.loopDetection.urlVisits),
304
+ lastActions: state.loopDetection.recentActions,
305
+ },
306
+ navigationBlocked: state.navigationBlocked,
307
+ };
308
+ // Clear navigation blocked after including in prompt
309
+ state.navigationBlocked = undefined;
310
+ // Request next action via sampling
311
+ if (!samplingClient.hasSampling()) {
312
+ // Return prompt for manual execution
313
+ const prompt = buildCrawlActionPrompt(promptParams);
314
+ const crawlIndex = await workspaceManager.readCrawlIndex(input.analysisId, crawlId);
315
+ return {
316
+ content: [
317
+ {
318
+ type: "text",
319
+ text: JSON.stringify({
320
+ needsManualInput: true,
321
+ crawlId,
322
+ step: state.step,
323
+ currentUrl: state.currentUrl,
324
+ prompt,
325
+ expectedResponseSchema: CrawlActionSchema._def,
326
+ instructions: "Execute this prompt with your LLM and call webtest_crawl_app again with manualNextActions",
327
+ partialResults: {
328
+ pagesVisited: crawlIndex.pages.length,
329
+ stepsCompleted: state.step,
330
+ },
331
+ }, null, 2),
332
+ },
333
+ ],
334
+ };
335
+ }
336
+ // Get action from sampling
337
+ const samplingResult = await samplingClient.createMessage({
338
+ systemPrompt: `You are analyzing a web application to achieve the following goal: ${input.goal}`,
339
+ userPrompt: buildCrawlActionPrompt(promptParams),
340
+ schema: CrawlActionSchema,
341
+ maxTokens: 2048,
342
+ });
343
+ if (!samplingResult.success || !samplingResult.data) {
344
+ iterationLogger.error("Sampling failed", {
345
+ error: samplingResult.error,
346
+ });
347
+ blocked = true;
348
+ blockedReason = `Sampling error: ${samplingResult.error}`;
349
+ break;
350
+ }
351
+ const actionResponse = samplingResult.data;
352
+ state.goalProgress = actionResponse.goalProgress;
353
+ // Check if goal is complete
354
+ if (actionResponse.goalComplete) {
355
+ goalComplete = true;
356
+ iterationLogger.info("Goal completed", {
357
+ reasoning: actionResponse.reasoning,
358
+ });
359
+ break;
360
+ }
361
+ // Check if blocked
362
+ if (actionResponse.blocked) {
363
+ blocked = true;
364
+ blockedReason = actionResponse.blockedReason;
365
+ iterationLogger.info("Crawl blocked", { reason: blockedReason });
366
+ break;
367
+ }
368
+ // Handle elicitation if needed
369
+ if (actionResponse.elicitationNeeded) {
370
+ const elicitResult = await handleElicitation(elicitationClient, actionResponse.elicitationNeeded, iterationLogger);
371
+ if (elicitResult.cancelled) {
372
+ blocked = true;
373
+ blockedReason = "User cancelled at elicitation prompt";
374
+ break;
375
+ }
376
+ // Use elicitation result to modify action if needed
377
+ if (elicitResult.selectedValue === "stop" ||
378
+ elicitResult.selectedValue === "reject") {
379
+ blocked = true;
380
+ blockedReason = `User chose: ${elicitResult.selectedValue}`;
381
+ break;
382
+ }
383
+ }
384
+ // Execute actions
385
+ for (const action of actionResponse.actions) {
386
+ // Check cancellation before each action
387
+ cancellationRegistry.checkCancelled(requestId);
388
+ // Navigation guard: block navigation to start URL after initial steps
389
+ if (action.tool === "navigate" && state.step >= 3) {
390
+ const targetUrl = action.args.url;
391
+ // Check if navigating back to start URL
392
+ if (isStartUrlNavigation(targetUrl, state.startUrl)) {
393
+ iterationLogger.warn("Navigation to start URL blocked", {
394
+ targetUrl,
395
+ startUrl: state.startUrl,
396
+ step: state.step,
397
+ });
398
+ // Set navigation blocked for next prompt feedback
399
+ state.navigationBlocked = {
400
+ url: targetUrl,
401
+ reason: "Cannot navigate back to start URL mid-flow. Try interacting with elements on the current page instead.",
402
+ };
403
+ continue;
404
+ }
405
+ }
406
+ // Validate action
407
+ const actionValidation = securityValidator.validateAction(action, allowedDomains);
408
+ if (!actionValidation.valid) {
409
+ iterationLogger.warn("Action blocked by security validator", {
410
+ action,
411
+ reason: actionValidation.reason,
412
+ });
413
+ continue;
414
+ }
415
+ // Check for exfiltration
416
+ const exfilCheck = securityValidator.detectExfiltrationAttempt(action, snapshot.content);
417
+ if (exfilCheck.detected) {
418
+ iterationLogger.warn("Exfiltration attempt blocked", {
419
+ type: exfilCheck.type,
420
+ evidence: exfilCheck.evidence,
421
+ });
422
+ continue;
423
+ }
424
+ // Execute action
425
+ const actionKey = `${action.tool}:${JSON.stringify(action.args)}`;
426
+ // Check for action repeat
427
+ const recentActions = state.loopDetection.recentActions;
428
+ if (recentActions.length >= 3 &&
429
+ recentActions.slice(-3).every((a) => a === actionKey)) {
430
+ iterationLogger.warn("Action repeat blocked", { action: actionKey });
431
+ continue;
432
+ }
433
+ await executeAction(playwrightClient, action, iterationLogger);
434
+ // Record action
435
+ await workspaceManager.recordAction(input.analysisId, crawlId, {
436
+ step: state.step,
437
+ timestamp: new Date().toISOString(),
438
+ tool: action.tool,
439
+ args: action.args,
440
+ result: "success",
441
+ reasoning: actionResponse.reasoning,
442
+ });
443
+ state.actionHistory.push(`Step ${state.step}: ${action.tool}(${JSON.stringify(action.args)}) - ${actionResponse.reasoning}`);
444
+ recentActions.push(actionKey);
445
+ if (recentActions.length > 10) {
446
+ recentActions.shift();
447
+ }
448
+ }
449
+ state.step++;
450
+ // Checkpoint every N steps
451
+ if (state.step % config.checkpointInterval === 0) {
452
+ const checkpoint = {
453
+ step: state.step,
454
+ timestamp: new Date().toISOString(),
455
+ visitedUrls: Array.from(state.visitedUrls),
456
+ currentUrl: state.currentUrl,
457
+ goalProgress: state.goalProgress,
458
+ canResume: true,
459
+ startUrl: state.startUrl,
460
+ // Preserve loop detection state for context continuity
461
+ loopDetection: {
462
+ domSignatures: Array.from(state.loopDetection.domSignatures.entries()),
463
+ urlVisits: Array.from(state.loopDetection.urlVisits.entries()),
464
+ recentActions: [...state.loopDetection.recentActions],
465
+ },
466
+ };
467
+ await workspaceManager.saveCheckpoint(input.analysisId, crawlId, checkpoint);
468
+ iterationLogger.info("Checkpoint saved", { step: state.step });
469
+ }
470
+ }
471
+ // Finalize crawl
472
+ const finalStatus = goalComplete
473
+ ? "completed"
474
+ : blocked
475
+ ? "failed"
476
+ : "completed";
477
+ await workspaceManager.updateCrawlIndex(input.analysisId, crawlId, {
478
+ status: finalStatus,
479
+ completedAt: new Date().toISOString(),
480
+ });
481
+ // Update workspace crawl reference
482
+ const updatedWorkspace = await workspaceManager.readWorkspaceIndex(input.analysisId);
483
+ const crawlRef = updatedWorkspace.crawls.find((c) => c.crawlId === crawlId);
484
+ if (crawlRef) {
485
+ crawlRef.status = finalStatus;
486
+ crawlRef.completedAt = new Date().toISOString();
487
+ crawlRef.pagesVisited = state.visitedUrls.size;
488
+ crawlRef.stepsExecuted = state.step;
489
+ await workspaceManager.updateWorkspaceIndex(input.analysisId, {
490
+ crawls: updatedWorkspace.crawls,
491
+ });
492
+ }
493
+ await resourceManager.notifyListChanged();
494
+ const crawlIndex = await workspaceManager.readCrawlIndex(input.analysisId, crawlId);
495
+ const result = {
496
+ crawlId,
497
+ status: finalStatus,
498
+ goalComplete,
499
+ blocked,
500
+ blockedReason,
501
+ pagesVisited: crawlIndex.pages.length,
502
+ stepsExecuted: state.step,
503
+ crawlIndexFilePath: join(crawlPath, "index.md"),
504
+ crawlIndexUri: `webtest://${input.analysisId}/crawls/${crawlId}/index.md`,
505
+ summaryUri: `webtest://${input.analysisId}/crawls/${crawlId}/summary.md`,
506
+ nextSteps: goalComplete
507
+ ? [
508
+ `Use webtest_analyze_app with analysisId="${input.analysisId}" to analyze the application`,
509
+ ]
510
+ : blocked
511
+ ? [`Resolve the blocker: ${blockedReason}`]
512
+ : [
513
+ `Crawl completed with partial results`,
514
+ `Use webtest_analyze_app to analyze what was discovered`,
515
+ ],
516
+ };
517
+ iterationLogger.info("Crawl completed", {
518
+ status: finalStatus,
519
+ pagesVisited: result.pagesVisited,
520
+ stepsExecuted: result.stepsExecuted,
521
+ });
522
+ return {
523
+ content: [
524
+ {
525
+ type: "text",
526
+ text: JSON.stringify(result, null, 2),
527
+ },
528
+ ],
529
+ };
530
+ }
531
+ catch (error) {
532
+ if (error instanceof CancellationError) {
533
+ crawlLogger.info("Crawl cancelled", { requestId: error.requestId });
534
+ return {
535
+ content: [
536
+ {
537
+ type: "text",
538
+ text: JSON.stringify({
539
+ status: "cancelled",
540
+ message: "Crawl was cancelled by user",
541
+ partialResultsAvailable: true,
542
+ }, null, 2),
543
+ },
544
+ ],
545
+ };
546
+ }
547
+ const message = error instanceof Error ? error.message : "Unknown error";
548
+ crawlLogger.error("Crawl failed", { error: message });
549
+ return {
550
+ content: [
551
+ {
552
+ type: "text",
553
+ text: `Error during crawl: ${message}`,
554
+ },
555
+ ],
556
+ isError: true,
557
+ };
558
+ }
559
+ finally {
560
+ cancellationRegistry.unregister(requestId);
561
+ }
562
+ },
563
+ };
564
+ }
565
+ function createInitialState(startUrl) {
566
+ return {
567
+ step: 0,
568
+ currentUrl: startUrl,
569
+ startUrl,
570
+ visitedUrls: new Set(),
571
+ actionHistory: [],
572
+ goalProgress: "Starting exploration",
573
+ loopDetection: {
574
+ domSignatures: new Map(),
575
+ urlVisits: new Map(),
576
+ recentActions: [],
577
+ },
578
+ };
579
+ }
580
+ /**
581
+ * Check if a target URL is the start URL (for navigation guard).
582
+ * Compares normalized URLs to handle variations like trailing slashes and default paths.
583
+ */
584
+ function isStartUrlNavigation(targetUrl, startUrl) {
585
+ try {
586
+ const target = new URL(targetUrl, startUrl);
587
+ const start = new URL(startUrl);
588
+ // Normalize paths (remove trailing slash, handle "/" as empty)
589
+ const normalizePathname = (p) => {
590
+ const normalized = p.replace(/\/$/, "") || "/";
591
+ return normalized;
592
+ };
593
+ // Compare origin and pathname
594
+ const targetPath = normalizePathname(target.pathname);
595
+ const startPath = normalizePathname(start.pathname);
596
+ return target.origin === start.origin && targetPath === startPath;
597
+ }
598
+ catch {
599
+ // If URL parsing fails, do simple string comparison
600
+ return targetUrl === startUrl || targetUrl === "/" || targetUrl === "";
601
+ }
602
+ }
603
+ async function executeAction(playwright, action, logger) {
604
+ logger.debug("Executing action", { tool: action.tool, args: action.args });
605
+ switch (action.tool) {
606
+ case "navigate":
607
+ await playwright.navigate(action.args.url);
608
+ break;
609
+ case "click":
610
+ await playwright.click(action.args.element, action.args.ref);
611
+ break;
612
+ case "type":
613
+ await playwright.type(action.args.element, action.args.ref, action.args.text, {
614
+ submit: action.args.submit,
615
+ slowly: action.args.slowly,
616
+ });
617
+ break;
618
+ case "fill":
619
+ await playwright.fill(action.args.element, action.args.ref, action.args.value);
620
+ break;
621
+ case "hover":
622
+ await playwright.hover(action.args.element, action.args.ref);
623
+ break;
624
+ case "select":
625
+ await playwright.select(action.args.element, action.args.ref, action.args.values);
626
+ break;
627
+ case "press":
628
+ await playwright.press(action.args.key);
629
+ break;
630
+ case "scroll":
631
+ await playwright.scroll(action.args.x, action.args.y);
632
+ break;
633
+ case "wait":
634
+ await playwright.wait(action.args.ms);
635
+ break;
636
+ default:
637
+ logger.debug("Unknown action tool", { tool: action.tool });
638
+ }
639
+ }
640
+ async function handleElicitation(elicitationClient, elicitation, logger) {
641
+ logger.info("Handling elicitation", {
642
+ type: elicitation.type,
643
+ context: elicitation.context,
644
+ });
645
+ let request;
646
+ switch (elicitation.type) {
647
+ case "cookie_consent":
648
+ request = elicitationClient.createCookieConsentRequest(elicitation.context);
649
+ break;
650
+ case "modal_blocking":
651
+ request = elicitationClient.createModalBlockingRequest(elicitation.context);
652
+ break;
653
+ case "ambiguous_navigation":
654
+ request = elicitationClient.createAmbiguousNavigationRequest((elicitation.options || []).map(opt => ({
655
+ url: opt.url || "",
656
+ label: opt.label,
657
+ })));
658
+ break;
659
+ case "auth_required":
660
+ request = elicitationClient.createAuthRequiredRequest();
661
+ break;
662
+ default:
663
+ return {};
664
+ }
665
+ const result = await elicitationClient.elicit(request);
666
+ if (!result.success) {
667
+ // Fallback: include questions in next sampling prompt
668
+ logger.info("Elicitation fallback", {
669
+ fallbackQuestions: result.fallbackQuestions,
670
+ });
671
+ return {};
672
+ }
673
+ return {
674
+ selectedValue: result.selectedValue,
675
+ cancelled: result.cancelled,
676
+ };
677
+ }
678
+ //# sourceMappingURL=crawl.js.map