@testdriverai/agent 7.11.174-canary → 7.11.175-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.
@@ -2,16 +2,16 @@
2
2
  "$schema": "./examples-manifest.schema.json",
3
3
  "examples": {
4
4
  "assert.test.mjs": {
5
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc423bc88afcb6e58c9b",
6
- "lastUpdated": "2026-09-22T00:30:58.162Z"
5
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d71565c44978ce8964d",
6
+ "lastUpdated": "2026-09-23T00:29:37.098Z"
7
7
  },
8
8
  "drag-and-drop.test.mjs": {
9
9
  "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b42fc0ac3cc632a918b",
10
10
  "lastUpdated": "2026-03-03T00:32:25.275Z"
11
11
  },
12
12
  "exec-pwsh.test.mjs": {
13
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc323bc88afcb6e58c95",
14
- "lastUpdated": "2026-09-22T00:31:15.486Z"
13
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d5c825adccbbd9ca378",
14
+ "lastUpdated": "2026-09-23T00:29:58.695Z"
15
15
  },
16
16
  "match-image.test.mjs": {
17
17
  "url": "https://console-test.testdriver.ai/runs/69c8738614b73310c7839412/69c8738c14b73310c783941d",
@@ -22,78 +22,78 @@
22
22
  "lastUpdated": "2026-03-03T00:32:25.282Z"
23
23
  },
24
24
  "hover-text-with-description.test.mjs": {
25
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc353bc88afcb6e58c96",
26
- "lastUpdated": "2026-09-22T00:31:18.391Z"
25
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d61825adccbbd9ca379",
26
+ "lastUpdated": "2026-09-23T00:30:02.507Z"
27
27
  },
28
28
  "windows-installer.test.mjs": {
29
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc375b14976f4242d2d2",
30
- "lastUpdated": "2026-09-22T00:34:29.084Z"
29
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d63825adccbbd9ca37a",
30
+ "lastUpdated": "2026-09-23T00:34:46.111Z"
31
31
  },
32
32
  "exec-output.test.mjs": {
33
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc385b14976f4242d2d3",
34
- "lastUpdated": "2026-09-22T00:31:52.882Z"
33
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d65825adccbbd9ca37b",
34
+ "lastUpdated": "2026-09-23T00:30:45.866Z"
35
35
  },
36
36
  "chrome-extension.test.mjs": {
37
37
  "url": "https://console-test.testdriver.ai/runs/6a87988fb7cddebeab9bfbea/6a8798913062df0fb9b85592",
38
38
  "lastUpdated": "2026-08-21T00:15:47.131Z"
39
39
  },
40
40
  "launch-vscode-linux.test.mjs": {
41
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc345b14976f4242d2d1",
42
- "lastUpdated": "2026-09-22T00:31:47.937Z"
41
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d5f5b31bd91beec444f",
42
+ "lastUpdated": "2026-09-23T00:30:39.661Z"
43
43
  },
44
44
  "hover-image.test.mjs": {
45
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc3a3bc88afcb6e58c97",
46
- "lastUpdated": "2026-09-22T00:30:50.385Z"
45
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d675b31bd91beec4450",
46
+ "lastUpdated": "2026-09-23T00:29:27.303Z"
47
47
  },
48
48
  "installer.test.mjs": {
49
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc3c3bc88afcb6e58c98",
50
- "lastUpdated": "2026-09-22T00:30:52.378Z"
49
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d6a825adccbbd9ca37c",
50
+ "lastUpdated": "2026-09-23T00:29:29.990Z"
51
51
  },
52
52
  "type.test.mjs": {
53
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc3e3bc88afcb6e58c99",
54
- "lastUpdated": "2026-09-22T00:31:57.849Z"
53
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d6c825adccbbd9ca37e",
54
+ "lastUpdated": "2026-09-23T00:30:52.193Z"
55
55
  },
56
56
  "press-keys.test.mjs": {
57
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc403bc88afcb6e58c9a",
58
- "replayUrl": "https://console-test.testdriver.ai/replay/6ab1ce515b14976f4242d306?share=vOGi7oCQfh7UA3PvITTX9Q",
59
- "embedUrl": "https://console-test.testdriver.ai/replay/6ab1ce515b14976f4242d306?share=vOGi7oCQfh7UA3PvITTX9Q&embed=true",
60
- "lastUpdated": "2026-09-22T00:39:50.871Z"
57
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d6f825adccbbd9ca37f",
58
+ "replayUrl": "https://console-test.testdriver.ai/replay/6ab31fe0825adccbbd9ca3b4?share=LuZwQKvHo7PpGpUp6Z9ovQ",
59
+ "embedUrl": "https://console-test.testdriver.ai/replay/6ab31fe0825adccbbd9ca3b4?share=LuZwQKvHo7PpGpUp6Z9ovQ&embed=true",
60
+ "lastUpdated": "2026-09-23T00:40:05.729Z"
61
61
  },
62
62
  "scroll-keyboard.test.mjs": {
63
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc453bc88afcb6e58c9d",
64
- "lastUpdated": "2026-09-22T00:32:04.764Z"
63
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d75565c44978ce8964f",
64
+ "lastUpdated": "2026-09-23T00:31:00.707Z"
65
65
  },
66
66
  "scroll.test.mjs": {
67
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc4c3bc88afcb6e58ca0",
68
- "lastUpdated": "2026-09-22T00:31:39.738Z"
67
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d7d825adccbbd9ca381",
68
+ "lastUpdated": "2026-09-23T00:30:29.129Z"
69
69
  },
70
70
  "scroll-until-image.test.mjs": {
71
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc475b14976f4242d2d6",
72
- "lastUpdated": "2026-09-22T00:31:03.691Z"
71
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d77565c44978ce89650",
72
+ "lastUpdated": "2026-09-23T00:29:44.052Z"
73
73
  },
74
74
  "prompt.test.mjs": {
75
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc483bc88afcb6e58c9e",
76
- "lastUpdated": "2026-09-22T00:32:07.952Z"
75
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d79565c44978ce89651",
76
+ "lastUpdated": "2026-09-23T00:31:04.765Z"
77
77
  },
78
78
  "focus-window.test.mjs": {
79
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc4a3bc88afcb6e58c9f",
80
- "lastUpdated": "2026-09-22T00:32:09.288Z"
79
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d7b825adccbbd9ca380",
80
+ "lastUpdated": "2026-09-23T00:31:06.563Z"
81
81
  },
82
82
  "captcha-api.test.mjs": {
83
83
  "url": "https://console.testdriver.ai/runs/698f7df69e27ce1528d7d087/698f7fb0d3b320ad547d9d44",
84
84
  "lastUpdated": "2026-02-13T19:55:05.951Z"
85
85
  },
86
86
  "element-not-found.test.mjs": {
87
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc4e5b14976f4242d2d7",
88
- "lastUpdated": "2026-09-22T00:31:41.452Z"
87
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d7f825adccbbd9ca382",
88
+ "lastUpdated": "2026-09-23T00:30:31.309Z"
89
89
  },
90
90
  "formatted-logging.test.mjs": {
91
91
  "url": "https://console-test.testdriver.ai/runs/69c8738614b73310c7839412/69c873a714b73310c7839450",
92
92
  "lastUpdated": "2026-03-29T00:36:10.628Z"
93
93
  },
94
94
  "hover-text.test.mjs": {
95
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc4f3bc88afcb6e58ca1",
96
- "lastUpdated": "2026-09-22T00:31:43.107Z"
95
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d825b31bd91beec4451",
96
+ "lastUpdated": "2026-09-23T00:30:33.432Z"
97
97
  },
98
98
  "no-provision.test.mjs": {
99
99
  "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62b7706a177a05bccd1cf",
@@ -138,12 +138,12 @@
138
138
  "lastUpdated": "2026-02-13T19:55:05.953Z"
139
139
  },
140
140
  "findall-coffee-icons.test.mjs": {
141
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc443bc88afcb6e58c9c",
142
- "lastUpdated": "2026-09-22T00:30:59.873Z"
141
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d73565c44978ce8964e",
142
+ "lastUpdated": "2026-09-23T00:29:39.384Z"
143
143
  },
144
144
  "parse.test.mjs": {
145
- "url": "https://console-test.testdriver.ai/runs/6ab1cc303bc88afcb6e58c93/6ab1cc515b14976f4242d2d8",
146
- "lastUpdated": "2026-09-22T00:31:44.748Z"
145
+ "url": "https://console-test.testdriver.ai/runs/6ab31d5a825adccbbd9ca376/6ab31d84825adccbbd9ca383",
146
+ "lastUpdated": "2026-09-23T00:30:35.532Z"
147
147
  },
148
148
  "flake-diffthreshold-001.test.mjs": {
149
149
  "url": "https://console.testdriver.ai/runs/69a62b3aaa712ecd3dea730a/69a62bcafc0ac3cc632a91aa",
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* assert.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc423bc88afcb6e58c9b/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d71565c44978ce8964d/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* element-not-found.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc4e5b14976f4242d2d7/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d7f825adccbbd9ca382/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -18,7 +18,7 @@ Watch this test execute in a real sandbox environment:
18
18
 
19
19
  {/* findall-coffee-icons.test.mjs output */}
20
20
  <iframe
21
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc443bc88afcb6e58c9c/replay"
21
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d73565c44978ce8964e/replay"
22
22
  width="100%"
23
23
  height="600"
24
24
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* hover-image.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc3a3bc88afcb6e58c97/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d675b31bd91beec4450/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -18,7 +18,7 @@ Watch this test execute in a real sandbox environment:
18
18
 
19
19
  {/* hover-text-with-description.test.mjs output */}
20
20
  <iframe
21
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc353bc88afcb6e58c96/replay"
21
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d61825adccbbd9ca379/replay"
22
22
  width="100%"
23
23
  height="600"
24
24
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* hover-text.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc4f3bc88afcb6e58ca1/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d825b31bd91beec4451/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* installer.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc3c3bc88afcb6e58c98/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d6a825adccbbd9ca37c/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* launch-vscode-linux.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc345b14976f4242d2d1/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d5f5b31bd91beec444f/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -18,7 +18,7 @@ Watch this test execute in a real sandbox environment:
18
18
 
19
19
  {/* parse.test.mjs output */}
20
20
  <iframe
21
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc515b14976f4242d2d8/replay"
21
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d84825adccbbd9ca383/replay"
22
22
  width="100%"
23
23
  height="600"
24
24
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* press-keys.test.mjs output */}
14
14
  <iframe
15
- src="https://console-test.testdriver.ai/replay/6ab1ce515b14976f4242d306?share=vOGi7oCQfh7UA3PvITTX9Q&embed=true"
15
+ src="https://console-test.testdriver.ai/replay/6ab31fe0825adccbbd9ca3b4?share=LuZwQKvHo7PpGpUp6Z9ovQ&embed=true"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* scroll-keyboard.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc453bc88afcb6e58c9d/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d75565c44978ce8964f/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* scroll.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc4c3bc88afcb6e58ca0/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d7d825adccbbd9ca381/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -12,7 +12,7 @@ Watch this test execute in a real sandbox environment:
12
12
 
13
13
  {/* type.test.mjs output */}
14
14
  <iframe
15
- src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab1cc3e3bc88afcb6e58c99/replay"
15
+ src="https://api-test.testdriver.ai/api/v1/testdriver/testcase/6ab31d6c825adccbbd9ca37e/replay"
16
16
  width="100%"
17
17
  height="390"
18
18
  style={{ border: "1px solid #333", borderRadius: "8px" }}
@@ -1663,32 +1663,6 @@ function readJsonBody(req) {
1663
1663
  req.on("error", () => resolve(undefined));
1664
1664
  });
1665
1665
  }
1666
- /**
1667
- * Serve the MCP server over Streamable HTTP with no authentication.
1668
- *
1669
- * This is what the eve agent (and any other Streamable-HTTP MCP client) connects
1670
- * to. Each MCP session gets its OWN transport + server + isolated action context,
1671
- * keyed by the `mcp-session-id` header:
1672
- *
1673
- * - An `initialize` request (no session id yet) mints a fresh connection: a new
1674
- * isolated {@link core.CoreContext}, a server built over it, and a transport
1675
- * whose generated session id we store. Every later request carrying that id
1676
- * routes back to the same connection and runs inside its context via
1677
- * {@link core.runInContext} — so one client's tool call can only ever touch
1678
- * its own sandbox / element refs / image store. This is the fix for the
1679
- * cross-session bleed: state is per-connection, not process-global.
1680
- * - `DELETE` (or transport close) tears the connection down and drops it from
1681
- * the map, so a disconnecting client frees its slot and its context is GC'd.
1682
- *
1683
- * No auth is applied here by design (the connection is expected to be local-only
1684
- * or otherwise protected outside the MCP layer). Do not expose this on a public
1685
- * network without putting a real authenticating proxy in front of it.
1686
- */
1687
- /**
1688
- * Exchange a verified Auth0 access token for the caller's native TestDriver team
1689
- * API key via the backend, so each hosted session provisions under the right
1690
- * team and quota. Returns null if the exchange fails (caller falls back / errors).
1691
- */
1692
1666
  async function exchangeOAuthTokenForApiKey(token) {
1693
1667
  const apiRoot = process.env.TD_API_ROOT || "https://api.testdriver.ai";
1694
1668
  try {
@@ -1698,18 +1672,36 @@ async function exchangeOAuthTokenForApiKey(token) {
1698
1672
  "content-type": "application/json",
1699
1673
  authorization: `Bearer ${token}`,
1700
1674
  },
1701
- body: JSON.stringify({}),
1675
+ // The backend reads `token` as a required body input (Sails machine
1676
+ // input), not from the Authorization header — omitting it 400s and was
1677
+ // being surfaced as a misleading `no_team`.
1678
+ body: JSON.stringify({ token }),
1702
1679
  });
1703
1680
  if (!resp.ok) {
1704
- logger.warn("http: exchange-oauth-token failed", { status: resp.status });
1705
- return null;
1681
+ let error = "exchange_failed";
1682
+ let description = `Token exchange failed (HTTP ${resp.status})`;
1683
+ try {
1684
+ const body = (await resp.json());
1685
+ if (body.error)
1686
+ error = body.error;
1687
+ if (body.message || body.error_description)
1688
+ description = body.message || body.error_description || description;
1689
+ }
1690
+ catch {
1691
+ // non-JSON error body; keep the generic description
1692
+ }
1693
+ logger.warn("http: exchange-oauth-token failed", { status: resp.status, error, description });
1694
+ return { ok: false, status: resp.status, error, description };
1706
1695
  }
1707
1696
  const data = (await resp.json());
1708
- return data.apiKey || null;
1697
+ if (!data.apiKey) {
1698
+ return { ok: false, status: 403, error: "no_team", description: "No TestDriver team is linked to this account" };
1699
+ }
1700
+ return { ok: true, apiKey: data.apiKey };
1709
1701
  }
1710
1702
  catch (error) {
1711
1703
  logger.error("http: exchange-oauth-token error", { error: String(error) });
1712
- return null;
1704
+ return { ok: false, status: 502, error: "exchange_unreachable", description: "Could not reach the TestDriver API to exchange the token" };
1713
1705
  }
1714
1706
  }
1715
1707
  // Fly.io machine that owns this process. When set (hosted, multi-machine), we
@@ -1793,12 +1785,13 @@ async function startHttpServer() {
1793
1785
  return;
1794
1786
  }
1795
1787
  resolvedToken = auth.token;
1796
- resolvedApiKey = await exchangeOAuthTokenForApiKey(auth.token);
1797
- if (!resolvedApiKey) {
1798
- res.writeHead(403, { "content-type": "application/json" });
1799
- res.end(JSON.stringify({ error: "no_team", error_description: "No TestDriver team is linked to this account" }));
1788
+ const exchange = await exchangeOAuthTokenForApiKey(auth.token);
1789
+ if (!exchange.ok) {
1790
+ res.writeHead(exchange.status, { "content-type": "application/json" });
1791
+ res.end(JSON.stringify({ error: exchange.error, error_description: exchange.description }));
1800
1792
  return;
1801
1793
  }
1794
+ resolvedApiKey = exchange.apiKey;
1802
1795
  }
1803
1796
  const sessionId = req.headers["mcp-session-id"];
1804
1797
  const sid = Array.isArray(sessionId) ? sessionId[0] : sessionId;
@@ -2059,9 +2059,15 @@ function readJsonBody(req: http.IncomingMessage): Promise<unknown> {
2059
2059
  /**
2060
2060
  * Exchange a verified Auth0 access token for the caller's native TestDriver team
2061
2061
  * API key via the backend, so each hosted session provisions under the right
2062
- * team and quota. Returns null if the exchange fails (caller falls back / errors).
2062
+ * team and quota. On failure, returns the backend's error so the caller can
2063
+ * surface the real reason (invalid token vs genuinely no team) instead of a
2064
+ * catch-all `no_team`.
2063
2065
  */
2064
- async function exchangeOAuthTokenForApiKey(token: string): Promise<string | null> {
2066
+ type ExchangeResult =
2067
+ | { ok: true; apiKey: string }
2068
+ | { ok: false; status: number; error: string; description: string };
2069
+
2070
+ async function exchangeOAuthTokenForApiKey(token: string): Promise<ExchangeResult> {
2065
2071
  const apiRoot = process.env.TD_API_ROOT || "https://api.testdriver.ai";
2066
2072
  try {
2067
2073
  const resp = await fetch(`${apiRoot.replace(/\/$/, "")}/auth/exchange-oauth-token`, {
@@ -2070,17 +2076,32 @@ async function exchangeOAuthTokenForApiKey(token: string): Promise<string | null
2070
2076
  "content-type": "application/json",
2071
2077
  authorization: `Bearer ${token}`,
2072
2078
  },
2073
- body: JSON.stringify({}),
2079
+ // The backend reads `token` as a required body input (Sails machine
2080
+ // input), not from the Authorization header — omitting it 400s and was
2081
+ // being surfaced as a misleading `no_team`.
2082
+ body: JSON.stringify({ token }),
2074
2083
  });
2075
2084
  if (!resp.ok) {
2076
- logger.warn("http: exchange-oauth-token failed", { status: resp.status });
2077
- return null;
2085
+ let error = "exchange_failed";
2086
+ let description = `Token exchange failed (HTTP ${resp.status})`;
2087
+ try {
2088
+ const body = (await resp.json()) as { error?: string; message?: string; error_description?: string };
2089
+ if (body.error) error = body.error;
2090
+ if (body.message || body.error_description) description = body.message || body.error_description || description;
2091
+ } catch {
2092
+ // non-JSON error body; keep the generic description
2093
+ }
2094
+ logger.warn("http: exchange-oauth-token failed", { status: resp.status, error, description });
2095
+ return { ok: false, status: resp.status, error, description };
2078
2096
  }
2079
2097
  const data = (await resp.json()) as { apiKey?: string };
2080
- return data.apiKey || null;
2098
+ if (!data.apiKey) {
2099
+ return { ok: false, status: 403, error: "no_team", description: "No TestDriver team is linked to this account" };
2100
+ }
2101
+ return { ok: true, apiKey: data.apiKey };
2081
2102
  } catch (error) {
2082
2103
  logger.error("http: exchange-oauth-token error", { error: String(error) });
2083
- return null;
2104
+ return { ok: false, status: 502, error: "exchange_unreachable", description: "Could not reach the TestDriver API to exchange the token" };
2084
2105
  }
2085
2106
  }
2086
2107
 
@@ -2179,12 +2200,13 @@ async function startHttpServer() {
2179
2200
  return;
2180
2201
  }
2181
2202
  resolvedToken = auth.token;
2182
- resolvedApiKey = await exchangeOAuthTokenForApiKey(auth.token);
2183
- if (!resolvedApiKey) {
2184
- res.writeHead(403, { "content-type": "application/json" });
2185
- res.end(JSON.stringify({ error: "no_team", error_description: "No TestDriver team is linked to this account" }));
2203
+ const exchange = await exchangeOAuthTokenForApiKey(auth.token);
2204
+ if (!exchange.ok) {
2205
+ res.writeHead(exchange.status, { "content-type": "application/json" });
2206
+ res.end(JSON.stringify({ error: exchange.error, error_description: exchange.description }));
2186
2207
  return;
2187
2208
  }
2209
+ resolvedApiKey = exchange.apiKey;
2188
2210
  }
2189
2211
 
2190
2212
  const sessionId = req.headers["mcp-session-id"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testdriverai/agent",
3
- "version": "7.11.174-canary",
3
+ "version": "7.11.175-test",
4
4
  "description": "Next generation autonomous AI agent for end-to-end testing of web & desktop",
5
5
  "main": "sdk.js",
6
6
  "types": "sdk.d.ts",