@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.
- package/README.md +18 -0
- package/dist/adapterRouting.d.ts +0 -1
- package/dist/adapterRouting.js +0 -1
- package/dist/bridge.d.ts +6 -57
- package/dist/bridge.js +293 -806
- package/dist/bridgeSession.d.ts +87 -0
- package/dist/bridgeSession.js +438 -0
- package/dist/cli.d.ts +3 -30
- package/dist/cli.js +54 -169
- 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 +53 -38
- package/dist/internal/agent-protocol.js +151 -42
- package/dist/project.d.ts +2 -7
- package/dist/project.js +5 -10
- package/dist/projectSessionClient.d.ts +4 -6
- package/dist/projectSessionClient.js +37 -15
- package/dist/protocol.d.ts +4 -20
- package/dist/protocol.js +3 -18
- package/dist/server.d.ts +3 -28
- package/dist/server.js +65 -137
- package/dist/sessionRegistry.d.ts +0 -2
- package/dist/sessionRegistry.js +1 -23
- 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
|
-
/**
|
|
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<
|
|
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
|
-
/**
|
|
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 {
|
|
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";
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
13
|
-
//
|
|
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
|
-
|
|
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 {
|
|
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<
|
|
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>;
|