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,399 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { createElicitationClient, ElicitationType } from "./index.js";
3
+ import type { Logger } from "../logger.js";
4
+ import type { ClientCapabilities } from "../types/capabilities.js";
5
+ import type { ElicitationRequest, ElicitationResponse } from "./types.js";
6
+
7
+ describe("elicitation", () => {
8
+ let mockLogger: Logger;
9
+
10
+ beforeEach(() => {
11
+ mockLogger = {
12
+ debug: vi.fn(),
13
+ info: vi.fn(),
14
+ warn: vi.fn(),
15
+ error: vi.fn(),
16
+ setLevel: vi.fn(),
17
+ withCorrelation: vi.fn().mockReturnThis(),
18
+ } as unknown as Logger;
19
+ });
20
+
21
+ const capabilitiesWithElicitation: ClientCapabilities = {
22
+ sampling: true,
23
+ elicitation: true,
24
+ logging: false,
25
+ progress: true,
26
+ resourcesListChanged: false,
27
+ resourcesSubscribe: false,
28
+ protocolVersion: "2025-06-18",
29
+ };
30
+
31
+ const capabilitiesWithoutElicitation: ClientCapabilities = {
32
+ sampling: true,
33
+ elicitation: false,
34
+ logging: false,
35
+ progress: true,
36
+ resourcesListChanged: false,
37
+ resourcesSubscribe: false,
38
+ protocolVersion: "2025-06-18",
39
+ };
40
+
41
+ describe("createElicitationClient", () => {
42
+ it("reports elicitation availability correctly", () => {
43
+ const clientWith = createElicitationClient(
44
+ vi.fn(),
45
+ capabilitiesWithElicitation,
46
+ mockLogger
47
+ );
48
+
49
+ const clientWithout = createElicitationClient(
50
+ vi.fn(),
51
+ capabilitiesWithoutElicitation,
52
+ mockLogger
53
+ );
54
+
55
+ expect(clientWith.hasElicitation()).toBe(true);
56
+ expect(clientWithout.hasElicitation()).toBe(false);
57
+ });
58
+ });
59
+
60
+ describe("elicit", () => {
61
+ it("returns fallback when elicitation unavailable", async () => {
62
+ const requestElicitation = vi.fn();
63
+ const client = createElicitationClient(
64
+ requestElicitation,
65
+ capabilitiesWithoutElicitation,
66
+ mockLogger
67
+ );
68
+
69
+ const request: ElicitationRequest = {
70
+ type: ElicitationType.CookieConsent,
71
+ title: "Cookie Banner",
72
+ message: "A cookie banner was detected",
73
+ options: [{ value: "accept", label: "Accept all" }],
74
+ };
75
+
76
+ const result = await client.elicit(request);
77
+
78
+ expect(result.success).toBe(false);
79
+ expect(result.error).toBe("Elicitation not available");
80
+ expect(result.fallbackQuestions).toBeDefined();
81
+ expect(requestElicitation).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it("blocks credential elicitation requests", async () => {
85
+ const requestElicitation = vi.fn();
86
+ const client = createElicitationClient(
87
+ requestElicitation,
88
+ capabilitiesWithElicitation,
89
+ mockLogger
90
+ );
91
+
92
+ const request: ElicitationRequest = {
93
+ type: ElicitationType.AuthRequired,
94
+ title: "Auth Required",
95
+ message: "Please authenticate",
96
+ options: [
97
+ { value: "enter_password", label: "Enter your password" },
98
+ { value: "skip", label: "Skip" },
99
+ ],
100
+ };
101
+
102
+ const result = await client.elicit(request);
103
+
104
+ expect(result.success).toBe(false);
105
+ expect(result.error).toBe("Security policy prevents credential elicitation");
106
+ expect(requestElicitation).not.toHaveBeenCalled();
107
+ expect(mockLogger.error).toHaveBeenCalledWith(
108
+ "Attempted to elicit credentials - blocked",
109
+ expect.any(Object)
110
+ );
111
+ });
112
+
113
+ it("handles user cancel action", async () => {
114
+ const mockResponse: ElicitationResponse = {
115
+ action: "cancel",
116
+ };
117
+
118
+ const requestElicitation = vi.fn().mockResolvedValue(mockResponse);
119
+ const client = createElicitationClient(
120
+ requestElicitation,
121
+ capabilitiesWithElicitation,
122
+ mockLogger
123
+ );
124
+
125
+ const request: ElicitationRequest = {
126
+ type: ElicitationType.CookieConsent,
127
+ title: "Cookie Banner",
128
+ message: "A cookie banner was detected",
129
+ options: [{ value: "accept", label: "Accept all" }],
130
+ };
131
+
132
+ const result = await client.elicit(request);
133
+
134
+ expect(result.success).toBe(true);
135
+ expect(result.cancelled).toBe(true);
136
+ });
137
+
138
+ it("handles user dismiss action", async () => {
139
+ const mockResponse: ElicitationResponse = {
140
+ action: "dismiss",
141
+ };
142
+
143
+ const requestElicitation = vi.fn().mockResolvedValue(mockResponse);
144
+ const client = createElicitationClient(
145
+ requestElicitation,
146
+ capabilitiesWithElicitation,
147
+ mockLogger
148
+ );
149
+
150
+ const request: ElicitationRequest = {
151
+ type: ElicitationType.ModalBlocking,
152
+ title: "Modal",
153
+ message: "A modal is blocking",
154
+ options: [{ value: "close", label: "Close" }],
155
+ };
156
+
157
+ const result = await client.elicit(request);
158
+
159
+ expect(result.success).toBe(true);
160
+ expect(result.selectedValue).toBe("dismiss");
161
+ });
162
+
163
+ it("extracts selected value from response", async () => {
164
+ const mockResponse: ElicitationResponse = {
165
+ action: "submit",
166
+ content: { selected: "accept_all" },
167
+ };
168
+
169
+ const requestElicitation = vi.fn().mockResolvedValue(mockResponse);
170
+ const client = createElicitationClient(
171
+ requestElicitation,
172
+ capabilitiesWithElicitation,
173
+ mockLogger
174
+ );
175
+
176
+ const request: ElicitationRequest = {
177
+ type: ElicitationType.CookieConsent,
178
+ title: "Cookie Banner",
179
+ message: "A cookie banner was detected",
180
+ options: [{ value: "accept_all", label: "Accept all" }],
181
+ };
182
+
183
+ const result = await client.elicit(request);
184
+
185
+ expect(result.success).toBe(true);
186
+ expect(result.selectedValue).toBe("accept_all");
187
+ });
188
+
189
+ it("extracts custom value from response", async () => {
190
+ const mockResponse: ElicitationResponse = {
191
+ action: "submit",
192
+ content: { custom: "/custom/path" },
193
+ };
194
+
195
+ const requestElicitation = vi.fn().mockResolvedValue(mockResponse);
196
+ const client = createElicitationClient(
197
+ requestElicitation,
198
+ capabilitiesWithElicitation,
199
+ mockLogger
200
+ );
201
+
202
+ const request: ElicitationRequest = {
203
+ type: ElicitationType.AmbiguousNavigation,
204
+ title: "Multiple options",
205
+ message: "Which path?",
206
+ options: [],
207
+ allowCustom: true,
208
+ };
209
+
210
+ const result = await client.elicit(request);
211
+
212
+ expect(result.success).toBe(true);
213
+ expect(result.customValue).toBe("/custom/path");
214
+ });
215
+
216
+ it("handles request errors gracefully", async () => {
217
+ const requestElicitation = vi
218
+ .fn()
219
+ .mockRejectedValue(new Error("Connection failed"));
220
+
221
+ const client = createElicitationClient(
222
+ requestElicitation,
223
+ capabilitiesWithElicitation,
224
+ mockLogger
225
+ );
226
+
227
+ const request: ElicitationRequest = {
228
+ type: ElicitationType.CookieConsent,
229
+ title: "Cookie Banner",
230
+ message: "A cookie banner was detected",
231
+ options: [{ value: "accept", label: "Accept" }],
232
+ };
233
+
234
+ const result = await client.elicit(request);
235
+
236
+ expect(result.success).toBe(false);
237
+ expect(result.error).toBe("Connection failed");
238
+ expect(result.fallbackQuestions).toBeDefined();
239
+ });
240
+ });
241
+
242
+ describe("createCookieConsentRequest", () => {
243
+ it("creates properly structured request", () => {
244
+ const client = createElicitationClient(
245
+ vi.fn(),
246
+ capabilitiesWithElicitation,
247
+ mockLogger
248
+ );
249
+
250
+ const request = client.createCookieConsentRequest(
251
+ "GDPR banner with accept/reject buttons"
252
+ );
253
+
254
+ expect(request.type).toBe(ElicitationType.CookieConsent);
255
+ expect(request.title).toBe("Cookie Consent Banner Detected");
256
+ expect(request.message).toContain("GDPR banner");
257
+ expect(request.options.length).toBeGreaterThan(0);
258
+ });
259
+ });
260
+
261
+ describe("createModalBlockingRequest", () => {
262
+ it("creates properly structured request", () => {
263
+ const client = createElicitationClient(
264
+ vi.fn(),
265
+ capabilitiesWithElicitation,
266
+ mockLogger
267
+ );
268
+
269
+ const request = client.createModalBlockingRequest(
270
+ "Sign up for our newsletter to get 10% off!"
271
+ );
272
+
273
+ expect(request.type).toBe(ElicitationType.ModalBlocking);
274
+ expect(request.title).toBe("Modal Dialog Blocking Page");
275
+ expect(request.message).toContain("Sign up");
276
+ expect(request.options.length).toBeGreaterThan(0);
277
+ });
278
+
279
+ it("truncates long modal content", () => {
280
+ const client = createElicitationClient(
281
+ vi.fn(),
282
+ capabilitiesWithElicitation,
283
+ mockLogger
284
+ );
285
+
286
+ const longContent = "A".repeat(500);
287
+ const request = client.createModalBlockingRequest(longContent);
288
+
289
+ expect(request.message.length).toBeLessThan(500);
290
+ expect(request.message).toContain("...");
291
+ });
292
+ });
293
+
294
+ describe("createAmbiguousNavigationRequest", () => {
295
+ it("creates properly structured request", () => {
296
+ const client = createElicitationClient(
297
+ vi.fn(),
298
+ capabilitiesWithElicitation,
299
+ mockLogger
300
+ );
301
+
302
+ const options = [
303
+ { url: "/products", label: "Products" },
304
+ { url: "/services", label: "Services" },
305
+ { url: "/about", label: "About Us" },
306
+ ];
307
+
308
+ const request = client.createAmbiguousNavigationRequest(options);
309
+
310
+ expect(request.type).toBe(ElicitationType.AmbiguousNavigation);
311
+ expect(request.title).toBe("Multiple Navigation Options");
312
+ expect(request.options.length).toBe(3);
313
+ expect(request.options[0].value).toBe("/products");
314
+ expect(request.options[0].label).toBe("Products");
315
+ expect(request.allowCustom).toBe(true);
316
+ });
317
+
318
+ it("limits options to 5 maximum", () => {
319
+ const client = createElicitationClient(
320
+ vi.fn(),
321
+ capabilitiesWithElicitation,
322
+ mockLogger
323
+ );
324
+
325
+ const options = Array.from({ length: 10 }, (_, i) => ({
326
+ url: `/path${i}`,
327
+ label: `Option ${i}`,
328
+ }));
329
+
330
+ const request = client.createAmbiguousNavigationRequest(options);
331
+
332
+ expect(request.options.length).toBe(5);
333
+ });
334
+
335
+ it("uses default labels when not provided", () => {
336
+ const client = createElicitationClient(
337
+ vi.fn(),
338
+ capabilitiesWithElicitation,
339
+ mockLogger
340
+ );
341
+
342
+ const options = [
343
+ { url: "/path1", label: "" },
344
+ { url: "/path2", label: "" },
345
+ ];
346
+
347
+ const request = client.createAmbiguousNavigationRequest(options);
348
+
349
+ expect(request.options[0].label).toBe("Option 1");
350
+ expect(request.options[1].label).toBe("Option 2");
351
+ });
352
+ });
353
+
354
+ describe("createAuthRequiredRequest", () => {
355
+ it("creates properly structured request", () => {
356
+ const client = createElicitationClient(
357
+ vi.fn(),
358
+ capabilitiesWithElicitation,
359
+ mockLogger
360
+ );
361
+
362
+ const request = client.createAuthRequiredRequest();
363
+
364
+ expect(request.type).toBe(ElicitationType.AuthRequired);
365
+ expect(request.title).toBe("Authentication Required");
366
+ expect(request.message).toContain("security reasons");
367
+ expect(request.message).toContain("cannot handle login automatically");
368
+ expect(request.options.length).toBeGreaterThan(0);
369
+ });
370
+
371
+ it("does not include credential entry options", () => {
372
+ const client = createElicitationClient(
373
+ vi.fn(),
374
+ capabilitiesWithElicitation,
375
+ mockLogger
376
+ );
377
+
378
+ const request = client.createAuthRequiredRequest();
379
+
380
+ const hasCredentialOption = request.options.some(
381
+ (opt) =>
382
+ opt.value.includes("password") ||
383
+ opt.value.includes("credential") ||
384
+ opt.value.includes("login")
385
+ );
386
+
387
+ expect(hasCredentialOption).toBe(false);
388
+ });
389
+ });
390
+
391
+ describe("ElicitationType enum", () => {
392
+ it("has expected values", () => {
393
+ expect(ElicitationType.CookieConsent).toBe("cookie_consent");
394
+ expect(ElicitationType.ModalBlocking).toBe("modal_blocking");
395
+ expect(ElicitationType.AmbiguousNavigation).toBe("ambiguous_navigation");
396
+ expect(ElicitationType.AuthRequired).toBe("auth_required");
397
+ });
398
+ });
399
+ });
@@ -0,0 +1,171 @@
1
+ import type { Logger } from "../logger.js";
2
+ import type { ClientCapabilities } from "../types/capabilities.js";
3
+ import { isElicitationSupported } from "../types/capabilities.js";
4
+ import {
5
+ type ElicitationRequest,
6
+ type ElicitationResponse,
7
+ type ElicitationResult,
8
+ ElicitationType,
9
+ COOKIE_CONSENT_OPTIONS,
10
+ MODAL_BLOCKING_OPTIONS,
11
+ AUTH_REQUIRED_OPTIONS,
12
+ } from "./types.js";
13
+
14
+ export * from "./types.js";
15
+
16
+ export interface ElicitationClient {
17
+ elicit(request: ElicitationRequest): Promise<ElicitationResult>;
18
+ hasElicitation(): boolean;
19
+ createCookieConsentRequest(context: string): ElicitationRequest;
20
+ createModalBlockingRequest(modalContent: string): ElicitationRequest;
21
+ createAmbiguousNavigationRequest(
22
+ options: Array<{ url: string; label: string }>
23
+ ): ElicitationRequest;
24
+ createAuthRequiredRequest(): ElicitationRequest;
25
+ }
26
+
27
+ export function createElicitationClient(
28
+ requestElicitation: (
29
+ request: ElicitationRequest
30
+ ) => Promise<ElicitationResponse>,
31
+ capabilities: ClientCapabilities,
32
+ logger: Logger
33
+ ): ElicitationClient {
34
+ const hasElicitationCapability = isElicitationSupported(capabilities);
35
+
36
+ async function elicit(
37
+ request: ElicitationRequest
38
+ ): Promise<ElicitationResult> {
39
+ // Validate that we never request credentials
40
+ if (request.type === ElicitationType.AuthRequired) {
41
+ // Ensure options don't include credential entry
42
+ const hasCredentialOption = request.options.some(
43
+ (opt) =>
44
+ opt.value.includes("password") ||
45
+ opt.value.includes("credential") ||
46
+ opt.value.includes("login")
47
+ );
48
+
49
+ if (hasCredentialOption) {
50
+ logger.error("Attempted to elicit credentials - blocked", {
51
+ type: request.type,
52
+ });
53
+ return {
54
+ success: false,
55
+ error: "Security policy prevents credential elicitation",
56
+ };
57
+ }
58
+ }
59
+
60
+ // If elicitation not available, return fallback
61
+ if (!hasElicitationCapability) {
62
+ logger.info("Elicitation unavailable, returning fallback questions");
63
+
64
+ return {
65
+ success: false,
66
+ error: "Elicitation not available",
67
+ fallbackQuestions: request,
68
+ };
69
+ }
70
+
71
+ logger.debug("Sending elicitation request", {
72
+ type: request.type,
73
+ title: request.title,
74
+ optionCount: request.options.length,
75
+ });
76
+
77
+ try {
78
+ const response = await requestElicitation(request);
79
+
80
+ if (response.action === "cancel") {
81
+ logger.info("User cancelled elicitation", { type: request.type });
82
+ return {
83
+ success: true,
84
+ cancelled: true,
85
+ };
86
+ }
87
+
88
+ if (response.action === "dismiss") {
89
+ logger.info("User dismissed elicitation", { type: request.type });
90
+ return {
91
+ success: true,
92
+ selectedValue: "dismiss",
93
+ };
94
+ }
95
+
96
+ // Extract selected value from content
97
+ const selectedValue = response.content?.selected || response.content?.value;
98
+
99
+ logger.info("Elicitation response received", {
100
+ type: request.type,
101
+ action: response.action,
102
+ selectedValue,
103
+ });
104
+
105
+ return {
106
+ success: true,
107
+ selectedValue,
108
+ customValue: response.content?.custom,
109
+ };
110
+ } catch (error) {
111
+ const message = error instanceof Error ? error.message : "Unknown error";
112
+ logger.error("Elicitation request failed", { error: message });
113
+
114
+ return {
115
+ success: false,
116
+ error: message,
117
+ fallbackQuestions: request,
118
+ };
119
+ }
120
+ }
121
+
122
+ return {
123
+ elicit,
124
+ hasElicitation: () => hasElicitationCapability,
125
+
126
+ createCookieConsentRequest(context: string): ElicitationRequest {
127
+ return {
128
+ type: ElicitationType.CookieConsent,
129
+ title: "Cookie Consent Banner Detected",
130
+ message: `A cookie consent banner was detected on the page. How should we handle it?\n\nContext: ${context}`,
131
+ options: COOKIE_CONSENT_OPTIONS,
132
+ };
133
+ },
134
+
135
+ createModalBlockingRequest(modalContent: string): ElicitationRequest {
136
+ return {
137
+ type: ElicitationType.ModalBlocking,
138
+ title: "Modal Dialog Blocking Page",
139
+ message: `A modal dialog is blocking page interaction.\n\nModal content: ${modalContent.slice(0, 200)}...`,
140
+ options: MODAL_BLOCKING_OPTIONS,
141
+ };
142
+ },
143
+
144
+ createAmbiguousNavigationRequest(
145
+ options: Array<{ url: string; label: string }>
146
+ ): ElicitationRequest {
147
+ return {
148
+ type: ElicitationType.AmbiguousNavigation,
149
+ title: "Multiple Navigation Options",
150
+ message:
151
+ "Multiple similar navigation options were found. Which path should we explore?",
152
+ options: options.slice(0, 5).map((opt, i) => ({
153
+ value: opt.url,
154
+ label: opt.label || `Option ${i + 1}`,
155
+ description: opt.url,
156
+ })),
157
+ allowCustom: true,
158
+ };
159
+ },
160
+
161
+ createAuthRequiredRequest(): ElicitationRequest {
162
+ return {
163
+ type: ElicitationType.AuthRequired,
164
+ title: "Authentication Required",
165
+ message:
166
+ "This page requires authentication. For security reasons, we cannot handle login automatically. How would you like to proceed?",
167
+ options: AUTH_REQUIRED_OPTIONS,
168
+ };
169
+ },
170
+ };
171
+ }
@@ -0,0 +1,68 @@
1
+ export enum ElicitationType {
2
+ CookieConsent = "cookie_consent",
3
+ ModalBlocking = "modal_blocking",
4
+ AmbiguousNavigation = "ambiguous_navigation",
5
+ AuthRequired = "auth_required",
6
+ ConfirmAction = "confirm_action",
7
+ }
8
+
9
+ export interface ElicitationOption {
10
+ value: string;
11
+ label: string;
12
+ description?: string;
13
+ }
14
+
15
+ export interface ElicitationRequest {
16
+ type: ElicitationType;
17
+ title: string;
18
+ message: string;
19
+ options: ElicitationOption[];
20
+ allowCustom?: boolean;
21
+ }
22
+
23
+ export interface ElicitationResponse {
24
+ action: "confirm" | "dismiss" | "cancel";
25
+ content?: Record<string, string>;
26
+ }
27
+
28
+ export interface ElicitationResult {
29
+ success: boolean;
30
+ selectedValue?: string;
31
+ customValue?: string;
32
+ cancelled?: boolean;
33
+ error?: string;
34
+ fallbackQuestions?: ElicitationRequest;
35
+ }
36
+
37
+ // Predefined option sets for common scenarios
38
+ export const COOKIE_CONSENT_OPTIONS: ElicitationOption[] = [
39
+ { value: "accept", label: "Accept All", description: "Accept all cookies" },
40
+ {
41
+ value: "reject",
42
+ label: "Reject All",
43
+ description: "Reject non-essential cookies",
44
+ },
45
+ {
46
+ value: "dismiss",
47
+ label: "Dismiss",
48
+ description: "Close the banner without choosing",
49
+ },
50
+ ];
51
+
52
+ export const MODAL_BLOCKING_OPTIONS: ElicitationOption[] = [
53
+ { value: "close", label: "Close Modal", description: "Close and continue" },
54
+ {
55
+ value: "interact",
56
+ label: "Interact",
57
+ description: "Interact with modal content",
58
+ },
59
+ ];
60
+
61
+ export const AUTH_REQUIRED_OPTIONS: ElicitationOption[] = [
62
+ { value: "stop", label: "Stop Analysis", description: "End the analysis" },
63
+ {
64
+ value: "continue",
65
+ label: "Continue Unauthenticated",
66
+ description: "Continue without logging in",
67
+ },
68
+ ];