@harness-fe/runtime 4.0.0-next.0 → 4.0.0-next.5

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.
package/dist/client.js CHANGED
@@ -123,7 +123,7 @@ export class RuntimeClient {
123
123
  this.recorder = new RrwebRecorder((chunk) => this.sendEvent(EVENT_NAME.RRWEB, chunk), { checkoutEveryNms: opts.rrwebCheckoutEveryNms });
124
124
  }
125
125
  start() {
126
- const daemonUrl = this.opts.mcpUrl ?? `ws://127.0.0.1:${DEFAULT_WS_PORT}`;
126
+ const daemonUrl = this.opts.mcpUrl ?? `ws://127.0.0.1:${DEFAULT_WS_PORT}/ws`;
127
127
  this.ctx.capture.install((name, payload) => this.sendEvent(name, payload), { daemonUrl });
128
128
  this.recorder.start();
129
129
  this.connect();
@@ -134,7 +134,7 @@ export class RuntimeClient {
134
134
  this.ws?.close();
135
135
  }
136
136
  connect() {
137
- const url = this.opts.mcpUrl ?? `ws://127.0.0.1:${DEFAULT_WS_PORT}`;
137
+ const url = this.opts.mcpUrl ?? `ws://127.0.0.1:${DEFAULT_WS_PORT}/ws`;
138
138
  try {
139
139
  this.ws = new WebSocket(url);
140
140
  }
@@ -1,15 +1,12 @@
1
1
  /**
2
- * Convert the runtime's `mcpUrl` (a WebSocket URL the plugin gave us) into
3
- * the dashboard URL the same daemon serves.
2
+ * Convert the runtime's `mcpUrl` (the gateway WebSocket URL the plugin gave us)
3
+ * into the console URL the same gateway serves: `<http>://<host>:<port>/console`,
4
+ * deep-linking to `/console/sessions/:id` when a sessionId is given.
4
5
  *
5
- * The daemon binds one HTTP+WS port; the dashboard lives at
6
- * `<http-scheme>://<host>:<port>/dashboard/`. The token, if any, is
7
- * carried in the query string so the browser is pre-authenticated on
8
- * first hit (after which mcp-server hands it off to a cookie — see
9
- * `packages/mcp-server/src/dashboardSpa.ts`).
10
- *
11
- * Optionally deep-links into a session's detail page when `sessionId` is
12
- * provided.
6
+ * This is a **pure shortcut** it carries NO token. Opening it is plain
7
+ * navigation; the console authorizes the viewer on its own (admin sign-in or a
8
+ * pasted read token). The runtime's token is write-only and must never be used
9
+ * as a console auth grant, so it's deliberately omitted.
13
10
  */
14
11
  export interface DashboardUrlInput {
15
12
  mcpUrl: string;
@@ -10,11 +10,7 @@ export function deriveDashboardUrl(input) {
10
10
  }
11
11
  const httpScheme = url.protocol === 'wss:' ? 'https:' : 'http:';
12
12
  const path = input.sessionId
13
- ? `/dashboard/sessions/${encodeURIComponent(input.sessionId)}`
14
- : '/dashboard/';
15
- const token = url.searchParams.get('token');
16
- const search = token ? `?token=${encodeURIComponent(token)}` : '';
17
- // Build manually so we don't leak any extra query/hash from the WS URL
18
- // (rare, but be defensive — the agent only ever sees what we hand it).
19
- return `${httpScheme}//${url.host}${path}${search}`;
13
+ ? `/console/sessions/${encodeURIComponent(input.sessionId)}`
14
+ : '/console';
15
+ return `${httpScheme}//${url.host}${path}`;
20
16
  }
package/dist/index.js CHANGED
@@ -7,8 +7,7 @@
7
7
  import { installOverlay } from './overlay.js';
8
8
  import { RuntimeClient, readInjectedConfig } from './client.js';
9
9
  import { registerOverlayPlugin, drainPluginQueue, } from './pluginRegistry.js';
10
- // Informational; keep in sync with package.json on release.
11
- const VERSION = '3.3.0';
10
+ import { VERSION } from './version.js';
12
11
  const w = window;
13
12
  if (typeof window !== 'undefined' && !w.__harness_fe_started__) {
14
13
  w.__harness_fe_started__ = true;
package/dist/overlay.js CHANGED
@@ -16,6 +16,7 @@
16
16
  import { EVENT_NAME, } from '@harness-fe/protocol';
17
17
  import { snapdom } from '@zumer/snapdom';
18
18
  import { deriveDashboardUrl } from './dashboardUrl.js';
19
+ import { VERSION } from './version.js';
19
20
  import { getCaptureStore } from './capture.js';
20
21
  import { collectPageLoadSnapshot } from './snapshot.js';
21
22
  import { getOverlayPlugins, subscribeOverlayPlugins, } from './pluginRegistry.js';
@@ -411,11 +412,13 @@ export function installOverlay(client) {
411
412
  // ─── Info card rendering ─────────────────────────────────────────────
412
413
  const renderInfo = () => {
413
414
  const proj = infoCard.querySelector('[data-role=project]');
415
+ const version = infoCard.querySelector('[data-role=version]');
414
416
  const build = infoCard.querySelector('[data-role=build]');
415
417
  const session = infoCard.querySelector('[data-role=session]');
416
418
  const tab = infoCard.querySelector('[data-role=tab]');
417
419
  const url = infoCard.querySelector('[data-role=url]');
418
420
  proj.textContent = client.displayName ?? client.projectId;
421
+ version.textContent = `v${VERSION}`;
419
422
  build.textContent = client.buildId ? abbr(client.buildId) : '—';
420
423
  build.title = client.buildId ?? 'No buildId — set HarnessScript buildId prop in prod';
421
424
  session.textContent = abbr(client.sessionId);
@@ -1969,6 +1972,7 @@ function buildInfoCard() {
1969
1972
  <button class="close-btn" data-role="close" title="Close (Esc)" type="button">×</button>
1970
1973
  </div>
1971
1974
  <div class="rows">
1975
+ <div class="row"><span class="key">version</span><span class="pill" data-role="version" title="harness runtime version"></span></div>
1972
1976
  <div class="row"><span class="key">build</span><span class="pill" data-role="build" title="Click to copy"></span></div>
1973
1977
  <div class="row"><span class="key">session</span><span class="pill" data-role="session" title="Click to copy"></span></div>
1974
1978
  <div class="row"><span class="key">tab</span><span class="pill" data-role="tab" title="Click to copy"></span></div>
@@ -0,0 +1 @@
1
+ export declare const VERSION = "4.0.0-next.5";
@@ -0,0 +1,3 @@
1
+ // AUTO-GENERATED by scripts/gen-version.mjs — do not edit by hand.
2
+ // Sourced from package.json at build time so the runtime reports its real version.
3
+ export const VERSION = '4.0.0-next.5';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-fe/runtime",
3
- "version": "4.0.0-next.0",
3
+ "version": "4.0.0-next.5",
4
4
  "description": "Browser-side SDK injected into the dev page. Connects to the MCP server via WebSocket and executes commands.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,21 +30,23 @@
30
30
  "dependencies": {
31
31
  "@zumer/snapdom": "^2.12.0",
32
32
  "rrweb": "2.0.0-alpha.4",
33
- "@harness-fe/protocol": "4.0.0-next.0",
33
+ "@harness-fe/protocol": "4.0.0-next.4",
34
34
  "@harness-fe/sandbox": "^3.2.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "happy-dom": "^20.9.0",
38
38
  "typescript": "^5.6.0",
39
- "vitest": "^2.1.9"
39
+ "vitest": "^2.1.9",
40
+ "@harness-fe/core": "4.0.0-next.5",
41
+ "@harness-fe/gateway": "4.0.0-next.5"
40
42
  },
41
43
  "publishConfig": {
42
44
  "access": "public"
43
45
  },
44
46
  "scripts": {
45
- "build": "tsc",
46
- "dev": "tsc --watch --preserveWatchOutput",
47
- "watch": "tsc --watch --preserveWatchOutput",
47
+ "build": "node scripts/gen-version.mjs && tsc",
48
+ "dev": "node scripts/gen-version.mjs && tsc --watch --preserveWatchOutput",
49
+ "watch": "node scripts/gen-version.mjs && tsc --watch --preserveWatchOutput",
48
50
  "typecheck": "tsc --noEmit",
49
51
  "test": "vitest run"
50
52
  }
package/src/client.ts CHANGED
@@ -190,7 +190,7 @@ export class RuntimeClient {
190
190
 
191
191
 
192
192
  start(): void {
193
- const daemonUrl = this.opts.mcpUrl ?? `ws://127.0.0.1:${DEFAULT_WS_PORT}`;
193
+ const daemonUrl = this.opts.mcpUrl ?? `ws://127.0.0.1:${DEFAULT_WS_PORT}/ws`;
194
194
  this.ctx.capture.install(
195
195
  (name, payload) => this.sendEvent(name, payload),
196
196
  { daemonUrl },
@@ -206,7 +206,7 @@ export class RuntimeClient {
206
206
  }
207
207
 
208
208
  private connect(): void {
209
- const url = this.opts.mcpUrl ?? `ws://127.0.0.1:${DEFAULT_WS_PORT}`;
209
+ const url = this.opts.mcpUrl ?? `ws://127.0.0.1:${DEFAULT_WS_PORT}/ws`;
210
210
  try {
211
211
  this.ws = new WebSocket(url);
212
212
  } catch (err) {
@@ -1,55 +1,36 @@
1
1
  import { describe, expect, it } from 'vitest';
2
2
  import { deriveDashboardUrl } from './dashboardUrl.js';
3
3
 
4
- describe('deriveDashboardUrl', () => {
5
- it('swaps ws:// to http:// and points at /dashboard/', () => {
6
- expect(deriveDashboardUrl({ mcpUrl: 'ws://127.0.0.1:47729' })).toBe(
7
- 'http://127.0.0.1:47729/dashboard/',
4
+ describe('deriveDashboardUrl (pure shortcut — no token)', () => {
5
+ it('swaps ws:// to http:// and points at /console', () => {
6
+ expect(deriveDashboardUrl({ mcpUrl: 'ws://127.0.0.1:47729/ws' })).toBe(
7
+ 'http://127.0.0.1:47729/console',
8
8
  );
9
9
  });
10
10
 
11
11
  it('swaps wss:// to https:// (production / LAN with TLS)', () => {
12
- expect(deriveDashboardUrl({ mcpUrl: 'wss://harness.lan:47729' })).toBe(
13
- 'https://harness.lan:47729/dashboard/',
12
+ expect(deriveDashboardUrl({ mcpUrl: 'wss://harness.lan:47729/ws' })).toBe(
13
+ 'https://harness.lan:47729/console',
14
14
  );
15
15
  });
16
16
 
17
- it('carries the token query through verbatim', () => {
17
+ it('deep-links to /console/sessions/:id when sessionId is provided', () => {
18
18
  expect(
19
- deriveDashboardUrl({ mcpUrl: 'ws://127.0.0.1:47729?token=abc' }),
20
- ).toBe('http://127.0.0.1:47729/dashboard/?token=abc');
21
- });
22
-
23
- it('URL-encodes the token (defensive against weird HARNESS_FE_TOKEN values)', () => {
24
- expect(
25
- deriveDashboardUrl({ mcpUrl: 'ws://127.0.0.1:47729?token=a%20b%26c' }),
26
- ).toBe('http://127.0.0.1:47729/dashboard/?token=a%20b%26c');
27
- });
28
-
29
- it('deep-links to /dashboard/sessions/:id when sessionId is provided', () => {
30
- expect(
31
- deriveDashboardUrl({
32
- mcpUrl: 'ws://127.0.0.1:47729?token=abc',
33
- sessionId: 'sess-1',
34
- }),
35
- ).toBe('http://127.0.0.1:47729/dashboard/sessions/sess-1?token=abc');
19
+ deriveDashboardUrl({ mcpUrl: 'ws://127.0.0.1:47729/ws?token=abc', sessionId: 'sess-1' }),
20
+ ).toBe('http://127.0.0.1:47729/console/sessions/sess-1');
36
21
  });
37
22
 
38
23
  it('URL-encodes the session id', () => {
39
24
  expect(
40
- deriveDashboardUrl({
41
- mcpUrl: 'ws://127.0.0.1:47729',
42
- sessionId: 'a/b c',
43
- }),
44
- ).toBe('http://127.0.0.1:47729/dashboard/sessions/a%2Fb%20c');
25
+ deriveDashboardUrl({ mcpUrl: 'ws://127.0.0.1:47729/ws', sessionId: 'a/b c' }),
26
+ ).toBe('http://127.0.0.1:47729/console/sessions/a%2Fb%20c');
45
27
  });
46
28
 
47
- it('strips other query/hash from the WS URLonly token is forwarded', () => {
48
- expect(
49
- deriveDashboardUrl({
50
- mcpUrl: 'ws://127.0.0.1:47729/?token=abc&other=secret#hash',
51
- }),
52
- ).toBe('http://127.0.0.1:47729/dashboard/?token=abc');
29
+ it('never carries the runtime tokenit is a navigation shortcut, not an auth grant', () => {
30
+ const url = deriveDashboardUrl({ mcpUrl: 'ws://127.0.0.1:47729/ws?token=secret&other=x#h', sessionId: 'sess-1' });
31
+ expect(url).toBe('http://127.0.0.1:47729/console/sessions/sess-1');
32
+ expect(url).not.toContain('token');
33
+ expect(url).not.toContain('secret');
53
34
  });
54
35
 
55
36
  it('returns undefined for empty or invalid input', () => {
@@ -1,15 +1,12 @@
1
1
  /**
2
- * Convert the runtime's `mcpUrl` (a WebSocket URL the plugin gave us) into
3
- * the dashboard URL the same daemon serves.
2
+ * Convert the runtime's `mcpUrl` (the gateway WebSocket URL the plugin gave us)
3
+ * into the console URL the same gateway serves: `<http>://<host>:<port>/console`,
4
+ * deep-linking to `/console/sessions/:id` when a sessionId is given.
4
5
  *
5
- * The daemon binds one HTTP+WS port; the dashboard lives at
6
- * `<http-scheme>://<host>:<port>/dashboard/`. The token, if any, is
7
- * carried in the query string so the browser is pre-authenticated on
8
- * first hit (after which mcp-server hands it off to a cookie — see
9
- * `packages/mcp-server/src/dashboardSpa.ts`).
10
- *
11
- * Optionally deep-links into a session's detail page when `sessionId` is
12
- * provided.
6
+ * This is a **pure shortcut** it carries NO token. Opening it is plain
7
+ * navigation; the console authorizes the viewer on its own (admin sign-in or a
8
+ * pasted read token). The runtime's token is write-only and must never be used
9
+ * as a console auth grant, so it's deliberately omitted.
13
10
  */
14
11
  export interface DashboardUrlInput {
15
12
  mcpUrl: string;
@@ -26,11 +23,7 @@ export function deriveDashboardUrl(input: DashboardUrlInput): string | undefined
26
23
  }
27
24
  const httpScheme = url.protocol === 'wss:' ? 'https:' : 'http:';
28
25
  const path = input.sessionId
29
- ? `/dashboard/sessions/${encodeURIComponent(input.sessionId)}`
30
- : '/dashboard/';
31
- const token = url.searchParams.get('token');
32
- const search = token ? `?token=${encodeURIComponent(token)}` : '';
33
- // Build manually so we don't leak any extra query/hash from the WS URL
34
- // (rare, but be defensive — the agent only ever sees what we hand it).
35
- return `${httpScheme}//${url.host}${path}${search}`;
26
+ ? `/console/sessions/${encodeURIComponent(input.sessionId)}`
27
+ : '/console';
28
+ return `${httpScheme}//${url.host}${path}`;
36
29
  }
package/src/index.ts CHANGED
@@ -12,9 +12,7 @@ import {
12
12
  drainPluginQueue,
13
13
  type OverlayPlugin,
14
14
  } from './pluginRegistry.js';
15
-
16
- // Informational; keep in sync with package.json on release.
17
- const VERSION = '3.3.0';
15
+ import { VERSION } from './version.js';
18
16
 
19
17
  const w = window as unknown as {
20
18
  __harness_fe_started__?: boolean;
@@ -58,6 +58,8 @@ describe('installOverlay', () => {
58
58
  const card = root.querySelector('.info-card') as HTMLElement;
59
59
  expect(card.style.display).toBe('flex');
60
60
  expect(root.querySelector('[data-role=project]')!.textContent).toBe('Demo App');
61
+ // Runtime version surfaced in the card (real value from version.ts).
62
+ expect(root.querySelector('[data-role=version]')!.textContent).toMatch(/^v\d/);
61
63
  // Abbreviated to 8 chars
62
64
  expect(root.querySelector('[data-role=build]')!.textContent).toBe('build-12');
63
65
  expect(root.querySelector('[data-role=session]')!.textContent).toBe('sess-123');
@@ -218,12 +220,13 @@ describe('installOverlay', () => {
218
220
 
219
221
  it('shows the "Open dashboard" button only when the client has an mcpUrl', () => {
220
222
  setupDom();
221
- installOverlay(makeFakeClient({ mcpUrl: 'ws://127.0.0.1:47729?token=demo' }));
223
+ installOverlay(makeFakeClient({ mcpUrl: 'ws://127.0.0.1:47729/ws?token=demo' }));
222
224
  const root = document.getElementById('__harness_fe_overlay__')!.shadowRoot!;
223
225
  const btn = root.querySelector('[data-role=open-dashboard]') as HTMLButtonElement;
224
226
  expect(btn.style.display).toBe('');
225
- expect(btn.title).toContain('http://127.0.0.1:47729/dashboard/sessions/');
226
- expect(btn.title).toContain('token=demo');
227
+ expect(btn.title).toContain('http://127.0.0.1:47729/console/sessions/');
228
+ // The overlay link is a pure shortcut — it must NOT carry the runtime token.
229
+ expect(btn.title).not.toContain('token=');
227
230
  });
228
231
 
229
232
  it('hides the "Open dashboard" button when mcpUrl is missing', () => {
@@ -236,7 +239,7 @@ describe('installOverlay', () => {
236
239
 
237
240
  it('clicking "Open dashboard" calls window.open with the derived URL in a new tab', () => {
238
241
  setupDom();
239
- installOverlay(makeFakeClient({ mcpUrl: 'wss://harness.lan:8443?token=t' }));
242
+ installOverlay(makeFakeClient({ mcpUrl: 'wss://harness.lan:8443/ws?token=t' }));
240
243
  const root = document.getElementById('__harness_fe_overlay__')!.shadowRoot!;
241
244
  const calls: Array<{ url: string; target: string; features: string }> = [];
242
245
  (globalThis.window as unknown as { open: typeof window.open }).open = ((
@@ -250,7 +253,7 @@ describe('installOverlay', () => {
250
253
  const btn = root.querySelector('[data-role=open-dashboard]') as HTMLButtonElement;
251
254
  btn.click();
252
255
  expect(calls).toHaveLength(1);
253
- expect(calls[0].url).toBe('https://harness.lan:8443/dashboard/sessions/sess-12345-abcdef-9876?token=t');
256
+ expect(calls[0].url).toBe('https://harness.lan:8443/console/sessions/sess-12345-abcdef-9876');
254
257
  expect(calls[0].target).toBe('_blank');
255
258
  expect(calls[0].features).toMatch(/noopener/);
256
259
  });
package/src/overlay.ts CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  } from '@harness-fe/protocol';
25
25
  import { snapdom } from '@zumer/snapdom';
26
26
  import { deriveDashboardUrl } from './dashboardUrl.js';
27
+ import { VERSION } from './version.js';
27
28
  import { getCaptureStore } from './capture.js';
28
29
  import { collectPageLoadSnapshot } from './snapshot.js';
29
30
  import {
@@ -482,11 +483,13 @@ export function installOverlay(client: OverlayClient): void {
482
483
  // ─── Info card rendering ─────────────────────────────────────────────
483
484
  const renderInfo = () => {
484
485
  const proj = infoCard.querySelector<HTMLElement>('[data-role=project]')!;
486
+ const version = infoCard.querySelector<HTMLElement>('[data-role=version]')!;
485
487
  const build = infoCard.querySelector<HTMLElement>('[data-role=build]')!;
486
488
  const session = infoCard.querySelector<HTMLElement>('[data-role=session]')!;
487
489
  const tab = infoCard.querySelector<HTMLElement>('[data-role=tab]')!;
488
490
  const url = infoCard.querySelector<HTMLElement>('[data-role=url]')!;
489
491
  proj.textContent = client.displayName ?? client.projectId;
492
+ version.textContent = `v${VERSION}`;
490
493
  build.textContent = client.buildId ? abbr(client.buildId) : '—';
491
494
  build.title = client.buildId ?? 'No buildId — set HarnessScript buildId prop in prod';
492
495
  session.textContent = abbr(client.sessionId);
@@ -2113,6 +2116,7 @@ function buildInfoCard(): HTMLDivElement {
2113
2116
  <button class="close-btn" data-role="close" title="Close (Esc)" type="button">×</button>
2114
2117
  </div>
2115
2118
  <div class="rows">
2119
+ <div class="row"><span class="key">version</span><span class="pill" data-role="version" title="harness runtime version"></span></div>
2116
2120
  <div class="row"><span class="key">build</span><span class="pill" data-role="build" title="Click to copy"></span></div>
2117
2121
  <div class="row"><span class="key">session</span><span class="pill" data-role="session" title="Click to copy"></span></div>
2118
2122
  <div class="row"><span class="key">tab</span><span class="pill" data-role="tab" title="Click to copy"></span></div>
@@ -24,15 +24,15 @@ vi.mock('rrweb', () => ({
24
24
  import { mkdtempSync, rmSync } from 'node:fs';
25
25
  import { tmpdir } from 'node:os';
26
26
  import { join } from 'node:path';
27
- import { Bridge } from '../../mcp-server/src/bridge.js';
28
- import { JsonlStore } from '../../mcp-server/src/store/index.js';
27
+ import { InProcessCoreClient, JsonlStore, type StoreEvent } from '@harness-fe/core';
28
+ import { createGateway, Policy, type GatewayHandle } from '@harness-fe/gateway';
29
29
  import { RuntimeClient } from './client.js';
30
30
  import { getCaptureStore } from './capture.js';
31
- import type { StoreEvent } from '../../mcp-server/src/store/index.js';
32
31
  import type { NetworkEntry, StorageEntry, WsEntry } from '@harness-fe/protocol';
33
32
 
34
33
  interface Env {
35
- bridge: Bridge;
34
+ core: InProcessCoreClient;
35
+ gw: GatewayHandle;
36
36
  store: JsonlStore;
37
37
  dir: string;
38
38
  port: number;
@@ -59,9 +59,12 @@ async function rmDirWithRetry(dir: string, attempts = 5): Promise<void> {
59
59
  async function setup(): Promise<Env> {
60
60
  const dir = mkdtempSync(join(tmpdir(), 'harness-rt-e2e-'));
61
61
  const store = new JsonlStore(dir);
62
- const bridge = new Bridge({ port: 0, host: '127.0.0.1', store, taskStore: null, autoPurge: { enabled: false } });
63
- await bridge.start();
64
- const port = bridge.getBoundPort();
62
+ // New architecture: an in-process core behind the gateway front door; the
63
+ // runtime connects to the gateway's /ws (Open policy → local principal).
64
+ const core = new InProcessCoreClient({ store, taskStore: null, autoPurge: { enabled: false } });
65
+ await core.start();
66
+ const gw = createGateway({ coreClient: core, policy: new Policy({ mode: 'open' }) });
67
+ const port = await gw.listen(0, '127.0.0.1');
65
68
  if (!port) throw new Error('no port');
66
69
 
67
70
  // happy-dom keeps singletons across tests — reset the patch state so this
@@ -70,7 +73,7 @@ async function setup(): Promise<Env> {
70
73
 
71
74
  const client = new RuntimeClient({
72
75
  projectId: 'rt-e2e',
73
- mcpUrl: `ws://127.0.0.1:${port}`,
76
+ mcpUrl: `ws://127.0.0.1:${port}/ws`,
74
77
  });
75
78
  client.start();
76
79
 
@@ -88,7 +91,7 @@ async function setup(): Promise<Env> {
88
91
  throw new Error('runtime-client never connected');
89
92
  }
90
93
 
91
- return { bridge, store, dir, port, client, sessionId: client.sessionId };
94
+ return { core, gw, store, dir, port, client, sessionId: client.sessionId };
92
95
  }
93
96
 
94
97
  beforeEach(async () => {
@@ -98,7 +101,8 @@ beforeEach(async () => {
98
101
  afterEach(async () => {
99
102
  if (!env) return;
100
103
  env.client.stop();
101
- await env.bridge.stop();
104
+ await env.gw.close();
105
+ await env.core.stop();
102
106
  // close() drains the async write queue — must await, else rmSync races
103
107
  // file writes and the dir-recursive-rm trips ENOTEMPTY on Linux CI.
104
108
  await env.store.close();
@@ -186,7 +190,7 @@ describe('RuntimeClient E2E — patched WebSocket flows to bridge', () => {
186
190
  (window as unknown as { WebSocket: typeof WebSocket }).WebSocket = FakeWS as unknown as typeof WebSocket;
187
191
  cap.install(
188
192
  (name, payload) => e.client.sendEvent(name, payload),
189
- { daemonUrl: `ws://127.0.0.1:${e.port}` },
193
+ { daemonUrl: `ws://127.0.0.1:${e.port}/ws` },
190
194
  );
191
195
 
192
196
  try {
@@ -245,7 +249,7 @@ describe('RuntimeClient E2E — patched fetch initiator round-trip', () => {
245
249
  cap.dispose();
246
250
  cap.install(
247
251
  (name, payload) => e.client.sendEvent(name, payload),
248
- { daemonUrl: `ws://127.0.0.1:${e.port}` },
252
+ { daemonUrl: `ws://127.0.0.1:${e.port}/ws` },
249
253
  );
250
254
 
251
255
  try {
package/src/version.ts ADDED
@@ -0,0 +1,3 @@
1
+ // AUTO-GENERATED by scripts/gen-version.mjs — do not edit by hand.
2
+ // Sourced from package.json at build time so the runtime reports its real version.
3
+ export const VERSION = '4.0.0-next.5';