nudge-ui 0.2.2 → 0.2.4
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 +164 -46
- package/dist/client.mjs +260 -61
- package/dist/css/value-semantics/colorSemantics.d.ts +4 -0
- package/dist/css/value-semantics/colorSemantics.js +17 -0
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -0
- package/dist/hosts/astro/clientTransport.js +38 -32
- package/dist/hosts/astro/integration.js +5 -1
- package/dist/hosts/astro/markdownIdentity.d.ts +28 -0
- package/dist/hosts/astro/markdownIdentity.js +81 -0
- package/dist/hosts/next/manifest.js +1 -1
- package/dist/hosts/next/sidecar.js +5 -1
- package/dist/hosts/static/manifest.js +1 -1
- package/dist/hosts/static/server.js +3 -0
- package/dist/hosts/vite/vite.d.ts +3 -0
- package/dist/hosts/vite/vite.js +24 -4
- package/dist/inspector/agent/McpConnectionDialog.js +13 -19
- package/dist/inspector/agent/activity.d.ts +18 -0
- package/dist/inspector/agent/activity.js +70 -0
- package/dist/inspector/agent/client.d.ts +2 -0
- package/dist/inspector/agent/client.js +29 -1
- package/dist/inspector/agent/connectionStatus.js +1 -1
- package/dist/inspector/agent/httpTransport.js +9 -2
- package/dist/inspector/agent/protocol.d.ts +6 -3
- package/dist/inspector/agent/protocol.js +1 -1
- package/dist/inspector/agent/verification.d.ts +15 -7
- package/dist/inspector/agent/verification.js +113 -41
- package/dist/inspector/canvas/AgentActivityOverlay.d.ts +13 -0
- package/dist/inspector/canvas/AgentActivityOverlay.js +111 -0
- package/dist/inspector/canvas/CanvasCard.d.ts +9 -2
- package/dist/inspector/canvas/CanvasCard.js +135 -38
- package/dist/inspector/canvas/CanvasElementOverlay.js +166 -16
- package/dist/inspector/canvas/CanvasToolIcons.d.ts +10 -0
- package/dist/inspector/canvas/CanvasToolIcons.js +20 -0
- package/dist/inspector/canvas/CanvasToolbar.js +8 -4
- package/dist/inspector/canvas/CanvasWorkspace.js +270 -122
- package/dist/inspector/canvas/StaleChangeIndicator.js +3 -13
- package/dist/inspector/canvas/agentPresentation.d.ts +8 -8
- package/dist/inspector/canvas/agentPresentation.js +64 -52
- package/dist/inspector/canvas/canvasGestures.d.ts +1 -1
- package/dist/inspector/canvas/canvasGestures.js +1 -1
- package/dist/inspector/canvas/canvasStore.d.ts +46 -82
- package/dist/inspector/canvas/canvasStore.js +251 -284
- package/dist/inspector/canvas/frameContent.d.ts +21 -0
- package/dist/inspector/canvas/frameContent.js +44 -0
- package/dist/inspector/canvas/frameProtocol.d.ts +21 -4
- package/dist/inspector/canvas/frameProtocol.js +3 -3
- package/dist/inspector/canvas/frameScrollbarStyles.d.ts +3 -0
- package/dist/inspector/canvas/frameScrollbarStyles.js +21 -0
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -1
- package/dist/inspector/canvas/keyboardShortcuts.js +4 -4
- package/dist/inspector/canvas/layout.d.ts +52 -0
- package/dist/inspector/canvas/layout.js +106 -0
- package/dist/inspector/canvas/projection.d.ts +15 -5
- package/dist/inspector/canvas/projection.js +69 -13
- package/dist/inspector/canvas/rendererBootstrap.js +32 -4
- package/dist/inspector/canvas/rendererElementSelector.js +47 -6
- package/dist/inspector/canvas/rendererStylesheet.js +2 -2
- package/dist/inspector/canvas/resizeHandleScale.js +1 -1
- package/dist/inspector/canvas/sessionStore.d.ts +11 -26
- package/dist/inspector/canvas/sessionStore.js +43 -183
- package/dist/inspector/canvas/staleChangeDetector.js +9 -3
- package/dist/inspector/canvas/useCanvasLayoutAnimation.d.ts +5 -0
- package/dist/inspector/canvas/useCanvasLayoutAnimation.js +107 -0
- package/dist/inspector/canvas/viewStore.d.ts +38 -0
- package/dist/inspector/canvas/viewStore.js +104 -0
- package/dist/inspector/canvas/wheelPan.d.ts +8 -0
- package/dist/inspector/canvas/wheelPan.js +8 -0
- package/dist/inspector/changes/changesLog.d.ts +9 -6
- package/dist/inspector/changes/changesLog.js +48 -32
- package/dist/inspector/changes/codecs.d.ts +1 -0
- package/dist/inspector/changes/codecs.js +10 -1
- package/dist/inspector/changes/draftChanges.d.ts +58 -0
- package/dist/inspector/changes/draftChanges.js +304 -0
- package/dist/inspector/changes/editModel.d.ts +10 -0
- package/dist/inspector/changes/editModel.js +9 -2
- package/dist/inspector/changes/previewDiagnostics.d.ts +4 -2
- package/dist/inspector/changes/previewDiagnostics.js +11 -25
- package/dist/inspector/changes/types.d.ts +7 -0
- package/dist/inspector/clientManifest.d.ts +1 -1
- package/dist/inspector/comments/CommentsOverlay.d.ts +2 -0
- package/dist/inspector/comments/CommentsOverlay.js +250 -0
- package/dist/inspector/comments/element.d.ts +6 -0
- package/dist/inspector/comments/element.js +54 -0
- package/dist/inspector/comments/store.d.ts +29 -0
- package/dist/inspector/comments/store.js +121 -0
- package/dist/inspector/comments/verification.d.ts +5 -0
- package/dist/inspector/comments/verification.js +52 -0
- package/dist/inspector/componentSemantics/adapterRegistry.d.ts +6 -1
- package/dist/inspector/componentSemantics/adapterRegistry.js +23 -1
- package/dist/inspector/componentSemantics/reactRuntime.d.ts +2 -1
- package/dist/inspector/componentSemantics/reactRuntime.js +38 -0
- package/dist/inspector/componentSemantics/runtimeBridge.d.ts +4 -2
- package/dist/inspector/componentSemantics/runtimeBridge.js +17 -0
- package/dist/inspector/componentSemantics/types.d.ts +11 -0
- package/dist/inspector/drafts/model.d.ts +35 -0
- package/dist/inspector/drafts/model.js +45 -0
- package/dist/inspector/drafts/store.d.ts +28 -0
- package/dist/inspector/drafts/store.js +231 -0
- package/dist/inspector/index.js +20 -27
- package/dist/inspector/inline-text/inlineTextEditor.js +7 -0
- package/dist/inspector/inspection/useBrowserCssInspection.js +15 -1
- package/dist/inspector/iterations/capture.d.ts +2 -0
- package/dist/inspector/iterations/capture.js +174 -0
- package/dist/inspector/iterations/client.d.ts +7 -0
- package/dist/inspector/iterations/client.js +52 -0
- package/dist/inspector/iterations/coordinator.d.ts +15 -0
- package/dist/inspector/iterations/coordinator.js +130 -0
- package/dist/inspector/iterations/prompt.d.ts +3 -0
- package/dist/inspector/iterations/prompt.js +26 -0
- package/dist/inspector/overlay/DomNavigator.js +21 -15
- package/dist/inspector/overlay/interactionStyles.js +3 -1
- package/dist/inspector/overlay/spacingGestures.d.ts +13 -1
- package/dist/inspector/overlay/spacingGestures.js +82 -5
- package/dist/inspector/projection/{workspaceProjection.d.ts → draftProjection.d.ts} +6 -6
- package/dist/inspector/projection/{workspaceProjection.js → draftProjection.js} +4 -4
- package/dist/inspector/projection/renderedInstance.d.ts +17 -3
- package/dist/inspector/projection/renderedInstance.js +62 -11
- package/dist/inspector/projection/structuralProjection.d.ts +2 -1
- package/dist/inspector/projection/structuralProjection.js +21 -17
- package/dist/inspector/projection/textProjection.d.ts +2 -1
- package/dist/inspector/projection/textProjection.js +64 -15
- package/dist/inspector/prompt/PromptSettingsDialog.js +1 -5
- package/dist/inspector/prompt/clipboardHandoff.d.ts +15 -18
- package/dist/inspector/prompt/clipboardHandoff.js +97 -143
- package/dist/inspector/prompt/generatePrompt.d.ts +4 -1
- package/dist/inspector/prompt/generatePrompt.js +22 -17
- package/dist/inspector/runtime/runtimeConfig.d.ts +9 -0
- package/dist/inspector/runtime/runtimeConfig.js +24 -0
- package/dist/inspector/selection/sourceSite.d.ts +12 -0
- package/dist/inspector/selection/sourceSite.js +28 -0
- package/dist/inspector/settings/SettingsDialog.js +21 -8
- package/dist/inspector/shell/AgentStatusButton.d.ts +11 -0
- package/dist/inspector/shell/AgentStatusButton.js +36 -0
- package/dist/inspector/shell/AppShell.js +4 -2
- package/dist/inspector/shell/ChangesLog.js +46 -26
- package/dist/inspector/shell/CopyPromptButton.js +184 -93
- package/dist/inspector/shell/EmptyState.js +9 -8
- package/dist/inspector/shell/InspectorShell.js +13 -21
- package/dist/inspector/shell/PeekOriginalButton.js +24 -23
- package/dist/inspector/shell/originalPreview.js +5 -5
- package/dist/inspector/shell/shortcuts.d.ts +3 -0
- package/dist/inspector/shell/shortcuts.js +11 -0
- package/dist/inspector/sketch/SketchEditor.js +14 -14
- package/dist/inspector/sketch/SketchEntryButton.js +1 -1
- package/dist/inspector/sketch/SketchFrameOverlay.d.ts +3 -1
- package/dist/inspector/sketch/SketchFrameOverlay.js +11 -8
- package/dist/inspector/sketch/SketchLayersPanel.js +13 -5
- package/dist/inspector/sketch/SketchOverlay.js +7 -7
- package/dist/inspector/sketch/SketchWorkspace.js +18 -12
- package/dist/inspector/sketch/interaction.d.ts +1 -0
- package/dist/inspector/sketch/interaction.js +8 -4
- package/dist/inspector/sketch/model.d.ts +5 -0
- package/dist/inspector/sketch/model.js +6 -2
- package/dist/inspector/sketch/raster.d.ts +1 -2
- package/dist/inspector/sketch/raster.js +68 -4
- package/dist/inspector/styleEditors/BackgroundEditor.d.ts +4 -0
- package/dist/inspector/styleEditors/BackgroundEditor.js +195 -0
- package/dist/inspector/styleEditors/BorderEditor.js +49 -78
- package/dist/inspector/styleEditors/BorderRadiusEditor.js +6 -1
- package/dist/inspector/styleEditors/BoxShadowEditor.d.ts +1 -2
- package/dist/inspector/styleEditors/BoxShadowEditor.js +132 -4
- package/dist/inspector/styleEditors/ColorPicker.d.ts +5 -1
- package/dist/inspector/styleEditors/ColorPicker.js +10 -4
- package/dist/inspector/styleEditors/GapField.js +3 -5
- package/dist/inspector/styleEditors/GridChildSection.d.ts +1 -1
- package/dist/inspector/styleEditors/GridChildSection.js +18 -9
- package/dist/inspector/styleEditors/GridPicker.d.ts +1 -1
- package/dist/inspector/styleEditors/GridPicker.js +8 -2
- package/dist/inspector/styleEditors/GridSection.js +1 -1
- package/dist/inspector/styleEditors/GridValueField.js +19 -14
- package/dist/inspector/styleEditors/ImageBackgroundEditor.d.ts +18 -0
- package/dist/inspector/styleEditors/ImageBackgroundEditor.js +118 -0
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.d.ts +2 -2
- package/dist/inspector/styleEditors/LayoutBlockedIndicator.js +3 -3
- package/dist/inspector/styleEditors/LayoutComboField.js +6 -4
- package/dist/inspector/styleEditors/LayoutDropdown.js +6 -3
- package/dist/inspector/styleEditors/LayoutSection.js +32 -11
- package/dist/inspector/styleEditors/SpacingBox.js +7 -5
- package/dist/inspector/styleEditors/Typography.js +11 -3
- package/dist/inspector/styleEditors/backgroundLayers.d.ts +24 -0
- package/dist/inspector/styleEditors/backgroundLayers.js +51 -0
- package/dist/inspector/styleEditors/gradientValue.d.ts +17 -0
- package/dist/inspector/styleEditors/gradientValue.js +114 -0
- package/dist/inspector/styleEditors/inlineAuthored.d.ts +10 -0
- package/dist/inspector/styleEditors/inlineAuthored.js +114 -0
- package/dist/inspector/styleEditors/layoutValue.d.ts +0 -8
- package/dist/inspector/styleEditors/layoutValue.js +0 -42
- package/dist/inspector/styleEditors/nudgeValue.d.ts +2 -2
- package/dist/inspector/styleEditors/nudgeValue.js +36 -8
- package/dist/inspector/tokens/TokenField.d.ts +7 -0
- package/dist/inspector/tokens/TokenField.js +35 -25
- package/dist/inspector/tokens/TokenLabel.js +5 -14
- package/dist/inspector/tokens/editActions.d.ts +1 -1
- package/dist/inspector/tokens/editActions.js +7 -1
- package/dist/inspector/ui/AtRuleContext.js +4 -6
- package/dist/inspector/ui/Button.d.ts +6 -2
- package/dist/inspector/ui/Button.js +4 -3
- package/dist/inspector/ui/ControlSurface.d.ts +2 -2
- package/dist/inspector/ui/ControlSurface.js +4 -3
- package/dist/inspector/ui/InlineStyleWarning.d.ts +11 -0
- package/dist/inspector/ui/InlineStyleWarning.js +13 -0
- package/dist/inspector/ui/InspectorPopover.d.ts +2 -1
- package/dist/inspector/ui/InspectorPopover.js +4 -5
- package/dist/inspector/ui/PopoverListbox.d.ts +2 -1
- package/dist/inspector/ui/PopoverListbox.js +8 -7
- package/dist/inspector/ui/SegmentedControl.d.ts +2 -1
- package/dist/inspector/ui/SegmentedControl.js +2 -2
- package/dist/inspector/ui/Select.d.ts +6 -1
- package/dist/inspector/ui/Select.js +7 -11
- package/dist/inspector/ui/SideValuesField.js +1 -1
- package/dist/inspector/ui/Tooltip.d.ts +26 -0
- package/dist/inspector/ui/Tooltip.js +32 -0
- package/dist/inspector/ui/classNames.d.ts +2 -0
- package/dist/inspector/ui/classNames.js +4 -0
- package/dist/inspector/ui/customCursors.d.ts +23 -0
- package/dist/inspector/ui/customCursors.js +122 -0
- package/dist/inspector/ui/dragCursor.d.ts +0 -2
- package/dist/inspector/ui/dragCursor.js +2 -10
- package/dist/inspector/ui/portalContainer.d.ts +2 -0
- package/dist/inspector/ui/portalContainer.js +6 -0
- package/dist/inspector/ui/styles.js +29 -21
- package/dist/inspector/workspace/commands.d.ts +9 -0
- package/dist/inspector/workspace/commands.js +82 -0
- package/dist/inspector/workspace/controller.d.ts +4 -0
- package/dist/inspector/workspace/controller.js +66 -0
- package/dist/inspector/workspace/handoff.d.ts +42 -0
- package/dist/inspector/workspace/handoff.js +115 -0
- package/dist/inspector/workspace/sentItems.d.ts +19 -0
- package/dist/inspector/workspace/sentItems.js +51 -0
- package/dist/inspector/workspace/timeline.d.ts +30 -0
- package/dist/inspector/workspace/timeline.js +108 -0
- package/dist/internal/agent-protocol.d.ts +19 -2
- package/dist/internal/agent-protocol.js +13 -0
- package/dist/nudge-ui.mjs +558 -311
- package/dist/project/artifacts.d.ts +13 -0
- package/dist/project/artifacts.js +253 -0
- package/dist/transport/artifacts.d.ts +8 -0
- package/dist/transport/artifacts.js +9 -0
- package/dist/transport/editor.js +3 -1
- package/dist/transport/routes.d.ts +2 -0
- package/dist/transport/routes.js +2 -0
- package/package.json +7 -3
- package/dist/inspector/changes/workspaceChanges.d.ts +0 -47
- package/dist/inspector/changes/workspaceChanges.js +0 -265
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportAgentActivity, synchronizeAgentActivity } from "./activity.js";
|
|
1
2
|
import { useEffect, useMemo, useSyncExternalStore } from "react";
|
|
2
3
|
import { isNudgeUiDev } from "../runtime/devFlag.js";
|
|
3
4
|
import { isDemoRuntime } from "../runtime/runtimeConfig.js";
|
|
@@ -153,6 +154,7 @@ export class AgentClient {
|
|
|
153
154
|
started = false;
|
|
154
155
|
connection = "offline";
|
|
155
156
|
listenerActive = false;
|
|
157
|
+
agentClientName;
|
|
156
158
|
companionReachable = false;
|
|
157
159
|
paired = false;
|
|
158
160
|
pairedElsewhere = false;
|
|
@@ -583,6 +585,10 @@ export class AgentClient {
|
|
|
583
585
|
}, this.discoveryIntervalMs);
|
|
584
586
|
}
|
|
585
587
|
handleEvent(event) {
|
|
588
|
+
if (event.type === "activity") {
|
|
589
|
+
reportAgentActivity(this.projectId, event.activity);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
586
592
|
if (event.type === "status" || event.type === "connected") {
|
|
587
593
|
if (event.status.projectId !== this.projectId || event.status.protocolVersion !== AGENT_PROTOCOL_VERSION) {
|
|
588
594
|
this.handleDisconnect("The agent bridge project or protocol does not match this page.");
|
|
@@ -649,6 +655,7 @@ export class AgentClient {
|
|
|
649
655
|
this.companionReachable = true;
|
|
650
656
|
this.lastError = undefined;
|
|
651
657
|
this.listenerActive = status.listenerActive;
|
|
658
|
+
this.agentClientName = status.agentClientName;
|
|
652
659
|
this.paired = status.paired;
|
|
653
660
|
this.pairedElsewhere = pairedElsewhere;
|
|
654
661
|
this.connection = statusForConnection(status);
|
|
@@ -754,6 +761,7 @@ export class AgentClient {
|
|
|
754
761
|
state,
|
|
755
762
|
connection: this.connection,
|
|
756
763
|
listenerActive: this.listenerActive,
|
|
764
|
+
...(this.agentClientName === undefined ? {} : { agentClientName: this.agentClientName }),
|
|
757
765
|
companionReachable: this.companionReachable,
|
|
758
766
|
paired: this.paired,
|
|
759
767
|
pairedElsewhere: this.pairedElsewhere,
|
|
@@ -788,17 +796,20 @@ export class AgentClient {
|
|
|
788
796
|
}
|
|
789
797
|
publish(stateOverride) {
|
|
790
798
|
this.snapshot = Object.freeze(this.makeSnapshot(stateOverride));
|
|
799
|
+
synchronizeAgentActivity(this.projectId, this.snapshot.request?.requestId, this.snapshot.paired && this.snapshot.request?.status === "working", this.snapshot.request?.clientDispatchId);
|
|
791
800
|
for (const listener of this.listeners)
|
|
792
801
|
listener();
|
|
793
802
|
}
|
|
794
803
|
}
|
|
795
804
|
let configuredTransport;
|
|
796
805
|
const clients = new Map();
|
|
806
|
+
const consumerCounts = new Map();
|
|
797
807
|
/** Sets a host/test transport for subsequently created project clients. */
|
|
798
808
|
export function configureAgentBridgeTransport(transport) {
|
|
799
809
|
for (const client of clients.values())
|
|
800
810
|
client.stop();
|
|
801
811
|
clients.clear();
|
|
812
|
+
consumerCounts.clear();
|
|
802
813
|
configuredTransport = transport;
|
|
803
814
|
}
|
|
804
815
|
/** Clears cached clients and their active streams. */
|
|
@@ -806,6 +817,22 @@ export function resetAgentClients() {
|
|
|
806
817
|
for (const client of clients.values())
|
|
807
818
|
client.stop();
|
|
808
819
|
clients.clear();
|
|
820
|
+
consumerCounts.clear();
|
|
821
|
+
}
|
|
822
|
+
function retainAgentClient(client) {
|
|
823
|
+
consumerCounts.set(client, (consumerCounts.get(client) ?? 0) + 1);
|
|
824
|
+
}
|
|
825
|
+
function releaseAgentClient(client) {
|
|
826
|
+
const count = consumerCounts.get(client);
|
|
827
|
+
// A reset already stopped the client and dropped its consumers.
|
|
828
|
+
if (count === undefined)
|
|
829
|
+
return;
|
|
830
|
+
if (count > 1) {
|
|
831
|
+
consumerCounts.set(client, count - 1);
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
consumerCounts.delete(client);
|
|
835
|
+
client.stop();
|
|
809
836
|
}
|
|
810
837
|
export function createAgentClient(options) {
|
|
811
838
|
return new AgentClient({ ...options, transport: options.transport ?? configuredTransport });
|
|
@@ -824,8 +851,9 @@ export function getAgentClient(projectId, options = {}) {
|
|
|
824
851
|
export function useAgentClient(projectId, suppliedClient) {
|
|
825
852
|
const client = useMemo(() => suppliedClient ?? getAgentClient(projectId), [projectId, suppliedClient]);
|
|
826
853
|
useEffect(() => {
|
|
854
|
+
retainAgentClient(client);
|
|
827
855
|
client.start();
|
|
828
|
-
return () => client
|
|
856
|
+
return () => releaseAgentClient(client);
|
|
829
857
|
}, [client]);
|
|
830
858
|
return useSyncExternalStore(client.subscribe, client.getSnapshot, client.getSnapshot);
|
|
831
859
|
}
|
|
@@ -23,7 +23,7 @@ export function getAgentConnectionStatus(snapshot) {
|
|
|
23
23
|
if (snapshot.paired && !snapshot.listenerActive) {
|
|
24
24
|
return {
|
|
25
25
|
kind: "connected-not-listening",
|
|
26
|
-
label: "
|
|
26
|
+
label: "MCP Ready - Ask agent to listen",
|
|
27
27
|
tone: "accent",
|
|
28
28
|
};
|
|
29
29
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AGENT_PROTOCOL_VERSION, defaultBridgePort, isAgentPromptRequest, } from "./protocol.js";
|
|
1
|
+
import { AGENT_PROTOCOL_VERSION, defaultBridgePort, isAgentPromptRequest, isAgentActivity, } from "./protocol.js";
|
|
2
2
|
import { getActiveCanvasDocument } from "../canvas/activeCanvasDocument.js";
|
|
3
3
|
function isRecord(value) {
|
|
4
4
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -168,6 +168,7 @@ function statusPayload(value) {
|
|
|
168
168
|
projectId: status.projectId,
|
|
169
169
|
connection: status.connection,
|
|
170
170
|
listenerActive: status.listenerActive,
|
|
171
|
+
...(typeof status.agentClientName === "string" ? { agentClientName: status.agentClientName.slice(0, 120) } : {}),
|
|
171
172
|
paired: status.paired,
|
|
172
173
|
request,
|
|
173
174
|
};
|
|
@@ -189,6 +190,8 @@ function eventPayload(value) {
|
|
|
189
190
|
if (type === "disconnected") {
|
|
190
191
|
return { type: "disconnected", ...(stringValue(event.reason) ? { reason: stringValue(event.reason) } : {}) };
|
|
191
192
|
}
|
|
193
|
+
if (type === "activity" && isAgentActivity(event.activity))
|
|
194
|
+
return { type: "activity", activity: event.activity };
|
|
192
195
|
if (type === "canvas-command" && isRecord(event.command)) {
|
|
193
196
|
// The Canvas controller owns command validation. Preserve the shared
|
|
194
197
|
// shape here so the parent stage can consume this event without a second
|
|
@@ -322,7 +325,10 @@ export class HttpAgentBridgeTransport {
|
|
|
322
325
|
const source = new EventSource(eventUrl.href);
|
|
323
326
|
let closed = false;
|
|
324
327
|
const handleMessage = (event) => {
|
|
325
|
-
const
|
|
328
|
+
const value = eventData(event);
|
|
329
|
+
if (isRecord(value) && "event" in value && (value.protocolVersion !== AGENT_PROTOCOL_VERSION || value.projectId !== request.projectId || value.sessionToken !== request.sessionToken))
|
|
330
|
+
return;
|
|
331
|
+
const parsed = eventPayload(value);
|
|
326
332
|
if (parsed)
|
|
327
333
|
handlers.onEvent(parsed);
|
|
328
334
|
};
|
|
@@ -330,6 +336,7 @@ export class HttpAgentBridgeTransport {
|
|
|
330
336
|
source.addEventListener("status", handleMessage);
|
|
331
337
|
source.addEventListener("connected", handleMessage);
|
|
332
338
|
source.addEventListener("disconnected", handleMessage);
|
|
339
|
+
source.addEventListener("activity", handleMessage);
|
|
333
340
|
source.addEventListener("canvas-command", handleMessage);
|
|
334
341
|
source.onerror = () => {
|
|
335
342
|
if (closed)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentStatusSnapshot, CanvasCommand, CanvasCommandAcknowledgement, CanvasCommandResult, AgentSketchAttachment, PairingResponse, PromptDispatchResponse } from "../../internal/agent-protocol.js";
|
|
1
|
+
import type { AgentActivity, 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, 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";
|
|
10
|
+
export { AGENT_PROTOCOL_LIMITS, AGENT_PROTOCOL_VERSION, defaultBridgePort, CANVAS_COMMAND_TYPES, canonicalOrigin, isAgentPromptRequest, isAgentSketchAttachment, isAgentSketchAnnotation, isAgentSketchCaptureMetadata, isAgentSketchMetadata, isAgentStatusUpdate, isAgentActivity, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateSketchAttachments, validateRoutes, } from "../../internal/agent-protocol.js";
|
|
11
|
+
export type { AgentActivity, BridgeActivityEvent, 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;
|
|
@@ -69,6 +69,9 @@ export interface AgentEventSubscription {
|
|
|
69
69
|
}
|
|
70
70
|
/** Events delivered by the companion's SSE stream. */
|
|
71
71
|
export type AgentBridgeEvent = {
|
|
72
|
+
readonly type: "activity";
|
|
73
|
+
readonly activity: AgentActivity;
|
|
74
|
+
} | {
|
|
72
75
|
readonly type: "status";
|
|
73
76
|
readonly status: AgentStatusSnapshot;
|
|
74
77
|
} | {
|
|
@@ -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, isAgentSketchAttachment, isAgentSketchAnnotation, isAgentSketchCaptureMetadata, isAgentSketchMetadata, isAgentStatusUpdate, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateSketchAttachments, 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, isAgentActivity, isAllowedOrigin, isCanvasCommand, isCanvasGroup, isCanvasState, isCanonicalOrigin, isProjectIdentity, isSameOriginRoute, protocolError, validateSketchAttachments, validateRoutes, } from "../../internal/agent-protocol.js";
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
+
import { type DraftTarget } from "../drafts/model.js";
|
|
1
2
|
import { type ChangeRecord } from "../changes/changesLog.js";
|
|
2
3
|
import { type StructuralChange } from "../projection/structuralProjection.js";
|
|
4
|
+
export interface HandoffOwner {
|
|
5
|
+
readonly draftId: string;
|
|
6
|
+
readonly draftRevision: number;
|
|
7
|
+
readonly target: DraftTarget;
|
|
8
|
+
readonly frameIds: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function captureHandoffOwner(cardId?: string | null): HandoffOwner;
|
|
3
11
|
export interface HandoffSnapshot {
|
|
12
|
+
readonly owner: HandoffOwner;
|
|
4
13
|
readonly changes: readonly ChangeRecord[];
|
|
5
14
|
readonly structuralChanges: readonly StructuralChange[];
|
|
6
15
|
}
|
|
7
|
-
/**
|
|
8
|
-
* User-facing agent completion state, named once.
|
|
9
|
-
* "completed" means the agent finished; "verified" means completed work was
|
|
10
|
-
* positively reconciled against source-rendered output.
|
|
11
|
-
*/
|
|
12
|
-
export type AgentCompletionStatus = "completed" | "verified";
|
|
13
16
|
/** Stable, preview-metadata-free identity for one prompt handoff record. */
|
|
14
17
|
export declare function handoffChangeFingerprint(change: ChangeRecord): string;
|
|
15
18
|
/** Stable identity for one structural record included in a prompt handoff. */
|
|
@@ -22,7 +25,7 @@ export declare function handoffStructuralFingerprint(change: StructuralChange):
|
|
|
22
25
|
*/
|
|
23
26
|
export declare function verifiedChangeKeys(sentChanges: readonly ChangeRecord[], doc?: Document): Set<string>;
|
|
24
27
|
/** Captures the exact canonical edits associated with one dispatched prompt. */
|
|
25
|
-
export declare function recordAgentDispatch(revision: number, changes: readonly ChangeRecord[], structuralChanges?: readonly StructuralChange[]): void;
|
|
28
|
+
export declare function recordAgentDispatch(revision: number, changes: readonly ChangeRecord[], structuralChanges?: readonly StructuralChange[], owner?: HandoffOwner): void;
|
|
26
29
|
/** Drops a captured revision when the bridge rejects its dispatch. */
|
|
27
30
|
export declare function discardAgentDispatch(revision: number): void;
|
|
28
31
|
/** Returns how many canonical edits were in flight for one dispatch. */
|
|
@@ -33,6 +36,11 @@ export declare function getAgentDispatchSize(revision: number): number;
|
|
|
33
36
|
* records are preserved.
|
|
34
37
|
*/
|
|
35
38
|
export declare function verifyAndReconcileHandoff(snapshot: HandoffSnapshot, doc?: Document | null): Promise<number>;
|
|
39
|
+
export declare function documentBelongsToHandoff(doc: Document, owner: HandoffOwner): boolean;
|
|
40
|
+
/** Returns the records captured for one dispatch until it is reconciled. */
|
|
41
|
+
export declare function getAgentDispatch(revision: number): HandoffSnapshot | null;
|
|
42
|
+
/** Records in the owner draft that still equal what a handoff sent. */
|
|
43
|
+
export declare function unchangedHandoffRecords(snapshot: HandoffSnapshot): Pick<HandoffSnapshot, "changes" | "structuralChanges">;
|
|
36
44
|
/** Reconciles the captured records for one completed connected-agent request. */
|
|
37
45
|
export declare function verifyAndReconcileAgentDispatch(revision: number): Promise<number>;
|
|
38
46
|
/** Test/session reset for in-memory dispatch snapshots. */
|
|
@@ -1,16 +1,26 @@
|
|
|
1
|
+
import { draftIdForCard } from "../drafts/store.js";
|
|
2
|
+
import { getCanvasCards } from "../canvas/canvasStore.js";
|
|
3
|
+
import { getRegisteredFrames, getCanvasCardIdForDocument } from "../canvas/projection.js";
|
|
4
|
+
import { applicationTarget, targetFromKey, targetKey } from "../drafts/model.js";
|
|
1
5
|
import { changeKey, tokenReference } from "../changes/model.js";
|
|
2
6
|
import { selectorForManagedChange } from "../changes/managedStyleProjection.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
7
|
+
import { getActiveDraftChanges, getDraftChanges, reconcileDraftChanges } from "../changes/draftChanges.js";
|
|
8
|
+
import { isComponentChange, isTextContentChange, isTokenChange, } from "../changes/changesLog.js";
|
|
5
9
|
import { verifyPreview } from "../projection/managedStylesheet.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
10
|
+
import { resolveMovedRenderedInstance } from "../projection/renderedInstance.js";
|
|
11
|
+
import { relocateSourceSite, sourceSiteSelector } from "../selection/sourceSite.js";
|
|
12
|
+
import { boundRuntimeEvidence, normalizeRuntimeTag } from "../runtime/staticHtmlRuntimeIdentity.js";
|
|
13
|
+
import { documentStateKey, } from "../projection/structuralProjection.js";
|
|
8
14
|
import { resolveTextProjectionTarget, resolveTextProjectionTextNode, } from "../projection/textProjection.js";
|
|
9
|
-
import {
|
|
10
|
-
import { isCanvasProjectionRevisionCurrent,
|
|
11
|
-
import { getActiveCanvasDocument } from "../canvas/activeCanvasDocument.js";
|
|
12
|
-
import { getSelectedElement } from "../selection/selectionStore.js";
|
|
15
|
+
import { applyHostDraftProjection, compileDraftProjection, } from "../projection/draftProjection.js";
|
|
16
|
+
import { isCanvasProjectionRevisionCurrent, restoreCanonicalFrameProjection, projectDraftToDocument, } from "../canvas/projection.js";
|
|
13
17
|
import { isEditorShellDocument } from "../runtime/editorShell.js";
|
|
18
|
+
export function captureHandoffOwner(cardId) {
|
|
19
|
+
const draftId = (cardId && draftIdForCard(cardId)) || getActiveDraftChanges().draftId;
|
|
20
|
+
const workspace = getDraftChanges(draftId);
|
|
21
|
+
return { draftId, draftRevision: workspace.revision, target: targetFromKey(draftId) ?? applicationTarget(),
|
|
22
|
+
frameIds: getCanvasCards().filter((card) => draftIdForCard(card.id) === workspace.draftId).map((card) => card.id) };
|
|
23
|
+
}
|
|
14
24
|
const dispatches = new Map();
|
|
15
25
|
function stableJsonValue(value) {
|
|
16
26
|
if (Array.isArray(value))
|
|
@@ -40,32 +50,60 @@ function requestedStyleValue(change) {
|
|
|
40
50
|
return tokenReference(change.newToken);
|
|
41
51
|
return change.rawValue ?? "";
|
|
42
52
|
}
|
|
43
|
-
function
|
|
44
|
-
if (!isTokenChange(change) && (change.state ?? "base") !== "base")
|
|
45
|
-
return false;
|
|
46
|
-
const selector = selectorForManagedChange(change);
|
|
53
|
+
function queryTargets(doc, selector) {
|
|
47
54
|
if (!selector)
|
|
48
|
-
return
|
|
49
|
-
let targets;
|
|
55
|
+
return [];
|
|
50
56
|
try {
|
|
51
|
-
|
|
57
|
+
return Array.from(doc.querySelectorAll(selector));
|
|
52
58
|
}
|
|
53
59
|
catch {
|
|
54
|
-
return
|
|
60
|
+
return [];
|
|
55
61
|
}
|
|
62
|
+
}
|
|
63
|
+
/** Resolves current style targets, following a source site whose position shifted. */
|
|
64
|
+
function styleTargets(change, doc) {
|
|
65
|
+
if (isTokenChange(change))
|
|
66
|
+
return queryTargets(doc, change.selector);
|
|
67
|
+
// The projection marker is lifted for verification; evidence names the instance.
|
|
68
|
+
if (change.scope === "rendered-instance") {
|
|
69
|
+
const resolved = change.instanceOverride
|
|
70
|
+
? resolveMovedRenderedInstance(doc, change.instanceOverride.target)
|
|
71
|
+
: null;
|
|
72
|
+
return resolved?.status === "resolved" ? [resolved.element] : [];
|
|
73
|
+
}
|
|
74
|
+
const targets = queryTargets(doc, selectorForManagedChange(change));
|
|
75
|
+
const evidence = change.sourceEvidence;
|
|
76
|
+
if (targets.length > 0 || !evidence || change.column === undefined)
|
|
77
|
+
return targets;
|
|
78
|
+
const site = relocateSourceSite(doc, { cid: change.cid, src: `${change.file}:${change.line}:${change.column}` }, (element) => normalizeRuntimeTag(element.tagName) === evidence.tagName
|
|
79
|
+
&& boundRuntimeEvidence(element.getAttribute("data-cprops")) === evidence.props);
|
|
80
|
+
return site ? queryTargets(doc, sourceSiteSelector(site.cid, site.src)) : [];
|
|
81
|
+
}
|
|
82
|
+
function verifyStyle(change, doc) {
|
|
83
|
+
if (!isTokenChange(change) && (change.state ?? "base") !== "base")
|
|
84
|
+
return false;
|
|
85
|
+
const targets = styleTargets(change, doc);
|
|
56
86
|
const requested = requestedStyleValue(change);
|
|
57
87
|
return requested.length > 0
|
|
58
88
|
&& targets.length > 0
|
|
59
89
|
&& targets.every((target) => verifyPreview(target, change.property, requested).status === "applied");
|
|
60
90
|
}
|
|
61
91
|
function verifyText(change, doc) {
|
|
62
|
-
|
|
92
|
+
let target = change.target;
|
|
93
|
+
let resolved = resolveTextProjectionTarget(doc, target, change.after);
|
|
94
|
+
if (resolved.status === "missing") {
|
|
95
|
+
const sourceSite = relocateSourceSite(doc, target.sourceSite, (element) => element.getAttribute("data-cprops") === target.props && element.getAttribute("aria-label") === target.ariaLabel);
|
|
96
|
+
if (sourceSite) {
|
|
97
|
+
target = { ...target, sourceSite };
|
|
98
|
+
resolved = resolveTextProjectionTarget(doc, target, change.after);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
63
101
|
if (resolved.status !== "resolved")
|
|
64
102
|
return false;
|
|
65
|
-
const node = resolveTextProjectionTextNode(resolved.element,
|
|
103
|
+
const node = resolveTextProjectionTextNode(resolved.element, target, change.after);
|
|
66
104
|
if (node)
|
|
67
105
|
return node.nodeValue === change.after;
|
|
68
|
-
return
|
|
106
|
+
return target.textNodePath === undefined
|
|
69
107
|
&& change.after.length === 0
|
|
70
108
|
&& (resolved.element.textContent ?? "") === "";
|
|
71
109
|
}
|
|
@@ -89,7 +127,7 @@ function verifyStructuralDelete(change, doc) {
|
|
|
89
127
|
}
|
|
90
128
|
return currentRoute === change.route
|
|
91
129
|
&& documentStateKey(doc) === change.state
|
|
92
|
-
&&
|
|
130
|
+
&& resolveMovedRenderedInstance(doc, change.target).status === "missing";
|
|
93
131
|
}
|
|
94
132
|
/**
|
|
95
133
|
* A move verifies when the element sits at its recorded destination without
|
|
@@ -98,17 +136,17 @@ function verifyStructuralDelete(change, doc) {
|
|
|
98
136
|
* stay unverified instead of trusting rendered placement alone.
|
|
99
137
|
*/
|
|
100
138
|
function verifyStructuralMove(change, doc) {
|
|
101
|
-
const target =
|
|
139
|
+
const target = resolveMovedRenderedInstance(doc, change.target);
|
|
102
140
|
if (target.status !== "resolved")
|
|
103
141
|
return false;
|
|
104
|
-
const parent =
|
|
142
|
+
const parent = resolveMovedRenderedInstance(doc, change.destination.parent);
|
|
105
143
|
if (parent.status !== "resolved")
|
|
106
144
|
return false;
|
|
107
145
|
if (target.element.parentElement !== parent.element)
|
|
108
146
|
return false;
|
|
109
147
|
if (!change.destination.before)
|
|
110
148
|
return target.element.nextElementSibling === null;
|
|
111
|
-
const before =
|
|
149
|
+
const before = resolveMovedRenderedInstance(doc, change.destination.before);
|
|
112
150
|
return before.status === "resolved" && target.element.nextElementSibling === before.element;
|
|
113
151
|
}
|
|
114
152
|
function verifiedStructuralChangeIds(eligible, doc) {
|
|
@@ -142,8 +180,9 @@ export function verifiedChangeKeys(sentChanges, doc = document) {
|
|
|
142
180
|
return verified;
|
|
143
181
|
}
|
|
144
182
|
/** Captures the exact canonical edits associated with one dispatched prompt. */
|
|
145
|
-
export function recordAgentDispatch(revision, changes, structuralChanges = []) {
|
|
183
|
+
export function recordAgentDispatch(revision, changes, structuralChanges = [], owner = captureHandoffOwner()) {
|
|
146
184
|
dispatches.set(revision, {
|
|
185
|
+
owner: structuredClone(owner),
|
|
147
186
|
changes: changes.map((change) => structuredClone(change)),
|
|
148
187
|
structuralChanges: structuralChanges.map((change) => structuredClone(change)),
|
|
149
188
|
});
|
|
@@ -186,32 +225,32 @@ function afterBrowserPaint(doc) {
|
|
|
186
225
|
* observable after removing Nudge's own preview. Newer edits and ambiguous
|
|
187
226
|
* records are preserved.
|
|
188
227
|
*/
|
|
189
|
-
export async function verifyAndReconcileHandoff(snapshot, doc =
|
|
190
|
-
if (!doc)
|
|
228
|
+
export async function verifyAndReconcileHandoff(snapshot, doc = handoffDocument(snapshot.owner)) {
|
|
229
|
+
if (!doc || !documentBelongsToHandoff(doc, snapshot.owner))
|
|
191
230
|
return 0;
|
|
192
231
|
const applyProjection = async (snapshot) => {
|
|
193
232
|
if (doc === document && !isEditorShellDocument(doc)) {
|
|
194
|
-
|
|
233
|
+
applyHostDraftProjection(compileDraftProjection(snapshot));
|
|
195
234
|
return "host";
|
|
196
235
|
}
|
|
197
236
|
if (doc === document)
|
|
198
237
|
return null;
|
|
199
|
-
return
|
|
238
|
+
return projectDraftToDocument(doc, snapshot);
|
|
200
239
|
};
|
|
201
240
|
const sentByKey = new Map(snapshot.changes.map((change) => [changeKey(change), handoffChangeFingerprint(change)]));
|
|
202
|
-
const current =
|
|
241
|
+
const current = getDraftChanges(snapshot.owner.draftId).changes;
|
|
203
242
|
const eligible = current.filter((change) => sentByKey.get(changeKey(change)) === handoffChangeFingerprint(change));
|
|
204
243
|
const eligibleKeys = new Set(eligible.map(changeKey));
|
|
205
244
|
const remaining = current.filter((change) => !eligibleKeys.has(changeKey(change)));
|
|
206
245
|
// The same "still unchanged" rule for structural intent: only canonical
|
|
207
246
|
// records byte-identical to the dispatched snapshot may be reconciled.
|
|
208
247
|
const sentStructural = new Map(snapshot.structuralChanges.map((change) => [change.id, handoffStructuralFingerprint(change)]));
|
|
209
|
-
const currentStructural =
|
|
248
|
+
const currentStructural = getDraftChanges(snapshot.owner.draftId).structuralChanges;
|
|
210
249
|
const eligibleStructural = currentStructural.filter((change) => sentStructural.get(change.id) === handoffStructuralFingerprint(change));
|
|
211
250
|
const eligibleStructuralIds = new Set(eligibleStructural.map((change) => change.id));
|
|
212
251
|
const remainingStructural = currentStructural.filter((change) => !eligibleStructuralIds.has(change.id));
|
|
213
252
|
try {
|
|
214
|
-
const currentWorkspace =
|
|
253
|
+
const currentWorkspace = getDraftChanges(snapshot.owner.draftId);
|
|
215
254
|
const previewRevision = await applyProjection({
|
|
216
255
|
revision: currentWorkspace.revision,
|
|
217
256
|
changes: remaining,
|
|
@@ -220,6 +259,8 @@ export async function verifyAndReconcileHandoff(snapshot, doc = activeVerificati
|
|
|
220
259
|
if (previewRevision === null)
|
|
221
260
|
return 0;
|
|
222
261
|
await afterBrowserPaint(doc);
|
|
262
|
+
if (!documentBelongsToHandoff(doc, snapshot.owner))
|
|
263
|
+
return 0;
|
|
223
264
|
if (typeof previewRevision === "number"
|
|
224
265
|
&& !isCanvasProjectionRevisionCurrent(doc, previewRevision))
|
|
225
266
|
return 0;
|
|
@@ -228,27 +269,58 @@ export async function verifyAndReconcileHandoff(snapshot, doc = activeVerificati
|
|
|
228
269
|
// A user can make another inspector edit while the two-frame verification
|
|
229
270
|
// window is open. Recheck byte identity immediately before reconciliation
|
|
230
271
|
// so a newer value at the same key can never be removed.
|
|
231
|
-
const currentByKey = new Map(
|
|
272
|
+
const currentByKey = new Map(getDraftChanges(snapshot.owner.draftId).changes.map((change) => [changeKey(change), handoffChangeFingerprint(change)]));
|
|
232
273
|
const stillVerified = new Set([...verified].filter((key) => currentByKey.get(key) === sentByKey.get(key)));
|
|
233
|
-
const currentStructuralById = new Map(
|
|
274
|
+
const currentStructuralById = new Map(getDraftChanges(snapshot.owner.draftId).structuralChanges.map((change) => [change.id, handoffStructuralFingerprint(change)]));
|
|
234
275
|
const stillVerifiedStructural = new Set([...verifiedStructural].filter((id) => currentStructuralById.get(id) === sentStructural.get(id)));
|
|
235
276
|
if (typeof previewRevision === "number"
|
|
236
277
|
&& !isCanvasProjectionRevisionCurrent(doc, previewRevision))
|
|
237
278
|
return 0;
|
|
238
|
-
|
|
279
|
+
if (!documentBelongsToHandoff(doc, snapshot.owner))
|
|
280
|
+
return 0;
|
|
281
|
+
return reconcileDraftChanges(snapshot.owner.draftId, stillVerified, stillVerifiedStructural);
|
|
239
282
|
}
|
|
240
283
|
finally {
|
|
241
284
|
// Reconciliation reapplies the canonical set. If it was unable to write
|
|
242
285
|
// (for example, a read-only Canvas lease), restore the untouched set here.
|
|
243
|
-
|
|
244
|
-
|
|
286
|
+
if (doc === document)
|
|
287
|
+
await applyProjection(getActiveDraftChanges());
|
|
288
|
+
else
|
|
289
|
+
restoreCanonicalFrameProjection(doc);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
export function documentBelongsToHandoff(doc, owner) {
|
|
293
|
+
if (owner.target.kind === "application"
|
|
294
|
+
&& targetKey(applicationTarget(doc.location.href)) !== targetKey(owner.target))
|
|
295
|
+
return false;
|
|
296
|
+
if (doc === document)
|
|
297
|
+
return owner.target.kind === "application" && !isEditorShellDocument(doc);
|
|
298
|
+
const id = getCanvasCardIdForDocument(doc);
|
|
299
|
+
return !!id && draftIdForCard(id) === owner.draftId;
|
|
300
|
+
}
|
|
301
|
+
function handoffDocument(owner) {
|
|
302
|
+
for (const [id, frame] of getRegisteredFrames()) {
|
|
303
|
+
if (draftIdForCard(id) !== owner.draftId)
|
|
304
|
+
continue;
|
|
305
|
+
const doc = frame.contentDocument;
|
|
306
|
+
if (doc && documentBelongsToHandoff(doc, owner))
|
|
307
|
+
return doc;
|
|
245
308
|
}
|
|
309
|
+
return owner.target.kind === "application" && !isEditorShellDocument() ? document : null;
|
|
310
|
+
}
|
|
311
|
+
/** Returns the records captured for one dispatch until it is reconciled. */
|
|
312
|
+
export function getAgentDispatch(revision) {
|
|
313
|
+
return dispatches.get(revision) ?? null;
|
|
246
314
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
315
|
+
/** Records in the owner draft that still equal what a handoff sent. */
|
|
316
|
+
export function unchangedHandoffRecords(snapshot) {
|
|
317
|
+
const sent = new Set(snapshot.changes.map(handoffChangeFingerprint));
|
|
318
|
+
const sentStructural = new Set(snapshot.structuralChanges.map(handoffStructuralFingerprint));
|
|
319
|
+
const current = getDraftChanges(snapshot.owner.draftId);
|
|
320
|
+
return {
|
|
321
|
+
changes: current.changes.filter((change) => sent.has(handoffChangeFingerprint(change))),
|
|
322
|
+
structuralChanges: current.structuralChanges.filter((change) => sentStructural.has(handoffStructuralFingerprint(change))),
|
|
323
|
+
};
|
|
252
324
|
}
|
|
253
325
|
/** Reconciles the captured records for one completed connected-agent request. */
|
|
254
326
|
export async function verifyAndReconcileAgentDispatch(revision) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ActivityFile } from "../agent/activity.js";
|
|
2
|
+
import { type FrameContent } from "./frameContent.js";
|
|
3
|
+
export interface ActivityAttribution {
|
|
4
|
+
files: readonly ActivityFile[];
|
|
5
|
+
elements: readonly Element[];
|
|
6
|
+
}
|
|
7
|
+
/** Reuses rendered source identities. Iteration provenance cannot target application source. */
|
|
8
|
+
export declare function attributeActivity(content: FrameContent, document: Document, files: readonly ActivityFile[]): ActivityAttribution;
|
|
9
|
+
export declare function AgentActivityOverlay({ cardId, content, iframe }: {
|
|
10
|
+
cardId: string;
|
|
11
|
+
content: FrameContent;
|
|
12
|
+
iframe: HTMLIFrameElement | null;
|
|
13
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
const activityStyles = ".canvas-agent-activity {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: 12;\n overflow: hidden;\n border-radius: 4px;\n}\n\n.canvas-agent-activity__label {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n position: absolute;\n top: 12px;\n right: 12px;\n max-width: 80%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n padding: 4px 10px 4px 4px;\n border-radius: 20px;\n background: #242424e8;\n color: #fff;\n font: 12px system-ui;\n box-shadow: 0 2px 12px #0003;\n}\n\n.canvas-agent-activity__label canvas {\n flex: 0 0 auto;\n}\n\n.canvas-agent-activity__label > span {\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.canvas-agent-activity__shimmer {\n position: absolute;\n border-radius: 5px;\n background: linear-gradient(110deg, transparent 20%, rgb(212 212 212 / 0.18) 35%, rgb(212 212 212 / 0.35) 50%, rgb(212 212 212 / 0.18) 65%, transparent 80%);\n background-size: 250% 100%;\n animation: nudge-component-shimmer 2.4s ease-in-out infinite;\n}\n\n@keyframes nudge-component-shimmer {\n from { background-position: 150% 0; }\n to { background-position: -150% 0; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .canvas-agent-activity__shimmer {\n animation: none;\n }\n}\n";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { ThinkingOrb } from "thinking-orbs";
|
|
5
|
+
import { useAgentActivity } from "../agent/activity.js";
|
|
6
|
+
import { getNudgeUiRuntimeConfig } from "../runtime/runtimeConfig.js";
|
|
7
|
+
import { parseDataSrc } from "../selection/resolveSelection.js";
|
|
8
|
+
import { targetKey } from "../drafts/model.js";
|
|
9
|
+
import { contentEditTarget } from "./frameContent.js";
|
|
10
|
+
/** Reuses rendered source identities. Iteration provenance cannot target application source. */
|
|
11
|
+
export function attributeActivity(content, document, files) {
|
|
12
|
+
const target = contentEditTarget(content);
|
|
13
|
+
if (target.kind === "html")
|
|
14
|
+
return { files: files.filter((file) => file.file === target.path), elements: [] };
|
|
15
|
+
const matched = new Set();
|
|
16
|
+
const elements = [];
|
|
17
|
+
for (const element of document.querySelectorAll("[data-src]")) {
|
|
18
|
+
const source = parseDataSrc(element.getAttribute("data-src") ?? "");
|
|
19
|
+
if (!source)
|
|
20
|
+
continue;
|
|
21
|
+
for (const file of files) {
|
|
22
|
+
if (source.file.replace(/^\.\//, "") !== file.file)
|
|
23
|
+
continue;
|
|
24
|
+
matched.add(file);
|
|
25
|
+
if (file.operation !== "edit" || (file.componentId && file.componentId !== element.getAttribute("data-cid")))
|
|
26
|
+
continue;
|
|
27
|
+
if (file.line !== undefined && (source.line < file.line || source.line > (file.endLine ?? file.line)))
|
|
28
|
+
continue;
|
|
29
|
+
elements.push(element);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Highlight outer matches once when one component contains matching descendants.
|
|
33
|
+
return { files: [...matched], elements: [...new Set(elements)].filter((element, _index, all) => !all.some((parent) => parent !== element && parent.contains(element))) };
|
|
34
|
+
}
|
|
35
|
+
export function AgentActivityOverlay({ cardId, content, iframe }) {
|
|
36
|
+
const activity = useAgentActivity(getNudgeUiRuntimeConfig().projectId);
|
|
37
|
+
const [visual, setVisual] = useState({ files: [], highlights: [] });
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!activity.requestId || !iframe || !activity.files.length) {
|
|
40
|
+
setVisual({ files: [], highlights: [] });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
let detachDocument = () => { };
|
|
44
|
+
const attachDocument = () => {
|
|
45
|
+
detachDocument();
|
|
46
|
+
let document = null;
|
|
47
|
+
try {
|
|
48
|
+
document = iframe.contentDocument;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
document = null;
|
|
52
|
+
}
|
|
53
|
+
if (!document?.defaultView) {
|
|
54
|
+
setVisual({ files: [], highlights: [] });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const view = document.defaultView;
|
|
58
|
+
const ownedDocument = document;
|
|
59
|
+
let attribution = attributeActivity(content, ownedDocument, activity.files);
|
|
60
|
+
let frame = null;
|
|
61
|
+
let sourceChanged = false;
|
|
62
|
+
const measure = () => {
|
|
63
|
+
frame = null;
|
|
64
|
+
if (sourceChanged) {
|
|
65
|
+
attribution = attributeActivity(content, ownedDocument, activity.files);
|
|
66
|
+
sourceChanged = false;
|
|
67
|
+
}
|
|
68
|
+
const highlights = attribution.elements.slice(0, 64).map((element) => {
|
|
69
|
+
const rect = element.getBoundingClientRect();
|
|
70
|
+
return { x: rect.left, y: rect.top, width: rect.width, height: rect.height };
|
|
71
|
+
}).filter((rect) => rect.width > 0 && rect.height > 0);
|
|
72
|
+
setVisual((previous) => {
|
|
73
|
+
const sameFiles = previous.files.length === attribution.files.length && previous.files.every((file, index) => file === attribution.files[index]);
|
|
74
|
+
const sameRects = previous.highlights.length === highlights.length && previous.highlights.every((rect, index) => { const next = highlights[index]; return rect.x === next.x && rect.y === next.y && rect.width === next.width && rect.height === next.height; });
|
|
75
|
+
return sameFiles && sameRects ? previous : { files: attribution.files, highlights };
|
|
76
|
+
});
|
|
77
|
+
};
|
|
78
|
+
const schedule = () => { if (frame === null)
|
|
79
|
+
frame = view.requestAnimationFrame(measure); };
|
|
80
|
+
const mutations = new MutationObserver((records) => {
|
|
81
|
+
sourceChanged ||= records.some((record) => record.type === "childList" || record.attributeName === "data-src" || record.attributeName === "data-cid");
|
|
82
|
+
schedule();
|
|
83
|
+
});
|
|
84
|
+
mutations.observe(ownedDocument.documentElement, { subtree: true, childList: true, characterData: true, attributes: true, attributeFilter: ["data-src", "data-cid", "class", "style"] });
|
|
85
|
+
const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(schedule);
|
|
86
|
+
resize?.observe(ownedDocument.documentElement);
|
|
87
|
+
for (const element of attribution.elements.slice(0, 64))
|
|
88
|
+
resize?.observe(element);
|
|
89
|
+
view.addEventListener("scroll", schedule, true);
|
|
90
|
+
view.addEventListener("resize", schedule);
|
|
91
|
+
measure();
|
|
92
|
+
detachDocument = () => {
|
|
93
|
+
mutations.disconnect();
|
|
94
|
+
resize?.disconnect();
|
|
95
|
+
view.removeEventListener("scroll", schedule, true);
|
|
96
|
+
view.removeEventListener("resize", schedule);
|
|
97
|
+
if (frame !== null)
|
|
98
|
+
view.cancelAnimationFrame(frame);
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
attachDocument();
|
|
102
|
+
iframe.addEventListener("load", attachDocument);
|
|
103
|
+
return () => { iframe.removeEventListener("load", attachDocument); detachDocument(); };
|
|
104
|
+
}, [activity, content, iframe]);
|
|
105
|
+
const submitted = activity.requestId !== null && activity.targets.includes(cardId)
|
|
106
|
+
&& (!activity.target || targetKey(activity.target) === targetKey(contentEditTarget(content)));
|
|
107
|
+
const working = activity.requestId !== null && (submitted || visual.files.length > 0);
|
|
108
|
+
if (!working)
|
|
109
|
+
return null;
|
|
110
|
+
return _jsxs("div", { className: "canvas-agent-activity", "data-test": `canvas-agent-activity-${cardId}`, children: [visual.highlights.map((rect, index) => _jsx("div", { className: "canvas-agent-activity__shimmer", "data-test": "canvas-agent-component-shimmer", style: { left: rect.x, top: rect.y, width: rect.width, height: rect.height } }, index)), _jsxs("span", { className: "canvas-agent-activity__label", role: "status", children: [_jsx(ThinkingOrb, { state: "solving", size: 20, theme: "dark", "aria-hidden": "true" }), _jsx("span", { children: "Agent working\u2026" })] }), _jsx("style", { children: activityStyles })] });
|
|
111
|
+
}
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
|
-
import { type CanvasCard
|
|
2
|
+
import { type CanvasCard } from "./canvasStore.js";
|
|
3
|
+
import { type CanvasPresentation } from "./viewStore.js";
|
|
3
4
|
import type { InspectorSession } from "../session/sessionFactory.js";
|
|
4
5
|
interface CanvasCardProps {
|
|
5
6
|
card: CanvasCard;
|
|
6
7
|
presentation?: CanvasPresentation;
|
|
7
8
|
presentationCard?: boolean;
|
|
9
|
+
linkedToActive?: boolean;
|
|
8
10
|
onShowFocus?: (cardId: string) => void;
|
|
11
|
+
onDuplicate?: (cardId: string) => void;
|
|
12
|
+
onCreateIteration?: (cardId: string, position?: {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
}) => Promise<string>;
|
|
9
16
|
documentOwner?: InspectorSession;
|
|
10
17
|
}
|
|
11
|
-
export declare function CanvasCard({ card, presentation, presentationCard, onShowFocus, documentOwner }: CanvasCardProps): ReactElement;
|
|
18
|
+
export declare function CanvasCard({ card, presentation, presentationCard, linkedToActive, onShowFocus, onDuplicate, onCreateIteration, documentOwner }: CanvasCardProps): ReactElement;
|
|
12
19
|
export {};
|