nudge-ui 0.2.0 → 0.2.2
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/bin/nudge-ui.mjs +3 -1
- package/dist/cli/agentCommand.d.ts +8 -0
- package/dist/cli/agentCommand.js +59 -0
- package/dist/client.mjs +878 -11
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +63 -11
- package/dist/hosts/astro/integration.d.ts +2 -2
- package/dist/hosts/astro/integration.js +4 -3
- package/dist/hosts/environment.d.ts +15 -0
- package/dist/hosts/environment.js +16 -0
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +56 -7
- package/dist/hosts/next/wrapper.d.ts +2 -0
- package/dist/hosts/next/wrapper.js +32 -3
- package/dist/hosts/projectBridge.d.ts +21 -0
- package/dist/hosts/projectBridge.js +98 -0
- package/dist/hosts/static/cli.d.ts +1 -1
- package/dist/hosts/static/cli.js +6 -1
- package/dist/hosts/static/manifest.d.ts +1 -1
- package/dist/hosts/static/manifest.js +3 -1
- package/dist/hosts/static/server.js +45 -3
- package/dist/hosts/vite/vite.d.ts +6 -1
- package/dist/hosts/vite/vite.js +82 -15
- package/dist/inspector/agent/McpConnectionDialog.d.ts +5 -4
- package/dist/inspector/agent/McpConnectionDialog.js +20 -37
- package/dist/inspector/agent/client.d.ts +16 -3
- package/dist/inspector/agent/client.js +114 -11
- package/dist/inspector/agent/connectionStatus.d.ts +3 -3
- package/dist/inspector/agent/connectionStatus.js +11 -4
- package/dist/inspector/agent/httpTransport.d.ts +8 -0
- package/dist/inspector/agent/httpTransport.js +78 -18
- package/dist/inspector/agent/protocol.d.ts +11 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +1 -1
- package/dist/inspector/agent/verification.js +69 -12
- package/dist/inspector/canvas/CanvasCard.d.ts +5 -3
- package/dist/inspector/canvas/CanvasCard.js +181 -91
- package/dist/inspector/canvas/CanvasElementOverlay.js +288 -52
- package/dist/inspector/canvas/CanvasToolbar.d.ts +12 -0
- package/dist/inspector/canvas/CanvasToolbar.js +24 -0
- package/dist/inspector/canvas/CanvasWorkspace.d.ts +4 -1
- package/dist/inspector/canvas/CanvasWorkspace.js +410 -72
- package/dist/inspector/canvas/activeCanvasDocument.d.ts +5 -0
- package/dist/inspector/canvas/activeCanvasDocument.js +23 -0
- package/dist/inspector/canvas/canvasResize.d.ts +30 -0
- package/dist/inspector/canvas/canvasResize.js +52 -0
- package/dist/inspector/canvas/canvasStore.d.ts +30 -6
- package/dist/inspector/canvas/canvasStore.js +128 -61
- package/dist/inspector/canvas/frameProtocol.d.ts +82 -5
- package/dist/inspector/canvas/frameProtocol.js +63 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +4 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +16 -0
- package/dist/inspector/canvas/linkEligibility.d.ts +0 -5
- package/dist/inspector/canvas/linkEligibility.js +0 -29
- package/dist/inspector/canvas/projection.d.ts +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +42 -29
- package/dist/inspector/canvas/rendererElementSelector.d.ts +1 -1
- package/dist/inspector/canvas/rendererElementSelector.js +385 -58
- package/dist/inspector/canvas/rendererMessageRouter.d.ts +11 -0
- package/dist/inspector/canvas/rendererMessageRouter.js +43 -0
- package/dist/inspector/canvas/rendererSelectionProxy.js +6 -2
- package/dist/inspector/canvas/sessionStore.d.ts +1 -0
- package/dist/inspector/canvas/sessionStore.js +12 -4
- package/dist/inspector/canvas/staleChangeDetector.js +16 -14
- package/dist/inspector/changes/changesLog.js +31 -1
- package/dist/inspector/changes/managedStyleProjection.d.ts +1 -1
- package/dist/inspector/changes/managedStyleProjection.js +3 -3
- package/dist/inspector/changes/structuralTypes.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.d.ts +4 -0
- package/dist/inspector/changes/workspaceChanges.js +29 -3
- package/dist/inspector/clientManifest.d.ts +7 -0
- package/dist/inspector/clientManifest.js +12 -0
- package/dist/inspector/componentSemantics/adapterRegistry.js +4 -3
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +2 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +16 -3
- package/dist/inspector/index.d.ts +2 -0
- package/dist/inspector/index.js +80 -32
- package/dist/inspector/inline-text/inlineTextDiagnostics.d.ts +10 -0
- package/dist/inspector/inline-text/inlineTextDiagnostics.js +65 -0
- package/dist/inspector/inline-text/inlineTextEditor.d.ts +3 -5
- package/dist/inspector/inline-text/inlineTextEditor.js +74 -206
- package/dist/inspector/inline-text/inlineTextFeedback.d.ts +5 -0
- package/dist/inspector/inline-text/inlineTextFeedback.js +27 -0
- package/dist/inspector/inline-text/inlineTextLifecycle.js +2 -0
- package/dist/inspector/inline-text/textEditChange.d.ts +14 -0
- package/dist/inspector/inline-text/textEditChange.js +165 -0
- package/dist/inspector/inspection/selectionProperty.d.ts +1 -0
- package/dist/inspector/inspection/selectionProperty.js +1 -0
- package/dist/inspector/overlay/DomNavigator.d.ts +10 -0
- package/dist/inspector/overlay/DomNavigator.js +40 -0
- package/dist/inspector/overlay/InspectorOverlay.js +6 -2
- package/dist/inspector/overlay/clickPolicy.d.ts +18 -2
- package/dist/inspector/overlay/clickPolicy.js +34 -3
- package/dist/inspector/overlay/elementSelector.d.ts +1 -1
- package/dist/inspector/overlay/elementSelector.js +18 -7
- package/dist/inspector/overlay/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +351 -0
- package/dist/inspector/overlay/structuralGestures.d.ts +2 -2
- package/dist/inspector/overlay/structuralGestures.js +12 -10
- package/dist/inspector/projection/managedStylesheet.js +4 -1
- package/dist/inspector/projection/renderedInstance.d.ts +2 -0
- package/dist/inspector/projection/renderedInstance.js +5 -1
- package/dist/inspector/projection/structuralProjection.d.ts +2 -0
- package/dist/inspector/projection/structuralProjection.js +85 -1
- package/dist/inspector/projection/structuralProjectionBoundary.js +17 -1
- package/dist/inspector/projection/textProjection.d.ts +2 -0
- package/dist/inspector/projection/textProjection.js +10 -4
- package/dist/inspector/prompt/clipboardHandoff.d.ts +1 -1
- package/dist/inspector/prompt/clipboardHandoff.js +3 -3
- package/dist/inspector/prompt/generatePrompt.d.ts +2 -1
- package/dist/inspector/prompt/generatePrompt.js +19 -2
- package/dist/inspector/runtime/editorShell.d.ts +2 -0
- package/dist/inspector/runtime/editorShell.js +4 -0
- package/dist/inspector/runtime/runtimeConfig.d.ts +5 -1
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/hierarchy.d.ts +6 -0
- package/dist/inspector/selection/hierarchy.js +8 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.d.ts +1 -1
- package/dist/inspector/settings/SettingsDialog.js +12 -2
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +26 -22
- package/dist/inspector/shell/CopyPromptButton.js +102 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +38 -42
- package/dist/inspector/shell/PeekOriginalButton.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalButton.js +57 -0
- package/dist/inspector/shell/PeekOriginalIcon.d.ts +7 -0
- package/dist/inspector/shell/PeekOriginalIcon.js +9 -0
- package/dist/inspector/shell/originalPreview.d.ts +9 -0
- package/dist/inspector/shell/originalPreview.js +61 -0
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +15 -0
- package/dist/inspector/sketch/SketchChanges.d.ts +2 -0
- package/dist/inspector/sketch/SketchChanges.js +72 -0
- package/dist/inspector/sketch/SketchEditor.d.ts +21 -0
- package/dist/inspector/sketch/SketchEditor.js +402 -0
- package/dist/inspector/sketch/SketchEntryButton.d.ts +4 -0
- package/dist/inspector/sketch/SketchEntryButton.js +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +11 -0
- package/dist/inspector/sketch/SketchFrameOverlay.js +119 -0
- package/dist/inspector/sketch/SketchLayersPanel.d.ts +2 -0
- package/dist/inspector/sketch/SketchLayersPanel.js +63 -0
- package/dist/inspector/sketch/SketchOverlay.d.ts +19 -0
- package/dist/inspector/sketch/SketchOverlay.js +420 -0
- package/dist/inspector/sketch/SketchPromptPanel.d.ts +15 -0
- package/dist/inspector/sketch/SketchPromptPanel.js +18 -0
- package/dist/inspector/sketch/SketchWorkspace.d.ts +13 -0
- package/dist/inspector/sketch/SketchWorkspace.js +152 -0
- package/dist/inspector/sketch/capture.d.ts +55 -0
- package/dist/inspector/sketch/capture.js +350 -0
- package/dist/inspector/sketch/freehand.d.ts +13 -0
- package/dist/inspector/sketch/freehand.js +64 -0
- package/dist/inspector/sketch/geometry.d.ts +17 -0
- package/dist/inspector/sketch/geometry.js +98 -0
- package/dist/inspector/sketch/handoff.d.ts +21 -0
- package/dist/inspector/sketch/handoff.js +72 -0
- package/dist/inspector/sketch/interaction.d.ts +21 -0
- package/dist/inspector/sketch/interaction.js +98 -0
- package/dist/inspector/sketch/model.d.ts +104 -0
- package/dist/inspector/sketch/model.js +282 -0
- package/dist/inspector/sketch/persistence.d.ts +21 -0
- package/dist/inspector/sketch/persistence.js +205 -0
- package/dist/inspector/sketch/raster.d.ts +22 -0
- package/dist/inspector/sketch/raster.js +203 -0
- package/dist/inspector/sketch/sketchNote.d.ts +10 -0
- package/dist/inspector/sketch/sketchNote.js +30 -0
- package/dist/inspector/sketch/store.d.ts +47 -0
- package/dist/inspector/sketch/store.js +434 -0
- package/dist/inspector/styleEditors/BorderEditor.js +15 -19
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -6
- package/dist/inspector/styleEditors/BoxShadowEditor.js +12 -1
- package/dist/inspector/styleEditors/ColorPicker.js +11 -36
- package/dist/inspector/styleEditors/GapField.d.ts +3 -2
- package/dist/inspector/styleEditors/GapField.js +2 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/LayoutSection.js +11 -8
- package/dist/inspector/styleEditors/SpacingBox.js +31 -33
- package/dist/inspector/styleEditors/layoutPreviewState.d.ts +5 -0
- package/dist/inspector/styleEditors/layoutPreviewState.js +19 -0
- package/dist/inspector/styleEditors/layoutValue.js +3 -0
- package/dist/inspector/styleEditors/nudgeValue.d.ts +11 -0
- package/dist/inspector/styleEditors/nudgeValue.js +93 -5
- package/dist/inspector/styleEditors/stylePresence.d.ts +7 -0
- package/dist/inspector/styleEditors/stylePresence.js +22 -0
- package/dist/inspector/styleEditors/textRelevance.d.ts +3 -0
- package/dist/inspector/styleEditors/textRelevance.js +35 -0
- package/dist/inspector/styleEditors/useFieldVisibility.d.ts +6 -0
- package/dist/inspector/styleEditors/useFieldVisibility.js +26 -0
- package/dist/inspector/tokens/TokenField.js +130 -7
- package/dist/inspector/tokens/TokensPanel.js +2 -1
- package/dist/inspector/tokens/dragPointerLock.d.ts +8 -0
- package/dist/inspector/tokens/dragPointerLock.js +70 -0
- package/dist/inspector/tokens/editActions.d.ts +2 -0
- package/dist/inspector/tokens/editActions.js +2 -2
- package/dist/inspector/tokens/resolution/cssomCollector.d.ts +1 -1
- package/dist/inspector/tokens/resolution/cssomCollector.js +9 -5
- package/dist/inspector/tokens/resolution/ruleMatching.d.ts +40 -0
- package/dist/inspector/tokens/resolution/ruleMatching.js +423 -0
- package/dist/inspector/tokens/resolution/selectorSemantics.d.ts +19 -3
- package/dist/inspector/tokens/resolution/selectorSemantics.js +378 -4
- package/dist/inspector/tokens/resolution.d.ts +3 -26
- package/dist/inspector/tokens/resolution.js +44 -735
- package/dist/inspector/tokens/tokenAvailability.d.ts +18 -0
- package/dist/inspector/tokens/tokenAvailability.js +204 -0
- package/dist/inspector/ui/Button.d.ts +1 -1
- package/dist/inspector/ui/Disclosure.d.ts +22 -0
- package/dist/inspector/ui/Disclosure.js +13 -0
- package/dist/inspector/ui/SideValuesField.js +2 -2
- package/dist/inspector/ui/StatusCallout.d.ts +4 -1
- package/dist/inspector/ui/StatusCallout.js +2 -2
- package/dist/inspector/ui/dragCursor.d.ts +8 -0
- package/dist/inspector/ui/dragCursor.js +21 -0
- package/dist/inspector/ui/styles.js +21 -17
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +276 -58
- package/dist/transport/editor.d.ts +47 -0
- package/dist/transport/editor.js +163 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +10 -0
- package/dist/transport/routes.js +10 -0
- package/package.json +3 -1
- package/dist/inspector/shell/DomNavigation.d.ts +0 -8
- package/dist/inspector/shell/DomNavigation.js +0 -30
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- package/dist/inspector/shell/panelLayout.js +0 -29
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useMemo, useSyncExternalStore } from "react";
|
|
2
2
|
import { isNudgeUiDev } from "../runtime/devFlag.js";
|
|
3
3
|
import { isDemoRuntime } from "../runtime/runtimeConfig.js";
|
|
4
|
-
import { HttpAgentBridgeTransport } from "./httpTransport.js";
|
|
4
|
+
import { AgentBridgeHttpError, HttpAgentBridgeTransport, shouldAutoConnectAgentBridge, } from "./httpTransport.js";
|
|
5
5
|
import { AGENT_PROTOCOL_VERSION, } from "./protocol.js";
|
|
6
6
|
const DISABLED_SNAPSHOT = Object.freeze({
|
|
7
7
|
state: "disabled",
|
|
@@ -9,10 +9,12 @@ const DISABLED_SNAPSHOT = Object.freeze({
|
|
|
9
9
|
listenerActive: false,
|
|
10
10
|
companionReachable: false,
|
|
11
11
|
paired: false,
|
|
12
|
+
pairedElsewhere: false,
|
|
12
13
|
request: null,
|
|
13
14
|
});
|
|
14
15
|
const DEFAULT_DISCOVERY_INTERVAL_MS = 2_000;
|
|
15
16
|
const SESSION_STORAGE_PREFIX = "nudge-ui-agent-session:";
|
|
17
|
+
const AUTO_CONNECT_DISABLED_PREFIX = "nudge-ui-agent-auto-connect-disabled:";
|
|
16
18
|
function randomId(prefix) {
|
|
17
19
|
const random = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
18
20
|
? crypto.randomUUID()
|
|
@@ -68,6 +70,31 @@ function clearStoredSession(projectId) {
|
|
|
68
70
|
// Ignore storage failures; disconnect still clears the live connection.
|
|
69
71
|
}
|
|
70
72
|
}
|
|
73
|
+
function autoConnectDisabled(projectId) {
|
|
74
|
+
if (typeof localStorage === "undefined")
|
|
75
|
+
return false;
|
|
76
|
+
try {
|
|
77
|
+
return localStorage.getItem(`${AUTO_CONNECT_DISABLED_PREFIX}${encodeURIComponent(projectId)}`) === "true";
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function setAutoConnectDisabled(projectId, disabled) {
|
|
84
|
+
if (typeof localStorage === "undefined")
|
|
85
|
+
return;
|
|
86
|
+
try {
|
|
87
|
+
const key = `${AUTO_CONNECT_DISABLED_PREFIX}${encodeURIComponent(projectId)}`;
|
|
88
|
+
if (disabled)
|
|
89
|
+
localStorage.setItem(key, "true");
|
|
90
|
+
else
|
|
91
|
+
localStorage.removeItem(key);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Privacy settings can disable storage. The current document still keeps
|
|
95
|
+
// the explicit disconnect preference in memory.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
71
98
|
function statusForConnection(status) {
|
|
72
99
|
if (status.connection === "working" || status.request?.status === "working")
|
|
73
100
|
return "working";
|
|
@@ -90,10 +117,10 @@ function hashRevision(value) {
|
|
|
90
117
|
return (hash >>> 0) & 0x7fff_ffff;
|
|
91
118
|
}
|
|
92
119
|
/** Creates a stable numeric revision for one prompt snapshot. */
|
|
93
|
-
export function createPromptRevision(changes, structuralChanges = []) {
|
|
120
|
+
export function createPromptRevision(changes, structuralChanges = [], sketches = []) {
|
|
94
121
|
let serialized;
|
|
95
122
|
try {
|
|
96
|
-
serialized = JSON.stringify({ changes, structuralChanges }) ?? "";
|
|
123
|
+
serialized = JSON.stringify({ changes, structuralChanges, sketches }) ?? "";
|
|
97
124
|
}
|
|
98
125
|
catch {
|
|
99
126
|
serialized = `${changes.length}:${structuralChanges.length}`;
|
|
@@ -128,13 +155,18 @@ export class AgentClient {
|
|
|
128
155
|
listenerActive = false;
|
|
129
156
|
companionReachable = false;
|
|
130
157
|
paired = false;
|
|
158
|
+
pairedElsewhere = false;
|
|
131
159
|
lastError;
|
|
160
|
+
autoConnect;
|
|
161
|
+
autoConnectSuppressed = false;
|
|
132
162
|
constructor(options) {
|
|
133
163
|
this.projectId = options.projectId;
|
|
134
164
|
this.origin = options.origin ?? (typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
135
165
|
this.transport = options.transport ?? new HttpAgentBridgeTransport(options.endpoint);
|
|
136
166
|
this.discoveryIntervalMs = options.discoveryIntervalMs ?? DEFAULT_DISCOVERY_INTERVAL_MS;
|
|
137
167
|
this.canvasCommandHandler = options.canvasCommandHandler;
|
|
168
|
+
this.autoConnect = options.autoConnect ?? shouldAutoConnectAgentBridge();
|
|
169
|
+
this.autoConnectSuppressed = this.autoConnect && autoConnectDisabled(this.projectId);
|
|
138
170
|
// The public demo renders the real inspector but intentionally keeps the
|
|
139
171
|
// local MCP/agent bridge inert.
|
|
140
172
|
this.enabled = isNudgeUiDev() && !isDemoRuntime();
|
|
@@ -181,6 +213,7 @@ export class AgentClient {
|
|
|
181
213
|
this.listenerActive = false;
|
|
182
214
|
this.companionReachable = false;
|
|
183
215
|
this.paired = false;
|
|
216
|
+
this.pairedElsewhere = false;
|
|
184
217
|
this.publish();
|
|
185
218
|
}
|
|
186
219
|
async restoreOrDiscover() {
|
|
@@ -261,14 +294,26 @@ export class AgentClient {
|
|
|
261
294
|
this.listenerActive = false;
|
|
262
295
|
this.companionReachable = false;
|
|
263
296
|
this.paired = false;
|
|
297
|
+
this.pairedElsewhere = false;
|
|
264
298
|
this.lastError = undefined;
|
|
265
299
|
this.publish();
|
|
266
300
|
void this.restoreOrDiscover();
|
|
267
301
|
}
|
|
268
302
|
/** Pairs the browser with a reachable companion, including an idle agent. */
|
|
269
303
|
async connect() {
|
|
304
|
+
return this.pairWith((request, signal) => this.transport.pair(request, signal));
|
|
305
|
+
}
|
|
306
|
+
/** Replaces an existing browser pairing after an explicit user action. */
|
|
307
|
+
async takeOver() {
|
|
308
|
+
if (!this.transport.takeOver)
|
|
309
|
+
return false;
|
|
310
|
+
return this.pairWith((request, signal) => this.transport.takeOver(request, signal));
|
|
311
|
+
}
|
|
312
|
+
async pairWith(pair) {
|
|
270
313
|
if (!this.enabled || !this.started || this.pairingAbort || this.paired || !this.companionReachable)
|
|
271
314
|
return false;
|
|
315
|
+
this.autoConnectSuppressed = false;
|
|
316
|
+
setAutoConnectDisabled(this.projectId, false);
|
|
272
317
|
if (this.connection === "working" || this.activeRequest?.status === "working")
|
|
273
318
|
return false;
|
|
274
319
|
const idleListenerActive = this.listenerActive;
|
|
@@ -285,7 +330,7 @@ export class AgentClient {
|
|
|
285
330
|
this.discoveryAbort?.abort();
|
|
286
331
|
this.pairingAbort = abortController;
|
|
287
332
|
try {
|
|
288
|
-
const pairing = await
|
|
333
|
+
const pairing = await pair(request, abortController.signal);
|
|
289
334
|
if (!this.started || abortController.signal.aborted)
|
|
290
335
|
return false;
|
|
291
336
|
if (pairing.protocolVersion !== undefined && pairing.protocolVersion !== AGENT_PROTOCOL_VERSION) {
|
|
@@ -301,6 +346,7 @@ export class AgentClient {
|
|
|
301
346
|
this.connection = "paired";
|
|
302
347
|
this.listenerActive = pairing.status.listenerActive;
|
|
303
348
|
this.paired = true;
|
|
349
|
+
this.pairedElsewhere = false;
|
|
304
350
|
if (this.discoveryTimer !== null) {
|
|
305
351
|
clearTimeout(this.discoveryTimer);
|
|
306
352
|
this.discoveryTimer = null;
|
|
@@ -319,6 +365,9 @@ export class AgentClient {
|
|
|
319
365
|
this.listenerActive = idleListenerActive;
|
|
320
366
|
this.connection = idleListenerActive ? "listening" : "offline";
|
|
321
367
|
this.paired = false;
|
|
368
|
+
this.pairedElsewhere = error instanceof AgentBridgeHttpError
|
|
369
|
+
&& error.status === 409
|
|
370
|
+
&& error.code === "already_paired";
|
|
322
371
|
this.publish(idleListenerActive ? "available" : "disconnected");
|
|
323
372
|
return false;
|
|
324
373
|
}
|
|
@@ -351,7 +400,7 @@ export class AgentClient {
|
|
|
351
400
|
* synchronously, before the transport promise resolves, so the UI cannot
|
|
352
401
|
* issue a second prompt while the first one is in flight.
|
|
353
402
|
*/
|
|
354
|
-
async dispatchPrompt(prompt, changeRevision) {
|
|
403
|
+
async dispatchPrompt(prompt, changeRevision, options = {}) {
|
|
355
404
|
if (!this.enabled || !this.started || !this.paired || !this.listenerActive || !this.sessionToken)
|
|
356
405
|
return null;
|
|
357
406
|
if (!prompt || this.activeRequest?.status === "working")
|
|
@@ -362,12 +411,17 @@ export class AgentClient {
|
|
|
362
411
|
sessionToken: this.sessionToken,
|
|
363
412
|
prompt,
|
|
364
413
|
...(changeRevision === undefined ? {} : { changeRevision }),
|
|
414
|
+
...(options.clientDispatchId === undefined ? {} : { clientDispatchId: options.clientDispatchId }),
|
|
415
|
+
...(options.attachments === undefined ? {} : { attachments: options.attachments }),
|
|
365
416
|
};
|
|
417
|
+
const sketches = options.attachments?.map(({ data: _data, ...metadata }) => metadata);
|
|
366
418
|
this.activeRequest = {
|
|
367
419
|
localRequestId,
|
|
368
420
|
requestId: localRequestId,
|
|
369
421
|
prompt,
|
|
370
422
|
...(changeRevision === undefined ? {} : { changeRevision }),
|
|
423
|
+
...(options.clientDispatchId === undefined ? {} : { clientDispatchId: options.clientDispatchId }),
|
|
424
|
+
...(sketches === undefined ? {} : { sketches }),
|
|
371
425
|
status: "working",
|
|
372
426
|
};
|
|
373
427
|
this.connection = "working";
|
|
@@ -381,7 +435,12 @@ export class AgentClient {
|
|
|
381
435
|
return null;
|
|
382
436
|
if (response?.request?.requestId) {
|
|
383
437
|
this.activeRequest = this.activeRequest && this.activeRequest.localRequestId === localRequestId
|
|
384
|
-
? {
|
|
438
|
+
? {
|
|
439
|
+
...this.activeRequest,
|
|
440
|
+
requestId: response.request.requestId,
|
|
441
|
+
...(response.request.clientDispatchId === undefined ? {} : { clientDispatchId: response.request.clientDispatchId }),
|
|
442
|
+
...(response.request.sketches === undefined ? {} : { sketches: response.request.sketches }),
|
|
443
|
+
}
|
|
385
444
|
: this.activeRequest;
|
|
386
445
|
}
|
|
387
446
|
if (response?.status && response.status !== "working") {
|
|
@@ -405,6 +464,9 @@ export class AgentClient {
|
|
|
405
464
|
}
|
|
406
465
|
/** Interrupts local request state and asks the companion to revoke pairing. */
|
|
407
466
|
disconnect(reason = "browser_disconnected") {
|
|
467
|
+
this.autoConnectSuppressed = true;
|
|
468
|
+
if (this.autoConnect)
|
|
469
|
+
setAutoConnectDisabled(this.projectId, true);
|
|
408
470
|
const token = this.sessionToken;
|
|
409
471
|
const active = this.activeRequest;
|
|
410
472
|
if (active?.status === "working") {
|
|
@@ -422,6 +484,7 @@ export class AgentClient {
|
|
|
422
484
|
this.connection = "offline";
|
|
423
485
|
this.listenerActive = false;
|
|
424
486
|
this.paired = false;
|
|
487
|
+
this.pairedElsewhere = false;
|
|
425
488
|
this.lastError = reason === "reconnect" || reason === "browser_disconnected" ? undefined : reason;
|
|
426
489
|
this.publish();
|
|
427
490
|
if (token) {
|
|
@@ -454,7 +517,22 @@ export class AgentClient {
|
|
|
454
517
|
else {
|
|
455
518
|
this.companionReachable = true;
|
|
456
519
|
if (this.connection !== "paired" && this.connection !== "working" && !this.pairingAbort) {
|
|
457
|
-
|
|
520
|
+
// Discovery is unauthenticated. Another browser tab can own the
|
|
521
|
+
// pairing, so do not adopt `paired` until this tab restores the
|
|
522
|
+
// shared token. The next poll runs restore first in case a sibling
|
|
523
|
+
// tab has just stored that token.
|
|
524
|
+
const pairedElsewhere = status.paired && !this.sessionToken;
|
|
525
|
+
this.applyStatus(status.paired ? {
|
|
526
|
+
...status,
|
|
527
|
+
connection: status.listenerActive ? "listening" : "offline",
|
|
528
|
+
paired: false,
|
|
529
|
+
request: null,
|
|
530
|
+
} : status, pairedElsewhere);
|
|
531
|
+
if (this.autoConnect
|
|
532
|
+
&& !status.paired
|
|
533
|
+
&& !this.autoConnectSuppressed
|
|
534
|
+
&& !autoConnectDisabled(this.projectId))
|
|
535
|
+
void this.connect();
|
|
458
536
|
}
|
|
459
537
|
}
|
|
460
538
|
}
|
|
@@ -463,6 +541,7 @@ export class AgentClient {
|
|
|
463
541
|
this.listenerActive = false;
|
|
464
542
|
this.companionReachable = false;
|
|
465
543
|
this.paired = false;
|
|
544
|
+
this.pairedElsewhere = false;
|
|
466
545
|
this.lastError = undefined;
|
|
467
546
|
this.publish();
|
|
468
547
|
}
|
|
@@ -473,6 +552,7 @@ export class AgentClient {
|
|
|
473
552
|
this.listenerActive = false;
|
|
474
553
|
this.companionReachable = false;
|
|
475
554
|
this.paired = false;
|
|
555
|
+
this.pairedElsewhere = false;
|
|
476
556
|
// Discovery failures are expected while the companion is not running;
|
|
477
557
|
// retain a quiet disconnected state rather than flashing an error.
|
|
478
558
|
this.lastError = undefined;
|
|
@@ -491,7 +571,7 @@ export class AgentClient {
|
|
|
491
571
|
return;
|
|
492
572
|
this.discoveryTimer = setTimeout(() => {
|
|
493
573
|
this.discoveryTimer = null;
|
|
494
|
-
void this.
|
|
574
|
+
void this.restoreOrDiscover();
|
|
495
575
|
}, this.discoveryIntervalMs);
|
|
496
576
|
}
|
|
497
577
|
scheduleReconnect() {
|
|
@@ -538,6 +618,12 @@ export class AgentClient {
|
|
|
538
618
|
},
|
|
539
619
|
};
|
|
540
620
|
}
|
|
621
|
+
// Every tab with the shared browser session receives the SSE command, but
|
|
622
|
+
// only the Canvas lease owner may answer it. A read-only tab reports this
|
|
623
|
+
// local sentinel; leave the command pending for the owning tab instead of
|
|
624
|
+
// racing its acknowledgement with a false failure.
|
|
625
|
+
if (!acknowledgement.ok && acknowledgement.error?.code === "workspace-locked")
|
|
626
|
+
return;
|
|
541
627
|
if (acknowledgement.commandId !== command.commandId) {
|
|
542
628
|
acknowledgement = {
|
|
543
629
|
commandId: command.commandId,
|
|
@@ -559,18 +645,19 @@ export class AgentClient {
|
|
|
559
645
|
// bounded timeout and reports any expired command to its agent host.
|
|
560
646
|
}
|
|
561
647
|
}
|
|
562
|
-
applyStatus(status) {
|
|
648
|
+
applyStatus(status, pairedElsewhere = false) {
|
|
563
649
|
this.companionReachable = true;
|
|
564
650
|
this.lastError = undefined;
|
|
565
651
|
this.listenerActive = status.listenerActive;
|
|
566
652
|
this.paired = status.paired;
|
|
653
|
+
this.pairedElsewhere = pairedElsewhere;
|
|
567
654
|
this.connection = statusForConnection(status);
|
|
568
655
|
if (status.paired && this.discoveryTimer !== null) {
|
|
569
656
|
clearTimeout(this.discoveryTimer);
|
|
570
657
|
this.discoveryTimer = null;
|
|
571
658
|
}
|
|
572
659
|
if (status.request) {
|
|
573
|
-
this.applyRequestStatus(status.request.status, status.request.requestId, status.request.summary, status.request.error, status.request.changeRevision);
|
|
660
|
+
this.applyRequestStatus(status.request.status, status.request.requestId, status.request.summary, status.request.error, status.request.changeRevision, status.request.clientDispatchId, status.request.sketches);
|
|
574
661
|
}
|
|
575
662
|
else if (status.listenerActive && status.paired && this.activeRequest?.status !== "working") {
|
|
576
663
|
// A re-armed MCP listen call is the lifecycle boundary for the previous
|
|
@@ -596,9 +683,10 @@ export class AgentClient {
|
|
|
596
683
|
this.listenerActive = false;
|
|
597
684
|
this.companionReachable = false;
|
|
598
685
|
this.paired = false;
|
|
686
|
+
this.pairedElsewhere = false;
|
|
599
687
|
this.publish();
|
|
600
688
|
}
|
|
601
|
-
applyRequestStatus(status, requestId, summary, error, changeRevision) {
|
|
689
|
+
applyRequestStatus(status, requestId, summary, error, changeRevision, clientDispatchId, sketches) {
|
|
602
690
|
const active = this.activeRequest;
|
|
603
691
|
if (active && active.requestId !== requestId && active.localRequestId !== requestId) {
|
|
604
692
|
// A fresh server id can arrive before the POST response. Associate it
|
|
@@ -613,6 +701,8 @@ export class AgentClient {
|
|
|
613
701
|
requestId,
|
|
614
702
|
prompt: "",
|
|
615
703
|
...(changeRevision === undefined ? {} : { changeRevision }),
|
|
704
|
+
...(clientDispatchId === undefined ? {} : { clientDispatchId }),
|
|
705
|
+
...(sketches === undefined ? {} : { sketches }),
|
|
616
706
|
status,
|
|
617
707
|
...(summary === undefined ? {} : { summary }),
|
|
618
708
|
...(error === undefined ? {} : { error }),
|
|
@@ -624,6 +714,8 @@ export class AgentClient {
|
|
|
624
714
|
requestId,
|
|
625
715
|
status,
|
|
626
716
|
...(changeRevision === undefined ? {} : { changeRevision }),
|
|
717
|
+
...(clientDispatchId === undefined ? {} : { clientDispatchId }),
|
|
718
|
+
...(sketches === undefined ? {} : { sketches }),
|
|
627
719
|
...(summary === undefined ? {} : { summary }),
|
|
628
720
|
...(error === undefined ? {} : { error }),
|
|
629
721
|
};
|
|
@@ -643,7 +735,15 @@ export class AgentClient {
|
|
|
643
735
|
this.listenerActive = false;
|
|
644
736
|
this.companionReachable = false;
|
|
645
737
|
this.paired = false;
|
|
738
|
+
this.pairedElsewhere = false;
|
|
646
739
|
this.lastError = reason;
|
|
740
|
+
if (reason === "browser_disconnected") {
|
|
741
|
+
// A remote takeover deliberately invalidates this browser's token. Do
|
|
742
|
+
// not immediately race the new browser by auto-pairing again.
|
|
743
|
+
this.autoConnectSuppressed = true;
|
|
744
|
+
if (this.autoConnect)
|
|
745
|
+
setAutoConnectDisabled(this.projectId, true);
|
|
746
|
+
}
|
|
647
747
|
this.publish();
|
|
648
748
|
this.scheduleReconnect();
|
|
649
749
|
}
|
|
@@ -656,9 +756,12 @@ export class AgentClient {
|
|
|
656
756
|
listenerActive: this.listenerActive,
|
|
657
757
|
companionReachable: this.companionReachable,
|
|
658
758
|
paired: this.paired,
|
|
759
|
+
pairedElsewhere: this.pairedElsewhere,
|
|
659
760
|
request: request ? {
|
|
660
761
|
requestId: request.requestId,
|
|
661
762
|
...(request.changeRevision === undefined ? {} : { changeRevision: request.changeRevision }),
|
|
763
|
+
...(request.clientDispatchId === undefined ? {} : { clientDispatchId: request.clientDispatchId }),
|
|
764
|
+
...(request.sketches === undefined ? {} : { sketches: request.sketches }),
|
|
662
765
|
status: request.status,
|
|
663
766
|
...(request.summary === undefined ? {} : { summary: request.summary }),
|
|
664
767
|
...(request.error === undefined ? {} : { error: request.error }),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { AgentClientSnapshot } from "./client.js";
|
|
2
|
-
export type AgentConnectionStatusKind = "unavailable" | "connecting" | "working-disconnected" | "working" | "not-found" | "ready-to-connect" | "connected-not-listening" | "listening";
|
|
2
|
+
export type AgentConnectionStatusKind = "unavailable" | "connecting" | "working-disconnected" | "working" | "paired-elsewhere" | "not-found" | "ready-to-connect" | "connected-not-listening" | "listening";
|
|
3
3
|
export interface AgentConnectionStatus {
|
|
4
4
|
readonly kind: AgentConnectionStatusKind;
|
|
5
5
|
readonly label: string;
|
|
6
6
|
readonly tone: "neutral" | "accent" | "warning" | "danger";
|
|
7
7
|
readonly action?: {
|
|
8
|
-
readonly kind: "connect" | "
|
|
9
|
-
readonly label: "Connect" | "
|
|
8
|
+
readonly kind: "connect" | "takeover";
|
|
9
|
+
readonly label: "Connect" | "Take over";
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
12
|
/** Returns the user-facing connection state shared by the prompt UI and MCP panel. */
|
|
@@ -6,6 +6,14 @@ export function getAgentConnectionStatus(snapshot) {
|
|
|
6
6
|
if (snapshot.state === "pairing") {
|
|
7
7
|
return { kind: "connecting", label: "Connecting…", tone: "accent" };
|
|
8
8
|
}
|
|
9
|
+
if (snapshot.pairedElsewhere) {
|
|
10
|
+
return {
|
|
11
|
+
kind: "paired-elsewhere",
|
|
12
|
+
label: "MCP active in another tab or window",
|
|
13
|
+
tone: "warning",
|
|
14
|
+
action: { kind: "takeover", label: "Take over" },
|
|
15
|
+
};
|
|
16
|
+
}
|
|
9
17
|
if (!snapshot.paired && (snapshot.state === "working" || snapshot.request?.status === "working")) {
|
|
10
18
|
return { kind: "working-disconnected", label: "Disconnected · Last work status unknown", tone: "warning" };
|
|
11
19
|
}
|
|
@@ -15,20 +23,19 @@ export function getAgentConnectionStatus(snapshot) {
|
|
|
15
23
|
if (snapshot.paired && !snapshot.listenerActive) {
|
|
16
24
|
return {
|
|
17
25
|
kind: "connected-not-listening",
|
|
18
|
-
label: "
|
|
26
|
+
label: "Project connected · Ask agent to listen",
|
|
19
27
|
tone: "accent",
|
|
20
|
-
action: { kind: "setup", label: "Set up" },
|
|
21
28
|
};
|
|
22
29
|
}
|
|
23
30
|
if (!snapshot.companionReachable) {
|
|
24
|
-
return { kind: "not-found", label: "
|
|
31
|
+
return { kind: "not-found", label: "Project bridge unavailable", tone: "neutral" };
|
|
25
32
|
}
|
|
26
33
|
if (snapshot.listenerActive) {
|
|
27
34
|
return { kind: "listening", label: "Agent listening", tone: "accent" };
|
|
28
35
|
}
|
|
29
36
|
return {
|
|
30
37
|
kind: "ready-to-connect",
|
|
31
|
-
label: "
|
|
38
|
+
label: "Project available · Connect this page",
|
|
32
39
|
tone: "accent",
|
|
33
40
|
action: { kind: "connect", label: "Connect" },
|
|
34
41
|
};
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { type AgentBridgeTransport, type AgentBridgeEndpointConfig, type AgentDiscoveryRequest, type AgentEventHandlers, type AgentEventSubscription, type AgentEventsRequest, type AgentPairRequest, type AgentPromptDispatch, type AgentDisconnectRequest, type AgentCanvasAcknowledgementRequest, type AgentSessionRequest, type AgentStatusSnapshot, type PairingResponse, type PromptDispatchResponse } from "./protocol.js";
|
|
2
|
+
/** Returns whether the dev host supplied a trusted project bridge endpoint. */
|
|
3
|
+
export declare function shouldAutoConnectAgentBridge(): boolean;
|
|
4
|
+
export declare class AgentBridgeHttpError extends Error {
|
|
5
|
+
readonly status: number;
|
|
6
|
+
readonly code?: string;
|
|
7
|
+
constructor(status: number, message: string, code?: string);
|
|
8
|
+
}
|
|
2
9
|
/**
|
|
3
10
|
* HTTP/SSE browser adapter for the companion's canonical endpoints.
|
|
4
11
|
*
|
|
@@ -11,6 +18,7 @@ export declare class HttpAgentBridgeTransport implements AgentBridgeTransport {
|
|
|
11
18
|
constructor(config?: AgentBridgeEndpointConfig);
|
|
12
19
|
discover(request: AgentDiscoveryRequest, signal?: AbortSignal): Promise<AgentStatusSnapshot | null>;
|
|
13
20
|
pair(request: AgentPairRequest, signal?: AbortSignal): Promise<PairingResponse>;
|
|
21
|
+
takeOver(request: AgentPairRequest, signal?: AbortSignal): Promise<PairingResponse>;
|
|
14
22
|
restore(request: AgentSessionRequest, signal?: AbortSignal): Promise<AgentStatusSnapshot | null>;
|
|
15
23
|
openEvents(request: AgentEventsRequest, handlers: AgentEventHandlers): AgentEventSubscription;
|
|
16
24
|
dispatch(request: AgentPromptDispatch, signal?: AbortSignal): Promise<PromptDispatchResponse>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { AGENT_PROTOCOL_VERSION, defaultBridgePort, } from "./protocol.js";
|
|
1
|
+
import { AGENT_PROTOCOL_VERSION, defaultBridgePort, isAgentPromptRequest, } from "./protocol.js";
|
|
2
|
+
import { getActiveCanvasDocument } from "../canvas/activeCanvasDocument.js";
|
|
2
3
|
function isRecord(value) {
|
|
3
4
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
5
|
}
|
|
@@ -17,21 +18,36 @@ function endpointValue(value) {
|
|
|
17
18
|
statusUrl: stringValue(value.statusUrl),
|
|
18
19
|
canvasAckUrl: stringValue(value.canvasAckUrl),
|
|
19
20
|
disconnectUrl: stringValue(value.disconnectUrl),
|
|
21
|
+
takeoverUrl: stringValue(value.takeoverUrl),
|
|
22
|
+
...(value.autoConnect === true ? { autoConnect: true } : {}),
|
|
20
23
|
};
|
|
21
24
|
return Object.values(config).some((entry) => entry !== undefined) ? config : undefined;
|
|
22
25
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
26
|
+
function configuredEndpointInDocument(doc) {
|
|
27
|
+
const target = doc.defaultView;
|
|
28
|
+
if (!target)
|
|
25
29
|
return undefined;
|
|
26
|
-
const target = window;
|
|
27
30
|
const globalConfig = endpointValue(target.__NUDGE_UI_AGENT_BRIDGE__)
|
|
28
31
|
?? endpointValue(target.__NUDGE_UI_AGENT__);
|
|
29
32
|
if (globalConfig)
|
|
30
33
|
return globalConfig;
|
|
31
|
-
const meta =
|
|
34
|
+
const meta = doc.querySelector('meta[name="nudge-ui-agent-bridge"]');
|
|
32
35
|
const content = meta?.content.trim();
|
|
33
36
|
return content ? { baseUrl: content } : undefined;
|
|
34
37
|
}
|
|
38
|
+
function configuredEndpoint() {
|
|
39
|
+
if (typeof document === "undefined")
|
|
40
|
+
return undefined;
|
|
41
|
+
const controllerConfig = configuredEndpointInDocument(document);
|
|
42
|
+
if (controllerConfig)
|
|
43
|
+
return controllerConfig;
|
|
44
|
+
const previewDocument = getActiveCanvasDocument();
|
|
45
|
+
return previewDocument ? configuredEndpointInDocument(previewDocument) : undefined;
|
|
46
|
+
}
|
|
47
|
+
/** Returns whether the dev host supplied a trusted project bridge endpoint. */
|
|
48
|
+
export function shouldAutoConnectAgentBridge() {
|
|
49
|
+
return configuredEndpoint()?.autoConnect === true;
|
|
50
|
+
}
|
|
35
51
|
function isLoopback(hostname) {
|
|
36
52
|
const normalized = hostname.toLowerCase();
|
|
37
53
|
return normalized === "localhost"
|
|
@@ -81,6 +97,16 @@ function headers() {
|
|
|
81
97
|
Accept: "application/json",
|
|
82
98
|
};
|
|
83
99
|
}
|
|
100
|
+
export class AgentBridgeHttpError extends Error {
|
|
101
|
+
status;
|
|
102
|
+
code;
|
|
103
|
+
constructor(status, message, code) {
|
|
104
|
+
super(message);
|
|
105
|
+
this.name = "AgentBridgeHttpError";
|
|
106
|
+
this.status = status;
|
|
107
|
+
this.code = code;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
84
110
|
async function readJson(response) {
|
|
85
111
|
let payload;
|
|
86
112
|
try {
|
|
@@ -92,7 +118,8 @@ async function readJson(response) {
|
|
|
92
118
|
if (!response.ok) {
|
|
93
119
|
const error = isRecord(payload) && isRecord(payload.error) ? payload.error : payload;
|
|
94
120
|
const message = isRecord(error) ? stringValue(error.message) : undefined;
|
|
95
|
-
|
|
121
|
+
const code = isRecord(error) ? stringValue(error.code) : undefined;
|
|
122
|
+
throw new AgentBridgeHttpError(response.status, message ?? `Agent bridge request failed (${response.status}).`, code);
|
|
96
123
|
}
|
|
97
124
|
return isRecord(payload) ? payload : {};
|
|
98
125
|
}
|
|
@@ -114,20 +141,24 @@ function statusPayload(value) {
|
|
|
114
141
|
let request = null;
|
|
115
142
|
if (rawRequest !== null && rawRequest !== undefined) {
|
|
116
143
|
if (!isRecord(rawRequest)
|
|
117
|
-
|| typeof rawRequest.requestId !== "string"
|
|
118
|
-
|| typeof rawRequest.projectId !== "string"
|
|
119
|
-
|| typeof rawRequest.prompt !== "string"
|
|
120
144
|
|| (rawRequest.status !== "working"
|
|
121
145
|
&& rawRequest.status !== "completed"
|
|
122
146
|
&& rawRequest.status !== "failed"
|
|
123
147
|
&& rawRequest.status !== "interrupted"))
|
|
124
148
|
return null;
|
|
125
|
-
|
|
149
|
+
const requestCandidate = {
|
|
126
150
|
requestId: rawRequest.requestId,
|
|
127
151
|
projectId: rawRequest.projectId,
|
|
128
152
|
prompt: rawRequest.prompt,
|
|
129
|
-
status: rawRequest.status,
|
|
130
153
|
...(typeof rawRequest.changeRevision === "number" ? { changeRevision: rawRequest.changeRevision } : {}),
|
|
154
|
+
...(rawRequest.clientDispatchId === undefined ? {} : { clientDispatchId: rawRequest.clientDispatchId }),
|
|
155
|
+
...(rawRequest.sketches === undefined ? {} : { sketches: rawRequest.sketches }),
|
|
156
|
+
};
|
|
157
|
+
if (!isAgentPromptRequest(requestCandidate))
|
|
158
|
+
return null;
|
|
159
|
+
request = {
|
|
160
|
+
...requestCandidate,
|
|
161
|
+
status: rawRequest.status,
|
|
131
162
|
...(typeof rawRequest.summary === "string" ? { summary: rawRequest.summary } : {}),
|
|
132
163
|
...(typeof rawRequest.error === "string" ? { error: rawRequest.error } : {}),
|
|
133
164
|
};
|
|
@@ -234,6 +265,34 @@ export class HttpAgentBridgeTransport {
|
|
|
234
265
|
status,
|
|
235
266
|
};
|
|
236
267
|
}
|
|
268
|
+
async takeOver(request, signal) {
|
|
269
|
+
const url = endpointUrl(this.config, request.projectId, "takeoverUrl", "/takeover", pageOrigin());
|
|
270
|
+
if (!url || typeof fetch !== "function")
|
|
271
|
+
throw new Error("Agent bridge is unavailable.");
|
|
272
|
+
const response = await fetch(url, {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: { ...headers(), "Content-Type": "application/json" },
|
|
275
|
+
body: JSON.stringify(request),
|
|
276
|
+
signal,
|
|
277
|
+
});
|
|
278
|
+
const payload = await readJson(response);
|
|
279
|
+
if (payload.protocolVersion !== AGENT_PROTOCOL_VERSION
|
|
280
|
+
|| payload.projectId !== request.projectId
|
|
281
|
+
|| payload.origin !== request.origin
|
|
282
|
+
|| typeof payload.sessionToken !== "string") {
|
|
283
|
+
throw new Error("Agent bridge returned an invalid takeover response.");
|
|
284
|
+
}
|
|
285
|
+
const status = statusPayload(payload.status);
|
|
286
|
+
if (!status)
|
|
287
|
+
throw new Error("Agent bridge returned an invalid status.");
|
|
288
|
+
return {
|
|
289
|
+
protocolVersion: AGENT_PROTOCOL_VERSION,
|
|
290
|
+
projectId: request.projectId,
|
|
291
|
+
origin: request.origin,
|
|
292
|
+
sessionToken: payload.sessionToken,
|
|
293
|
+
status,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
237
296
|
async restore(request, signal) {
|
|
238
297
|
const url = endpointUrl(this.config, request.projectId, "statusUrl", "/status", pageOrigin());
|
|
239
298
|
if (!url || typeof fetch !== "function")
|
|
@@ -299,20 +358,21 @@ export class HttpAgentBridgeTransport {
|
|
|
299
358
|
signal,
|
|
300
359
|
});
|
|
301
360
|
const payload = await readJson(response);
|
|
302
|
-
if (!isRecord(payload.request)
|
|
303
|
-
|| typeof payload.request.requestId !== "string"
|
|
304
|
-
|| typeof payload.request.projectId !== "string"
|
|
305
|
-
|| typeof payload.request.prompt !== "string"
|
|
306
|
-
|| payload.status !== "working") {
|
|
361
|
+
if (!isRecord(payload.request) || payload.status !== "working") {
|
|
307
362
|
throw new Error("Agent bridge returned an invalid prompt response.");
|
|
308
363
|
}
|
|
309
|
-
const
|
|
364
|
+
const requestCandidate = {
|
|
310
365
|
requestId: payload.request.requestId,
|
|
311
366
|
projectId: payload.request.projectId,
|
|
312
367
|
prompt: payload.request.prompt,
|
|
313
368
|
...(typeof payload.request.changeRevision === "number" ? { changeRevision: payload.request.changeRevision } : {}),
|
|
369
|
+
...(payload.request.clientDispatchId === undefined ? {} : { clientDispatchId: payload.request.clientDispatchId }),
|
|
370
|
+
...(payload.request.sketches === undefined ? {} : { sketches: payload.request.sketches }),
|
|
314
371
|
};
|
|
315
|
-
|
|
372
|
+
if (!isAgentPromptRequest(requestCandidate) || requestCandidate.projectId !== request.projectId) {
|
|
373
|
+
throw new Error("Agent bridge returned an invalid prompt response.");
|
|
374
|
+
}
|
|
375
|
+
return { request: requestCandidate, status: "working" };
|
|
316
376
|
}
|
|
317
377
|
async disconnect(request, signal) {
|
|
318
378
|
const url = endpointUrl(this.config, request.projectId, "disconnectUrl", "/disconnect", pageOrigin());
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentStatusSnapshot, CanvasCommand, CanvasCommandAcknowledgement, CanvasCommandResult, PairingResponse, PromptDispatchResponse } from "../../internal/agent-protocol.js";
|
|
1
|
+
import type { AgentStatusSnapshot, CanvasCommand, CanvasCommandAcknowledgement, CanvasCommandResult, AgentSketchAttachment, PairingResponse, PromptDispatchResponse } from "../../internal/agent-protocol.js";
|
|
2
2
|
/**
|
|
3
3
|
* Browser transport port for the shared Nudge agent protocol.
|
|
4
4
|
*
|
|
@@ -7,8 +7,8 @@ import type { AgentStatusSnapshot, CanvasCommand, CanvasCommandAcknowledgement,
|
|
|
7
7
|
* boundary local lets inspector tests use a deterministic fake while the
|
|
8
8
|
* companion remains the owner of MCP and Node-specific code.
|
|
9
9
|
*/
|
|
10
|
-
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, defaultBridgePort, CANVAS_COMMAND_TYPES, canonicalOrigin, isAgentPromptRequest, isAgentStatusUpdate, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateRoutes, } from "../../internal/agent-protocol.js";
|
|
11
|
-
export type { AgentConnectionState, AgentProjectIdentity, AgentPromptRequest, 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, AgentProtocolVersion, } from "../../internal/agent-protocol.js";
|
|
10
|
+
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, defaultBridgePort, CANVAS_COMMAND_TYPES, canonicalOrigin, isAgentPromptRequest, isAgentSketchAttachment, isAgentSketchAnnotation, isAgentSketchCaptureMetadata, isAgentSketchMetadata, isAgentStatusUpdate, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateSketchAttachments, validateRoutes, } from "../../internal/agent-protocol.js";
|
|
11
|
+
export type { AgentConnectionState, AgentProjectIdentity, AgentPromptRequest, AgentDeliveredPrompt, AgentSketchAttachment, AgentSketchAnnotation, 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, AgentProtocolVersion, } from "../../internal/agent-protocol.js";
|
|
12
12
|
export interface AgentBridgeEndpointConfig {
|
|
13
13
|
readonly baseUrl?: string;
|
|
14
14
|
readonly healthUrl?: string;
|
|
@@ -18,6 +18,9 @@ export interface AgentBridgeEndpointConfig {
|
|
|
18
18
|
readonly statusUrl?: string;
|
|
19
19
|
readonly canvasAckUrl?: string;
|
|
20
20
|
readonly disconnectUrl?: string;
|
|
21
|
+
readonly takeoverUrl?: string;
|
|
22
|
+
/** Pair automatically when this endpoint came from the trusted dev host. */
|
|
23
|
+
readonly autoConnect?: boolean;
|
|
21
24
|
}
|
|
22
25
|
export interface AgentDiscoveryRequest {
|
|
23
26
|
readonly projectId: string;
|
|
@@ -43,6 +46,8 @@ export interface AgentPromptDispatch {
|
|
|
43
46
|
readonly sessionToken: string;
|
|
44
47
|
readonly prompt: string;
|
|
45
48
|
readonly changeRevision?: number;
|
|
49
|
+
readonly clientDispatchId?: string;
|
|
50
|
+
readonly attachments?: readonly AgentSketchAttachment[];
|
|
46
51
|
}
|
|
47
52
|
export interface AgentDisconnectRequest {
|
|
48
53
|
readonly projectId: string;
|
|
@@ -83,6 +88,8 @@ export interface AgentBridgeTransport {
|
|
|
83
88
|
readonly openEvents: (request: AgentEventsRequest, handlers: AgentEventHandlers) => AgentEventSubscription;
|
|
84
89
|
readonly dispatch: (request: AgentPromptDispatch, signal?: AbortSignal) => Promise<PromptDispatchResponse | null>;
|
|
85
90
|
readonly disconnect?: (request: AgentDisconnectRequest, signal?: AbortSignal) => Promise<void>;
|
|
91
|
+
/** Deliberately revokes another browser pairing before pairing this page. */
|
|
92
|
+
readonly takeOver?: (request: AgentPairRequest, signal?: AbortSignal) => Promise<PairingResponse>;
|
|
86
93
|
readonly acknowledgeCanvasCommand?: (request: AgentCanvasAcknowledgementRequest, signal?: AbortSignal) => Promise<void>;
|
|
87
94
|
}
|
|
88
95
|
export interface AgentClientOptions {
|
|
@@ -90,6 +97,7 @@ export interface AgentClientOptions {
|
|
|
90
97
|
readonly origin?: string;
|
|
91
98
|
readonly transport?: AgentBridgeTransport;
|
|
92
99
|
readonly endpoint?: AgentBridgeEndpointConfig;
|
|
100
|
+
readonly autoConnect?: boolean;
|
|
93
101
|
readonly discoveryIntervalMs?: number;
|
|
94
102
|
readonly canvasCommandHandler?: (command: CanvasCommand) => CanvasCommandResult | Promise<CanvasCommandResult>;
|
|
95
103
|
}
|
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
* boundary local lets inspector tests use a deterministic fake while the
|
|
7
7
|
* companion remains the owner of MCP and Node-specific code.
|
|
8
8
|
*/
|
|
9
|
-
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, defaultBridgePort, CANVAS_COMMAND_TYPES, canonicalOrigin, isAgentPromptRequest, isAgentStatusUpdate, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateRoutes, } from "../../internal/agent-protocol.js";
|
|
9
|
+
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, defaultBridgePort, CANVAS_COMMAND_TYPES, canonicalOrigin, isAgentPromptRequest, isAgentSketchAttachment, isAgentSketchAnnotation, isAgentSketchCaptureMetadata, isAgentSketchMetadata, isAgentStatusUpdate, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateSketchAttachments, validateRoutes, } from "../../internal/agent-protocol.js";
|
|
@@ -32,7 +32,7 @@ export declare function getAgentDispatchSize(revision: number): number;
|
|
|
32
32
|
* observable after removing Nudge's own preview. Newer edits and ambiguous
|
|
33
33
|
* records are preserved.
|
|
34
34
|
*/
|
|
35
|
-
export declare function verifyAndReconcileHandoff(snapshot: HandoffSnapshot): Promise<number>;
|
|
35
|
+
export declare function verifyAndReconcileHandoff(snapshot: HandoffSnapshot, doc?: Document | null): Promise<number>;
|
|
36
36
|
/** Reconciles the captured records for one completed connected-agent request. */
|
|
37
37
|
export declare function verifyAndReconcileAgentDispatch(revision: number): Promise<number>;
|
|
38
38
|
/** Test/session reset for in-memory dispatch snapshots. */
|