@jobshimo/browser-link 0.2.0 → 0.4.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 (60) hide show
  1. package/README.md +37 -0
  2. package/dist/bridge/client.d.ts +101 -0
  3. package/dist/bridge/client.js +435 -0
  4. package/dist/bridge/client.js.map +1 -0
  5. package/dist/bridge/dispatch.d.ts +29 -0
  6. package/dist/bridge/dispatch.js +39 -0
  7. package/dist/bridge/dispatch.js.map +1 -0
  8. package/dist/bridge/events.d.ts +39 -0
  9. package/dist/bridge/events.js +47 -0
  10. package/dist/bridge/events.js.map +1 -0
  11. package/dist/bridge/protocol.d.ts +80 -0
  12. package/dist/bridge/protocol.js +79 -0
  13. package/dist/bridge/protocol.js.map +1 -0
  14. package/dist/bridge/server.d.ts +42 -0
  15. package/dist/bridge/server.js +336 -0
  16. package/dist/bridge/server.js.map +1 -0
  17. package/dist/bridge/token.d.ts +17 -0
  18. package/dist/bridge/token.js +79 -0
  19. package/dist/bridge/token.js.map +1 -0
  20. package/dist/cli.js +94 -15
  21. package/dist/cli.js.map +1 -1
  22. package/dist/commands/doctor.d.ts +12 -1
  23. package/dist/commands/doctor.js +90 -20
  24. package/dist/commands/doctor.js.map +1 -1
  25. package/dist/commands/extension.d.ts +3 -2
  26. package/dist/commands/extension.js +53 -28
  27. package/dist/commands/extension.js.map +1 -1
  28. package/dist/commands/multi-agent.d.ts +7 -0
  29. package/dist/commands/multi-agent.js +109 -0
  30. package/dist/commands/multi-agent.js.map +1 -0
  31. package/dist/commands/tools.d.ts +11 -0
  32. package/dist/commands/tools.js +168 -0
  33. package/dist/commands/tools.js.map +1 -0
  34. package/dist/commands/updates.d.ts +2 -1
  35. package/dist/commands/updates.js +24 -7
  36. package/dist/commands/updates.js.map +1 -1
  37. package/dist/config.d.ts +25 -3
  38. package/dist/config.js +35 -2
  39. package/dist/config.js.map +1 -1
  40. package/dist/messages.d.ts +7 -0
  41. package/dist/permissions.d.ts +37 -0
  42. package/dist/permissions.js +156 -0
  43. package/dist/permissions.js.map +1 -0
  44. package/dist/server.d.ts +7 -3
  45. package/dist/server.js +160 -32
  46. package/dist/server.js.map +1 -1
  47. package/dist/tools/browser-definitions.js +18 -0
  48. package/dist/tools/browser-definitions.js.map +1 -1
  49. package/dist/tools/browser-dispatch.d.ts +7 -0
  50. package/dist/tools/browser-dispatch.js +11 -0
  51. package/dist/tools/browser-dispatch.js.map +1 -1
  52. package/dist/tools/server-instructions.d.ts +1 -1
  53. package/dist/tools/server-instructions.js +4 -0
  54. package/dist/tools/server-instructions.js.map +1 -1
  55. package/dist/ui/app.js +16 -1
  56. package/dist/ui/app.js.map +1 -1
  57. package/dist/ui/screens.d.ts +14 -1
  58. package/dist/ui/screens.js +314 -2
  59. package/dist/ui/screens.js.map +1 -1
  60. package/package.json +4 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../../src/bridge/dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjF,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EACL,iBAAiB,EACjB,aAAa,GAEd,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAiBhE,MAAM,UAAU,eAAe,CAAC,IAAkB;IAChD,OAAO;QACL,KAAK,EAAE,CAAC,GAAG,wBAAwB,EAAE,GAAG,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACzE,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,CAC1C;KACF,CAAC;AACJ,CAAC;AAOD,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAoB,EAAE,IAAkB;IAC3E,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC;IACtC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QAC7C,uEAAuE;QACvE,qEAAqE;QACrE,OAAO,SAAS,CACd,SAAS,IAAI,6CAA6C;YACxD,8EAA8E,IAAI,KAAK,CAC1F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,IAAI,SAAS,CAAC,IAAI,CAAC;YAAE,OAAO,YAAY,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;QACpE,IAAI,aAAa,CAAC,IAAI,CAAC;YACrB,OAAO,YAAY,CAAC,MAAM,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;QAC9E,OAAO,SAAS,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,SAAS,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE,CAAC;AACH,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * In-memory ring buffer of bridge lifecycle events. Exposed to the agent
3
+ * through the `browser.events` MCP tool so it can self-recover after a
4
+ * primary swap or tab id rename: instead of failing blind on a stale
5
+ * tab_id, the agent calls browser.events, sees a `tab-renamed` entry,
6
+ * and resumes work on the new id.
7
+ *
8
+ * The buffer is per-process. When the primary dies and a new primary
9
+ * takes over, the new primary starts with an empty buffer plus its own
10
+ * `primary-elected` event as the fundamental marker — older history is
11
+ * intentionally lost (the agent has nothing useful to do with it anyway).
12
+ */
13
+ export type BridgeEventKind = 'primary-elected' | 'tab-registered' | 'tab-disconnected' | 'tab-renamed';
14
+ export interface BridgeEvent {
15
+ /** Monotonic id assigned by addEvent. */
16
+ id: number;
17
+ /** ISO-8601 timestamp. */
18
+ at: string;
19
+ kind: BridgeEventKind;
20
+ /** Kind-specific payload. Documented per-kind in the JSDoc below. */
21
+ data: Record<string, unknown>;
22
+ }
23
+ export declare class BridgeEventLog {
24
+ private buffer;
25
+ private nextId;
26
+ /** Append an event and return it. Drops the oldest if the buffer is full. */
27
+ add(kind: BridgeEventKind, data?: Record<string, unknown>): BridgeEvent;
28
+ /** Return up to `limit` recent events, optionally filtered to those
29
+ * with id > sinceId. Cursor pattern — pass the previous-call's last id
30
+ * back in as sinceId to get only what's new. */
31
+ recent(opts?: {
32
+ sinceId?: number;
33
+ limit?: number;
34
+ }): BridgeEvent[];
35
+ /** The highest id currently in the buffer (0 if empty). */
36
+ latestId(): number;
37
+ /** Total number of events in the buffer. */
38
+ size(): number;
39
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * In-memory ring buffer of bridge lifecycle events. Exposed to the agent
3
+ * through the `browser.events` MCP tool so it can self-recover after a
4
+ * primary swap or tab id rename: instead of failing blind on a stale
5
+ * tab_id, the agent calls browser.events, sees a `tab-renamed` entry,
6
+ * and resumes work on the new id.
7
+ *
8
+ * The buffer is per-process. When the primary dies and a new primary
9
+ * takes over, the new primary starts with an empty buffer plus its own
10
+ * `primary-elected` event as the fundamental marker — older history is
11
+ * intentionally lost (the agent has nothing useful to do with it anyway).
12
+ */
13
+ const MAX_EVENTS = 200;
14
+ export class BridgeEventLog {
15
+ buffer = [];
16
+ nextId = 1;
17
+ /** Append an event and return it. Drops the oldest if the buffer is full. */
18
+ add(kind, data = {}) {
19
+ const event = {
20
+ id: this.nextId++,
21
+ at: new Date().toISOString(),
22
+ kind,
23
+ data,
24
+ };
25
+ this.buffer.push(event);
26
+ if (this.buffer.length > MAX_EVENTS)
27
+ this.buffer.shift();
28
+ return event;
29
+ }
30
+ /** Return up to `limit` recent events, optionally filtered to those
31
+ * with id > sinceId. Cursor pattern — pass the previous-call's last id
32
+ * back in as sinceId to get only what's new. */
33
+ recent(opts = {}) {
34
+ const limit = Math.max(1, Math.min(opts.limit ?? 20, MAX_EVENTS));
35
+ const filtered = opts.sinceId === undefined ? this.buffer : this.buffer.filter((e) => e.id > opts.sinceId);
36
+ return filtered.slice(-limit);
37
+ }
38
+ /** The highest id currently in the buffer (0 if empty). */
39
+ latestId() {
40
+ return this.buffer.length === 0 ? 0 : this.buffer[this.buffer.length - 1].id;
41
+ }
42
+ /** Total number of events in the buffer. */
43
+ size() {
44
+ return this.buffer.length;
45
+ }
46
+ }
47
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/bridge/events.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkBH,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB,MAAM,OAAO,cAAc;IACjB,MAAM,GAAkB,EAAE,CAAC;IAC3B,MAAM,GAAG,CAAC,CAAC;IAEnB,6EAA6E;IAC7E,GAAG,CAAC,IAAqB,EAAE,OAAgC,EAAE;QAC3D,MAAM,KAAK,GAAgB;YACzB,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE;YACjB,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YAC5B,IAAI;YACJ,IAAI;SACL,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,UAAU;YAAE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACzD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;oDAEgD;IAChD,MAAM,CAAC,OAA6C,EAAE;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC;QAClE,MAAM,QAAQ,GACZ,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,OAAQ,CAAC,CAAC;QAC7F,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC;IAChC,CAAC;IAED,2DAA2D;IAC3D,QAAQ;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,EAAE,CAAC;IAChF,CAAC;IAED,4CAA4C;IAC5C,IAAI;QACF,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IAC5B,CAAC;CACF"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * IPC protocol between browser-link primary and proxy instances.
3
+ *
4
+ * Wire format: newline-delimited JSON (NDJSON). Each line is one frame.
5
+ * The protocol version is bumped on any non-backwards-compatible change
6
+ * to frame shapes. A primary running version N rejects proxies that
7
+ * don't advertise the same N — they fall back to a clear error.
8
+ *
9
+ * This module is pure data + helpers; no I/O.
10
+ */
11
+ export declare const IPC_HOST = "127.0.0.1";
12
+ export declare const IPC_PORT = 17530;
13
+ export declare const IPC_PROTOCOL_VERSION = "1";
14
+ /** Pings cadence. If a side doesn't reply within IPC_PONG_TIMEOUT_MS,
15
+ * the connection is considered dead. */
16
+ export declare const IPC_PING_INTERVAL_MS = 5000;
17
+ export declare const IPC_PONG_TIMEOUT_MS = 10000;
18
+ /** Initial handshake — proxy → primary. */
19
+ export interface HelloFrame {
20
+ kind: 'hello';
21
+ /** Protocol version the proxy speaks. Must match the primary's version. */
22
+ version: string;
23
+ /** Per-session token the proxy read from the data dir. */
24
+ token: string;
25
+ }
26
+ /** Successful handshake — primary → proxy. */
27
+ export interface HelloAckFrame {
28
+ kind: 'hello-ack';
29
+ /** Protocol version (always === IPC_PROTOCOL_VERSION). */
30
+ version: string;
31
+ /** Server-assigned id for this proxy session — used for logging. */
32
+ sessionId: string;
33
+ }
34
+ /** Rejected handshake — primary → proxy. The proxy closes after seeing this. */
35
+ export interface HelloRejectFrame {
36
+ kind: 'hello-reject';
37
+ reason: string;
38
+ }
39
+ /** Forwarded MCP request — proxy → primary. */
40
+ export interface McpRequestFrame {
41
+ kind: 'mcp.request';
42
+ /** Correlation id; the primary echoes it on the response. */
43
+ requestId: number;
44
+ /** Raw JSON-RPC 2.0 request frame from the proxy's stdin. */
45
+ payload: unknown;
46
+ }
47
+ /** Forwarded MCP response — primary → proxy. */
48
+ export interface McpResponseFrame {
49
+ kind: 'mcp.response';
50
+ requestId: number;
51
+ /** Raw JSON-RPC 2.0 response frame to be written to the proxy's stdout. */
52
+ payload: unknown;
53
+ }
54
+ /** Forwarded MCP notification — both directions. No response expected. */
55
+ export interface McpNotificationFrame {
56
+ kind: 'mcp.notification';
57
+ /** Raw JSON-RPC 2.0 notification frame (no id). */
58
+ payload: unknown;
59
+ }
60
+ /** Keep-alive frames. */
61
+ export interface PingFrame {
62
+ kind: 'ping';
63
+ }
64
+ export interface PongFrame {
65
+ kind: 'pong';
66
+ }
67
+ /** Broadcast from primary just before it closes its IPC server. Proxies
68
+ * use this to trigger the autoReelect race. */
69
+ export interface PrimaryClosingFrame {
70
+ kind: 'primary-closing';
71
+ reason?: string;
72
+ }
73
+ export type Frame = HelloFrame | HelloAckFrame | HelloRejectFrame | McpRequestFrame | McpResponseFrame | McpNotificationFrame | PingFrame | PongFrame | PrimaryClosingFrame;
74
+ /** Serialise one frame to its on-the-wire representation (a single NDJSON line). */
75
+ export declare function encodeFrame(frame: Frame): string;
76
+ /** Parse one NDJSON line into a frame. Returns null when the input is
77
+ * not a recognised frame (defensive — never throw on malformed data). */
78
+ export declare function parseFrame(line: string): Frame | null;
79
+ /** True when a peer's announced protocol version is compatible with ours. */
80
+ export declare function isCompatibleVersion(peerVersion: string): boolean;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * IPC protocol between browser-link primary and proxy instances.
3
+ *
4
+ * Wire format: newline-delimited JSON (NDJSON). Each line is one frame.
5
+ * The protocol version is bumped on any non-backwards-compatible change
6
+ * to frame shapes. A primary running version N rejects proxies that
7
+ * don't advertise the same N — they fall back to a clear error.
8
+ *
9
+ * This module is pure data + helpers; no I/O.
10
+ */
11
+ export const IPC_HOST = '127.0.0.1';
12
+ export const IPC_PORT = 17530;
13
+ export const IPC_PROTOCOL_VERSION = '1';
14
+ /** Pings cadence. If a side doesn't reply within IPC_PONG_TIMEOUT_MS,
15
+ * the connection is considered dead. */
16
+ export const IPC_PING_INTERVAL_MS = 5_000;
17
+ export const IPC_PONG_TIMEOUT_MS = 10_000;
18
+ /** Serialise one frame to its on-the-wire representation (a single NDJSON line). */
19
+ export function encodeFrame(frame) {
20
+ return JSON.stringify(frame) + '\n';
21
+ }
22
+ /** Parse one NDJSON line into a frame. Returns null when the input is
23
+ * not a recognised frame (defensive — never throw on malformed data). */
24
+ export function parseFrame(line) {
25
+ let obj;
26
+ try {
27
+ obj = JSON.parse(line);
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj))
33
+ return null;
34
+ const o = obj;
35
+ if (typeof o.kind !== 'string')
36
+ return null;
37
+ switch (o.kind) {
38
+ case 'hello':
39
+ if (typeof o.version === 'string' && typeof o.token === 'string')
40
+ return o;
41
+ return null;
42
+ case 'hello-ack':
43
+ if (typeof o.version === 'string' && typeof o.sessionId === 'string')
44
+ return o;
45
+ return null;
46
+ case 'hello-reject':
47
+ if (typeof o.reason === 'string')
48
+ return o;
49
+ return null;
50
+ case 'mcp.request':
51
+ if (typeof o.requestId === 'number' && 'payload' in o)
52
+ return o;
53
+ return null;
54
+ case 'mcp.response':
55
+ if (typeof o.requestId === 'number' && 'payload' in o)
56
+ return o;
57
+ return null;
58
+ case 'mcp.notification':
59
+ if ('payload' in o)
60
+ return o;
61
+ return null;
62
+ case 'ping':
63
+ return { kind: 'ping' };
64
+ case 'pong':
65
+ return { kind: 'pong' };
66
+ case 'primary-closing':
67
+ return {
68
+ kind: 'primary-closing',
69
+ reason: typeof o.reason === 'string' ? o.reason : undefined,
70
+ };
71
+ default:
72
+ return null;
73
+ }
74
+ }
75
+ /** True when a peer's announced protocol version is compatible with ours. */
76
+ export function isCompatibleVersion(peerVersion) {
77
+ return peerVersion === IPC_PROTOCOL_VERSION;
78
+ }
79
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/bridge/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,QAAQ,GAAG,WAAW,CAAC;AACpC,MAAM,CAAC,MAAM,QAAQ,GAAG,KAAK,CAAC;AAC9B,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAExC;wCACwC;AACxC,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAC1C,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AA4E1C,oFAAoF;AACpF,MAAM,UAAU,WAAW,CAAC,KAAY;IACtC,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;AACtC,CAAC;AAED;yEACyE;AACzE,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvE,MAAM,CAAC,GAAG,GAA8B,CAAC;IACzC,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC5C,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QACf,KAAK,OAAO;YACV,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;gBAC9D,OAAO,CAA0B,CAAC;YACpC,OAAO,IAAI,CAAC;QACd,KAAK,WAAW;YACd,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ;gBAClE,OAAO,CAA6B,CAAC;YACvC,OAAO,IAAI,CAAC;QACd,KAAK,cAAc;YACjB,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;gBAAE,OAAO,CAAgC,CAAC;YAC1E,OAAO,IAAI,CAAC;QACd,KAAK,aAAa;YAChB,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,SAAS,IAAI,CAAC;gBAAE,OAAO,CAA+B,CAAC;YAC9F,OAAO,IAAI,CAAC;QACd,KAAK,cAAc;YACjB,IAAI,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,IAAI,SAAS,IAAI,CAAC;gBACnD,OAAO,CAAgC,CAAC;YAC1C,OAAO,IAAI,CAAC;QACd,KAAK,kBAAkB;YACrB,IAAI,SAAS,IAAI,CAAC;gBAAE,OAAO,CAAoC,CAAC;YAChE,OAAO,IAAI,CAAC;QACd,KAAK,MAAM;YACT,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,KAAK,MAAM;YACT,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAC1B,KAAK,iBAAiB;YACpB,OAAO;gBACL,IAAI,EAAE,iBAAiB;gBACvB,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;aAC5D,CAAC;QACJ;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,mBAAmB,CAAC,WAAmB;IACrD,OAAO,WAAW,KAAK,oBAAoB,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { type DispatchDeps } from './dispatch.js';
2
+ export interface IpcServerOptions {
3
+ /** Override the bind host. Default 127.0.0.1. Production callers should
4
+ * never set this — tests use it to dodge cross-suite port collisions. */
5
+ host?: string;
6
+ /** Override the bind port. Default 17530. Pass 0 to let the OS pick a
7
+ * free port; the chosen port is exposed via boundPort() after start(). */
8
+ port?: number;
9
+ }
10
+ export declare class IpcServer {
11
+ private deps;
12
+ private options;
13
+ private server;
14
+ private sessions;
15
+ private token;
16
+ private boundHost;
17
+ private boundPortValue;
18
+ constructor(deps: DispatchDeps, options?: IpcServerOptions);
19
+ /** Bind the IPC port. Rejects on EADDRINUSE so the caller (server.ts)
20
+ * can log and continue without multi-agent — never throws after listen. */
21
+ start(): Promise<void>;
22
+ /** The actual host+port the server is listening on. Set after start()
23
+ * resolves; meaningful even when port:0 was passed. */
24
+ boundAddress(): {
25
+ host: string;
26
+ port: number;
27
+ };
28
+ /** Tell every connected proxy we're going down (so auto-reelect can fire),
29
+ * then close the listening socket. Safe to call when start() never ran. */
30
+ stop(): Promise<void>;
31
+ /** Visible to tests + doctor. */
32
+ sessionCount(): number;
33
+ /** Exposed for tests only. Production callers never need this. */
34
+ currentToken(): string;
35
+ private handleConnection;
36
+ private heartbeat;
37
+ private handleFrame;
38
+ /** Dispatch a JSON-RPC 2.0 MCP request to the shared handlers and return
39
+ * a JSON-RPC 2.0 response object. Errors are converted to JSON-RPC error
40
+ * envelopes — never thrown back to the socket layer. */
41
+ private dispatchMcpRequest;
42
+ }
@@ -0,0 +1,336 @@
1
+ import { createServer } from 'node:net';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { lookupPeerProcess } from '../auth/process-identity.js';
4
+ import { SERVER_INSTRUCTIONS } from '../tools/server-instructions.js';
5
+ import { VERSION } from '../version.js';
6
+ import { IPC_HOST, IPC_PING_INTERVAL_MS, IPC_PONG_TIMEOUT_MS, IPC_PORT, IPC_PROTOCOL_VERSION, encodeFrame, isCompatibleVersion, parseFrame, } from './protocol.js';
7
+ import { handleToolCall, handleToolsList } from './dispatch.js';
8
+ import { rotateToken } from './token.js';
9
+ /**
10
+ * Primary's side of the IPC bridge. Listens on 127.0.0.1:17530, validates
11
+ * incoming peer processes via the same kernel-level process binding that
12
+ * protects the WS bridge, then runs the hello/token handshake. After auth,
13
+ * forwards MCP JSON-RPC frames through the shared dispatch handlers.
14
+ *
15
+ * Construction does NOT bind the port. Call `start()` and await.
16
+ */
17
+ function log(msg) {
18
+ // stderr — stdout belongs to the MCP transport.
19
+ console.error(`[browser-link ipc] ${msg}`);
20
+ }
21
+ /** Binaries we'll accept as legitimate peers. The token is the real auth;
22
+ * this is defence in depth. Names normalised to lowercase before comparison. */
23
+ const NODE_PROCESS_NAMES = new Set([
24
+ 'node',
25
+ 'node.exe',
26
+ 'nodejs',
27
+ 'tsx',
28
+ 'tsx.exe',
29
+ 'browser-link',
30
+ 'browser-link.exe',
31
+ ]);
32
+ /** Same loopback normalisation as server.ts uses for the WS bridge. */
33
+ function normaliseLoopback(addr) {
34
+ if (addr === '::1' || addr === '::ffff:127.0.0.1')
35
+ return '127.0.0.1';
36
+ if (addr.startsWith('::ffff:'))
37
+ return addr.slice('::ffff:'.length);
38
+ return addr;
39
+ }
40
+ export class IpcServer {
41
+ deps;
42
+ options;
43
+ server = null;
44
+ sessions = new Map();
45
+ token;
46
+ boundHost = IPC_HOST;
47
+ boundPortValue = IPC_PORT;
48
+ constructor(deps, options = {}) {
49
+ this.deps = deps;
50
+ this.options = options;
51
+ // Rotate the token on every primary startup. Any stale token left by a
52
+ // crashed previous primary is invalidated immediately.
53
+ this.token = rotateToken();
54
+ }
55
+ /** Bind the IPC port. Rejects on EADDRINUSE so the caller (server.ts)
56
+ * can log and continue without multi-agent — never throws after listen. */
57
+ start() {
58
+ return new Promise((resolve, reject) => {
59
+ const host = this.options.host ?? IPC_HOST;
60
+ const port = this.options.port ?? IPC_PORT;
61
+ const server = createServer((socket) => {
62
+ this.handleConnection(socket).catch((err) => {
63
+ log(`Connection handler error: ${err instanceof Error ? err.message : String(err)}`);
64
+ socket.destroy();
65
+ });
66
+ });
67
+ let settled = false;
68
+ server.on('error', (err) => {
69
+ if (!settled) {
70
+ settled = true;
71
+ if (err.code === 'EADDRINUSE') {
72
+ reject(new Error(`IPC port ${host}:${port} already in use — another browser-link primary may already be running.`));
73
+ }
74
+ else {
75
+ reject(err);
76
+ }
77
+ }
78
+ else {
79
+ log(`Server error after listening: ${err.message}`);
80
+ }
81
+ });
82
+ server.listen(port, host, () => {
83
+ settled = true;
84
+ this.server = server;
85
+ const addr = server.address();
86
+ if (addr && typeof addr === 'object') {
87
+ this.boundHost = addr.address;
88
+ this.boundPortValue = addr.port;
89
+ }
90
+ else {
91
+ this.boundHost = host;
92
+ this.boundPortValue = port;
93
+ }
94
+ log(`IPC server listening on ${this.boundHost}:${this.boundPortValue}`);
95
+ resolve();
96
+ });
97
+ });
98
+ }
99
+ /** The actual host+port the server is listening on. Set after start()
100
+ * resolves; meaningful even when port:0 was passed. */
101
+ boundAddress() {
102
+ return { host: this.boundHost, port: this.boundPortValue };
103
+ }
104
+ /** Tell every connected proxy we're going down (so auto-reelect can fire),
105
+ * then close the listening socket. Safe to call when start() never ran. */
106
+ async stop() {
107
+ for (const session of this.sessions.values()) {
108
+ try {
109
+ session.socket.write(encodeFrame({ kind: 'primary-closing', reason: 'shutdown' }));
110
+ }
111
+ catch {
112
+ /* socket already gone */
113
+ }
114
+ clearInterval(session.pingTimer);
115
+ if (session.pongDeadline)
116
+ clearTimeout(session.pongDeadline);
117
+ session.socket.end();
118
+ }
119
+ this.sessions.clear();
120
+ if (this.server) {
121
+ await new Promise((res) => this.server.close(() => res()));
122
+ this.server = null;
123
+ }
124
+ }
125
+ /** Visible to tests + doctor. */
126
+ sessionCount() {
127
+ return this.sessions.size;
128
+ }
129
+ /** Exposed for tests only. Production callers never need this. */
130
+ currentToken() {
131
+ return this.token;
132
+ }
133
+ async handleConnection(socket) {
134
+ const remoteAddr = normaliseLoopback(socket.remoteAddress ?? '');
135
+ const remotePort = socket.remotePort;
136
+ if (!remoteAddr || remotePort == null) {
137
+ log('Rejected IPC connection: peer address/port not exposed by socket.');
138
+ socket.destroy();
139
+ return;
140
+ }
141
+ // Kernel-level process binding. Same trick as the WS bridge: we ask the
142
+ // OS which process owns the peer's TCP port, and reject anything that
143
+ // does not look like a Node-family binary.
144
+ const peer = await lookupPeerProcess(remoteAddr, remotePort).catch(() => null);
145
+ if (!peer || !NODE_PROCESS_NAMES.has(peer.binaryName.toLowerCase())) {
146
+ log(`Rejected IPC connection from ${remoteAddr}:${remotePort}: not a known Node process (${peer?.binaryName ?? 'unknown'}).`);
147
+ socket.destroy();
148
+ return;
149
+ }
150
+ let buffer = '';
151
+ let authenticated = false;
152
+ let sessionId = null;
153
+ const handleLine = (line) => {
154
+ if (line.length === 0)
155
+ return;
156
+ const frame = parseFrame(line);
157
+ if (!frame) {
158
+ log('Invalid frame received; closing connection.');
159
+ socket.destroy();
160
+ return;
161
+ }
162
+ if (!authenticated) {
163
+ if (frame.kind !== 'hello') {
164
+ log(`First frame was not "hello" (got ${frame.kind}); closing.`);
165
+ socket.destroy();
166
+ return;
167
+ }
168
+ if (!isCompatibleVersion(frame.version)) {
169
+ socket.write(encodeFrame({
170
+ kind: 'hello-reject',
171
+ reason: `version mismatch: primary ${IPC_PROTOCOL_VERSION}, proxy ${frame.version}`,
172
+ }));
173
+ socket.end();
174
+ return;
175
+ }
176
+ if (frame.token !== this.token) {
177
+ socket.write(encodeFrame({ kind: 'hello-reject', reason: 'invalid token' }));
178
+ socket.end();
179
+ return;
180
+ }
181
+ // Auth ok — register session and start the heartbeat.
182
+ authenticated = true;
183
+ sessionId = randomUUID();
184
+ socket.write(encodeFrame({ kind: 'hello-ack', version: IPC_PROTOCOL_VERSION, sessionId }));
185
+ const sid = sessionId;
186
+ const session = {
187
+ id: sid,
188
+ socket,
189
+ pingTimer: setInterval(() => this.heartbeat(sid), IPC_PING_INTERVAL_MS),
190
+ pongDeadline: null,
191
+ };
192
+ this.sessions.set(sid, session);
193
+ log(`Proxy connected: session=${sid} pid=${peer.pid}`);
194
+ return;
195
+ }
196
+ void this.handleFrame(frame, sessionId, socket);
197
+ };
198
+ socket.on('data', (chunk) => {
199
+ buffer += chunk.toString('utf8');
200
+ let nl;
201
+ while ((nl = buffer.indexOf('\n')) >= 0) {
202
+ const line = buffer.slice(0, nl);
203
+ buffer = buffer.slice(nl + 1);
204
+ handleLine(line);
205
+ }
206
+ });
207
+ socket.on('close', () => {
208
+ if (sessionId) {
209
+ const session = this.sessions.get(sessionId);
210
+ if (session) {
211
+ clearInterval(session.pingTimer);
212
+ if (session.pongDeadline)
213
+ clearTimeout(session.pongDeadline);
214
+ this.sessions.delete(sessionId);
215
+ log(`Proxy disconnected: session=${sessionId}`);
216
+ }
217
+ }
218
+ });
219
+ socket.on('error', (err) => {
220
+ log(`Proxy socket error: ${err.message}`);
221
+ });
222
+ }
223
+ heartbeat(sid) {
224
+ const session = this.sessions.get(sid);
225
+ if (!session)
226
+ return;
227
+ try {
228
+ session.socket.write(encodeFrame({ kind: 'ping' }));
229
+ }
230
+ catch {
231
+ // socket already closed; the 'close' handler will clean up
232
+ return;
233
+ }
234
+ if (!session.pongDeadline) {
235
+ session.pongDeadline = setTimeout(() => {
236
+ log(`Proxy ${sid} did not pong in time; dropping.`);
237
+ session.socket.destroy();
238
+ }, IPC_PONG_TIMEOUT_MS);
239
+ }
240
+ }
241
+ async handleFrame(frame, sessionId, socket) {
242
+ switch (frame.kind) {
243
+ case 'pong': {
244
+ const session = this.sessions.get(sessionId);
245
+ if (session?.pongDeadline) {
246
+ clearTimeout(session.pongDeadline);
247
+ session.pongDeadline = null;
248
+ }
249
+ return;
250
+ }
251
+ case 'mcp.request': {
252
+ const response = await this.dispatchMcpRequest(frame.payload);
253
+ try {
254
+ socket.write(encodeFrame({ kind: 'mcp.response', requestId: frame.requestId, payload: response }));
255
+ }
256
+ catch {
257
+ /* socket gone */
258
+ }
259
+ return;
260
+ }
261
+ case 'mcp.notification': {
262
+ // Proxies forward notifications like notifications/initialized. We
263
+ // don't reply (notifications have no id). Future work: route to map
264
+ // event log when we add the traceability layer.
265
+ return;
266
+ }
267
+ case 'ping': {
268
+ try {
269
+ socket.write(encodeFrame({ kind: 'pong' }));
270
+ }
271
+ catch {
272
+ /* socket gone */
273
+ }
274
+ return;
275
+ }
276
+ default:
277
+ // hello/hello-ack/hello-reject/primary-closing should not arrive
278
+ // post-handshake from a proxy. Ignore defensively.
279
+ return;
280
+ }
281
+ }
282
+ /** Dispatch a JSON-RPC 2.0 MCP request to the shared handlers and return
283
+ * a JSON-RPC 2.0 response object. Errors are converted to JSON-RPC error
284
+ * envelopes — never thrown back to the socket layer. */
285
+ async dispatchMcpRequest(payload) {
286
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
287
+ return { jsonrpc: '2.0', id: null, error: { code: -32600, message: 'Invalid request' } };
288
+ }
289
+ const req = payload;
290
+ const id = req.id ?? null;
291
+ const method = req.method ?? '';
292
+ try {
293
+ switch (method) {
294
+ case 'initialize': {
295
+ const result = {
296
+ protocolVersion: '2024-11-05',
297
+ capabilities: { tools: {} },
298
+ serverInfo: { name: 'browser-link', version: VERSION },
299
+ instructions: SERVER_INSTRUCTIONS,
300
+ };
301
+ return { jsonrpc: '2.0', id, result };
302
+ }
303
+ case 'tools/list': {
304
+ const result = handleToolsList(this.deps);
305
+ return { jsonrpc: '2.0', id, result };
306
+ }
307
+ case 'tools/call': {
308
+ const params = req.params;
309
+ if (!params || typeof params.name !== 'string') {
310
+ return {
311
+ jsonrpc: '2.0',
312
+ id,
313
+ error: { code: -32602, message: 'Missing tool name in params' },
314
+ };
315
+ }
316
+ const result = await handleToolCall({ name: params.name, arguments: params.arguments }, this.deps);
317
+ return { jsonrpc: '2.0', id, result };
318
+ }
319
+ case 'ping': {
320
+ return { jsonrpc: '2.0', id, result: {} };
321
+ }
322
+ default:
323
+ return {
324
+ jsonrpc: '2.0',
325
+ id,
326
+ error: { code: -32601, message: `Method not found: ${method}` },
327
+ };
328
+ }
329
+ }
330
+ catch (err) {
331
+ const message = err instanceof Error ? err.message : String(err);
332
+ return { jsonrpc: '2.0', id, error: { code: -32603, message } };
333
+ }
334
+ }
335
+ }
336
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/bridge/server.ts"],"names":[],"mappings":"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"}