@elizaos/app-core 2.0.0-alpha.21 → 2.0.0-alpha.22

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 (38) hide show
  1. package/.turbo/turbo-lint$colon$check.log +2 -0
  2. package/.turbo/turbo-typecheck.log +1 -0
  3. package/dist/actions/onboarding.d.ts +1 -1
  4. package/dist/actions/onboarding.d.ts.map +1 -1
  5. package/dist/actions/onboarding.js +0 -2
  6. package/dist/components/OnboardingWizard.d.ts.map +1 -1
  7. package/dist/components/OnboardingWizard.js +14 -4
  8. package/dist/components/onboarding/IdentityStep.d.ts.map +1 -1
  9. package/dist/components/onboarding/IdentityStep.js +161 -42
  10. package/dist/package.json +3 -3
  11. package/dist/state/AppContext.d.ts.map +1 -1
  12. package/dist/state/AppContext.js +2 -10
  13. package/dist/state/onboarding-resume.js +1 -1
  14. package/dist/state/persistence.d.ts.map +1 -1
  15. package/dist/state/persistence.js +0 -1
  16. package/dist/state/types.d.ts +1 -1
  17. package/dist/state/types.d.ts.map +1 -1
  18. package/dist/state/types.js +0 -5
  19. package/package.json +4 -4
  20. package/src/actions/onboarding.ts +0 -4
  21. package/src/components/OnboardingWizard.test.tsx +4 -8
  22. package/src/components/OnboardingWizard.tsx +26 -9
  23. package/src/components/onboarding/IdentityStep.tsx +224 -87
  24. package/src/state/AppContext.tsx +2 -13
  25. package/src/state/onboarding-resume.test.ts +2 -2
  26. package/src/state/onboarding-resume.ts +1 -1
  27. package/src/state/persistence.ts +0 -1
  28. package/src/state/types.ts +0 -6
  29. package/test/app/advanced-trajectory-fine-tuning.e2e.test.ts +15 -11
  30. package/test/browser-extension/README.md +138 -0
  31. package/test/browser-extension/test-harness.ts +499 -0
  32. package/test/capacitor-plugins.e2e.test.ts +153 -0
  33. package/test/test-types.ts +5 -0
  34. package/tsconfig.typecheck.json +12 -0
  35. package/dist/components/onboarding/WakeUpStep.d.ts +0 -2
  36. package/dist/components/onboarding/WakeUpStep.d.ts.map +0 -1
  37. package/dist/components/onboarding/WakeUpStep.js +0 -82
  38. package/src/components/onboarding/WakeUpStep.tsx +0 -184
@@ -0,0 +1,138 @@
1
+ # Browser Extension Test Harness
2
+
3
+ This directory contains tools for testing the ComputerUse Bridge Extension.
4
+
5
+ ## Overview
6
+
7
+ The ComputerUse Bridge Extension is a Chrome MV3 extension that enables JavaScript
8
+ evaluation in browser tabs via a WebSocket connection. This test harness validates
9
+ that the extension correctly:
10
+
11
+ 1. Connects to the WebSocket server at `ws://127.0.0.1:17373`
12
+ 2. Receives eval requests with the correct protocol
13
+ 3. Evaluates JavaScript in the active tab
14
+ 4. Returns results (or errors) with the correct format
15
+
16
+ ## Prerequisites
17
+
18
+ 1. **Chrome/Chromium browser** with Developer Mode enabled
19
+ 2. **The extension loaded** from `eliza/packages/computeruse/crates/computeruse/browser-extension/`
20
+ 3. **At least one browser tab** open (extension needs a tab to evaluate code in)
21
+ 4. **Node.js 22+** with tsx installed
22
+
23
+ ## Loading the Extension
24
+
25
+ 1. Open Chrome and navigate to `chrome://extensions`
26
+ 2. Enable "Developer mode" (toggle in top right)
27
+ 3. Click "Load unpacked"
28
+ 4. Select the `browser-extension/` directory
29
+ 5. Verify the extension appears and is enabled
30
+
31
+ ## Running the Test Harness
32
+
33
+ ### Basic Test Run
34
+
35
+ ```bash
36
+ # From milady root
37
+ npx tsx test/browser-extension/test-harness.ts
38
+ ```
39
+
40
+ ### Options
41
+
42
+ ```bash
43
+ # Verbose output (show all messages)
44
+ npx tsx test/browser-extension/test-harness.ts --verbose
45
+
46
+ # Interactive mode (keep server running for manual testing)
47
+ npx tsx test/browser-extension/test-harness.ts --interactive
48
+
49
+ # Custom port (default: 17373)
50
+ npx tsx test/browser-extension/test-harness.ts --port 17374
51
+
52
+ # Custom timeout (default: 30000ms)
53
+ npx tsx test/browser-extension/test-harness.ts --timeout 60000
54
+ ```
55
+
56
+ ## Protocol Specification
57
+
58
+ ### Request Format
59
+
60
+ ```json
61
+ {
62
+ "id": "unique-request-id",
63
+ "action": "eval",
64
+ "code": "document.title",
65
+ "awaitPromise": false
66
+ }
67
+ ```
68
+
69
+ ### Success Response
70
+
71
+ ```json
72
+ {
73
+ "id": "unique-request-id",
74
+ "ok": true,
75
+ "result": "Page Title"
76
+ }
77
+ ```
78
+
79
+ ### Error Response
80
+
81
+ ```json
82
+ {
83
+ "id": "unique-request-id",
84
+ "ok": false,
85
+ "error": "ReferenceError: foo is not defined"
86
+ }
87
+ ```
88
+
89
+ ## Test Categories
90
+
91
+ The harness runs tests in these categories:
92
+
93
+ 1. **Basic JavaScript Evaluation** - Simple expressions, arrays, objects
94
+ 2. **DOM Access** - document.title, location.href, querySelectorAll
95
+ 3. **Promise Handling** - awaitPromise flag, async operations
96
+ 4. **Error Handling** - Syntax errors, runtime errors, rejected promises
97
+ 5. **Complex Operations** - Page metadata extraction, finding elements
98
+
99
+ ## Troubleshooting
100
+
101
+ ### Extension doesn't connect
102
+
103
+ 1. Check that the extension is loaded and enabled in `chrome://extensions`
104
+ 2. Check that Developer mode is enabled
105
+ 3. Reload the extension by clicking the refresh button
106
+ 4. Make sure at least one tab is open (not just chrome:// pages)
107
+
108
+ ### Tests timeout
109
+
110
+ 1. The extension may need a moment to connect after loading
111
+ 2. Try increasing the timeout: `--timeout 60000`
112
+ 3. Check Chrome DevTools for extension errors (go to `chrome://extensions`, click "Inspect views" on the extension)
113
+
114
+ ### "Cannot evaluate in this tab"
115
+
116
+ Some pages block extension access:
117
+ - `chrome://` pages
118
+ - `edge://` pages
119
+ - Extension pages
120
+ - Some protected sites
121
+
122
+ Navigate to a regular webpage (like https://example.com) before running tests.
123
+
124
+ ## Manual Testing
125
+
126
+ For manual testing, start the harness in interactive mode:
127
+
128
+ ```bash
129
+ npx tsx test/browser-extension/test-harness.ts --interactive --verbose
130
+ ```
131
+
132
+ Then send requests manually using a tool like `wscat`:
133
+
134
+ ```bash
135
+ wscat -c ws://127.0.0.1:17373
136
+ > {"id":"1","action":"eval","code":"document.title"}
137
+ < {"id":"1","ok":true,"result":"Page Title"}
138
+ ```
@@ -0,0 +1,499 @@
1
+ /**
2
+ * Browser Extension Test Harness
3
+ *
4
+ * This test harness validates the ComputerUse Bridge Extension by:
5
+ * 1. Starting a WebSocket server on port 17373 (extension's expected port)
6
+ * 2. Accepting connections from the extension
7
+ * 3. Sending eval requests and validating responses
8
+ *
9
+ * Usage:
10
+ * npx tsx test/browser-extension/test-harness.ts [options]
11
+ *
12
+ * Options:
13
+ * --port <number> Port to listen on (default: 17373)
14
+ * --timeout <ms> Test timeout (default: 30000)
15
+ * --interactive Keep server running for manual testing
16
+ * --verbose Show all messages
17
+ *
18
+ * The extension must be loaded in Chrome and the browser must have
19
+ * at least one tab open for tests to work.
20
+ */
21
+
22
+ import { createServer, type Server } from "node:http";
23
+ import { type WebSocket, WebSocketServer } from "ws";
24
+
25
+ // Configuration
26
+ const DEFAULT_PORT = 17373;
27
+ const DEFAULT_TIMEOUT = 30000;
28
+
29
+ interface EvalRequest {
30
+ id: string;
31
+ action: "eval";
32
+ code: string;
33
+ awaitPromise?: boolean;
34
+ }
35
+
36
+ interface EvalResponse {
37
+ id: string;
38
+ ok: boolean;
39
+ result?: unknown;
40
+ error?: string;
41
+ }
42
+
43
+ interface TestResult {
44
+ name: string;
45
+ passed: boolean;
46
+ duration: number;
47
+ error?: string;
48
+ details?: string;
49
+ }
50
+
51
+ // Parse command line args
52
+ const args = process.argv.slice(2);
53
+ const PORT = parseInt(
54
+ args.find((_a, i) => args[i - 1] === "--port") || String(DEFAULT_PORT),
55
+ 10,
56
+ );
57
+ const TIMEOUT = parseInt(
58
+ args.find((_a, i) => args[i - 1] === "--timeout") || String(DEFAULT_TIMEOUT),
59
+ 10,
60
+ );
61
+ const INTERACTIVE = args.includes("--interactive");
62
+ const VERBOSE = args.includes("--verbose");
63
+
64
+ // Test harness class
65
+ class ExtensionTestHarness {
66
+ private server: Server;
67
+ private wss: WebSocketServer;
68
+ private connection: WebSocket | null = null;
69
+ private pendingRequests: Map<
70
+ string,
71
+ {
72
+ resolve: (value: EvalResponse) => void;
73
+ reject: (error: Error) => void;
74
+ timeout: NodeJS.Timeout;
75
+ }
76
+ > = new Map();
77
+ private requestId = 0;
78
+ private results: TestResult[] = [];
79
+
80
+ constructor(private port: number) {
81
+ this.server = createServer();
82
+ this.wss = new WebSocketServer({ server: this.server });
83
+
84
+ this.wss.on("connection", (ws) => {
85
+ this.log("Extension connected");
86
+ this.connection = ws;
87
+
88
+ ws.on("message", (data) => {
89
+ try {
90
+ const message = JSON.parse(data.toString()) as EvalResponse;
91
+ this.handleResponse(message);
92
+ } catch (err) {
93
+ this.log(`Failed to parse message: ${err}`);
94
+ }
95
+ });
96
+
97
+ ws.on("close", () => {
98
+ this.log("Extension disconnected");
99
+ this.connection = null;
100
+ });
101
+
102
+ ws.on("error", (err) => {
103
+ this.log(`WebSocket error: ${err.message}`);
104
+ });
105
+ });
106
+ }
107
+
108
+ private log(message: string) {
109
+ if (VERBOSE) {
110
+ console.log(`[harness] ${message}`);
111
+ }
112
+ }
113
+
114
+ private handleResponse(response: EvalResponse) {
115
+ this.log(`Received response: ${JSON.stringify(response).slice(0, 200)}...`);
116
+
117
+ const pending = this.pendingRequests.get(response.id);
118
+ if (pending) {
119
+ clearTimeout(pending.timeout);
120
+ this.pendingRequests.delete(response.id);
121
+ pending.resolve(response);
122
+ }
123
+ }
124
+
125
+ async start(): Promise<void> {
126
+ return new Promise((resolve) => {
127
+ this.server.listen(this.port, () => {
128
+ console.log(`Test harness listening on ws://127.0.0.1:${this.port}`);
129
+ resolve();
130
+ });
131
+ });
132
+ }
133
+
134
+ async stop(): Promise<void> {
135
+ return new Promise((resolve) => {
136
+ this.wss.close(() => {
137
+ this.server.close(() => {
138
+ resolve();
139
+ });
140
+ });
141
+ });
142
+ }
143
+
144
+ async waitForConnection(timeoutMs: number = 30000): Promise<void> {
145
+ const start = Date.now();
146
+ while (!this.connection && Date.now() - start < timeoutMs) {
147
+ await new Promise((r) => setTimeout(r, 100));
148
+ }
149
+ if (!this.connection) {
150
+ throw new Error(
151
+ "Extension did not connect within timeout. Make sure:\n" +
152
+ "1. The extension is loaded in Chrome (chrome://extensions)\n" +
153
+ "2. Developer mode is enabled\n" +
154
+ "3. At least one browser tab is open",
155
+ );
156
+ }
157
+ }
158
+
159
+ async eval(code: string, awaitPromise = false): Promise<EvalResponse> {
160
+ if (!this.connection) {
161
+ throw new Error("No extension connected");
162
+ }
163
+
164
+ const id = `test-${++this.requestId}`;
165
+ const request: EvalRequest = {
166
+ id,
167
+ action: "eval",
168
+ code,
169
+ awaitPromise,
170
+ };
171
+
172
+ return new Promise((resolve, reject) => {
173
+ const timeout = setTimeout(() => {
174
+ this.pendingRequests.delete(id);
175
+ reject(new Error(`Eval request timed out: ${code.slice(0, 50)}...`));
176
+ }, TIMEOUT);
177
+
178
+ this.pendingRequests.set(id, { resolve, reject, timeout });
179
+
180
+ this.log(`Sending eval: ${JSON.stringify(request)}`);
181
+ this.connection?.send(JSON.stringify(request));
182
+ });
183
+ }
184
+
185
+ // =========================================================================
186
+ // TEST CASES
187
+ // =========================================================================
188
+
189
+ async runTest(
190
+ name: string,
191
+ testFn: () => Promise<void>,
192
+ ): Promise<TestResult> {
193
+ const start = Date.now();
194
+ try {
195
+ await testFn();
196
+ const result: TestResult = {
197
+ name,
198
+ passed: true,
199
+ duration: Date.now() - start,
200
+ };
201
+ this.results.push(result);
202
+ console.log(` ✓ ${name} (${result.duration}ms)`);
203
+ return result;
204
+ } catch (err) {
205
+ const result: TestResult = {
206
+ name,
207
+ passed: false,
208
+ duration: Date.now() - start,
209
+ error: err instanceof Error ? err.message : String(err),
210
+ };
211
+ this.results.push(result);
212
+ console.log(` ✗ ${name} (${result.duration}ms)`);
213
+ console.log(` Error: ${result.error}`);
214
+ return result;
215
+ }
216
+ }
217
+
218
+ async runAllTests(): Promise<void> {
219
+ console.log(
220
+ "\n═══════════════════════════════════════════════════════════",
221
+ );
222
+ console.log(" Browser Extension Test Suite");
223
+ console.log(
224
+ "═══════════════════════════════════════════════════════════\n",
225
+ );
226
+
227
+ // Basic eval tests
228
+ console.log("Basic JavaScript Evaluation:");
229
+
230
+ await this.runTest("Simple expression evaluation", async () => {
231
+ const response = await this.eval("1 + 1");
232
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
233
+ if (response.result !== 2) {
234
+ throw new Error(`Expected 2, got ${response.result}`);
235
+ }
236
+ });
237
+
238
+ await this.runTest("String expression", async () => {
239
+ const response = await this.eval('"hello" + " " + "world"');
240
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
241
+ if (response.result !== "hello world") {
242
+ throw new Error(`Expected 'hello world', got ${response.result}`);
243
+ }
244
+ });
245
+
246
+ await this.runTest("Array operations", async () => {
247
+ const response = await this.eval("[1, 2, 3].map(x => x * 2)");
248
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
249
+ const result = response.result as number[];
250
+ if (!Array.isArray(result) || result.join(",") !== "2,4,6") {
251
+ throw new Error(`Expected [2,4,6], got ${JSON.stringify(result)}`);
252
+ }
253
+ });
254
+
255
+ await this.runTest("Object creation", async () => {
256
+ const response = await this.eval('({ name: "test", value: 42 })');
257
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
258
+ const result = response.result as { name: string; value: number };
259
+ if (result.name !== "test" || result.value !== 42) {
260
+ throw new Error(`Unexpected object: ${JSON.stringify(result)}`);
261
+ }
262
+ });
263
+
264
+ // DOM access tests
265
+ console.log("\nDOM Access:");
266
+
267
+ await this.runTest("Get document title", async () => {
268
+ const response = await this.eval("document.title");
269
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
270
+ if (typeof response.result !== "string") {
271
+ throw new Error(`Expected string, got ${typeof response.result}`);
272
+ }
273
+ });
274
+
275
+ await this.runTest("Get current URL", async () => {
276
+ const response = await this.eval("window.location.href");
277
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
278
+ if (
279
+ typeof response.result !== "string" ||
280
+ !response.result.startsWith("http")
281
+ ) {
282
+ throw new Error(`Expected URL, got ${response.result}`);
283
+ }
284
+ });
285
+
286
+ await this.runTest("Query DOM elements", async () => {
287
+ const response = await this.eval(
288
+ "document.querySelectorAll('*').length > 0",
289
+ );
290
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
291
+ if (response.result !== true) {
292
+ throw new Error("Expected page to have DOM elements");
293
+ }
294
+ });
295
+
296
+ await this.runTest("Get body text content", async () => {
297
+ const response = await this.eval(
298
+ "document.body ? document.body.innerText.slice(0, 100) : 'no body'",
299
+ );
300
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
301
+ if (typeof response.result !== "string") {
302
+ throw new Error(`Expected string, got ${typeof response.result}`);
303
+ }
304
+ });
305
+
306
+ // Promise handling tests
307
+ console.log("\nPromise Handling:");
308
+
309
+ await this.runTest("Await simple promise", async () => {
310
+ const response = await this.eval(
311
+ "Promise.resolve(42)",
312
+ true, // awaitPromise
313
+ );
314
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
315
+ if (response.result !== 42) {
316
+ throw new Error(`Expected 42, got ${response.result}`);
317
+ }
318
+ });
319
+
320
+ await this.runTest("Await delayed promise", async () => {
321
+ const response = await this.eval(
322
+ "new Promise(r => setTimeout(() => r('delayed'), 100))",
323
+ true,
324
+ );
325
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
326
+ if (response.result !== "delayed") {
327
+ throw new Error(`Expected 'delayed', got ${response.result}`);
328
+ }
329
+ });
330
+
331
+ await this.runTest("Await fetch (if available)", async () => {
332
+ const response = await this.eval(
333
+ `typeof fetch === 'function' ?
334
+ fetch('https://httpbin.org/get').then(r => r.ok) :
335
+ true`,
336
+ true,
337
+ );
338
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
339
+ if (response.result !== true) {
340
+ throw new Error(`Expected true, got ${response.result}`);
341
+ }
342
+ });
343
+
344
+ // Error handling tests
345
+ console.log("\nError Handling:");
346
+
347
+ await this.runTest("Handle syntax error", async () => {
348
+ const response = await this.eval("function { invalid");
349
+ if (response.ok) {
350
+ throw new Error("Expected syntax error to fail");
351
+ }
352
+ if (!response.error) {
353
+ throw new Error("Expected error message");
354
+ }
355
+ });
356
+
357
+ await this.runTest("Handle runtime error", async () => {
358
+ const response = await this.eval("nonExistentVariable.property");
359
+ if (response.ok) {
360
+ throw new Error("Expected reference error to fail");
361
+ }
362
+ if (!response.error) {
363
+ throw new Error("Expected error message");
364
+ }
365
+ });
366
+
367
+ await this.runTest("Handle rejected promise", async () => {
368
+ const response = await this.eval(
369
+ "Promise.reject(new Error('test rejection'))",
370
+ true,
371
+ );
372
+ if (response.ok) {
373
+ throw new Error("Expected promise rejection to fail");
374
+ }
375
+ if (!response.error || !response.error.includes("test rejection")) {
376
+ throw new Error(`Expected rejection error, got: ${response.error}`);
377
+ }
378
+ });
379
+
380
+ // Complex operations
381
+ console.log("\nComplex Operations:");
382
+
383
+ await this.runTest("Get page metadata", async () => {
384
+ const response = await this.eval(`({
385
+ title: document.title,
386
+ url: window.location.href,
387
+ elementCount: document.querySelectorAll('*').length,
388
+ hasBody: !!document.body
389
+ })`);
390
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
391
+ const result = response.result as Record<string, unknown>;
392
+ if (!result.title || !result.url || !result.elementCount) {
393
+ throw new Error(`Incomplete metadata: ${JSON.stringify(result)}`);
394
+ }
395
+ });
396
+
397
+ await this.runTest("Find all links", async () => {
398
+ const response = await this.eval(
399
+ "Array.from(document.querySelectorAll('a')).slice(0, 10).map(a => ({href: a.href, text: a.textContent?.slice(0, 50)}))",
400
+ );
401
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
402
+ if (!Array.isArray(response.result)) {
403
+ throw new Error(`Expected array, got ${typeof response.result}`);
404
+ }
405
+ });
406
+
407
+ await this.runTest("Find interactive elements", async () => {
408
+ const response = await this.eval(`
409
+ Array.from(document.querySelectorAll('button, input, select, textarea, [role="button"]'))
410
+ .slice(0, 10)
411
+ .map(el => ({
412
+ tag: el.tagName,
413
+ type: el.getAttribute('type'),
414
+ name: el.getAttribute('name'),
415
+ id: el.id || null
416
+ }))
417
+ `);
418
+ if (!response.ok) throw new Error(`Eval failed: ${response.error}`);
419
+ if (!Array.isArray(response.result)) {
420
+ throw new Error(`Expected array, got ${typeof response.result}`);
421
+ }
422
+ });
423
+
424
+ // Print summary
425
+ this.printSummary();
426
+ }
427
+
428
+ printSummary(): void {
429
+ const passed = this.results.filter((r) => r.passed).length;
430
+ const failed = this.results.filter((r) => !r.passed).length;
431
+ const totalDuration = this.results.reduce((sum, r) => sum + r.duration, 0);
432
+
433
+ console.log(
434
+ "\n═══════════════════════════════════════════════════════════",
435
+ );
436
+ console.log(" Summary");
437
+ console.log("═══════════════════════════════════════════════════════════");
438
+ console.log(` Passed: ${passed}`);
439
+ console.log(` Failed: ${failed}`);
440
+ console.log(` Total: ${this.results.length}`);
441
+ console.log(` Duration: ${totalDuration}ms`);
442
+ console.log(
443
+ "═══════════════════════════════════════════════════════════\n",
444
+ );
445
+
446
+ if (failed > 0) {
447
+ console.log("Failed tests:");
448
+ for (const result of this.results.filter((r) => !r.passed)) {
449
+ console.log(` - ${result.name}: ${result.error}`);
450
+ }
451
+ console.log("");
452
+ }
453
+ }
454
+
455
+ getResults(): TestResult[] {
456
+ return this.results;
457
+ }
458
+ }
459
+
460
+ // ============================================================================
461
+ // MAIN
462
+ // ============================================================================
463
+
464
+ async function main() {
465
+ console.log("Browser Extension Test Harness");
466
+ console.log("==============================\n");
467
+
468
+ const harness = new ExtensionTestHarness(PORT);
469
+
470
+ try {
471
+ await harness.start();
472
+
473
+ console.log("Waiting for extension to connect...");
474
+ console.log("Make sure the extension is loaded in Chrome and");
475
+ console.log("at least one tab is open.\n");
476
+
477
+ if (INTERACTIVE) {
478
+ console.log("Interactive mode - server will stay running.");
479
+ console.log("Press Ctrl+C to stop.\n");
480
+ // Keep running
481
+ await new Promise(() => {});
482
+ } else {
483
+ await harness.waitForConnection(TIMEOUT);
484
+ await harness.runAllTests();
485
+
486
+ const results = harness.getResults();
487
+ const failed = results.filter((r) => !r.passed).length;
488
+
489
+ await harness.stop();
490
+ process.exit(failed > 0 ? 1 : 0);
491
+ }
492
+ } catch (err) {
493
+ console.error("\nError:", err instanceof Error ? err.message : String(err));
494
+ await harness.stop();
495
+ process.exit(1);
496
+ }
497
+ }
498
+
499
+ main();