@flrande/browserctl 0.5.0-dev.22.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/client.d.ts +34 -0
  2. package/dist/client.js +138 -0
  3. package/dist/commandRegistry.d.ts +16 -0
  4. package/dist/commandRegistry.js +21 -0
  5. package/dist/help.d.ts +4 -0
  6. package/dist/help.js +24 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.js +23 -0
  9. package/dist/runCli.d.ts +5 -0
  10. package/dist/runCli.js +170 -0
  11. package/package.json +32 -59
  12. package/INSTALL-CN.md +0 -92
  13. package/INSTALL.md +0 -92
  14. package/LICENSE +0 -21
  15. package/README-CN.md +0 -69
  16. package/README.md +0 -69
  17. package/apps/browserctl/src/commands/a11y-snapshot.ts +0 -20
  18. package/apps/browserctl/src/commands/act.test.ts +0 -71
  19. package/apps/browserctl/src/commands/act.ts +0 -64
  20. package/apps/browserctl/src/commands/command-wrappers.test.ts +0 -688
  21. package/apps/browserctl/src/commands/common.test.ts +0 -87
  22. package/apps/browserctl/src/commands/common.ts +0 -191
  23. package/apps/browserctl/src/commands/console-list.test.ts +0 -102
  24. package/apps/browserctl/src/commands/console-list.ts +0 -108
  25. package/apps/browserctl/src/commands/cookie-clear.ts +0 -18
  26. package/apps/browserctl/src/commands/cookie-get.ts +0 -18
  27. package/apps/browserctl/src/commands/cookie-set.ts +0 -22
  28. package/apps/browserctl/src/commands/dialog-arm.ts +0 -20
  29. package/apps/browserctl/src/commands/dom-query-all.ts +0 -18
  30. package/apps/browserctl/src/commands/dom-query.ts +0 -18
  31. package/apps/browserctl/src/commands/download-trigger.ts +0 -22
  32. package/apps/browserctl/src/commands/download-wait.test.ts +0 -67
  33. package/apps/browserctl/src/commands/download-wait.ts +0 -27
  34. package/apps/browserctl/src/commands/element-screenshot.ts +0 -20
  35. package/apps/browserctl/src/commands/frame-list.ts +0 -16
  36. package/apps/browserctl/src/commands/frame-snapshot.ts +0 -18
  37. package/apps/browserctl/src/commands/har-export.test.ts +0 -112
  38. package/apps/browserctl/src/commands/har-export.ts +0 -120
  39. package/apps/browserctl/src/commands/memory-delete.ts +0 -20
  40. package/apps/browserctl/src/commands/memory-inspect.ts +0 -20
  41. package/apps/browserctl/src/commands/memory-list.ts +0 -90
  42. package/apps/browserctl/src/commands/memory-mode-set.ts +0 -29
  43. package/apps/browserctl/src/commands/memory-purge.ts +0 -16
  44. package/apps/browserctl/src/commands/memory-resolve.ts +0 -56
  45. package/apps/browserctl/src/commands/memory-status.ts +0 -16
  46. package/apps/browserctl/src/commands/memory-ttl-set.ts +0 -28
  47. package/apps/browserctl/src/commands/memory-upsert.ts +0 -142
  48. package/apps/browserctl/src/commands/network-list.test.ts +0 -110
  49. package/apps/browserctl/src/commands/network-list.ts +0 -112
  50. package/apps/browserctl/src/commands/network-wait-for.test.ts +0 -90
  51. package/apps/browserctl/src/commands/network-wait-for.ts +0 -100
  52. package/apps/browserctl/src/commands/profile-list.ts +0 -16
  53. package/apps/browserctl/src/commands/profile-use.ts +0 -18
  54. package/apps/browserctl/src/commands/response-body.ts +0 -24
  55. package/apps/browserctl/src/commands/screenshot.ts +0 -16
  56. package/apps/browserctl/src/commands/session-drop.test.ts +0 -36
  57. package/apps/browserctl/src/commands/session-drop.ts +0 -16
  58. package/apps/browserctl/src/commands/session-list.test.ts +0 -81
  59. package/apps/browserctl/src/commands/session-list.ts +0 -70
  60. package/apps/browserctl/src/commands/snapshot.ts +0 -16
  61. package/apps/browserctl/src/commands/status.ts +0 -10
  62. package/apps/browserctl/src/commands/storage-get.ts +0 -20
  63. package/apps/browserctl/src/commands/storage-set.ts +0 -22
  64. package/apps/browserctl/src/commands/tab-close.ts +0 -20
  65. package/apps/browserctl/src/commands/tab-focus.ts +0 -20
  66. package/apps/browserctl/src/commands/tab-open.ts +0 -19
  67. package/apps/browserctl/src/commands/tabs.ts +0 -13
  68. package/apps/browserctl/src/commands/trace-get.test.ts +0 -61
  69. package/apps/browserctl/src/commands/trace-get.ts +0 -62
  70. package/apps/browserctl/src/commands/upload-arm.ts +0 -26
  71. package/apps/browserctl/src/commands/wait-element.test.ts +0 -80
  72. package/apps/browserctl/src/commands/wait-element.ts +0 -76
  73. package/apps/browserctl/src/commands/wait-text.test.ts +0 -110
  74. package/apps/browserctl/src/commands/wait-text.ts +0 -93
  75. package/apps/browserctl/src/commands/wait-url.test.ts +0 -80
  76. package/apps/browserctl/src/commands/wait-url.ts +0 -76
  77. package/apps/browserctl/src/daemon-client.test.ts +0 -512
  78. package/apps/browserctl/src/daemon-client.ts +0 -632
  79. package/apps/browserctl/src/e2e.test.ts +0 -103
  80. package/apps/browserctl/src/main.dispatch.test.ts +0 -461
  81. package/apps/browserctl/src/main.test.ts +0 -334
  82. package/apps/browserctl/src/main.ts +0 -957
  83. package/apps/browserctl/src/smoke.e2e.test.ts +0 -97
  84. package/apps/browserctl/src/test-port.ts +0 -26
  85. package/apps/browserd/src/bootstrap.ts +0 -432
  86. package/apps/browserd/src/chrome-relay-extension-bridge.test.ts +0 -250
  87. package/apps/browserd/src/chrome-relay-extension-bridge.ts +0 -506
  88. package/apps/browserd/src/container.ts +0 -3088
  89. package/apps/browserd/src/main.test.ts +0 -1522
  90. package/apps/browserd/src/main.ts +0 -7
  91. package/apps/browserd/src/test-port.ts +0 -26
  92. package/apps/browserd/src/tool-matrix.test.ts +0 -887
  93. package/bin/browserctl.cjs +0 -21
  94. package/bin/browserd.cjs +0 -21
  95. package/extensions/chrome-relay/README-CN.md +0 -39
  96. package/extensions/chrome-relay/README.md +0 -39
  97. package/extensions/chrome-relay/background.js +0 -1687
  98. package/extensions/chrome-relay/manifest.json +0 -15
  99. package/extensions/chrome-relay/popup.html +0 -369
  100. package/extensions/chrome-relay/popup.js +0 -972
  101. package/packages/core/src/bootstrap.test.ts +0 -10
  102. package/packages/core/src/driver-registry.test.ts +0 -45
  103. package/packages/core/src/driver-registry.ts +0 -22
  104. package/packages/core/src/driver.ts +0 -47
  105. package/packages/core/src/index.ts +0 -6
  106. package/packages/core/src/navigation-memory.test.ts +0 -259
  107. package/packages/core/src/navigation-memory.ts +0 -360
  108. package/packages/core/src/ref-cache.test.ts +0 -61
  109. package/packages/core/src/ref-cache.ts +0 -28
  110. package/packages/core/src/session-store.test.ts +0 -82
  111. package/packages/core/src/session-store.ts +0 -138
  112. package/packages/core/src/types.ts +0 -9
  113. package/packages/driver-chrome-relay/src/chrome-relay-driver.test.ts +0 -744
  114. package/packages/driver-chrome-relay/src/chrome-relay-driver.ts +0 -2429
  115. package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.test.ts +0 -264
  116. package/packages/driver-chrome-relay/src/chrome-relay-extension-runtime.ts +0 -521
  117. package/packages/driver-chrome-relay/src/index.ts +0 -26
  118. package/packages/driver-managed/src/index.ts +0 -22
  119. package/packages/driver-managed/src/managed-driver.test.ts +0 -183
  120. package/packages/driver-managed/src/managed-driver.ts +0 -341
  121. package/packages/driver-managed/src/managed-local-driver.test.ts +0 -608
  122. package/packages/driver-managed/src/managed-local-driver.ts +0 -2243
  123. package/packages/driver-remote-cdp/src/index.ts +0 -19
  124. package/packages/driver-remote-cdp/src/remote-cdp-driver.test.ts +0 -727
  125. package/packages/driver-remote-cdp/src/remote-cdp-driver.ts +0 -2264
  126. package/packages/protocol/src/envelope.test.ts +0 -25
  127. package/packages/protocol/src/envelope.ts +0 -31
  128. package/packages/protocol/src/errors.test.ts +0 -17
  129. package/packages/protocol/src/errors.ts +0 -11
  130. package/packages/protocol/src/index.ts +0 -3
  131. package/packages/protocol/src/tools.ts +0 -3
  132. package/packages/transport-mcp-stdio/src/index.ts +0 -3
  133. package/packages/transport-mcp-stdio/src/sdk-server.ts +0 -139
  134. package/packages/transport-mcp-stdio/src/server.test.ts +0 -281
  135. package/packages/transport-mcp-stdio/src/server.ts +0 -183
  136. package/packages/transport-mcp-stdio/src/tool-map.ts +0 -84
@@ -1,887 +0,0 @@
1
- import { PassThrough } from "node:stream";
2
-
3
- import { describe, expect, it } from "vitest";
4
-
5
- import { bootstrapBrowserd } from "./bootstrap";
6
- import { reserveLoopbackPort } from "./test-port";
7
-
8
- function waitForNextJsonLine(stream: PassThrough): Promise<Record<string, unknown>> {
9
- return new Promise((resolve, reject) => {
10
- let buffer = "";
11
- const timeout = setTimeout(() => {
12
- stream.off("data", onData);
13
- reject(new Error("Timed out waiting for JSON line response."));
14
- }, 1000);
15
-
16
- const onData = (chunk: string | Buffer) => {
17
- buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
18
- const newlineIndex = buffer.indexOf("\n");
19
- if (newlineIndex < 0) {
20
- return;
21
- }
22
-
23
- clearTimeout(timeout);
24
- stream.off("data", onData);
25
- const line = buffer.slice(0, newlineIndex);
26
-
27
- resolve(JSON.parse(line) as Record<string, unknown>);
28
- };
29
-
30
- stream.on("data", onData);
31
- });
32
- }
33
-
34
- function sendToolRequest(
35
- input: PassThrough,
36
- output: PassThrough,
37
- request: Record<string, unknown>
38
- ): Promise<Record<string, unknown>> {
39
- const responsePromise = waitForNextJsonLine(output);
40
- input.write(`${JSON.stringify(request)}\n`);
41
- return responsePromise;
42
- }
43
-
44
- async function createManagedLegacyRuntime(
45
- overrides: Record<string, string | undefined> = {}
46
- ) {
47
- const relayPort = await reserveLoopbackPort();
48
-
49
- const input = new PassThrough();
50
- const output = new PassThrough();
51
- const runtime = bootstrapBrowserd({
52
- env: {
53
- BROWSERD_DEFAULT_DRIVER: "managed",
54
- BROWSERD_CHROME_RELAY_URL: `http://127.0.0.1:${relayPort}`,
55
- ...overrides
56
- },
57
- input,
58
- output,
59
- stdioProtocol: "legacy"
60
- });
61
-
62
- return {
63
- input,
64
- output,
65
- runtime
66
- };
67
- }
68
-
69
- describe("browserd tool matrix", () => {
70
- it("returns E_INVALID_ARG for missing required arguments on routed tools", async () => {
71
- const { input, output, runtime } = await createManagedLegacyRuntime();
72
-
73
- try {
74
- const toolNames = [
75
- "browser.profile.use",
76
- "browser.tab.focus",
77
- "browser.tab.close",
78
- "browser.snapshot",
79
- "browser.dom.query",
80
- "browser.dom.queryAll",
81
- "browser.element.screenshot",
82
- "browser.a11y.snapshot",
83
- "browser.wait.element",
84
- "browser.wait.text",
85
- "browser.wait.url",
86
- "browser.cookie.get",
87
- "browser.cookie.set",
88
- "browser.cookie.clear",
89
- "browser.storage.get",
90
- "browser.storage.set",
91
- "browser.frame.list",
92
- "browser.frame.snapshot",
93
- "browser.act",
94
- "browser.dialog.arm",
95
- "browser.download.trigger",
96
- "browser.network.list",
97
- "browser.network.harExport",
98
- "browser.network.waitFor",
99
- "browser.console.list",
100
- "browser.session.drop"
101
- ] as const;
102
-
103
- for (const [index, toolName] of toolNames.entries()) {
104
- const response = await sendToolRequest(input, output, {
105
- id: `request-missing-arg-${index}`,
106
- name: toolName,
107
- traceId: `trace:missing-arg:${index}`,
108
- arguments: {
109
- sessionId: "session:missing-arg"
110
- }
111
- });
112
-
113
- expect(response.ok).toBe(false);
114
- expect(response.error).toMatchObject({
115
- code: "E_INVALID_ARG"
116
- });
117
- }
118
- } finally {
119
- runtime.close();
120
- input.end();
121
- output.end();
122
- }
123
- });
124
-
125
- it("routes browser.memory tools with validation and deterministic state", async () => {
126
- const { input, output, runtime } = await createManagedLegacyRuntime();
127
-
128
- try {
129
- const statusResponse = await sendToolRequest(input, output, {
130
- id: "request-memory-status",
131
- name: "browser.memory.status",
132
- traceId: "trace:memory:status",
133
- arguments: {
134
- sessionId: "session:memory"
135
- }
136
- });
137
- expect(statusResponse.ok).toBe(true);
138
- const statusPayload = statusResponse.data as {
139
- path?: unknown;
140
- mode?: unknown;
141
- ttlDays?: unknown;
142
- totalEntries?: unknown;
143
- };
144
- expect(typeof statusPayload.path).toBe("string");
145
- expect((statusPayload.path as string).length).toBeGreaterThan(0);
146
- expect(statusPayload.mode).toBe("ask");
147
- expect(typeof statusPayload.ttlDays).toBe("number");
148
- expect(typeof statusPayload.totalEntries).toBe("number");
149
-
150
- const resolveMissingArgResponse = await sendToolRequest(input, output, {
151
- id: "request-memory-resolve-missing",
152
- name: "browser.memory.resolve",
153
- traceId: "trace:memory:resolve:missing",
154
- arguments: {
155
- sessionId: "session:memory"
156
- }
157
- });
158
- expect(resolveMissingArgResponse.ok).toBe(false);
159
- expect(resolveMissingArgResponse.error).toMatchObject({
160
- code: "E_INVALID_ARG"
161
- });
162
-
163
- const upsertMissingArgResponse = await sendToolRequest(input, output, {
164
- id: "request-memory-upsert-missing",
165
- name: "browser.memory.upsert",
166
- traceId: "trace:memory:upsert:missing",
167
- arguments: {
168
- sessionId: "session:memory"
169
- }
170
- });
171
- expect(upsertMissingArgResponse.ok).toBe(false);
172
- expect(upsertMissingArgResponse.error).toMatchObject({
173
- code: "E_INVALID_ARG"
174
- });
175
-
176
- const upsertFreeTextSignalResponse = await sendToolRequest(input, output, {
177
- id: "request-memory-upsert-free-text-signal",
178
- name: "browser.memory.upsert",
179
- traceId: "trace:memory:upsert:free-text-signal",
180
- arguments: {
181
- sessionId: "session:memory",
182
- domain: "example.com",
183
- profileId: "managed",
184
- intentKey: "checkout",
185
- signals: [
186
- {
187
- kind: "urlPattern",
188
- value: "this is copied page body text that should never be stored"
189
- }
190
- ],
191
- confirmed: true
192
- }
193
- });
194
- expect(upsertFreeTextSignalResponse.ok).toBe(false);
195
- expect(upsertFreeTextSignalResponse.error).toMatchObject({
196
- code: "E_INVALID_ARG"
197
- });
198
-
199
- const upsertResponse = await sendToolRequest(input, output, {
200
- id: "request-memory-upsert",
201
- name: "browser.memory.upsert",
202
- traceId: "trace:memory:upsert",
203
- arguments: {
204
- sessionId: "session:memory",
205
- domain: "example.com",
206
- profileId: "managed",
207
- intentKey: "checkout",
208
- signals: [
209
- {
210
- kind: "urlPattern",
211
- value: "/checkout"
212
- }
213
- ],
214
- confidence: 0.8,
215
- confirmed: true
216
- }
217
- });
218
- expect(upsertResponse.ok).toBe(true);
219
- const upsertPayload = upsertResponse.data as {
220
- created?: unknown;
221
- entry?: {
222
- id?: unknown;
223
- domain?: unknown;
224
- profileId?: unknown;
225
- intentKey?: unknown;
226
- confidence?: unknown;
227
- hitCount?: unknown;
228
- };
229
- };
230
- expect(upsertPayload.created).toBe(true);
231
- expect(upsertPayload.entry?.domain).toBe("example.com");
232
- expect(upsertPayload.entry?.profileId).toBe("managed");
233
- expect(upsertPayload.entry?.intentKey).toBe("checkout");
234
- expect(upsertPayload.entry?.confidence).toBe(0.8);
235
- expect(upsertPayload.entry?.hitCount).toBe(0);
236
- expect(typeof upsertPayload.entry?.id).toBe("string");
237
- expect((upsertPayload.entry?.id as string).length).toBeGreaterThan(0);
238
-
239
- const memoryId = upsertPayload.entry?.id as string;
240
-
241
- const listResponse = await sendToolRequest(input, output, {
242
- id: "request-memory-list",
243
- name: "browser.memory.list",
244
- traceId: "trace:memory:list",
245
- arguments: {
246
- sessionId: "session:memory"
247
- }
248
- });
249
- expect(listResponse.ok).toBe(true);
250
- expect(listResponse.data).toMatchObject({
251
- entries: [{ id: memoryId }]
252
- });
253
-
254
- const inspectResponse = await sendToolRequest(input, output, {
255
- id: "request-memory-inspect",
256
- name: "browser.memory.inspect",
257
- traceId: "trace:memory:inspect",
258
- arguments: {
259
- sessionId: "session:memory",
260
- id: memoryId
261
- }
262
- });
263
- expect(inspectResponse.ok).toBe(true);
264
- expect(inspectResponse.data).toMatchObject({
265
- entry: {
266
- id: memoryId
267
- }
268
- });
269
-
270
- const resolveResponse = await sendToolRequest(input, output, {
271
- id: "request-memory-resolve",
272
- name: "browser.memory.resolve",
273
- traceId: "trace:memory:resolve",
274
- arguments: {
275
- sessionId: "session:memory",
276
- domain: "example.com",
277
- profileId: "managed",
278
- intentKey: "checkout"
279
- }
280
- });
281
- expect(resolveResponse.ok).toBe(true);
282
- expect(resolveResponse.data).toMatchObject({
283
- hit: true,
284
- entry: {
285
- id: memoryId,
286
- hitCount: 1
287
- }
288
- });
289
-
290
- const deleteResponse = await sendToolRequest(input, output, {
291
- id: "request-memory-delete",
292
- name: "browser.memory.delete",
293
- traceId: "trace:memory:delete",
294
- arguments: {
295
- sessionId: "session:memory",
296
- id: memoryId
297
- }
298
- });
299
- expect(deleteResponse.ok).toBe(true);
300
- expect(deleteResponse.data).toMatchObject({
301
- deleted: true
302
- });
303
-
304
- const purgeResponse = await sendToolRequest(input, output, {
305
- id: "request-memory-purge",
306
- name: "browser.memory.purge",
307
- traceId: "trace:memory:purge",
308
- arguments: {
309
- sessionId: "session:memory"
310
- }
311
- });
312
- expect(purgeResponse.ok).toBe(true);
313
- expect(purgeResponse.data).toMatchObject({
314
- purged: 0
315
- });
316
-
317
- const modeSetResponse = await sendToolRequest(input, output, {
318
- id: "request-memory-mode-set",
319
- name: "browser.memory.mode.set",
320
- traceId: "trace:memory:mode:set",
321
- arguments: {
322
- sessionId: "session:memory",
323
- mode: "ask"
324
- }
325
- });
326
- expect(modeSetResponse.ok).toBe(true);
327
- expect(modeSetResponse.data).toMatchObject({
328
- mode: "ask"
329
- });
330
-
331
- const modeOffResponse = await sendToolRequest(input, output, {
332
- id: "request-memory-mode-set-off",
333
- name: "browser.memory.mode.set",
334
- traceId: "trace:memory:mode:set:off",
335
- arguments: {
336
- sessionId: "session:memory",
337
- mode: "off"
338
- }
339
- });
340
- expect(modeOffResponse.ok).toBe(true);
341
- expect(modeOffResponse.data).toMatchObject({
342
- mode: "off"
343
- });
344
-
345
- const policyRejectResponse = await sendToolRequest(input, output, {
346
- id: "request-memory-upsert-policy-reject",
347
- name: "browser.memory.upsert",
348
- traceId: "trace:memory:upsert:policy:reject",
349
- arguments: {
350
- sessionId: "session:memory",
351
- domain: "example.com",
352
- profileId: "managed",
353
- intentKey: "checkout",
354
- signals: [
355
- {
356
- kind: "urlPattern",
357
- value: "/checkout"
358
- }
359
- ],
360
- confidence: 0.6
361
- }
362
- });
363
- expect(policyRejectResponse.ok).toBe(false);
364
- expect(policyRejectResponse.error).toMatchObject({
365
- code: "E_PERMISSION"
366
- });
367
-
368
- const ttlSetResponse = await sendToolRequest(input, output, {
369
- id: "request-memory-ttl-set",
370
- name: "browser.memory.ttl.set",
371
- traceId: "trace:memory:ttl:set",
372
- arguments: {
373
- sessionId: "session:memory",
374
- ttlDays: 7
375
- }
376
- });
377
- expect(ttlSetResponse.ok).toBe(true);
378
- expect(ttlSetResponse.data).toMatchObject({
379
- ttlDays: 7
380
- });
381
- } finally {
382
- runtime.close();
383
- input.end();
384
- output.end();
385
- }
386
- });
387
-
388
- it("records memory-hit marker in browser.trace.get after browser.memory.resolve", async () => {
389
- const { input, output, runtime } = await createManagedLegacyRuntime();
390
-
391
- try {
392
- const upsertResponse = await sendToolRequest(input, output, {
393
- id: "request-memory-hit-upsert",
394
- name: "browser.memory.upsert",
395
- traceId: "trace:memory:hit:upsert",
396
- arguments: {
397
- sessionId: "session:memory-hit",
398
- domain: "example.com",
399
- profileId: "managed",
400
- intentKey: "checkout",
401
- signals: [
402
- {
403
- kind: "urlPattern",
404
- value: "/checkout"
405
- }
406
- ],
407
- confidence: 0.9,
408
- confirmed: true
409
- }
410
- });
411
- expect(upsertResponse.ok).toBe(true);
412
-
413
- const resolveResponse = await sendToolRequest(input, output, {
414
- id: "request-memory-hit-resolve",
415
- name: "browser.memory.resolve",
416
- traceId: "trace:memory:hit:resolve",
417
- arguments: {
418
- sessionId: "session:memory-hit",
419
- domain: "example.com",
420
- profileId: "managed",
421
- intentKey: "checkout"
422
- }
423
- });
424
- expect(resolveResponse.ok).toBe(true);
425
- expect(resolveResponse.data).toMatchObject({
426
- hit: true
427
- });
428
-
429
- const traceGetResponse = await sendToolRequest(input, output, {
430
- id: "request-memory-hit-trace-get",
431
- name: "browser.trace.get",
432
- traceId: "trace:memory:hit:trace-get",
433
- arguments: {
434
- sessionId: "session:memory-hit",
435
- limit: 20
436
- }
437
- });
438
- expect(traceGetResponse.ok).toBe(true);
439
- const tracePayload = traceGetResponse.data as {
440
- keyResponses?: Array<{
441
- kind?: string;
442
- data?: {
443
- memoryHit?: boolean;
444
- };
445
- }>;
446
- };
447
- expect(tracePayload.keyResponses?.some((response) =>
448
- response.kind === "memory.resolve" && response.data?.memoryHit === true
449
- )).toBe(true);
450
- } finally {
451
- runtime.close();
452
- input.end();
453
- output.end();
454
- }
455
- });
456
-
457
- it("keeps browser.memory disabled when BROWSERD_MEMORY_ENABLED is false", async () => {
458
- const { input, output, runtime } = await createManagedLegacyRuntime({
459
- BROWSERD_MEMORY_ENABLED: "false"
460
- });
461
-
462
- try {
463
- const statusResponse = await sendToolRequest(input, output, {
464
- id: "request-memory-disabled-status",
465
- name: "browser.memory.status",
466
- traceId: "trace:memory:disabled:status",
467
- arguments: {
468
- sessionId: "session:memory:disabled"
469
- }
470
- });
471
-
472
- expect(statusResponse.ok).toBe(true);
473
- expect(statusResponse.data).toMatchObject({
474
- mode: "off"
475
- });
476
-
477
- const modeSetResponse = await sendToolRequest(input, output, {
478
- id: "request-memory-disabled-mode-set",
479
- name: "browser.memory.mode.set",
480
- traceId: "trace:memory:disabled:mode:set",
481
- arguments: {
482
- sessionId: "session:memory:disabled",
483
- mode: "auto"
484
- }
485
- });
486
-
487
- expect(modeSetResponse.ok).toBe(false);
488
- expect(modeSetResponse.error).toMatchObject({
489
- code: "E_PERMISSION"
490
- });
491
-
492
- const statusAfterResponse = await sendToolRequest(input, output, {
493
- id: "request-memory-disabled-status-after",
494
- name: "browser.memory.status",
495
- traceId: "trace:memory:disabled:status:after",
496
- arguments: {
497
- sessionId: "session:memory:disabled"
498
- }
499
- });
500
- expect(statusAfterResponse.ok).toBe(true);
501
- expect(statusAfterResponse.data).toMatchObject({
502
- mode: "off"
503
- });
504
- } finally {
505
- runtime.close();
506
- input.end();
507
- output.end();
508
- }
509
- });
510
-
511
- it("returns E_DRIVER_UNAVAILABLE for structured action tools on managed driver", async () => {
512
- const { input, output, runtime } = await createManagedLegacyRuntime();
513
-
514
- try {
515
- const openResponse = await sendToolRequest(input, output, {
516
- id: "request-unavailable-open",
517
- name: "browser.tab.open",
518
- traceId: "trace:unavailable:open",
519
- arguments: {
520
- sessionId: "session:unavailable",
521
- url: "https://example.com/unavailable"
522
- }
523
- });
524
- const targetId = (openResponse.data as { targetId: string }).targetId;
525
-
526
- const cases: Array<{ name: string; arguments: Record<string, unknown> }> = [
527
- {
528
- name: "browser.dom.query",
529
- arguments: {
530
- sessionId: "session:unavailable",
531
- targetId,
532
- selector: "#root"
533
- }
534
- },
535
- {
536
- name: "browser.dom.queryAll",
537
- arguments: {
538
- sessionId: "session:unavailable",
539
- targetId,
540
- selector: ".item"
541
- }
542
- },
543
- {
544
- name: "browser.element.screenshot",
545
- arguments: {
546
- sessionId: "session:unavailable",
547
- targetId,
548
- selector: "#hero"
549
- }
550
- },
551
- {
552
- name: "browser.a11y.snapshot",
553
- arguments: {
554
- sessionId: "session:unavailable",
555
- targetId
556
- }
557
- },
558
- {
559
- name: "browser.wait.element",
560
- arguments: {
561
- sessionId: "session:unavailable",
562
- targetId,
563
- selector: "#ready",
564
- timeoutMs: 5,
565
- pollMs: 1
566
- }
567
- },
568
- {
569
- name: "browser.wait.text",
570
- arguments: {
571
- sessionId: "session:unavailable",
572
- targetId,
573
- text: "ready",
574
- selector: "#status",
575
- timeoutMs: 5,
576
- pollMs: 1
577
- }
578
- },
579
- {
580
- name: "browser.cookie.get",
581
- arguments: {
582
- sessionId: "session:unavailable",
583
- targetId
584
- }
585
- },
586
- {
587
- name: "browser.cookie.set",
588
- arguments: {
589
- sessionId: "session:unavailable",
590
- targetId,
591
- name: "sid",
592
- value: "abc"
593
- }
594
- },
595
- {
596
- name: "browser.cookie.clear",
597
- arguments: {
598
- sessionId: "session:unavailable",
599
- targetId
600
- }
601
- },
602
- {
603
- name: "browser.storage.get",
604
- arguments: {
605
- sessionId: "session:unavailable",
606
- targetId,
607
- scope: "local",
608
- key: "theme"
609
- }
610
- },
611
- {
612
- name: "browser.storage.set",
613
- arguments: {
614
- sessionId: "session:unavailable",
615
- targetId,
616
- scope: "local",
617
- key: "theme",
618
- value: "dark"
619
- }
620
- },
621
- {
622
- name: "browser.frame.list",
623
- arguments: {
624
- sessionId: "session:unavailable",
625
- targetId
626
- }
627
- },
628
- {
629
- name: "browser.frame.snapshot",
630
- arguments: {
631
- sessionId: "session:unavailable",
632
- targetId,
633
- frameId: "frame:0"
634
- }
635
- }
636
- ];
637
-
638
- for (const [index, testCase] of cases.entries()) {
639
- const response = await sendToolRequest(input, output, {
640
- id: `request-driver-unavailable-${index}`,
641
- name: testCase.name,
642
- traceId: `trace:driver-unavailable:${index}`,
643
- arguments: testCase.arguments
644
- });
645
-
646
- expect(response.ok).toBe(false);
647
- expect(response.error).toMatchObject({
648
- code: "E_DRIVER_UNAVAILABLE"
649
- });
650
- }
651
- } finally {
652
- runtime.close();
653
- input.end();
654
- output.end();
655
- }
656
- });
657
-
658
- it("routes managed-driver tools including profile.use, act, focus/close, and waitFor timeout", async () => {
659
- const { input, output, runtime } = await createManagedLegacyRuntime();
660
-
661
- try {
662
- const profileUseResponse = await sendToolRequest(input, output, {
663
- id: "request-profile-use",
664
- name: "browser.profile.use",
665
- traceId: "trace:matrix:profile-use",
666
- arguments: {
667
- sessionId: "session:profile-use",
668
- profile: "managed"
669
- }
670
- });
671
- expect(profileUseResponse.ok).toBe(true);
672
- expect(profileUseResponse.data).toEqual({
673
- profile: "managed"
674
- });
675
-
676
- const openResponse = await sendToolRequest(input, output, {
677
- id: "request-flow-open",
678
- name: "browser.tab.open",
679
- traceId: "trace:matrix:open",
680
- arguments: {
681
- sessionId: "session:flow",
682
- url: "https://example.com/flow"
683
- }
684
- });
685
- const targetId = (openResponse.data as { targetId: string }).targetId;
686
-
687
- const snapshotResponse = await sendToolRequest(input, output, {
688
- id: "request-flow-snapshot",
689
- name: "browser.snapshot",
690
- traceId: "trace:matrix:snapshot",
691
- arguments: {
692
- sessionId: "session:flow",
693
- targetId
694
- }
695
- });
696
- expect(snapshotResponse.ok).toBe(true);
697
- expect(snapshotResponse.data).toMatchObject({
698
- driver: "managed",
699
- targetId
700
- });
701
-
702
- const focusResponse = await sendToolRequest(input, output, {
703
- id: "request-flow-focus",
704
- name: "browser.tab.focus",
705
- traceId: "trace:matrix:focus",
706
- arguments: {
707
- sessionId: "session:flow",
708
- targetId
709
- }
710
- });
711
- expect(focusResponse.ok).toBe(true);
712
- expect(focusResponse.data).toMatchObject({
713
- driver: "managed",
714
- targetId,
715
- focused: true
716
- });
717
-
718
- const actResponse = await sendToolRequest(input, output, {
719
- id: "request-flow-act",
720
- name: "browser.act",
721
- traceId: "trace:matrix:act",
722
- arguments: {
723
- sessionId: "session:flow",
724
- targetId,
725
- action: {
726
- type: "click",
727
- payload: {
728
- selector: "#go"
729
- }
730
- }
731
- }
732
- });
733
- expect(actResponse.ok).toBe(true);
734
- expect(actResponse.data).toMatchObject({
735
- driver: "managed",
736
- targetId,
737
- result: {
738
- actionType: "click",
739
- targetId,
740
- targetKnown: true,
741
- ok: true
742
- }
743
- });
744
-
745
- const dialogResponse = await sendToolRequest(input, output, {
746
- id: "request-flow-dialog",
747
- name: "browser.dialog.arm",
748
- traceId: "trace:matrix:dialog",
749
- arguments: {
750
- sessionId: "session:flow",
751
- targetId
752
- }
753
- });
754
- expect(dialogResponse.ok).toBe(true);
755
- expect(dialogResponse.data).toMatchObject({
756
- driver: "managed",
757
- targetId,
758
- armed: true
759
- });
760
-
761
- const triggerResponse = await sendToolRequest(input, output, {
762
- id: "request-flow-trigger",
763
- name: "browser.download.trigger",
764
- traceId: "trace:matrix:trigger",
765
- arguments: {
766
- sessionId: "session:flow",
767
- targetId
768
- }
769
- });
770
- expect(triggerResponse.ok).toBe(true);
771
- expect(triggerResponse.data).toMatchObject({
772
- driver: "managed",
773
- targetId,
774
- triggered: true
775
- });
776
-
777
- const waitForResponse = await sendToolRequest(input, output, {
778
- id: "request-flow-waitfor",
779
- name: "browser.network.waitFor",
780
- traceId: "trace:matrix:waitfor",
781
- arguments: {
782
- sessionId: "session:flow",
783
- targetId,
784
- urlPattern: "/never-match",
785
- timeoutMs: 5,
786
- pollMs: 1
787
- }
788
- });
789
- expect(waitForResponse.ok).toBe(false);
790
- expect(waitForResponse.error).toMatchObject({
791
- code: "E_TIMEOUT"
792
- });
793
-
794
- const networkListResponse = await sendToolRequest(input, output, {
795
- id: "request-flow-network-list",
796
- name: "browser.network.list",
797
- traceId: "trace:matrix:network-list",
798
- arguments: {
799
- sessionId: "session:flow",
800
- targetId
801
- }
802
- });
803
- expect(networkListResponse.ok).toBe(true);
804
- expect(networkListResponse.data).toMatchObject({
805
- driver: "managed",
806
- targetId,
807
- requests: []
808
- });
809
-
810
- const harExportResponse = await sendToolRequest(input, output, {
811
- id: "request-flow-har-export",
812
- name: "browser.network.harExport",
813
- traceId: "trace:matrix:har-export",
814
- arguments: {
815
- sessionId: "session:flow",
816
- targetId
817
- }
818
- });
819
- expect(harExportResponse.ok).toBe(true);
820
- expect(harExportResponse.data).toMatchObject({
821
- driver: "managed",
822
- targetId,
823
- har: {
824
- log: {
825
- entries: []
826
- }
827
- }
828
- });
829
-
830
- const traceGetResponse = await sendToolRequest(input, output, {
831
- id: "request-flow-trace-get",
832
- name: "browser.trace.get",
833
- traceId: "trace:matrix:trace-get",
834
- arguments: {
835
- sessionId: "session:flow",
836
- limit: 20
837
- }
838
- });
839
- expect(traceGetResponse.ok).toBe(true);
840
- expect(traceGetResponse.data).toMatchObject({
841
- sessionId: "session:flow"
842
- });
843
- const tracePayload = traceGetResponse.data as {
844
- steps?: Array<{ tool?: string }>;
845
- };
846
- expect(Array.isArray(tracePayload.steps)).toBe(true);
847
- expect(tracePayload.steps?.some((step) => step.tool === "browser.tab.open")).toBe(true);
848
-
849
- const waitUrlResponse = await sendToolRequest(input, output, {
850
- id: "request-flow-waiturl",
851
- name: "browser.wait.url",
852
- traceId: "trace:matrix:waiturl",
853
- arguments: {
854
- sessionId: "session:flow",
855
- targetId,
856
- urlPattern: "/never-match",
857
- timeoutMs: 5,
858
- pollMs: 1
859
- }
860
- });
861
- expect(waitUrlResponse.ok).toBe(false);
862
- expect(waitUrlResponse.error).toMatchObject({
863
- code: "E_TIMEOUT"
864
- });
865
-
866
- const closeResponse = await sendToolRequest(input, output, {
867
- id: "request-flow-close",
868
- name: "browser.tab.close",
869
- traceId: "trace:matrix:close",
870
- arguments: {
871
- sessionId: "session:flow",
872
- targetId
873
- }
874
- });
875
- expect(closeResponse.ok).toBe(true);
876
- expect(closeResponse.data).toMatchObject({
877
- driver: "managed",
878
- targetId,
879
- closed: true
880
- });
881
- } finally {
882
- runtime.close();
883
- input.end();
884
- output.end();
885
- }
886
- });
887
- });