@nudge-ui/mcp 0.2.4 → 0.2.6

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.
@@ -1,6 +1,6 @@
1
1
  import type { AgentActivity } from "./internal/agent-protocol.js";
2
- import type { AgentPromptRequest, AgentStatusSnapshot, AgentStatusUpdate, CanvasCommandResult } from "./internal/agent-protocol.js";
3
- import type { CanvasCommandInput } from "./bridge.js";
2
+ import type { AgentDeliveredPrompt, AgentStatusSnapshot, AgentStatusUpdate, CanvasCommandResult } from "./internal/agent-protocol.js";
3
+ import type { CanvasCommandInput } from "./bridgeSession.js";
4
4
  import { type ProjectSession, type StoredProjectSession } from "./project.js";
5
5
  export interface DiscoveredProjectSession extends ProjectSession {
6
6
  readonly matchesWorkspace: boolean;
@@ -9,7 +9,7 @@ export interface DiscoveredProjectSession extends ProjectSession {
9
9
  readonly status: Pick<AgentStatusSnapshot, "connection" | "listenerActive" | "paired">;
10
10
  }
11
11
  export declare function discoverProjectSessions(applicationRoot: string | undefined, registryRoot?: string): Promise<DiscoveredProjectSession[]>;
12
- /** Routes MCP tools to a live project in the adapter's exact worktree. */
12
+ /** Follows one selected live project across bridge restarts. */
13
13
  export declare class DiscoveredProjectRouter {
14
14
  readonly workspaceRoot: string;
15
15
  readonly applicationRoot: string;
@@ -21,11 +21,10 @@ export declare class DiscoveredProjectRouter {
21
21
  private constructor();
22
22
  get selectedSessionId(): string | undefined;
23
23
  static forSession(session: StoredProjectSession, registryRoot: string): DiscoveredProjectRouter;
24
- static create(workspaceRoot: string, registryRoot?: string): Promise<DiscoveredProjectRouter>;
25
- listSessions(): Promise<DiscoveredProjectSession[]>;
26
24
  private connectToSession;
27
25
  private selectSession;
28
- waitForPrompt(signal?: AbortSignal, sessionId?: string, agentClientName?: string): Promise<AgentPromptRequest>;
26
+ waitForPrompt(signal?: AbortSignal, sessionId?: string, agentClientName?: string): Promise<AgentDeliveredPrompt>;
27
+ getRequest(requestId: string): Promise<AgentDeliveredPrompt>;
29
28
  getStatus(): Promise<AgentStatusSnapshot>;
30
29
  reportActivity(activity: AgentActivity): Promise<void>;
31
30
  updateRequestStatus(update: AgentStatusUpdate): Promise<AgentStatusSnapshot>;
package/dist/discovery.js CHANGED
@@ -33,7 +33,7 @@ export async function discoverProjectSessions(applicationRoot, registryRoot = de
33
33
  };
34
34
  }).sort((left, right) => right.startedAt.localeCompare(left.startedAt));
35
35
  }
36
- /** Routes MCP tools to a live project in the adapter's exact worktree. */
36
+ /** Follows one selected live project across bridge restarts. */
37
37
  export class DiscoveredProjectRouter {
38
38
  workspaceRoot;
39
39
  applicationRoot;
@@ -56,13 +56,6 @@ export class DiscoveredProjectRouter {
56
56
  router.client = new ProjectSessionClient(session, router.agentId);
57
57
  return router;
58
58
  }
59
- static async create(workspaceRoot, registryRoot = defaultSessionRegistryRoot()) {
60
- const applicationRoot = await realpath(requireAbsoluteWorkspaceRoot(workspaceRoot));
61
- return new DiscoveredProjectRouter(await resolveWorkspaceRoot(applicationRoot), applicationRoot, resolve(registryRoot), randomUUID());
62
- }
63
- async listSessions() {
64
- return await discoverProjectSessions(this.applicationRoot, this.registryRoot);
65
- }
66
59
  connectToSession(sessionId) {
67
60
  const connection = this.connectionQueue.then(() => this.selectSession(sessionId));
68
61
  this.connectionQueue = connection.then(() => undefined, () => undefined);
@@ -104,6 +97,10 @@ export class DiscoveredProjectRouter {
104
97
  return await replacement.waitForPrompt(signal, agentClientName);
105
98
  }
106
99
  }
100
+ async getRequest(requestId) {
101
+ const client = await this.connectToSession();
102
+ return await client.getRequest(requestId);
103
+ }
107
104
  async getStatus() {
108
105
  const client = await this.connectToSession();
109
106
  return await client.getStatus();
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { createAgentCompanion, createDiscoveredAgentAdapter, type AgentCompanion, type AgentCompanionOptions, type DiscoveredAgentAdapter, } from "./server.js";
1
+ export { createDiscoveredAgentAdapter, type DiscoveredAgentAdapter, } from "./server.js";
2
2
  export { DiscoveredProjectRouter, discoverProjectSessions, type DiscoveredProjectSession, } from "./discovery.js";
3
3
  export { defaultSessionRegistryRoot, resolveWorkspaceRoot, startProjectBridge, type ProjectBridgeOptions, type ProjectBridgeRuntime, type ProjectSession, } from "./project.js";
4
- export { createLoopbackBridge, type BrowserBridge, type BrowserBridgeOptions, type BridgeAddress, type BridgeClock, type CanvasCommandInput, } from "./bridge.js";
5
- export { openPairedPage, parseCliArguments, runCli, type CliOptions, type PageLaunchDependencies, } from "./cli.js";
4
+ export { createLoopbackBridge, type BrowserBridge, type BrowserBridgeOptions, type BridgeAddress, } from "./bridge.js";
5
+ export { BridgeSessionError, type BridgeClock, type BridgeSessionErrorCode, type CanvasCommandInput, type ListenOptions, } from "./bridgeSession.js";
6
6
  export * from "./protocol.js";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- export { createAgentCompanion, createDiscoveredAgentAdapter, } from "./server.js";
1
+ export { createDiscoveredAgentAdapter, } from "./server.js";
2
2
  export { DiscoveredProjectRouter, discoverProjectSessions, } from "./discovery.js";
3
3
  export { defaultSessionRegistryRoot, resolveWorkspaceRoot, startProjectBridge, } from "./project.js";
4
4
  export { createLoopbackBridge, } from "./bridge.js";
5
- export { openPairedPage, parseCliArguments, runCli, } from "./cli.js";
5
+ export { BridgeSessionError, } from "./bridgeSession.js";
6
6
  export * from "./protocol.js";
@@ -1,12 +1,5 @@
1
- /**
2
- * Public, host-neutral contracts shared by the Nudge browser inspector and
3
- * its local MCP companion.
4
- *
5
- * This module deliberately has no Node, browser, MCP SDK, or framework
6
- * imports. Hosts can use the same validation and message shapes without
7
- * pulling the companion into a browser bundle.
8
- */
9
- export declare const AGENT_PROTOCOL_VERSION: 2;
1
+ /** Host-neutral contracts shared by the browser inspector and the local MCP companion; no Node, browser, MCP SDK, or framework imports. */
2
+ export declare const AGENT_PROTOCOL_VERSION: 3;
10
3
  export declare const AGENT_PROTOCOL_LIMITS: {
11
4
  readonly projectId: 256;
12
5
  readonly workspaceRoot: 4096;
@@ -35,16 +28,21 @@ export declare const AGENT_PROTOCOL_LIMITS: {
35
28
  readonly routeTitle: 512;
36
29
  readonly routeCount: 64;
37
30
  readonly canvasCardCount: 128;
31
+ readonly agentClientName: 120;
38
32
  };
39
- /**
40
- * Returns the default project-specific loopback port used by the CLI.
41
- *
42
- * The value is deterministic so a browser can probe the companion without a
43
- * port file, while the range stays high enough to avoid common dev servers.
44
- * Callers may still override it with an explicit port when a collision occurs.
45
- */
46
- export declare function defaultBridgePort(projectId: string): number;
47
33
  export type AgentProtocolVersion = typeof AGENT_PROTOCOL_VERSION;
34
+ /** Stable loopback endpoint names shared by the browser client and the MCP bridge. */
35
+ export declare const BRIDGE_ENDPOINTS: {
36
+ readonly health: "/health";
37
+ readonly pair: "/pair";
38
+ readonly takeover: "/takeover";
39
+ readonly events: "/events";
40
+ readonly prompt: "/prompt";
41
+ readonly status: "/status";
42
+ readonly canvasAcknowledgement: "/canvas/ack";
43
+ readonly disconnect: "/disconnect";
44
+ };
45
+ export type BridgeEndpoint = (typeof BRIDGE_ENDPOINTS)[keyof typeof BRIDGE_ENDPOINTS];
48
46
  /** A project identity is the pairing boundary for one companion process. */
49
47
  export interface AgentProjectIdentity {
50
48
  readonly projectId: string;
@@ -61,14 +59,12 @@ export interface AgentRoute {
61
59
  }
62
60
  /** The owner marker lets the browser remove only groups created by the agent. */
63
61
  export type CanvasGroupOwner = "agent" | "user";
64
- /** A browser-reported comparison group. */
65
62
  export interface CanvasGroup {
66
63
  readonly id: string;
67
64
  readonly label: string;
68
65
  readonly owner: CanvasGroupOwner;
69
66
  readonly routes: readonly AgentRoute[];
70
67
  }
71
- /** A compact, serialisable Canvas snapshot returned by the browser. */
72
68
  export interface CanvasState {
73
69
  readonly mode: "inspect" | "canvas";
74
70
  readonly groups: readonly CanvasGroup[];
@@ -164,14 +160,14 @@ export interface AgentSketchAttachment extends AgentSketchMetadata {
164
160
  export interface AgentDeliveredPrompt extends AgentPromptRequest {
165
161
  readonly attachments?: readonly AgentSketchAttachment[];
166
162
  }
167
- export type AgentRequestStatus = "working" | "completed" | "failed" | "interrupted";
163
+ export type AgentRequestStatus = "delivered" | "working" | "completed" | "failed" | "interrupted";
168
164
  export interface AgentRequestOutcome {
169
165
  readonly requestId: string;
170
- readonly status: AgentRequestStatus;
166
+ readonly status: Exclude<AgentRequestStatus, "delivered">;
171
167
  readonly summary?: string;
172
168
  readonly error?: string;
173
169
  }
174
- export type AgentConnectionState = "offline" | "listening" | "paired" | "working";
170
+ export type AgentConnectionState = "offline" | "listening" | "paired" | "delivered" | "working";
175
171
  export interface AgentStatusSnapshot {
176
172
  readonly protocolVersion: AgentProtocolVersion;
177
173
  readonly projectId: string;
@@ -269,7 +265,7 @@ export interface StatusEventRequest {
269
265
  }
270
266
  export interface AgentStatusUpdate {
271
267
  readonly requestId: string;
272
- readonly status: AgentRequestStatus;
268
+ readonly status: Exclude<AgentRequestStatus, "delivered">;
273
269
  readonly summary?: string;
274
270
  readonly error?: string;
275
271
  }
@@ -277,26 +273,33 @@ export interface ProtocolErrorShape {
277
273
  readonly code: string;
278
274
  readonly message: string;
279
275
  }
280
- /**
281
- * Returns a canonical origin or null. Credentials, paths, and fragments are
282
- * rejected so the browser cannot broaden the pairing boundary accidentally.
283
- */
276
+ /** Options a development host passes to the optional project bridge. */
277
+ export interface ProjectBridgeStartOptions {
278
+ readonly appRoot: string;
279
+ readonly projectId: string;
280
+ readonly origin: string;
281
+ /** Other explicit browser origins, such as loopback aliases on the same port. */
282
+ readonly allowedOrigins?: readonly string[];
283
+ }
284
+ /** The started project bridge as seen by the development host that owns it. */
285
+ export interface ProjectBridgeHandle {
286
+ readonly browser: {
287
+ readonly projectId: string;
288
+ readonly origin: string;
289
+ readonly bridgeUrl: string;
290
+ };
291
+ close(): Promise<void>;
292
+ }
293
+ /** The `@nudge-ui/mcp/project` export; hosts load it dynamically, so this is the only compile-time link between the packages. */
294
+ export type StartProjectBridge = (options: ProjectBridgeStartOptions) => Promise<ProjectBridgeHandle>;
295
+ /** Canonical origin or null; credentials, paths, and fragments are rejected so pairing cannot broaden accidentally. */
284
296
  export declare function canonicalOrigin(value: unknown): string | null;
285
- /** Returns true only for exact, canonical HTTP(S) origins. */
286
297
  export declare function isCanonicalOrigin(value: unknown): value is string;
287
- /**
288
- * Checks an origin against an explicit allow-list. An empty allow-list does
289
- * not mean wildcard access; callers must choose an origin before pairing.
290
- */
298
+ /** An empty allow-list is not wildcard access; callers must choose an origin before pairing. */
291
299
  export declare function isAllowedOrigin(value: unknown, allowedOrigins: readonly string[]): value is string;
292
- /**
293
- * Validates an agent route and ensures it belongs to the paired origin.
294
- * Fragments are retained because they are useful initial Canvas locations.
295
- */
300
+ /** Fragments are retained because they are useful initial Canvas locations. */
296
301
  export declare function isSameOriginRoute(value: unknown, origin: string): value is AgentRoute;
297
- /** Validates and normalises route values for a presentation command. */
298
302
  export declare function validateRoutes(value: unknown, origin: string): AgentRoute[];
299
- /** Validates the fields shared by every companion message. */
300
303
  export declare function isProtocolVersion(value: unknown): value is AgentProtocolVersion;
301
304
  export declare function isProjectIdentity(value: unknown): value is AgentProjectIdentity;
302
305
  export declare function isAgentSketchCaptureMetadata(value: unknown): value is AgentSketchCaptureMetadata;
@@ -309,5 +312,17 @@ export declare function isAgentStatusUpdate(value: unknown): value is AgentStatu
309
312
  export declare function isCanvasState(value: unknown): value is CanvasState;
310
313
  export declare function isCanvasGroup(value: unknown): value is CanvasGroup;
311
314
  export declare function isCanvasCommand(value: unknown): value is CanvasCommand;
315
+ export declare function isAgentDeliveredPrompt(value: unknown): value is AgentDeliveredPrompt;
316
+ /** A successful result never carries an error. */
317
+ export declare function isCanvasCommandResult(value: unknown): value is CanvasCommandResult;
318
+ export declare function deriveConnectionState(status: Pick<AgentStatusSnapshot, "listenerActive" | "paired" | "request">): AgentConnectionState;
319
+ /** Unknown fields are dropped and the connection state is recomputed, so a snapshot cannot claim a state its own fields contradict. */
320
+ export declare function decodeAgentStatusSnapshot(value: unknown): AgentStatusSnapshot | null;
321
+ /** A Canvas command is passed through as received: the Canvas controller validates it so a malformed command still receives an error acknowledgement. */
322
+ export declare function decodeBridgeEvent(value: unknown): BridgeEvent | null;
323
+ /** Decodes a pairing or takeover response for the exact project and origin requested. */
324
+ export declare function decodePairingResponse(value: unknown, expected: Pick<PairingRequest, "projectId" | "origin">): PairingResponse | null;
325
+ /** Decodes the accepted-prompt response for the project that dispatched it. */
326
+ export declare function decodePromptDispatchResponse(value: unknown, projectId: string): PromptDispatchResponse | null;
312
327
  /** Creates a stable protocol error shape without exposing thrown values. */
313
328
  export declare function protocolError(code: string, message: string): ProtocolErrorShape;
@@ -1,12 +1,5 @@
1
- /**
2
- * Public, host-neutral contracts shared by the Nudge browser inspector and
3
- * its local MCP companion.
4
- *
5
- * This module deliberately has no Node, browser, MCP SDK, or framework
6
- * imports. Hosts can use the same validation and message shapes without
7
- * pulling the companion into a browser bundle.
8
- */
9
- export const AGENT_PROTOCOL_VERSION = 2;
1
+ /** Host-neutral contracts shared by the browser inspector and the local MCP companion; no Node, browser, MCP SDK, or framework imports. */
2
+ export const AGENT_PROTOCOL_VERSION = 3;
10
3
  export const AGENT_PROTOCOL_LIMITS = {
11
4
  projectId: 256,
12
5
  workspaceRoot: 4096,
@@ -35,25 +28,19 @@ export const AGENT_PROTOCOL_LIMITS = {
35
28
  routeTitle: 512,
36
29
  routeCount: 64,
37
30
  canvasCardCount: 128,
31
+ agentClientName: 120,
32
+ };
33
+ /** Stable loopback endpoint names shared by the browser client and the MCP bridge. */
34
+ export const BRIDGE_ENDPOINTS = {
35
+ health: "/health",
36
+ pair: "/pair",
37
+ takeover: "/takeover",
38
+ events: "/events",
39
+ prompt: "/prompt",
40
+ status: "/status",
41
+ canvasAcknowledgement: "/canvas/ack",
42
+ disconnect: "/disconnect",
38
43
  };
39
- /**
40
- * Returns the default project-specific loopback port used by the CLI.
41
- *
42
- * The value is deterministic so a browser can probe the companion without a
43
- * port file, while the range stays high enough to avoid common dev servers.
44
- * Callers may still override it with an explicit port when a collision occurs.
45
- */
46
- export function defaultBridgePort(projectId) {
47
- if (!nonEmptyString(projectId, AGENT_PROTOCOL_LIMITS.projectId)) {
48
- throw new TypeError("projectId must be a bounded non-empty string");
49
- }
50
- let hash = 2_166_136_261;
51
- for (let index = 0; index < projectId.length; index += 1) {
52
- hash ^= projectId.charCodeAt(index);
53
- hash = Math.imul(hash, 16_777_619);
54
- }
55
- return 30_000 + ((hash >>> 0) % 20_000);
56
- }
57
44
  export const CANVAS_COMMAND_TYPES = [
58
45
  "read-state",
59
46
  "present-routes",
@@ -110,10 +97,7 @@ function encodedByteLength(value) {
110
97
  const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
111
98
  return Math.max(0, Math.floor(value.length * 3 / 4) - padding);
112
99
  }
113
- /**
114
- * Returns a canonical origin or null. Credentials, paths, and fragments are
115
- * rejected so the browser cannot broaden the pairing boundary accidentally.
116
- */
100
+ /** Canonical origin or null; credentials, paths, and fragments are rejected so pairing cannot broaden accidentally. */
117
101
  export function canonicalOrigin(value) {
118
102
  if (!nonEmptyString(value, AGENT_PROTOCOL_LIMITS.origin))
119
103
  return null;
@@ -130,24 +114,17 @@ export function canonicalOrigin(value) {
130
114
  return null;
131
115
  return parsed.origin;
132
116
  }
133
- /** Returns true only for exact, canonical HTTP(S) origins. */
134
117
  export function isCanonicalOrigin(value) {
135
118
  return canonicalOrigin(value) === value;
136
119
  }
137
- /**
138
- * Checks an origin against an explicit allow-list. An empty allow-list does
139
- * not mean wildcard access; callers must choose an origin before pairing.
140
- */
120
+ /** An empty allow-list is not wildcard access; callers must choose an origin before pairing. */
141
121
  export function isAllowedOrigin(value, allowedOrigins) {
142
122
  const origin = canonicalOrigin(value);
143
123
  if (!origin || allowedOrigins.length === 0)
144
124
  return false;
145
125
  return allowedOrigins.some((candidate) => canonicalOrigin(candidate) === origin);
146
126
  }
147
- /**
148
- * Validates an agent route and ensures it belongs to the paired origin.
149
- * Fragments are retained because they are useful initial Canvas locations.
150
- */
127
+ /** Fragments are retained because they are useful initial Canvas locations. */
151
128
  export function isSameOriginRoute(value, origin) {
152
129
  if (!isRecord(value) || !hasOnlyKeys(value, ["url", "title", "label"]))
153
130
  return false;
@@ -171,7 +148,6 @@ export function isSameOriginRoute(value, origin) {
171
148
  return false;
172
149
  return true;
173
150
  }
174
- /** Validates and normalises route values for a presentation command. */
175
151
  export function validateRoutes(value, origin) {
176
152
  if (!Array.isArray(value) || value.length === 0 || value.length > AGENT_PROTOCOL_LIMITS.routeCount) {
177
153
  throw new TypeError(`routes must contain between 1 and ${AGENT_PROTOCOL_LIMITS.routeCount} items`);
@@ -192,7 +168,6 @@ export function validateRoutes(value, origin) {
192
168
  };
193
169
  });
194
170
  }
195
- /** Validates the fields shared by every companion message. */
196
171
  export function isProtocolVersion(value) {
197
172
  return value === AGENT_PROTOCOL_VERSION;
198
173
  }
@@ -421,6 +396,140 @@ export function isCanvasCommand(value) {
421
396
  return false;
422
397
  }
423
398
  }
399
+ export function isAgentDeliveredPrompt(value) {
400
+ if (!isRecord(value))
401
+ return false;
402
+ const { attachments, ...request } = value;
403
+ return isAgentPromptRequest(request)
404
+ && (attachments === undefined || (Array.isArray(attachments)
405
+ && attachments.length <= AGENT_PROTOCOL_LIMITS.sketchCount
406
+ && attachments.every(isAgentSketchAttachment)));
407
+ }
408
+ function isCanvasCommandError(value) {
409
+ return isRecord(value) && hasOnlyKeys(value, ["code", "message"])
410
+ && nonEmptyString(value.code, 128)
411
+ && typeof value.message === "string" && value.message.length <= AGENT_PROTOCOL_LIMITS.prompt;
412
+ }
413
+ /** A successful result never carries an error. */
414
+ export function isCanvasCommandResult(value) {
415
+ if (!isRecord(value) || !hasOnlyKeys(value, ["commandId", "ok", "state", "group", "error"]))
416
+ return false;
417
+ return nonEmptyString(value.commandId, AGENT_PROTOCOL_LIMITS.commandId)
418
+ && typeof value.ok === "boolean"
419
+ && (value.state === undefined || isCanvasState(value.state))
420
+ && (value.group === undefined || isCanvasGroup(value.group))
421
+ && (value.error === undefined || (!value.ok && isCanvasCommandError(value.error)));
422
+ }
423
+ export function deriveConnectionState(status) {
424
+ if (status.request?.status === "delivered")
425
+ return "delivered";
426
+ if (status.request?.status === "working")
427
+ return "working";
428
+ if (status.paired)
429
+ return "paired";
430
+ if (status.listenerActive)
431
+ return "listening";
432
+ return "offline";
433
+ }
434
+ function isRequestStatus(value) {
435
+ return value === "delivered" || value === "working" || value === "completed" || value === "failed" || value === "interrupted";
436
+ }
437
+ function decodeStatusRequest(value) {
438
+ if (value === null || value === undefined)
439
+ return null;
440
+ if (!isRecord(value))
441
+ return undefined;
442
+ const { status, summary, error, ...request } = value;
443
+ if (!isRequestStatus(status) || !isAgentPromptRequest(request)
444
+ || !optionalString(summary, AGENT_PROTOCOL_LIMITS.prompt)
445
+ || !optionalString(error, AGENT_PROTOCOL_LIMITS.prompt))
446
+ return undefined;
447
+ return {
448
+ ...request,
449
+ status,
450
+ ...(summary === undefined ? {} : { summary }),
451
+ ...(error === undefined ? {} : { error }),
452
+ };
453
+ }
454
+ /** Unknown fields are dropped and the connection state is recomputed, so a snapshot cannot claim a state its own fields contradict. */
455
+ export function decodeAgentStatusSnapshot(value) {
456
+ if (!isRecord(value)
457
+ || value.protocolVersion !== AGENT_PROTOCOL_VERSION
458
+ || !nonEmptyString(value.projectId, AGENT_PROTOCOL_LIMITS.projectId)
459
+ || (value.connection !== "offline" && value.connection !== "listening"
460
+ && value.connection !== "paired" && value.connection !== "delivered" && value.connection !== "working")
461
+ || typeof value.listenerActive !== "boolean"
462
+ || typeof value.paired !== "boolean"
463
+ || !(value.pageUrl === undefined || value.pageUrl === null
464
+ || nonEmptyString(value.pageUrl, AGENT_PROTOCOL_LIMITS.routeUrl)))
465
+ return null;
466
+ const request = decodeStatusRequest(value.request);
467
+ if (request === undefined)
468
+ return null;
469
+ const fields = { listenerActive: value.listenerActive, paired: value.paired, request };
470
+ return {
471
+ protocolVersion: AGENT_PROTOCOL_VERSION,
472
+ projectId: value.projectId,
473
+ connection: deriveConnectionState(fields),
474
+ ...fields,
475
+ ...(value.pageUrl === undefined ? {} : { pageUrl: value.pageUrl }),
476
+ ...(typeof value.agentClientName === "string" && value.agentClientName.length > 0
477
+ ? { agentClientName: value.agentClientName.slice(0, AGENT_PROTOCOL_LIMITS.agentClientName) }
478
+ : {}),
479
+ };
480
+ }
481
+ /** A Canvas command is passed through as received: the Canvas controller validates it so a malformed command still receives an error acknowledgement. */
482
+ export function decodeBridgeEvent(value) {
483
+ if (!isRecord(value))
484
+ return null;
485
+ switch (value.type) {
486
+ case "status":
487
+ case "connected": {
488
+ const status = decodeAgentStatusSnapshot(value.status);
489
+ return status ? { type: value.type, status } : null;
490
+ }
491
+ case "disconnected":
492
+ return {
493
+ type: "disconnected",
494
+ ...(nonEmptyString(value.reason, AGENT_PROTOCOL_LIMITS.prompt) ? { reason: value.reason } : {}),
495
+ };
496
+ case "activity":
497
+ return isAgentActivity(value.activity) ? { type: "activity", activity: value.activity } : null;
498
+ case "canvas-command":
499
+ // SAFETY: the Canvas controller validates the command before acting on it.
500
+ return isRecord(value.command) ? { type: "canvas-command", command: value.command } : null;
501
+ default:
502
+ return null;
503
+ }
504
+ }
505
+ /** Decodes a pairing or takeover response for the exact project and origin requested. */
506
+ export function decodePairingResponse(value, expected) {
507
+ if (!isRecord(value)
508
+ || value.protocolVersion !== AGENT_PROTOCOL_VERSION
509
+ || value.projectId !== expected.projectId
510
+ || value.origin !== expected.origin
511
+ || !nonEmptyString(value.sessionToken, AGENT_PROTOCOL_LIMITS.sessionToken)
512
+ || !(value.pageUrl === undefined || nonEmptyString(value.pageUrl, AGENT_PROTOCOL_LIMITS.routeUrl)))
513
+ return null;
514
+ const status = decodeAgentStatusSnapshot(value.status);
515
+ if (!status)
516
+ return null;
517
+ return {
518
+ protocolVersion: AGENT_PROTOCOL_VERSION,
519
+ projectId: expected.projectId,
520
+ origin: expected.origin,
521
+ sessionToken: value.sessionToken,
522
+ ...(value.pageUrl === undefined ? {} : { pageUrl: value.pageUrl }),
523
+ status,
524
+ };
525
+ }
526
+ /** Decodes the accepted-prompt response for the project that dispatched it. */
527
+ export function decodePromptDispatchResponse(value, projectId) {
528
+ if (!isRecord(value) || (value.status !== "delivered" && value.status !== "working") || !isAgentPromptRequest(value.request)
529
+ || value.request.projectId !== projectId)
530
+ return null;
531
+ return { request: value.request, status: value.status };
532
+ }
424
533
  /** Creates a stable protocol error shape without exposing thrown values. */
425
534
  export function protocolError(code, message) {
426
535
  if (!nonEmptyString(code, 128) || !nonEmptyString(message, AGENT_PROTOCOL_LIMITS.prompt)) {
package/dist/project.d.ts CHANGED
@@ -19,7 +19,6 @@ export interface ProjectBridgeOptions {
19
19
  readonly workspaceRoot?: string;
20
20
  readonly projectId?: string;
21
21
  readonly origin: string;
22
- /** Other explicit browser origins, such as loopback aliases on the same port. */
23
22
  readonly allowedOrigins?: readonly string[];
24
23
  readonly host?: "127.0.0.1" | "::1" | "localhost";
25
24
  readonly port?: number;
@@ -45,10 +44,6 @@ export interface StoredProjectSession extends ProjectSession {
45
44
  export declare function defaultSessionRegistryRoot(): string;
46
45
  /** Resolves a checkout or worktree without conflating related worktrees. */
47
46
  export declare function resolveWorkspaceRoot(input: string): Promise<string>;
48
- /**
49
- * Starts the project-owned browser bridge and registers it for local MCP
50
- * adapters. The descriptor contains a private control credential and is never
51
- * suitable for browser bundles or user-facing tool results.
52
- */
53
- export declare function startProjectBridge(options: ProjectBridgeOptions): Promise<ProjectBridgeRuntime>;
47
+ /** The descriptor carries a private control credential; never expose it to browser bundles or tool results. */
48
+ export declare const startProjectBridge: (options: ProjectBridgeOptions) => Promise<ProjectBridgeRuntime>;
54
49
  export {};
package/dist/project.js CHANGED
@@ -9,9 +9,8 @@ import { createLoopbackBridge } from "./bridge.js";
9
9
  const execFileAsync = promisify(execFile);
10
10
  const REGISTRY_SCHEMA_VERSION = 1;
11
11
  export function defaultSessionRegistryRoot() {
12
- // MCP hosts commonly sanitize TMPDIR and XDG_RUNTIME_DIR for stdio child
13
- // processes. The OS account home is stable across the development server
14
- // and agent adapter, so both processes discover the same private registry.
12
+ // MCP hosts sanitize TMPDIR and XDG_RUNTIME_DIR for stdio child processes;
13
+ // the OS account home is stable across the development server and adapter.
15
14
  return join(userInfo().homedir, ".cache", "nudge-ui", "sessions");
16
15
  }
17
16
  async function gitValue(cwd, args) {
@@ -92,12 +91,8 @@ async function closeProjectBridge(descriptorPath, bridge) {
92
91
  await bridge.close();
93
92
  }
94
93
  }
95
- /**
96
- * Starts the project-owned browser bridge and registers it for local MCP
97
- * adapters. The descriptor contains a private control credential and is never
98
- * suitable for browser bundles or user-facing tool results.
99
- */
100
- export async function startProjectBridge(options) {
94
+ /** The descriptor carries a private control credential; never expose it to browser bundles or tool results. */
95
+ export const startProjectBridge = (async (options) => {
101
96
  const origin = canonicalOrigin(options.origin);
102
97
  if (!origin)
103
98
  throw new TypeError("origin must be a canonical HTTP(S) origin");
@@ -148,4 +143,4 @@ export async function startProjectBridge(options) {
148
143
  browser: { projectId, origin, bridgeUrl: address.url },
149
144
  close,
150
145
  };
151
- }
146
+ });
@@ -1,6 +1,5 @@
1
- import type { AgentActivity } from "./internal/agent-protocol.js";
2
- import type { AgentPromptRequest, AgentStatusSnapshot, AgentStatusUpdate, CanvasCommandResult } from "./internal/agent-protocol.js";
3
- import type { CanvasCommandInput } from "./bridge.js";
1
+ import type { AgentActivity, AgentDeliveredPrompt, AgentStatusSnapshot, AgentStatusUpdate, CanvasCommandResult } from "./internal/agent-protocol.js";
2
+ import type { CanvasCommandInput } from "./bridgeSession.js";
4
3
  import type { StoredProjectSession } from "./project.js";
5
4
  export interface SessionHealth {
6
5
  readonly projectId: string;
@@ -8,13 +7,11 @@ export interface SessionHealth {
8
7
  readonly claimed: boolean;
9
8
  readonly status: AgentStatusSnapshot;
10
9
  }
11
- /** Represents an HTTP response from a project bridge that could be classified. */
12
10
  export declare class ProjectSessionResponseError extends Error {
13
11
  readonly status: number;
14
12
  constructor(status: number, message: string);
15
13
  }
16
14
  export declare function readSessionHealth(session: StoredProjectSession, timeoutMs?: number): Promise<SessionHealth>;
17
- /** Owns one project's claim, heartbeat, and authenticated control requests. */
18
15
  export declare class ProjectSessionClient {
19
16
  readonly descriptor: StoredProjectSession;
20
17
  readonly agentId: string;
@@ -26,7 +23,8 @@ export declare class ProjectSessionClient {
26
23
  private send;
27
24
  private acquireClaim;
28
25
  private claim;
29
- waitForPrompt(signal?: AbortSignal, agentClientName?: string): Promise<AgentPromptRequest>;
26
+ waitForPrompt(signal?: AbortSignal, agentClientName?: string): Promise<AgentDeliveredPrompt>;
27
+ getRequest(requestId: string): Promise<AgentDeliveredPrompt>;
30
28
  getStatus(): Promise<AgentStatusSnapshot>;
31
29
  reportActivity(activity: AgentActivity): Promise<void>;
32
30
  updateRequestStatus(update: AgentStatusUpdate): Promise<AgentStatusSnapshot>;