@testdriverai/mcp 7.10.0-canary.0 → 7.10.1-test

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 (196) hide show
  1. package/.claude-plugin/marketplace.json +17 -0
  2. package/agent/index.js +116 -71
  3. package/agent/interface.js +13 -1
  4. package/agent/lib/commands.js +35 -15
  5. package/agent/lib/provision-commands.js +92 -11
  6. package/agent/lib/sandbox.js +263 -30
  7. package/agent/lib/sdk.js +16 -12
  8. package/agent/lib/system.js +107 -31
  9. package/ai/.claude-plugin/plugin.json +89 -4
  10. package/ai/agents/testdriver.md +4 -8
  11. package/ai/skills/testdriver-assert/SKILL.md +0 -1
  12. package/ai/skills/testdriver-aws-setup/SKILL.md +1 -1
  13. package/ai/skills/testdriver-caching/SKILL.md +13 -6
  14. package/ai/skills/testdriver-ci-cd/SKILL.md +57 -14
  15. package/ai/skills/testdriver-client/SKILL.md +10 -6
  16. package/ai/skills/testdriver-customizing-devices/SKILL.md +7 -5
  17. package/ai/skills/testdriver-dashcam/SKILL.md +36 -3
  18. package/ai/skills/testdriver-debugging-with-screenshots/SKILL.md +28 -4
  19. package/ai/skills/testdriver-device-config/SKILL.md +5 -5
  20. package/ai/skills/testdriver-find/SKILL.md +50 -11
  21. package/ai/skills/testdriver-generating-tests/SKILL.md +163 -8
  22. package/ai/skills/testdriver-hosted/SKILL.md +1 -1
  23. package/ai/skills/testdriver-machine-setup/SKILL.md +331 -0
  24. package/ai/skills/testdriver-making-assertions/SKILL.md +78 -2
  25. package/ai/skills/testdriver-mcp-workflow/SKILL.md +2 -2
  26. package/ai/skills/testdriver-parse/SKILL.md +1 -1
  27. package/ai/skills/testdriver-performing-actions/SKILL.md +97 -3
  28. package/ai/skills/testdriver-provision/SKILL.md +0 -20
  29. package/ai/skills/testdriver-quickstart/SKILL.md +299 -21
  30. package/ai/skills/testdriver-reusable-code/SKILL.md +3 -3
  31. package/ai/skills/testdriver-running-tests/SKILL.md +1 -1
  32. package/ai/skills/testdriver-screenshot/SKILL.md +5 -5
  33. package/ai/skills/testdriver-scroll/SKILL.md +30 -160
  34. package/ai/skills/testdriver-test-writer/SKILL.md +3 -5
  35. package/ai/skills/testdriver-testdriver/SKILL.md +3 -7
  36. package/ai/skills/testdriver-wait/SKILL.md +1 -1
  37. package/{docs/v7/ai.mdx → ai/skills/testdriver:ai/SKILL.md} +3 -4
  38. package/ai/skills/testdriver:assert/SKILL.md +315 -0
  39. package/ai/skills/testdriver:aws-setup/SKILL.md +448 -0
  40. package/ai/skills/testdriver:cache/SKILL.md +221 -0
  41. package/ai/skills/testdriver:caching/SKILL.md +124 -0
  42. package/ai/skills/testdriver:captcha/SKILL.md +158 -0
  43. package/ai/skills/testdriver:ci-cd/SKILL.md +659 -0
  44. package/ai/skills/testdriver:click/SKILL.md +286 -0
  45. package/ai/skills/testdriver:client/SKILL.md +477 -0
  46. package/ai/skills/testdriver:customizing-devices/SKILL.md +319 -0
  47. package/ai/skills/testdriver:dashcam/SKILL.md +451 -0
  48. package/ai/skills/testdriver:debugging-with-screenshots/SKILL.md +415 -0
  49. package/{docs/v7/device-config.mdx → ai/skills/testdriver:device-config/SKILL.md} +8 -8
  50. package/ai/skills/testdriver:double-click/SKILL.md +102 -0
  51. package/ai/skills/testdriver:elements/SKILL.md +605 -0
  52. package/ai/skills/testdriver:enterprise/SKILL.md +7 -0
  53. package/ai/skills/testdriver:errors/SKILL.md +246 -0
  54. package/ai/skills/testdriver:events/SKILL.md +356 -0
  55. package/ai/skills/testdriver:exec/SKILL.md +317 -0
  56. package/ai/skills/testdriver:find/SKILL.md +860 -0
  57. package/ai/skills/testdriver:focus-application/SKILL.md +293 -0
  58. package/ai/skills/testdriver:generating-tests/SKILL.md +25 -0
  59. package/ai/skills/testdriver:hosted/SKILL.md +156 -0
  60. package/ai/skills/testdriver:hover/SKILL.md +278 -0
  61. package/{docs/v7/locating-elements.mdx → ai/skills/testdriver:locating-elements/SKILL.md} +3 -3
  62. package/ai/skills/testdriver:making-assertions/SKILL.md +32 -0
  63. package/ai/skills/testdriver:mcp/SKILL.md +7 -0
  64. package/ai/skills/testdriver:mouse-down/SKILL.md +161 -0
  65. package/ai/skills/testdriver:mouse-up/SKILL.md +164 -0
  66. package/ai/skills/testdriver:parse/SKILL.md +236 -0
  67. package/ai/skills/testdriver:performing-actions/SKILL.md +53 -0
  68. package/ai/skills/testdriver:press-keys/SKILL.md +348 -0
  69. package/ai/skills/testdriver:provision/SKILL.md +331 -0
  70. package/ai/skills/testdriver:quickstart/SKILL.md +172 -0
  71. package/ai/skills/testdriver:redraw/SKILL.md +214 -0
  72. package/ai/skills/testdriver:reusable-code/SKILL.md +249 -0
  73. package/ai/skills/testdriver:right-click/SKILL.md +123 -0
  74. package/{docs/v7/running-tests.mdx → ai/skills/testdriver:running-tests/SKILL.md} +4 -4
  75. package/ai/skills/testdriver:screenshot/SKILL.md +248 -0
  76. package/ai/skills/testdriver:screenshots/SKILL.md +184 -0
  77. package/ai/skills/testdriver:scroll/SKILL.md +205 -0
  78. package/ai/skills/testdriver:secrets/SKILL.md +115 -0
  79. package/ai/skills/testdriver:self-hosted/SKILL.md +147 -0
  80. package/ai/skills/testdriver:test-results-json/SKILL.md +257 -0
  81. package/ai/skills/testdriver:testdriver/SKILL.md +624 -0
  82. package/ai/skills/testdriver:type/SKILL.md +357 -0
  83. package/ai/skills/testdriver:variables/SKILL.md +111 -0
  84. package/ai/skills/testdriver:wait/SKILL.md +50 -0
  85. package/{docs/v7/waiting-for-elements.mdx → ai/skills/testdriver:waiting-for-elements/SKILL.md} +3 -3
  86. package/ai/skills/testdriver:what-is-testdriver/SKILL.md +54 -0
  87. package/debugger/index.html +12 -0
  88. package/docs/_data/examples-manifest.json +54 -46
  89. package/docs/_data/examples-manifest.schema.json +12 -2
  90. package/docs/_scripts/extract-example-urls.js +21 -3
  91. package/docs/_scripts/generate-examples.js +64 -16
  92. package/docs/changelog.mdx +29 -2
  93. package/docs/claude-mcp-plugin.mdx +65 -18
  94. package/docs/docs.json +16 -33
  95. package/docs/snippets/tests/scroll-yaml.mdx +2 -2
  96. package/docs/v6/commands/scroll-until-image.mdx +1 -1
  97. package/docs/v6/commands/scroll-until-text.mdx +2 -2
  98. package/docs/v6/commands/scroll.mdx +2 -2
  99. package/docs/v7/_drafts/agents.mdx +0 -4
  100. package/docs/v7/_drafts/best-practices.mdx +0 -12
  101. package/docs/v7/_drafts/commands/scroll-until-image.mdx +1 -1
  102. package/docs/v7/_drafts/commands/scroll-until-text.mdx +2 -2
  103. package/docs/v7/_drafts/commands/scroll.mdx +1 -1
  104. package/docs/v7/_drafts/configuration.mdx +0 -12
  105. package/docs/v7/_drafts/faq.mdx +2 -10
  106. package/docs/v7/_drafts/readme.mdx +0 -1
  107. package/docs/v7/_drafts/troubleshooting.mdx +1 -4
  108. package/docs/v7/assert.mdx +0 -1
  109. package/docs/v7/aws-setup.mdx +1 -1
  110. package/docs/v7/caching.mdx +14 -10
  111. package/docs/v7/ci-cd.mdx +98 -14
  112. package/docs/v7/client.mdx +10 -6
  113. package/docs/v7/copilot/auto-healing.mdx +167 -18
  114. package/docs/v7/copilot/running-tests.mdx +915 -54
  115. package/docs/v7/customizing-devices.mdx +7 -5
  116. package/docs/v7/dashcam.mdx +36 -3
  117. package/docs/v7/debugging-with-screenshots.mdx +31 -7
  118. package/docs/v7/examples/ai.mdx +13 -8
  119. package/docs/v7/examples/assert.mdx +1 -1
  120. package/docs/v7/examples/chrome-extension.mdx +30 -13
  121. package/docs/v7/examples/element-not-found.mdx +1 -1
  122. package/docs/v7/examples/findall-coffee-icons.mdx +3 -2
  123. package/docs/v7/examples/formatted-logging.mdx +74 -0
  124. package/docs/v7/examples/hover-image.mdx +1 -1
  125. package/docs/v7/examples/hover-text-with-description.mdx +2 -2
  126. package/docs/v7/examples/hover-text.mdx +1 -1
  127. package/docs/v7/examples/installer.mdx +1 -1
  128. package/docs/v7/examples/launch-vscode-linux.mdx +27 -27
  129. package/docs/v7/examples/match-image.mdx +1 -1
  130. package/docs/v7/examples/parse.mdx +3 -2
  131. package/docs/v7/examples/press-keys.mdx +2 -2
  132. package/docs/v7/examples/scroll-keyboard.mdx +2 -2
  133. package/docs/v7/examples/scroll-until-text.mdx +1 -1
  134. package/docs/v7/examples/scroll.mdx +1 -1
  135. package/docs/v7/examples/type.mdx +1 -1
  136. package/docs/v7/find.mdx +50 -11
  137. package/docs/v7/generating-tests.mdx +166 -10
  138. package/docs/v7/hosted.mdx +1 -1
  139. package/docs/v7/making-assertions.mdx +81 -4
  140. package/docs/v7/parse.mdx +1 -1
  141. package/docs/v7/performing-actions.mdx +100 -5
  142. package/docs/v7/provision.mdx +0 -20
  143. package/docs/v7/quickstart.mdx +299 -21
  144. package/docs/v7/reusable-code.mdx +3 -3
  145. package/docs/v7/screenshot.mdx +5 -5
  146. package/docs/v7/scroll.mdx +30 -160
  147. package/docs/v7/wait.mdx +1 -1
  148. package/examples/chrome-extension.test.mjs +29 -12
  149. package/examples/config.mjs +1 -1
  150. package/examples/launch-vscode-linux.test.mjs +25 -25
  151. package/interfaces/cli/commands/init.js +84 -2
  152. package/interfaces/vitest-plugin.mjs +51 -61
  153. package/lib/core/Dashcam.js +43 -2
  154. package/lib/core/index.d.ts +13 -1
  155. package/lib/github-comment.mjs +11 -5
  156. package/lib/init-project.js +98 -44
  157. package/lib/install-clients.js +470 -0
  158. package/lib/provision.js +749 -0
  159. package/lib/resolve-channel.js +2 -2
  160. package/lib/sentry.js +15 -2
  161. package/lib/vitest/hooks.mjs +44 -34
  162. package/mcp-server/README.md +32 -0
  163. package/mcp-server/dist/core/actions.d.ts +176 -0
  164. package/mcp-server/dist/core/actions.js +753 -0
  165. package/mcp-server/dist/env-utils.d.ts +45 -0
  166. package/mcp-server/dist/env-utils.js +63 -0
  167. package/mcp-server/dist/provision-types.d.ts +6 -2
  168. package/mcp-server/dist/provision-types.js +3 -1
  169. package/mcp-server/dist/server.mjs +449 -751
  170. package/mcp-server/package-lock.json +384 -1
  171. package/mcp-server/package.json +5 -2
  172. package/mcp-server/src/core/actions.ts +912 -0
  173. package/mcp-server/src/env-utils.test.ts +82 -0
  174. package/mcp-server/src/env-utils.ts +77 -0
  175. package/mcp-server/src/provision-types.ts +4 -1
  176. package/mcp-server/src/server.ts +533 -954
  177. package/mcp-server/tsconfig.json +1 -1
  178. package/mcp-server/vitest.config.ts +7 -0
  179. package/package.json +15 -3
  180. package/sdk.d.ts +11 -9
  181. package/sdk.js +116 -746
  182. package/setup/aws/spawn-runner.sh +24 -3
  183. package/docs/v7/_drafts/caching-ai.mdx +0 -215
  184. package/docs/v7/_drafts/caching.mdx +0 -366
  185. package/docs/v7/_drafts/prompt-cache.mdx +0 -200
  186. package/docs/v7/copilot/creating-tests.mdx +0 -156
  187. package/docs/v7/copilot/github.mdx +0 -143
  188. package/docs/v7/copilot/setup.mdx +0 -143
  189. package/docs/v7/examples/exec-output.mdx +0 -84
  190. package/docs/v7/examples/exec-pwsh.mdx +0 -82
  191. package/docs/v7/examples/focus-window.mdx +0 -61
  192. package/docs/v7/examples/scroll-until-image.mdx +0 -82
  193. package/docs/v7/examples/windows-installer.mdx +0 -95
  194. package/examples/formatted-logging.test.mjs +0 -27
  195. package/examples/match-image.test.mjs +0 -55
  196. package/manual/exec-stream-logs.test.mjs +0 -25
@@ -64,10 +64,10 @@ const tdEnv = resolveTdEnv();
64
64
 
65
65
  /**
66
66
  * Resolves the Sentry environment name.
67
- * Uses the infrastructure tier (dev | staging | production).
67
+ * Uses the release channel (dev | test | canary | stable).
68
68
  */
69
69
  function resolveSentryEnvironment() {
70
- return tdEnv;
70
+ return active;
71
71
  }
72
72
 
73
73
  const sentryEnvironment = resolveSentryEnvironment();
package/lib/sentry.js CHANGED
@@ -48,8 +48,8 @@ if (isEnabled()) {
48
48
  "https://452bd5a00dbd83a38ee8813e11c57694@o4510262629236736.ingest.us.sentry.io/4510480443637760",
49
49
  environment: sentryEnvironment,
50
50
  release: version,
51
- sampleRate: 1.0,
52
- tracesSampleRate: 1.0, // Sample 20% of transactions for performance
51
+ sampleRate: 0.01,
52
+ tracesSampleRate: 0.01,
53
53
  enableLogs: true,
54
54
  integrations: [Sentry.httpIntegration(), Sentry.nodeContextIntegration()],
55
55
  // Set initial context
@@ -414,6 +414,18 @@ function startTransaction(name, op = "cli") {
414
414
  return Sentry.startSpan({ name, op });
415
415
  }
416
416
 
417
+ /**
418
+ * Execute a callback within a Sentry performance span.
419
+ * If Sentry is not enabled, the callback is executed directly.
420
+ * @param {Object} options - Span options ({ name, op, attributes })
421
+ * @param {Function} callback - Async callback receiving the span
422
+ * @returns {Promise<*>} Result of the callback
423
+ */
424
+ async function withSpan(options, callback) {
425
+ if (!isEnabled()) return callback(null);
426
+ return Sentry.startSpan(options, callback);
427
+ }
428
+
417
429
  /**
418
430
  * Flush pending events before process exit
419
431
  * @param {number} timeout - Timeout in milliseconds
@@ -436,5 +448,6 @@ module.exports = {
436
448
  getTraceId,
437
449
  attachLogListeners,
438
450
  startTransaction,
451
+ withSpan,
439
452
  flush,
440
453
  };
@@ -433,25 +433,20 @@ export function TestDriver(context, options = {}) {
433
433
  testdriver._debugOnFailure = mergedOptions.debugOnFailure || false;
434
434
  testDriverInstances.set(context.task, testdriver);
435
435
 
436
- // Set platform metadata early so the reporter can show the correct OS from the start
436
+ // Set platform metadata early so the reporter can show the correct OS from
437
+ // the start. Test identity (file / suite / name) comes from Vitest's own
438
+ // task tree in the reporter — we deliberately do not duplicate it here.
437
439
  if (!context.task.meta) {
438
440
  context.task.meta = {};
439
441
  }
440
- const platform = mergedOptions.os || "linux";
441
- const absolutePath =
442
- context.task.file?.filepath || context.task.file?.name || "unknown";
443
- const projectRoot = process.cwd();
444
- const testFile =
445
- absolutePath !== "unknown"
446
- ? path.relative(projectRoot, absolutePath)
447
- : absolutePath;
448
-
449
- context.task.meta.platform = platform;
450
- context.task.meta.testFile = testFile;
451
- context.task.meta.testOrder = 0;
452
-
453
- // Pass test file name to SDK for debugger display
454
- testdriver.testFile = testFile;
442
+ context.task.meta.platform = mergedOptions.os || "linux";
443
+
444
+ // Pass test file name to SDK for debugger display. Use Vitest's standard
445
+ // runner-task `file.filepath` rather than reconstructing identity ourselves.
446
+ const taskFilepath = context.task.file?.filepath || null;
447
+ testdriver.testFile = taskFilepath
448
+ ? path.relative(process.cwd(), taskFilepath)
449
+ : null;
455
450
 
456
451
  const debugConsoleSpy = process.env.TD_DEBUG_CONSOLE_SPY === "true";
457
452
 
@@ -570,21 +565,10 @@ export function TestDriver(context, options = {}) {
570
565
  context.task.meta = {};
571
566
  }
572
567
 
573
- // Always set test metadata, even if dashcam never started or fails to stop
574
- // This ensures the reporter can record test results even for early failures
575
- const platform = currentInstance.os || "linux";
576
- const absolutePath =
577
- context.task.file?.filepath || context.task.file?.name || "unknown";
578
- const projectRoot = process.cwd();
579
- const testFile =
580
- absolutePath !== "unknown"
581
- ? path.relative(projectRoot, absolutePath)
582
- : absolutePath;
583
-
584
- // Set basic metadata that's always available
585
- context.task.meta.platform = platform;
586
- context.task.meta.testFile = testFile;
587
- context.task.meta.testOrder = 0;
568
+ // Refresh SDK-owned metadata. Test identity (file / suite / name) is
569
+ // read directly from the Vitest task tree by the reporter we only
570
+ // store things Vitest can't know about (platform, session, dashcam).
571
+ context.task.meta.platform = currentInstance.os || "linux";
588
572
  context.task.meta.sessionId = currentInstance.getSessionId?.() || null;
589
573
 
590
574
  // Initialize dashcamUrls array for tracking per-attempt URLs (persists across retries)
@@ -597,6 +581,11 @@ export function TestDriver(context, options = {}) {
597
581
  const isRetry = attemptNumber > 1;
598
582
  const attemptLabel = isRetry ? ` (attempt ${attemptNumber})` : "";
599
583
 
584
+ // Capture the current attempt's test error (before retry resets result)
585
+ const attemptResult = context.task.result?.errors?.[0];
586
+ const attemptErrorMessage = attemptResult?.message || null;
587
+ const attemptErrorStack = attemptResult?.stack || null;
588
+
600
589
  // Stop dashcam if it was started - with timeout to prevent hanging
601
590
  if (currentInstance._dashcam && currentInstance._dashcam.recording) {
602
591
  try {
@@ -607,6 +596,8 @@ export function TestDriver(context, options = {}) {
607
596
  attempt: attemptNumber,
608
597
  url: dashcamUrl || null,
609
598
  sessionId: currentInstance.getSessionId?.() || null,
599
+ errorMessage: attemptErrorMessage,
600
+ errorStack: attemptErrorStack,
610
601
  });
611
602
 
612
603
  // Keep backward compatibility - last attempt's URL
@@ -617,7 +608,7 @@ export function TestDriver(context, options = {}) {
617
608
  globalThis.__testdriverPlugin.registerDashcamUrl(
618
609
  context.task.id,
619
610
  dashcamUrl,
620
- platform,
611
+ context.task.meta.platform,
621
612
  attemptNumber,
622
613
  );
623
614
  }
@@ -660,6 +651,8 @@ export function TestDriver(context, options = {}) {
660
651
  url: null,
661
652
  sessionId: currentInstance.getSessionId?.() || null,
662
653
  error: error.message,
654
+ errorMessage: attemptErrorMessage,
655
+ errorStack: attemptErrorStack,
663
656
  });
664
657
  // Ensure dashcamUrl is set to null if stop failed
665
658
  context.task.meta.dashcamUrl = null;
@@ -670,6 +663,8 @@ export function TestDriver(context, options = {}) {
670
663
  attempt: attemptNumber,
671
664
  url: null,
672
665
  sessionId: currentInstance.getSessionId?.() || null,
666
+ errorMessage: attemptErrorMessage,
667
+ errorStack: attemptErrorStack,
673
668
  });
674
669
  context.task.meta.dashcamUrl = null;
675
670
  }
@@ -684,6 +679,21 @@ export function TestDriver(context, options = {}) {
684
679
  const sbx = currentInstance.sandbox || {};
685
680
  const apiRoot = currentInstance.config?.TD_API_ROOT || null;
686
681
 
682
+ const taskFilepath = context.task.file?.filepath || null;
683
+ const testFileRel = taskFilepath
684
+ ? path.relative(process.cwd(), taskFilepath)
685
+ : null;
686
+ const suiteParts = [];
687
+ {
688
+ let cursor = context.task.suite;
689
+ while (cursor && cursor.type === "suite") {
690
+ suiteParts.unshift(cursor.name);
691
+ cursor = cursor.suite;
692
+ }
693
+ }
694
+ const fullSuiteName =
695
+ suiteParts.length > 0 ? suiteParts.join(" > ") : null;
696
+
687
697
  context.task.meta.testResult = {
688
698
  // Versions
689
699
  sdkVersion: sdkPkg.version || null,
@@ -704,9 +714,9 @@ export function TestDriver(context, options = {}) {
704
714
  sessionId: currentInstance.getSessionId?.() || null,
705
715
 
706
716
  // Test info
707
- testFile: context.task.meta.testFile || null,
717
+ testFile: testFileRel,
708
718
  testName: context.task.name || null,
709
- suiteName: context.task.suite?.name || null,
719
+ suiteName: fullSuiteName,
710
720
 
711
721
  // Test result
712
722
  testPassed: context.task.result?.state === "pass",
@@ -89,6 +89,38 @@ To use the TestDriver MCP server with GitHub Copilot coding agent:
89
89
 
90
90
  For more information, see [Extending GitHub Copilot coding agent with Model Context Protocol](https://docs.github.com/en/copilot/customizing-copilot/extending-copilot-coding-agent-with-mcp).
91
91
 
92
+ ### With Claude Code (plugin)
93
+
94
+ TestDriver ships as a [Claude Code plugin](https://docs.claude.com/en/docs/claude-code/plugins) so you get the MCP server, the `testdriver` expert agent, and all TestDriver skills with a single install.
95
+
96
+ 1. Set your TestDriver API key in your shell:
97
+
98
+ ```bash
99
+ export TD_API_KEY=your_api_key_from_https_console_testdriver_ai_team
100
+ ```
101
+
102
+ 2. Add this repo as a Claude Code plugin marketplace and install the `testdriver` plugin:
103
+
104
+ ```bash
105
+ # Add the marketplace (sources `.claude-plugin/marketplace.json` from this repo)
106
+ /plugin marketplace add testdriverai/testdriverai
107
+
108
+ # Install the plugin
109
+ /plugin install testdriver@testdriver
110
+ ```
111
+
112
+ This reads `.claude-plugin/plugin.json` (shipped inside the `testdriverai` npm package) and:
113
+
114
+ - Registers the `testdriver` MCP server (`npx -p testdriverai testdriverai-mcp`)
115
+ - Registers the `testdriver` sub-agent (from `ai/agents/testdriver.md`)
116
+ - Registers all TestDriver skills (from `ai/skills/testdriver-*/SKILL.md`)
117
+
118
+ 3. In a Claude Code session, delegate to the agent with:
119
+
120
+ ```text
121
+ @testdriver Write a test that signs into example.com and adds an item to the cart.
122
+ ```
123
+
92
124
  ### With Claude Desktop / Cursor
93
125
 
94
126
  Add to your MCP config (`~/.cursor/mcp.json` or `~/Library/Application Support/Claude/claude_desktop_config.json`):
@@ -0,0 +1,176 @@
1
+ /**
2
+ * TestDriver action core — framework-agnostic.
3
+ *
4
+ * This module owns the actual computer-use logic: it holds the singleton SDK,
5
+ * the element-reference map, and the "last screenshot" used by `check`, and
6
+ * exposes one async function per action. Every function returns a neutral
7
+ * {@link ActionResult} — no MCP types, no eve types — so it can be wrapped by:
8
+ *
9
+ * - the MCP server (`server.ts`), which turns images into resource URIs and
10
+ * calls `createToolResult`, and
11
+ * - eve `defineTool()` files, which return the text/code/images as eve content.
12
+ *
13
+ * Keep MCP- and eve-specific concerns (Sentry, progress heartbeats, abort
14
+ * racing, image stores, UI resources) in the adapters. This file only knows
15
+ * about the TestDriver SDK and `generateActionCode`.
16
+ *
17
+ * State is process-global and single-session by design: one sandbox at a time,
18
+ * mirroring how the stdio MCP server has always behaved.
19
+ */
20
+ import { SessionStartInputSchema, type SessionStartInput } from "../provision-types.js";
21
+ import { type SessionState } from "../session.js";
22
+ export { SessionStartInputSchema, type SessionStartInput };
23
+ export interface ActionImage {
24
+ kind: "screenshot" | "cropped";
25
+ /** Raw base64 (no `data:` prefix). */
26
+ base64: string;
27
+ }
28
+ export interface ActionResult {
29
+ /** Whether the action succeeded (assertions/finds may legitimately be false). */
30
+ ok: boolean;
31
+ /** Human/agent-readable summary line(s). */
32
+ text: string;
33
+ /** Structured fields for programmatic consumers and UIs. */
34
+ data: Record<string, unknown>;
35
+ /** Code to append to the test file, when the action produces test code. */
36
+ code?: string;
37
+ /** Images produced by the action (screenshots / cropped element shots). */
38
+ images?: ActionImage[];
39
+ }
40
+ /** Thrown when an action runs without an active/valid session. */
41
+ export declare class NoActiveSessionError extends Error {
42
+ readonly code: "NO_SESSION" | "SESSION_EXPIRED";
43
+ readonly expiredSessionId?: string;
44
+ constructor(code: "NO_SESSION" | "SESSION_EXPIRED", message: string, expiredSessionId?: string);
45
+ }
46
+ /**
47
+ * Optional adapter-supplied recovery hook. When the singleton SDK is missing or
48
+ * stale at action time, {@link requireActiveSession} calls this to obtain the
49
+ * params needed to rebuild the connection from the still-alive sandbox, then
50
+ * reconnects before throwing NO_SESSION.
51
+ *
52
+ * Why a hook instead of mcp-core owning the handle: the durable facts (sandbox
53
+ * id, config) and the API key must survive a host process recycle, but this
54
+ * module's globals do NOT — they reset on the very recycle we're recovering
55
+ * from. So the *durable owner* (eve's per-session state) registers a resolver
56
+ * that reads its own durable store and re-resolves the key per call. Hosts with
57
+ * one long-lived process (stdio MCP server, CLI) register nothing: the resolver
58
+ * stays null and behavior is unchanged — `requireActiveSession` throws the same
59
+ * NO_SESSION/SESSION_EXPIRED as before.
60
+ *
61
+ * The resolver returns `null` when it has nothing to recover from (no sandbox
62
+ * provisioned this session), in which case we fall through to the normal throw.
63
+ * It is re-registered each step by the adapter (module globals reset on recycle),
64
+ * so a stale closure can't outlive the process it was bound to.
65
+ */
66
+ export type ReconnectResolver = () => Promise<ReconnectParams | null>;
67
+ /**
68
+ * Register (or clear, with `null`) the recovery hook. Durable adapters call this
69
+ * at the start of each step with a resolver bound to the current tool context.
70
+ */
71
+ export declare function setReconnectResolver(resolver: ReconnectResolver | null): void;
72
+ /** Expose internals the adapters legitimately need (read-only intent). */
73
+ export declare function getSdk(): any;
74
+ export declare function getLastScreenshotBase64(): string | null;
75
+ export declare function getElementRef(ref: string): {
76
+ element: any;
77
+ description: string;
78
+ coords: {
79
+ x: number;
80
+ y: number;
81
+ centerX: number;
82
+ centerY: number;
83
+ };
84
+ } | undefined;
85
+ /**
86
+ * Cheap, side-effect-free check for a usable in-process session: a live SDK and
87
+ * a current session that hasn't expired. Durable adapters call this *before*
88
+ * doing any expensive reconnect prep (e.g. re-resolving an API key), so the
89
+ * common warm-process path stays free. Note: unlike `requireActiveSession`, this
90
+ * does NOT refresh the keepAlive window — it only reports liveness.
91
+ */
92
+ export declare function hasLiveSession(): boolean;
93
+ export interface SessionStartHooks {
94
+ /** Called before a long await; return a stop fn. Lets adapters heartbeat. */
95
+ onProgress?: (message: string) => void;
96
+ }
97
+ /**
98
+ * Start a session and provision a sandbox. The adapter is responsible for
99
+ * progress/heartbeat and abort; this performs the provisioning and returns the
100
+ * neutral result (including the initial screenshot and provision code).
101
+ */
102
+ export declare function sessionStart(params: SessionStartInput, resolved: {
103
+ os: "linux" | "windows";
104
+ e2bTemplateId?: string;
105
+ apiKey?: string;
106
+ }, hooks?: SessionStartHooks): Promise<ActionResult>;
107
+ /**
108
+ * Parameters needed to rebuild the singleton SDK and reconnect to a sandbox
109
+ * that is still alive on the server but whose in-process connection was lost
110
+ * (e.g. the host process was recycled between durable workflow steps).
111
+ */
112
+ export interface ReconnectParams {
113
+ /** Cloud sandbox id (`sb-…`) to reconnect to. */
114
+ sandboxId: string;
115
+ os: "linux" | "windows";
116
+ keepAlive: number;
117
+ apiKey: string;
118
+ apiRoot?: string;
119
+ e2bTemplateId?: string;
120
+ /** Self-hosted instance IP, when the original session used one. */
121
+ ip?: string;
122
+ testFile?: string;
123
+ }
124
+ /**
125
+ * Rebuild the process-global SDK and reconnect it to an existing sandbox by id,
126
+ * skipping provisioning. This is the recovery path for hosts that do NOT keep a
127
+ * single long-lived process: eve runs each turn as a durable workflow on Vercel
128
+ * and may park or recycle the process between steps, which drops the SDK's
129
+ * realtime connection and wipes this module's singletons. The sandbox itself can
130
+ * still be alive on the server (the API holds it for `keepAlive`/grace), so we
131
+ * re-`connect({ sandboxId })` and re-register a local session instead of failing
132
+ * the action with NO_SESSION.
133
+ *
134
+ * Returns the (re)activated session id. Throws if the reconnect fails (the
135
+ * sandbox is gone) — callers translate that into a SESSION_EXPIRED so the agent
136
+ * knows to call session_start again.
137
+ */
138
+ export declare function reconnectSession(params: ReconnectParams): Promise<string>;
139
+ /**
140
+ * Ensure there is a usable session before an action, reconnecting from durable
141
+ * state if the in-process singleton was lost. Returns true when a fresh
142
+ * reconnect happened (the caller may want to recapture context), false when the
143
+ * existing session was already usable.
144
+ *
145
+ * Durable adapters call this at the top of each tool, passing the sandbox id and
146
+ * config they persisted at session_start. Hosts with a single long-lived process
147
+ * (the stdio MCP server, the CLI) never persist a sandbox id, so they pass none
148
+ * and this is a no-op — `requireActiveSession()` inside each action still guards
149
+ * them exactly as before.
150
+ */
151
+ export declare function ensureActiveSession(params?: ReconnectParams): Promise<boolean>;
152
+ /** Disconnect the SDK (best-effort); used by adapters on cancel/teardown. */
153
+ export declare function disconnect(): Promise<void>;
154
+ export declare function sessionStatus(): ActionResult;
155
+ export declare function sessionExtend(additionalMs: number): ActionResult;
156
+ export declare function find(description: string, timeout?: number): Promise<ActionResult>;
157
+ export declare function findAll(description: string, timeout?: number): Promise<ActionResult>;
158
+ export declare function click(ref: string, action?: "click" | "double-click" | "right-click"): Promise<ActionResult>;
159
+ export declare function hover(ref: string): Promise<ActionResult>;
160
+ export declare function findAndClick(description: string, action?: "click" | "double-click" | "right-click"): Promise<ActionResult>;
161
+ export declare function type(text: string, secret?: boolean, delay?: number): Promise<ActionResult>;
162
+ export declare function pressKeys(keys: string[]): Promise<ActionResult>;
163
+ export declare function scroll(direction?: "up" | "down" | "left" | "right", amount?: number): Promise<ActionResult>;
164
+ export declare function focusApplication(name: string): Promise<ActionResult>;
165
+ export declare function wait(timeout: number): Promise<ActionResult>;
166
+ export declare function exec(language: "sh" | "pwsh", code: string, timeout?: number): Promise<ActionResult>;
167
+ export declare function assert(assertion: string): Promise<ActionResult>;
168
+ /**
169
+ * AI analysis of the current screen vs. a "before" state. Does NOT generate
170
+ * code. `referenceImage` (bare base64), if provided, is used as the before
171
+ * image instead of the last captured screenshot.
172
+ */
173
+ export declare function check(task: string, referenceImage?: string): Promise<ActionResult>;
174
+ /** Capture a screenshot for the user (cursor visible). */
175
+ export declare function screenshot(): Promise<ActionResult>;
176
+ export type { SessionState };