@nudge-ui/mcp 0.2.3 → 0.2.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/README.md +18 -0
- package/dist/bridge.d.ts +6 -52
- package/dist/bridge.js +292 -802
- package/dist/bridgeSession.d.ts +96 -0
- package/dist/bridgeSession.js +438 -0
- package/dist/cli.d.ts +3 -29
- package/dist/cli.js +54 -168
- package/dist/cli.mjs +237 -1372
- package/dist/discovery.d.ts +5 -6
- package/dist/discovery.js +5 -8
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/internal/agent-protocol.d.ts +50 -13
- package/dist/internal/agent-protocol.js +145 -19
- package/dist/project.d.ts +1 -1
- package/dist/project.js +2 -2
- package/dist/projectSessionClient.d.ts +5 -4
- package/dist/projectSessionClient.js +38 -13
- package/dist/protocol.d.ts +2 -1
- package/dist/protocol.js +2 -1
- package/dist/server.d.ts +3 -27
- package/dist/server.js +65 -136
- package/dist/sessionRegistry.js +1 -21
- package/package.json +1 -16
package/dist/discovery.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentActivity } from "./internal/agent-protocol.js";
|
|
2
|
-
import type {
|
|
3
|
-
import type { CanvasCommandInput } from "./
|
|
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
|
|
12
|
+
/** Routes MCP tools to one selected live project, following it 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<
|
|
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
|
|
36
|
+
/** Routes MCP tools to one selected live project, following it 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 {
|
|
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,
|
|
5
|
-
export {
|
|
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 {
|
|
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 {
|
|
5
|
+
export { BridgeSessionError, } from "./bridgeSession.js";
|
|
6
6
|
export * from "./protocol.js";
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* imports. Hosts can use the same validation and message shapes without
|
|
7
7
|
* pulling the companion into a browser bundle.
|
|
8
8
|
*/
|
|
9
|
-
export declare const AGENT_PROTOCOL_VERSION:
|
|
9
|
+
export declare const AGENT_PROTOCOL_VERSION: 3;
|
|
10
10
|
export declare const AGENT_PROTOCOL_LIMITS: {
|
|
11
11
|
readonly projectId: 256;
|
|
12
12
|
readonly workspaceRoot: 4096;
|
|
@@ -35,15 +35,8 @@ export declare const AGENT_PROTOCOL_LIMITS: {
|
|
|
35
35
|
readonly routeTitle: 512;
|
|
36
36
|
readonly routeCount: 64;
|
|
37
37
|
readonly canvasCardCount: 128;
|
|
38
|
+
readonly agentClientName: 120;
|
|
38
39
|
};
|
|
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
40
|
export type AgentProtocolVersion = typeof AGENT_PROTOCOL_VERSION;
|
|
48
41
|
/** A project identity is the pairing boundary for one companion process. */
|
|
49
42
|
export interface AgentProjectIdentity {
|
|
@@ -164,14 +157,14 @@ export interface AgentSketchAttachment extends AgentSketchMetadata {
|
|
|
164
157
|
export interface AgentDeliveredPrompt extends AgentPromptRequest {
|
|
165
158
|
readonly attachments?: readonly AgentSketchAttachment[];
|
|
166
159
|
}
|
|
167
|
-
export type AgentRequestStatus = "working" | "completed" | "failed" | "interrupted";
|
|
160
|
+
export type AgentRequestStatus = "delivered" | "working" | "completed" | "failed" | "interrupted";
|
|
168
161
|
export interface AgentRequestOutcome {
|
|
169
162
|
readonly requestId: string;
|
|
170
|
-
readonly status: AgentRequestStatus
|
|
163
|
+
readonly status: Exclude<AgentRequestStatus, "delivered">;
|
|
171
164
|
readonly summary?: string;
|
|
172
165
|
readonly error?: string;
|
|
173
166
|
}
|
|
174
|
-
export type AgentConnectionState = "offline" | "listening" | "paired" | "working";
|
|
167
|
+
export type AgentConnectionState = "offline" | "listening" | "paired" | "delivered" | "working";
|
|
175
168
|
export interface AgentStatusSnapshot {
|
|
176
169
|
readonly protocolVersion: AgentProtocolVersion;
|
|
177
170
|
readonly projectId: string;
|
|
@@ -269,7 +262,7 @@ export interface StatusEventRequest {
|
|
|
269
262
|
}
|
|
270
263
|
export interface AgentStatusUpdate {
|
|
271
264
|
readonly requestId: string;
|
|
272
|
-
readonly status: AgentRequestStatus
|
|
265
|
+
readonly status: Exclude<AgentRequestStatus, "delivered">;
|
|
273
266
|
readonly summary?: string;
|
|
274
267
|
readonly error?: string;
|
|
275
268
|
}
|
|
@@ -277,6 +270,29 @@ export interface ProtocolErrorShape {
|
|
|
277
270
|
readonly code: string;
|
|
278
271
|
readonly message: string;
|
|
279
272
|
}
|
|
273
|
+
/** Options a development host passes to the optional project bridge. */
|
|
274
|
+
export interface ProjectBridgeStartOptions {
|
|
275
|
+
readonly appRoot: string;
|
|
276
|
+
readonly projectId: string;
|
|
277
|
+
readonly origin: string;
|
|
278
|
+
/** Other explicit browser origins, such as loopback aliases on the same port. */
|
|
279
|
+
readonly allowedOrigins?: readonly string[];
|
|
280
|
+
}
|
|
281
|
+
/** The started project bridge as seen by the development host that owns it. */
|
|
282
|
+
export interface ProjectBridgeHandle {
|
|
283
|
+
readonly browser: {
|
|
284
|
+
readonly projectId: string;
|
|
285
|
+
readonly origin: string;
|
|
286
|
+
readonly bridgeUrl: string;
|
|
287
|
+
};
|
|
288
|
+
close(): Promise<void>;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* The `startProjectBridge` export of `@nudge-ui/mcp/project`. Hosts load it
|
|
292
|
+
* dynamically from the application's own dependencies, so this type is the
|
|
293
|
+
* only compile-time link between the two packages.
|
|
294
|
+
*/
|
|
295
|
+
export type StartProjectBridge = (options: ProjectBridgeStartOptions) => Promise<ProjectBridgeHandle>;
|
|
280
296
|
/**
|
|
281
297
|
* Returns a canonical origin or null. Credentials, paths, and fragments are
|
|
282
298
|
* rejected so the browser cannot broaden the pairing boundary accidentally.
|
|
@@ -309,5 +325,26 @@ export declare function isAgentStatusUpdate(value: unknown): value is AgentStatu
|
|
|
309
325
|
export declare function isCanvasState(value: unknown): value is CanvasState;
|
|
310
326
|
export declare function isCanvasGroup(value: unknown): value is CanvasGroup;
|
|
311
327
|
export declare function isCanvasCommand(value: unknown): value is CanvasCommand;
|
|
328
|
+
export declare function isAgentDeliveredPrompt(value: unknown): value is AgentDeliveredPrompt;
|
|
329
|
+
/** A Canvas acknowledgement or result; a successful one never carries an error. */
|
|
330
|
+
export declare function isCanvasCommandResult(value: unknown): value is CanvasCommandResult;
|
|
331
|
+
/** The connection state shown for a pairing, a listener, and the current request. */
|
|
332
|
+
export declare function deriveConnectionState(status: Pick<AgentStatusSnapshot, "listenerActive" | "paired" | "request">): AgentConnectionState;
|
|
333
|
+
/**
|
|
334
|
+
* Decodes a status snapshot from any peer. Unknown fields are dropped and the
|
|
335
|
+
* connection state is recomputed, so a snapshot can never claim a state its
|
|
336
|
+
* own fields contradict.
|
|
337
|
+
*/
|
|
338
|
+
export declare function decodeAgentStatusSnapshot(value: unknown): AgentStatusSnapshot | null;
|
|
339
|
+
/**
|
|
340
|
+
* Decodes the event inside a bridge envelope. A Canvas command is passed
|
|
341
|
+
* through as received: the Canvas controller validates it so a malformed
|
|
342
|
+
* command still receives an error acknowledgement instead of a timeout.
|
|
343
|
+
*/
|
|
344
|
+
export declare function decodeBridgeEvent(value: unknown): BridgeEvent | null;
|
|
345
|
+
/** Decodes a pairing or takeover response for the exact project and origin requested. */
|
|
346
|
+
export declare function decodePairingResponse(value: unknown, expected: Pick<PairingRequest, "projectId" | "origin">): PairingResponse | null;
|
|
347
|
+
/** Decodes the accepted-prompt response for the project that dispatched it. */
|
|
348
|
+
export declare function decodePromptDispatchResponse(value: unknown, projectId: string): PromptDispatchResponse | null;
|
|
312
349
|
/** Creates a stable protocol error shape without exposing thrown values. */
|
|
313
350
|
export declare function protocolError(code: string, message: string): ProtocolErrorShape;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* imports. Hosts can use the same validation and message shapes without
|
|
7
7
|
* pulling the companion into a browser bundle.
|
|
8
8
|
*/
|
|
9
|
-
export const AGENT_PROTOCOL_VERSION =
|
|
9
|
+
export const AGENT_PROTOCOL_VERSION = 3;
|
|
10
10
|
export const AGENT_PROTOCOL_LIMITS = {
|
|
11
11
|
projectId: 256,
|
|
12
12
|
workspaceRoot: 4096,
|
|
@@ -35,25 +35,8 @@ export const AGENT_PROTOCOL_LIMITS = {
|
|
|
35
35
|
routeTitle: 512,
|
|
36
36
|
routeCount: 64,
|
|
37
37
|
canvasCardCount: 128,
|
|
38
|
+
agentClientName: 120,
|
|
38
39
|
};
|
|
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
40
|
export const CANVAS_COMMAND_TYPES = [
|
|
58
41
|
"read-state",
|
|
59
42
|
"present-routes",
|
|
@@ -421,6 +404,149 @@ export function isCanvasCommand(value) {
|
|
|
421
404
|
return false;
|
|
422
405
|
}
|
|
423
406
|
}
|
|
407
|
+
export function isAgentDeliveredPrompt(value) {
|
|
408
|
+
if (!isRecord(value))
|
|
409
|
+
return false;
|
|
410
|
+
const { attachments, ...request } = value;
|
|
411
|
+
return isAgentPromptRequest(request)
|
|
412
|
+
&& (attachments === undefined || (Array.isArray(attachments)
|
|
413
|
+
&& attachments.length <= AGENT_PROTOCOL_LIMITS.sketchCount
|
|
414
|
+
&& attachments.every(isAgentSketchAttachment)));
|
|
415
|
+
}
|
|
416
|
+
function isCanvasCommandError(value) {
|
|
417
|
+
return isRecord(value) && hasOnlyKeys(value, ["code", "message"])
|
|
418
|
+
&& nonEmptyString(value.code, 128)
|
|
419
|
+
&& typeof value.message === "string" && value.message.length <= AGENT_PROTOCOL_LIMITS.prompt;
|
|
420
|
+
}
|
|
421
|
+
/** A Canvas acknowledgement or result; a successful one never carries an error. */
|
|
422
|
+
export function isCanvasCommandResult(value) {
|
|
423
|
+
if (!isRecord(value) || !hasOnlyKeys(value, ["commandId", "ok", "state", "group", "error"]))
|
|
424
|
+
return false;
|
|
425
|
+
return nonEmptyString(value.commandId, AGENT_PROTOCOL_LIMITS.commandId)
|
|
426
|
+
&& typeof value.ok === "boolean"
|
|
427
|
+
&& (value.state === undefined || isCanvasState(value.state))
|
|
428
|
+
&& (value.group === undefined || isCanvasGroup(value.group))
|
|
429
|
+
&& (value.error === undefined || (!value.ok && isCanvasCommandError(value.error)));
|
|
430
|
+
}
|
|
431
|
+
/** The connection state shown for a pairing, a listener, and the current request. */
|
|
432
|
+
export function deriveConnectionState(status) {
|
|
433
|
+
if (status.request?.status === "delivered")
|
|
434
|
+
return "delivered";
|
|
435
|
+
if (status.request?.status === "working")
|
|
436
|
+
return "working";
|
|
437
|
+
if (status.paired)
|
|
438
|
+
return "paired";
|
|
439
|
+
if (status.listenerActive)
|
|
440
|
+
return "listening";
|
|
441
|
+
return "offline";
|
|
442
|
+
}
|
|
443
|
+
function isRequestStatus(value) {
|
|
444
|
+
return value === "delivered" || value === "working" || value === "completed" || value === "failed" || value === "interrupted";
|
|
445
|
+
}
|
|
446
|
+
function decodeStatusRequest(value) {
|
|
447
|
+
if (value === null || value === undefined)
|
|
448
|
+
return null;
|
|
449
|
+
if (!isRecord(value))
|
|
450
|
+
return undefined;
|
|
451
|
+
const { status, summary, error, ...request } = value;
|
|
452
|
+
if (!isRequestStatus(status) || !isAgentPromptRequest(request)
|
|
453
|
+
|| !optionalString(summary, AGENT_PROTOCOL_LIMITS.prompt)
|
|
454
|
+
|| !optionalString(error, AGENT_PROTOCOL_LIMITS.prompt))
|
|
455
|
+
return undefined;
|
|
456
|
+
return {
|
|
457
|
+
...request,
|
|
458
|
+
status,
|
|
459
|
+
...(summary === undefined ? {} : { summary }),
|
|
460
|
+
...(error === undefined ? {} : { error }),
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Decodes a status snapshot from any peer. Unknown fields are dropped and the
|
|
465
|
+
* connection state is recomputed, so a snapshot can never claim a state its
|
|
466
|
+
* own fields contradict.
|
|
467
|
+
*/
|
|
468
|
+
export function decodeAgentStatusSnapshot(value) {
|
|
469
|
+
if (!isRecord(value)
|
|
470
|
+
|| value.protocolVersion !== AGENT_PROTOCOL_VERSION
|
|
471
|
+
|| !nonEmptyString(value.projectId, AGENT_PROTOCOL_LIMITS.projectId)
|
|
472
|
+
|| (value.connection !== "offline" && value.connection !== "listening"
|
|
473
|
+
&& value.connection !== "paired" && value.connection !== "delivered" && value.connection !== "working")
|
|
474
|
+
|| typeof value.listenerActive !== "boolean"
|
|
475
|
+
|| typeof value.paired !== "boolean"
|
|
476
|
+
|| !(value.pageUrl === undefined || value.pageUrl === null
|
|
477
|
+
|| nonEmptyString(value.pageUrl, AGENT_PROTOCOL_LIMITS.routeUrl)))
|
|
478
|
+
return null;
|
|
479
|
+
const request = decodeStatusRequest(value.request);
|
|
480
|
+
if (request === undefined)
|
|
481
|
+
return null;
|
|
482
|
+
const fields = { listenerActive: value.listenerActive, paired: value.paired, request };
|
|
483
|
+
return {
|
|
484
|
+
protocolVersion: AGENT_PROTOCOL_VERSION,
|
|
485
|
+
projectId: value.projectId,
|
|
486
|
+
connection: deriveConnectionState(fields),
|
|
487
|
+
...fields,
|
|
488
|
+
...(value.pageUrl === undefined ? {} : { pageUrl: value.pageUrl }),
|
|
489
|
+
...(typeof value.agentClientName === "string" && value.agentClientName.length > 0
|
|
490
|
+
? { agentClientName: value.agentClientName.slice(0, AGENT_PROTOCOL_LIMITS.agentClientName) }
|
|
491
|
+
: {}),
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Decodes the event inside a bridge envelope. A Canvas command is passed
|
|
496
|
+
* through as received: the Canvas controller validates it so a malformed
|
|
497
|
+
* command still receives an error acknowledgement instead of a timeout.
|
|
498
|
+
*/
|
|
499
|
+
export function decodeBridgeEvent(value) {
|
|
500
|
+
if (!isRecord(value))
|
|
501
|
+
return null;
|
|
502
|
+
switch (value.type) {
|
|
503
|
+
case "status":
|
|
504
|
+
case "connected": {
|
|
505
|
+
const status = decodeAgentStatusSnapshot(value.status);
|
|
506
|
+
return status ? { type: value.type, status } : null;
|
|
507
|
+
}
|
|
508
|
+
case "disconnected":
|
|
509
|
+
return {
|
|
510
|
+
type: "disconnected",
|
|
511
|
+
...(nonEmptyString(value.reason, AGENT_PROTOCOL_LIMITS.prompt) ? { reason: value.reason } : {}),
|
|
512
|
+
};
|
|
513
|
+
case "activity":
|
|
514
|
+
return isAgentActivity(value.activity) ? { type: "activity", activity: value.activity } : null;
|
|
515
|
+
case "canvas-command":
|
|
516
|
+
// SAFETY: the Canvas controller validates the command before acting on it.
|
|
517
|
+
return isRecord(value.command) ? { type: "canvas-command", command: value.command } : null;
|
|
518
|
+
default:
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/** Decodes a pairing or takeover response for the exact project and origin requested. */
|
|
523
|
+
export function decodePairingResponse(value, expected) {
|
|
524
|
+
if (!isRecord(value)
|
|
525
|
+
|| value.protocolVersion !== AGENT_PROTOCOL_VERSION
|
|
526
|
+
|| value.projectId !== expected.projectId
|
|
527
|
+
|| value.origin !== expected.origin
|
|
528
|
+
|| !nonEmptyString(value.sessionToken, AGENT_PROTOCOL_LIMITS.sessionToken)
|
|
529
|
+
|| !(value.pageUrl === undefined || nonEmptyString(value.pageUrl, AGENT_PROTOCOL_LIMITS.routeUrl)))
|
|
530
|
+
return null;
|
|
531
|
+
const status = decodeAgentStatusSnapshot(value.status);
|
|
532
|
+
if (!status)
|
|
533
|
+
return null;
|
|
534
|
+
return {
|
|
535
|
+
protocolVersion: AGENT_PROTOCOL_VERSION,
|
|
536
|
+
projectId: expected.projectId,
|
|
537
|
+
origin: expected.origin,
|
|
538
|
+
sessionToken: value.sessionToken,
|
|
539
|
+
...(value.pageUrl === undefined ? {} : { pageUrl: value.pageUrl }),
|
|
540
|
+
status,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
/** Decodes the accepted-prompt response for the project that dispatched it. */
|
|
544
|
+
export function decodePromptDispatchResponse(value, projectId) {
|
|
545
|
+
if (!isRecord(value) || (value.status !== "delivered" && value.status !== "working") || !isAgentPromptRequest(value.request)
|
|
546
|
+
|| value.request.projectId !== projectId)
|
|
547
|
+
return null;
|
|
548
|
+
return { request: value.request, status: value.status };
|
|
549
|
+
}
|
|
424
550
|
/** Creates a stable protocol error shape without exposing thrown values. */
|
|
425
551
|
export function protocolError(code, message) {
|
|
426
552
|
if (!nonEmptyString(code, 128) || !nonEmptyString(message, AGENT_PROTOCOL_LIMITS.prompt)) {
|
package/dist/project.d.ts
CHANGED
|
@@ -50,5 +50,5 @@ export declare function resolveWorkspaceRoot(input: string): Promise<string>;
|
|
|
50
50
|
* adapters. The descriptor contains a private control credential and is never
|
|
51
51
|
* suitable for browser bundles or user-facing tool results.
|
|
52
52
|
*/
|
|
53
|
-
export declare
|
|
53
|
+
export declare const startProjectBridge: (options: ProjectBridgeOptions) => Promise<ProjectBridgeRuntime>;
|
|
54
54
|
export {};
|
package/dist/project.js
CHANGED
|
@@ -97,7 +97,7 @@ async function closeProjectBridge(descriptorPath, bridge) {
|
|
|
97
97
|
* adapters. The descriptor contains a private control credential and is never
|
|
98
98
|
* suitable for browser bundles or user-facing tool results.
|
|
99
99
|
*/
|
|
100
|
-
export
|
|
100
|
+
export const startProjectBridge = (async (options) => {
|
|
101
101
|
const origin = canonicalOrigin(options.origin);
|
|
102
102
|
if (!origin)
|
|
103
103
|
throw new TypeError("origin must be a canonical HTTP(S) origin");
|
|
@@ -148,4 +148,4 @@ export async function startProjectBridge(options) {
|
|
|
148
148
|
browser: { projectId, origin, bridgeUrl: address.url },
|
|
149
149
|
close,
|
|
150
150
|
};
|
|
151
|
-
}
|
|
151
|
+
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { AgentActivity } from "./internal/agent-protocol.js";
|
|
2
|
-
import type {
|
|
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;
|
|
@@ -13,6 +12,7 @@ export declare class ProjectSessionResponseError extends Error {
|
|
|
13
12
|
readonly status: number;
|
|
14
13
|
constructor(status: number, message: string);
|
|
15
14
|
}
|
|
15
|
+
/** Reads and validates a bridge's health; an invalid body is a classified response error. */
|
|
16
16
|
export declare function readSessionHealth(session: StoredProjectSession, timeoutMs?: number): Promise<SessionHealth>;
|
|
17
17
|
/** Owns one project's claim, heartbeat, and authenticated control requests. */
|
|
18
18
|
export declare class ProjectSessionClient {
|
|
@@ -26,7 +26,8 @@ export declare class ProjectSessionClient {
|
|
|
26
26
|
private send;
|
|
27
27
|
private acquireClaim;
|
|
28
28
|
private claim;
|
|
29
|
-
waitForPrompt(signal?: AbortSignal, agentClientName?: string): Promise<
|
|
29
|
+
waitForPrompt(signal?: AbortSignal, agentClientName?: string): Promise<AgentDeliveredPrompt>;
|
|
30
|
+
getRequest(requestId: string): Promise<AgentDeliveredPrompt>;
|
|
30
31
|
getStatus(): Promise<AgentStatusSnapshot>;
|
|
31
32
|
reportActivity(activity: AgentActivity): Promise<void>;
|
|
32
33
|
updateRequestStatus(update: AgentStatusUpdate): Promise<AgentStatusSnapshot>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { decodeAgentStatusSnapshot, isAgentDeliveredPrompt, isCanvasCommandResult, } from "./internal/agent-protocol.js";
|
|
2
3
|
import { AGENT_CONTROL_ENDPOINTS } from "./protocol.js";
|
|
3
4
|
const CONTROL_TIMEOUT_MS = 5_000;
|
|
4
5
|
const CANVAS_TIMEOUT_MS = 65_000;
|
|
@@ -11,7 +12,21 @@ export class ProjectSessionResponseError extends Error {
|
|
|
11
12
|
this.status = status;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
const ignoreBody = () => undefined;
|
|
16
|
+
function decodeSessionHealth(body) {
|
|
17
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
18
|
+
return null;
|
|
19
|
+
const health = body;
|
|
20
|
+
const status = decodeAgentStatusSnapshot(health.status);
|
|
21
|
+
if (typeof health.projectId !== "string" || health.available !== true
|
|
22
|
+
|| typeof health.claimed !== "boolean" || !status)
|
|
23
|
+
return null;
|
|
24
|
+
return { projectId: health.projectId, available: true, claimed: health.claimed, status };
|
|
25
|
+
}
|
|
26
|
+
function guarded(guard) {
|
|
27
|
+
return (body) => guard(body) ? body : null;
|
|
28
|
+
}
|
|
29
|
+
async function requestJson(session, path, decode, init = {}, { signal, timeoutMs = CONTROL_TIMEOUT_MS } = {}) {
|
|
15
30
|
const response = await fetch(`${session.endpoint}${path}`, {
|
|
16
31
|
...init,
|
|
17
32
|
redirect: "error",
|
|
@@ -24,12 +39,18 @@ async function requestJson(session, path, init = {}, { signal, timeoutMs = CONTR
|
|
|
24
39
|
});
|
|
25
40
|
const body = await response.json().catch(() => undefined);
|
|
26
41
|
if (!response.ok) {
|
|
27
|
-
|
|
42
|
+
const error = body?.error;
|
|
43
|
+
throw new ProjectSessionResponseError(response.status, typeof error?.message === "string" ? error.message : `Nudge project bridge returned ${response.status}.`);
|
|
44
|
+
}
|
|
45
|
+
const decoded = decode(body);
|
|
46
|
+
if (decoded === null) {
|
|
47
|
+
throw new ProjectSessionResponseError(response.status, `Nudge project bridge returned an invalid ${path} response.`);
|
|
28
48
|
}
|
|
29
|
-
return
|
|
49
|
+
return decoded;
|
|
30
50
|
}
|
|
51
|
+
/** Reads and validates a bridge's health; an invalid body is a classified response error. */
|
|
31
52
|
export async function readSessionHealth(session, timeoutMs = CONTROL_TIMEOUT_MS) {
|
|
32
|
-
return await requestJson(session, AGENT_CONTROL_ENDPOINTS.health, {}, { timeoutMs });
|
|
53
|
+
return await requestJson(session, AGENT_CONTROL_ENDPOINTS.health, decodeSessionHealth, {}, { timeoutMs });
|
|
33
54
|
}
|
|
34
55
|
/** Owns one project's claim, heartbeat, and authenticated control requests. */
|
|
35
56
|
export class ProjectSessionClient {
|
|
@@ -43,17 +64,17 @@ export class ProjectSessionClient {
|
|
|
43
64
|
this.descriptor = descriptor;
|
|
44
65
|
this.agentId = agentId;
|
|
45
66
|
}
|
|
46
|
-
async send(path, payload = {}, options) {
|
|
47
|
-
return await requestJson(this.descriptor, path, {
|
|
67
|
+
async send(path, decode, payload = {}, options) {
|
|
68
|
+
return await requestJson(this.descriptor, path, decode, {
|
|
48
69
|
method: "POST",
|
|
49
70
|
body: JSON.stringify({ agentId: this.agentId, ...payload }),
|
|
50
71
|
}, options);
|
|
51
72
|
}
|
|
52
73
|
async acquireClaim() {
|
|
53
|
-
await this.send(AGENT_CONTROL_ENDPOINTS.claim);
|
|
74
|
+
await this.send(AGENT_CONTROL_ENDPOINTS.claim, ignoreBody);
|
|
54
75
|
this.claimed = true;
|
|
55
76
|
this.heartbeat = setInterval(() => {
|
|
56
|
-
void this.send(AGENT_CONTROL_ENDPOINTS.heartbeat).catch(() => undefined);
|
|
77
|
+
void this.send(AGENT_CONTROL_ENDPOINTS.heartbeat, ignoreBody).catch(() => undefined);
|
|
57
78
|
}, 10_000);
|
|
58
79
|
this.heartbeat.unref();
|
|
59
80
|
}
|
|
@@ -74,7 +95,11 @@ export class ProjectSessionClient {
|
|
|
74
95
|
}
|
|
75
96
|
async waitForPrompt(signal, agentClientName) {
|
|
76
97
|
await this.claim();
|
|
77
|
-
return await this.send(AGENT_CONTROL_ENDPOINTS.listen, { agentClientName }, { signal, timeoutMs: null });
|
|
98
|
+
return await this.send(AGENT_CONTROL_ENDPOINTS.listen, guarded(isAgentDeliveredPrompt), { agentClientName }, { signal, timeoutMs: null });
|
|
99
|
+
}
|
|
100
|
+
async getRequest(requestId) {
|
|
101
|
+
await this.claim();
|
|
102
|
+
return await this.send(AGENT_CONTROL_ENDPOINTS.request, guarded(isAgentDeliveredPrompt), { requestId });
|
|
78
103
|
}
|
|
79
104
|
async getStatus() {
|
|
80
105
|
const health = await readSessionHealth(this.descriptor);
|
|
@@ -82,16 +107,16 @@ export class ProjectSessionClient {
|
|
|
82
107
|
}
|
|
83
108
|
async reportActivity(activity) {
|
|
84
109
|
await this.claim();
|
|
85
|
-
await this.send(AGENT_CONTROL_ENDPOINTS.activity, { activity });
|
|
110
|
+
await this.send(AGENT_CONTROL_ENDPOINTS.activity, ignoreBody, { activity });
|
|
86
111
|
}
|
|
87
112
|
async updateRequestStatus(update) {
|
|
88
113
|
await this.claim();
|
|
89
|
-
return await this.send(AGENT_CONTROL_ENDPOINTS.reportStatus, { update });
|
|
114
|
+
return await this.send(AGENT_CONTROL_ENDPOINTS.reportStatus, decodeAgentStatusSnapshot, { update });
|
|
90
115
|
}
|
|
91
116
|
async dispatchCanvasCommand(input) {
|
|
92
117
|
await this.claim();
|
|
93
118
|
const command = { ...input, commandId: input.commandId ?? randomUUID() };
|
|
94
|
-
return await this.send(AGENT_CONTROL_ENDPOINTS.canvas, { command }, { timeoutMs: CANVAS_TIMEOUT_MS });
|
|
119
|
+
return await this.send(AGENT_CONTROL_ENDPOINTS.canvas, guarded(isCanvasCommandResult), { command }, { timeoutMs: CANVAS_TIMEOUT_MS });
|
|
95
120
|
}
|
|
96
121
|
async close() {
|
|
97
122
|
this.closing = true;
|
|
@@ -102,6 +127,6 @@ export class ProjectSessionClient {
|
|
|
102
127
|
if (this.heartbeat)
|
|
103
128
|
clearInterval(this.heartbeat);
|
|
104
129
|
this.heartbeat = null;
|
|
105
|
-
await this.send(AGENT_CONTROL_ENDPOINTS.release).catch(() => undefined);
|
|
130
|
+
await this.send(AGENT_CONTROL_ENDPOINTS.release, ignoreBody).catch(() => undefined);
|
|
106
131
|
}
|
|
107
132
|
}
|
package/dist/protocol.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* subpath keeps transport names discoverable for Node consumers while
|
|
6
6
|
* preserving one protocol vocabulary for browser and agent adapters.
|
|
7
7
|
*/
|
|
8
|
-
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, CANVAS_COMMAND_TYPES, canonicalOrigin,
|
|
8
|
+
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, CANVAS_COMMAND_TYPES, canonicalOrigin, isAgentPromptRequest, isAgentSketchAttachment, isAgentSketchCaptureMetadata, isAgentSketchMetadata, isAgentStatusUpdate, isAgentActivity, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateSketchAttachments, validateRoutes, } from "./internal/agent-protocol.js";
|
|
9
9
|
export type { AgentActivity, BridgeActivityEvent, AgentConnectionState, AgentProjectIdentity, AgentPromptRequest, AgentDeliveredPrompt, AgentSketchAttachment, AgentSketchCaptureMetadata, AgentSketchMetadata, AgentRequestOutcome, AgentRequestStatus, AgentRoute, AgentStatusSnapshot, AgentStatusUpdate, BridgeCanvasCommandEvent, BridgeConnectedEvent, BridgeDisconnectedEvent, BridgeEnvelope, BridgeEvent, BridgeEventType, BridgeStatusEvent, CanvasCommand, CanvasCommandAcknowledgement, CanvasCommandError, CanvasCommandResult, CanvasGroup, CanvasGroupOwner, CanvasState, FitCanvasCommand, FocusCanvasGroupCommand, PairingRequest, PairingResponse, PresentRoutesCommand, PromptDispatchRequest, PromptDispatchResponse, ReadCanvasStateCommand, RemoveCanvasGroupCommand, StatusEventRequest, } from "./internal/agent-protocol.js";
|
|
10
10
|
/** Stable endpoint names used by the loopback browser bridge. */
|
|
11
11
|
export declare const BRIDGE_ENDPOINTS: {
|
|
@@ -26,6 +26,7 @@ export declare const AGENT_CONTROL_ENDPOINTS: {
|
|
|
26
26
|
readonly release: "/__nudge/agent/release";
|
|
27
27
|
readonly listen: "/__nudge/agent/listen";
|
|
28
28
|
readonly status: "/__nudge/agent/status";
|
|
29
|
+
readonly request: "/__nudge/agent/request";
|
|
29
30
|
readonly reportStatus: "/__nudge/agent/report-status";
|
|
30
31
|
readonly activity: "/__nudge/agent/activity";
|
|
31
32
|
readonly canvas: "/__nudge/agent/canvas";
|
package/dist/protocol.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* subpath keeps transport names discoverable for Node consumers while
|
|
6
6
|
* preserving one protocol vocabulary for browser and agent adapters.
|
|
7
7
|
*/
|
|
8
|
-
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, CANVAS_COMMAND_TYPES, canonicalOrigin,
|
|
8
|
+
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, CANVAS_COMMAND_TYPES, canonicalOrigin, isAgentPromptRequest, isAgentSketchAttachment, isAgentSketchCaptureMetadata, isAgentSketchMetadata, isAgentStatusUpdate, isAgentActivity, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateSketchAttachments, validateRoutes, } from "./internal/agent-protocol.js";
|
|
9
9
|
/** Stable endpoint names used by the loopback browser bridge. */
|
|
10
10
|
export const BRIDGE_ENDPOINTS = {
|
|
11
11
|
health: "/health",
|
|
@@ -25,6 +25,7 @@ export const AGENT_CONTROL_ENDPOINTS = {
|
|
|
25
25
|
release: "/__nudge/agent/release",
|
|
26
26
|
listen: "/__nudge/agent/listen",
|
|
27
27
|
status: "/__nudge/agent/status",
|
|
28
|
+
request: "/__nudge/agent/request",
|
|
28
29
|
reportStatus: "/__nudge/agent/report-status",
|
|
29
30
|
activity: "/__nudge/agent/activity",
|
|
30
31
|
canvas: "/__nudge/agent/canvas",
|
package/dist/server.d.ts
CHANGED
|
@@ -1,43 +1,19 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
3
|
-
import type { AgentProjectIdentity } from "./internal/agent-protocol.js";
|
|
4
|
-
import { type BrowserBridge, type BrowserBridgeOptions, type BridgeAddress } from "./bridge.js";
|
|
5
3
|
import { AdapterRouting } from "./adapterRouting.js";
|
|
6
|
-
import { DiscoveredProjectRouter } from "./discovery.js";
|
|
7
4
|
export declare const MCP_SERVER_NAME = "nudge-ui";
|
|
8
5
|
export declare const MCP_SERVER_VERSION: string;
|
|
9
6
|
/** Instructions are sent through MCP initialization for every host. */
|
|
10
7
|
export declare const MCP_SERVER_INSTRUCTIONS: string;
|
|
11
|
-
export interface AgentCompanionOptions extends BrowserBridgeOptions {
|
|
12
|
-
readonly workspaceRoot?: string;
|
|
13
|
-
readonly serverName?: string;
|
|
14
|
-
readonly serverVersion?: string;
|
|
15
|
-
}
|
|
16
|
-
export interface AgentCompanionStartResult {
|
|
17
|
-
readonly address: BridgeAddress;
|
|
18
|
-
}
|
|
19
|
-
export interface AgentCompanion {
|
|
20
|
-
readonly bridge: BrowserBridge;
|
|
21
|
-
readonly mcpServer: McpServer;
|
|
22
|
-
readonly project: AgentProjectIdentity;
|
|
23
|
-
start(transport?: Transport): Promise<AgentCompanionStartResult>;
|
|
24
|
-
close(): Promise<void>;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Creates one project-scoped MCP companion and its loopback browser bridge.
|
|
28
|
-
* The returned companion is inert until `start` is called.
|
|
29
|
-
*/
|
|
30
|
-
export declare function createAgentCompanion(options: AgentCompanionOptions): AgentCompanion;
|
|
31
8
|
export interface DiscoveredAgentAdapter {
|
|
32
9
|
readonly mcpServer: McpServer;
|
|
33
|
-
readonly router:
|
|
10
|
+
readonly router: AdapterRouting;
|
|
34
11
|
start(transport?: Transport): Promise<void>;
|
|
35
12
|
close(): Promise<void>;
|
|
36
13
|
}
|
|
37
14
|
/** Creates a stdio MCP adapter that discovers project-owned bridges. */
|
|
38
|
-
export declare function createDiscoveredAgentAdapter(options
|
|
39
|
-
readonly workspaceRoot?: string;
|
|
15
|
+
export declare function createDiscoveredAgentAdapter(options?: {
|
|
40
16
|
readonly registryRoot?: string;
|
|
41
17
|
readonly serverName?: string;
|
|
42
18
|
readonly serverVersion?: string;
|
|
43
|
-
}):
|
|
19
|
+
}): DiscoveredAgentAdapter;
|