nudge-ui 0.2.0 → 0.2.1
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 +872 -12
- package/dist/hosts/astro/astroRuntimeConfig.js +1 -1
- package/dist/hosts/astro/clientTransport.js +60 -11
- package/dist/hosts/next/sidecar.d.ts +4 -0
- package/dist/hosts/next/sidecar.js +49 -6
- package/dist/hosts/next/wrapper.js +30 -2
- package/dist/hosts/projectBridge.d.ts +20 -0
- package/dist/hosts/projectBridge.js +97 -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 +1 -1
- package/dist/hosts/static/server.js +44 -3
- package/dist/hosts/vite/vite.d.ts +5 -1
- package/dist/hosts/vite/vite.js +78 -14
- package/dist/inspector/agent/McpConnectionDialog.d.ts +2 -2
- package/dist/inspector/agent/McpConnectionDialog.js +11 -35
- package/dist/inspector/agent/client.d.ts +10 -3
- package/dist/inspector/agent/client.js +80 -9
- package/dist/inspector/agent/connectionStatus.js +3 -3
- package/dist/inspector/agent/httpTransport.d.ts +2 -0
- package/dist/inspector/agent/httpTransport.js +37 -17
- package/dist/inspector/agent/protocol.d.ts +8 -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 +246 -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 +392 -73
- 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 +69 -5
- package/dist/inspector/canvas/frameProtocol.js +56 -2
- package/dist/inspector/canvas/keyboardShortcuts.d.ts +3 -0
- package/dist/inspector/canvas/keyboardShortcuts.js +14 -0
- package/dist/inspector/canvas/projection.d.ts +7 -0
- package/dist/inspector/canvas/projection.js +118 -25
- package/dist/inspector/canvas/rendererBootstrap.js +29 -29
- package/dist/inspector/canvas/rendererElementSelector.js +341 -30
- 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/clientManifest.d.ts +5 -0
- package/dist/inspector/clientManifest.js +11 -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 +74 -24
- 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/selectedGeometry.js +21 -2
- package/dist/inspector/overlay/spacingGestures.d.ts +45 -0
- package/dist/inspector/overlay/spacingGestures.js +353 -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 +4 -0
- package/dist/inspector/runtime/runtimeConfig.js +4 -0
- package/dist/inspector/selection/selectionStore.js +2 -0
- package/dist/inspector/settings/SettingsDialog.js +10 -0
- package/dist/inspector/shell/AppShell.js +5 -1
- package/dist/inspector/shell/ChangesLog.js +8 -4
- package/dist/inspector/shell/CopyPromptButton.js +95 -19
- package/dist/inspector/shell/EmptyState.js +1 -1
- package/dist/inspector/shell/InspectorShell.d.ts +0 -1
- package/dist/inspector/shell/InspectorShell.js +37 -38
- 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 +409 -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 +23 -30
- 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 +91 -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 +111 -7
- 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/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/styles.js +13 -11
- package/dist/internal/agent-protocol.d.ts +65 -1
- package/dist/internal/agent-protocol.js +183 -3
- package/dist/nudge-ui.mjs +261 -57
- package/dist/transport/editor.d.ts +28 -0
- package/dist/transport/editor.js +132 -0
- package/dist/transport/index.d.ts +1 -0
- package/dist/transport/index.js +1 -0
- package/dist/transport/routes.d.ts +7 -0
- package/dist/transport/routes.js +7 -0
- package/package.json +3 -1
- package/dist/inspector/shell/panelLayout.d.ts +0 -2
- package/dist/inspector/shell/panelLayout.js +0 -29
package/dist/hosts/vite/vite.js
CHANGED
|
@@ -2,11 +2,12 @@ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { basename, join, relative, resolve, sep, } from "node:path";
|
|
4
4
|
import { createTokenInventory, } from "../../css/token-inventory/index.js";
|
|
5
|
-
import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_MANIFEST_PATH } from "../../transport/index.js";
|
|
5
|
+
import { NUDGE_UI_CLIENT_PATH, NUDGE_UI_EDITOR_PATH, NUDGE_UI_MANIFEST_PATH, createNudgeUiEditorDocument, isNudgeUiEditorDocumentRequest, } from "../../transport/index.js";
|
|
6
6
|
import { discoverCssImportGraph, stripCssQuery } from "./tokens/activeStylesheets.js";
|
|
7
7
|
import { catalogSourcePath, createViteStylesheetArtifact, isHostApplicationSource, isPackageStylesheet, orderViteStylesheetGraph, } from "./tokens/viteStylesheetArtifacts.js";
|
|
8
8
|
import { interpretDialects } from "../../css/dialects/index.js";
|
|
9
9
|
import { detectStylingSystem, } from "../../inspector/clientManifest.js";
|
|
10
|
+
import { loopbackOrigin, startOptionalProjectBridge, } from "../projectBridge.js";
|
|
10
11
|
function isRecord(value) {
|
|
11
12
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
13
|
}
|
|
@@ -106,6 +107,8 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
106
107
|
let command = "serve";
|
|
107
108
|
let demoBuild = false;
|
|
108
109
|
let devServer;
|
|
110
|
+
let projectBridge = null;
|
|
111
|
+
let projectBridgeStart = null;
|
|
109
112
|
let postTransformPromise = null;
|
|
110
113
|
const inventory = createTokenInventory();
|
|
111
114
|
const activeHostCssFiles = new Set();
|
|
@@ -473,14 +476,41 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
473
476
|
return postTransformPromise;
|
|
474
477
|
}
|
|
475
478
|
async function buildClientManifest() {
|
|
479
|
+
await projectBridgeStart;
|
|
476
480
|
await ensurePostTransformCss();
|
|
477
481
|
await ensurePublishedThemeContract();
|
|
478
482
|
return {
|
|
479
483
|
version: 1,
|
|
480
484
|
revision: 0,
|
|
481
485
|
runtime: buildRuntimeSnapshot(),
|
|
486
|
+
...(projectBridge ? { agentBridge: projectBridge.browser } : {}),
|
|
482
487
|
};
|
|
483
488
|
}
|
|
489
|
+
function startProjectBridge(server) {
|
|
490
|
+
// The public demo uses the shared editor runtime without project-owned
|
|
491
|
+
// sessions, an agent bridge, or filesystem persistence.
|
|
492
|
+
if (options.demo === true || !root || projectBridgeStart)
|
|
493
|
+
return;
|
|
494
|
+
const address = server.httpServer?.address();
|
|
495
|
+
if (!address || typeof address === "string")
|
|
496
|
+
return;
|
|
497
|
+
const origin = server.resolvedUrls?.local[0]
|
|
498
|
+
? new URL(server.resolvedUrls.local[0]).origin
|
|
499
|
+
: loopbackOrigin(address, server.config.server.https ? "https" : "http");
|
|
500
|
+
const allowedOrigins = [
|
|
501
|
+
...(server.resolvedUrls?.local ?? []),
|
|
502
|
+
...(server.resolvedUrls?.network ?? []),
|
|
503
|
+
].map((url) => new URL(url).origin);
|
|
504
|
+
projectBridgeStart = startOptionalProjectBridge({
|
|
505
|
+
appRoot: root,
|
|
506
|
+
projectId: options.projectId ?? basename(root),
|
|
507
|
+
origin,
|
|
508
|
+
...(allowedOrigins.length > 0 ? { allowedOrigins } : {}),
|
|
509
|
+
warn: (message) => server.config.logger.warn(message),
|
|
510
|
+
}).then((bridge) => {
|
|
511
|
+
projectBridge = bridge;
|
|
512
|
+
});
|
|
513
|
+
}
|
|
484
514
|
function buildRuntimeSnapshot() {
|
|
485
515
|
syncInventoryContributions();
|
|
486
516
|
const snapshot = inventory.snapshot();
|
|
@@ -497,6 +527,9 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
497
527
|
host: framework?.hostLabel ?? "static-html",
|
|
498
528
|
framework: framework?.framework ?? "HTML",
|
|
499
529
|
stylingSystem: detectStylingSystem(snapshot.tokens),
|
|
530
|
+
...(options.demo === true ? { demo: true } : {}),
|
|
531
|
+
...(options.demoPages === undefined ? {} : { demoPages: options.demoPages }),
|
|
532
|
+
...(options.demoCardLabels === undefined ? {} : { demoCardLabels: options.demoCardLabels }),
|
|
500
533
|
capabilities: { canvas: true, componentSemantics: framework !== null },
|
|
501
534
|
tokenCatalog: snapshot.definitions.map((definition) => ({
|
|
502
535
|
...definition,
|
|
@@ -536,16 +569,38 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
536
569
|
if (!enabled || command !== "serve")
|
|
537
570
|
return;
|
|
538
571
|
devServer = server;
|
|
572
|
+
if (server.httpServer?.listening)
|
|
573
|
+
startProjectBridge(server);
|
|
574
|
+
else
|
|
575
|
+
server.httpServer?.once("listening", () => startProjectBridge(server));
|
|
576
|
+
server.httpServer?.once("close", () => {
|
|
577
|
+
void projectBridgeStart?.then(() => projectBridge?.close()).finally(() => {
|
|
578
|
+
projectBridge = null;
|
|
579
|
+
projectBridgeStart = null;
|
|
580
|
+
});
|
|
581
|
+
});
|
|
539
582
|
const runtimeWarning = framework?.unresolvedRuntimeWarning();
|
|
540
583
|
if (runtimeWarning)
|
|
541
584
|
server.config.logger.warn(runtimeWarning);
|
|
542
585
|
server.middlewares?.use(async (request, response, next) => {
|
|
543
586
|
const pathname = new URL(request.url ?? "/", "http://nudge-ui.local").pathname;
|
|
544
|
-
|
|
587
|
+
const invalidReservedMethod = (pathname === CLIENT_PATH || pathname === MANIFEST_PATH)
|
|
588
|
+
? request.method !== "GET"
|
|
589
|
+
: pathname === NUDGE_UI_EDITOR_PATH
|
|
590
|
+
&& request.method !== "GET"
|
|
591
|
+
&& request.method !== "HEAD";
|
|
592
|
+
if (invalidReservedMethod) {
|
|
545
593
|
response.statusCode = 405;
|
|
546
594
|
response.end();
|
|
547
595
|
return;
|
|
548
596
|
}
|
|
597
|
+
if (isNudgeUiEditorDocumentRequest(request.url ?? "/", request.method, request.headers)) {
|
|
598
|
+
response.statusCode = 200;
|
|
599
|
+
response.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
600
|
+
response.setHeader("Cache-Control", "no-store");
|
|
601
|
+
response.end(request.method === "HEAD" ? undefined : createNudgeUiEditorDocument());
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
549
604
|
if (pathname === CLIENT_PATH) {
|
|
550
605
|
try {
|
|
551
606
|
response.statusCode = 200;
|
|
@@ -628,29 +683,39 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
628
683
|
return `export {};\n`;
|
|
629
684
|
}
|
|
630
685
|
if (options.demo === true) {
|
|
631
|
-
const
|
|
686
|
+
const demoControllerExpression = demoBuild
|
|
632
687
|
? "true"
|
|
633
688
|
: '(import.meta.env.DEV && window.location.pathname === "/")';
|
|
634
689
|
// Taken from the composed framework, so the demo bootstrap cannot drift
|
|
635
690
|
// from the manifest the transport serves.
|
|
636
691
|
const identity = buildRuntimeSnapshot();
|
|
637
692
|
return [
|
|
638
|
-
'import { bootstrapNudgeUi, configureNudgeUiRuntime, detectFramework,
|
|
693
|
+
'import { bootstrapNudgeUi, configureNudgeUiRuntime, createNudgeUiEditorUrl, detectFramework, hasNudgeUiDirectTabIntent, isCanvasRenderer, isNudgeUiDirectUrl, readNudgeUiEditorTarget, rememberNudgeUiDirectTabIntent } from "nudge-ui/internal/inspector";',
|
|
639
694
|
'import { tokenCatalog, tokens, tokenDiagnostics, tokenGeneration, nudgeUiProjectId } from "virtual:design-tokens";',
|
|
640
695
|
...(framework
|
|
641
696
|
? [`import { componentContracts } from ${JSON.stringify(framework.virtualModuleId)};`]
|
|
642
697
|
: []),
|
|
643
|
-
'const
|
|
644
|
-
|
|
645
|
-
'
|
|
646
|
-
'
|
|
698
|
+
'const __nudge_ui_renderer = isCanvasRenderer();',
|
|
699
|
+
'const __nudge_ui_explicit_direct = isNudgeUiDirectUrl(window.location.href);',
|
|
700
|
+
'if (__nudge_ui_explicit_direct) rememberNudgeUiDirectTabIntent();',
|
|
701
|
+
'const __nudge_ui_direct_tab = hasNudgeUiDirectTabIntent();',
|
|
702
|
+
`const __nudge_ui_demo_controller = !__nudge_ui_renderer && !__nudge_ui_explicit_direct && !__nudge_ui_direct_tab && ${demoControllerExpression};`,
|
|
703
|
+
'const __nudge_ui_editor_target = readNudgeUiEditorTarget(window.location.href) ?? (__nudge_ui_demo_controller ? window.location.href : null);',
|
|
704
|
+
'if (__nudge_ui_demo_controller && readNudgeUiEditorTarget(window.location.href) === null) {',
|
|
705
|
+
' window.history.replaceState(window.history.state, "", createNudgeUiEditorUrl(window.location.href));',
|
|
706
|
+
'}',
|
|
707
|
+
'if (__nudge_ui_editor_target) document.documentElement.setAttribute("data-nudge-ui-editor", "");',
|
|
708
|
+
'if (__nudge_ui_renderer) document.documentElement.setAttribute("data-nudge-ui-renderer", "");',
|
|
709
|
+
'if (__nudge_ui_editor_target || __nudge_ui_renderer) {',
|
|
647
710
|
' configureNudgeUiRuntime({',
|
|
648
711
|
' projectId: nudgeUiProjectId,',
|
|
649
712
|
` host: ${JSON.stringify(identity.host)},`,
|
|
650
713
|
` framework: ${JSON.stringify(identity.framework)},`,
|
|
651
714
|
' stylingSystem: detectFramework(tokens).stylingSystem,',
|
|
652
|
-
'
|
|
653
|
-
`
|
|
715
|
+
' demo: true,',
|
|
716
|
+
` demoPages: ${JSON.stringify(options.demoPages ?? [])},`,
|
|
717
|
+
` demoCardLabels: ${JSON.stringify(options.demoCardLabels ?? [])},`,
|
|
718
|
+
` capabilities: { canvas: true, componentSemantics: ${String(framework !== null)} },`,
|
|
654
719
|
' tokenCatalog,',
|
|
655
720
|
' tokens,',
|
|
656
721
|
' tokenDiagnostics,',
|
|
@@ -660,12 +725,11 @@ export function createVitePlugins(options = {}, createFramework = null) {
|
|
|
660
725
|
' const __dt_root = document.getElementById("nudge-ui-root");',
|
|
661
726
|
' if (__dt_root) {',
|
|
662
727
|
' bootstrapNudgeUi(__dt_root);',
|
|
663
|
-
' if (__nudge_ui_demo_runtime) {',
|
|
664
|
-
' setInspectorOpen(false);',
|
|
665
|
-
' window.addEventListener("nudge-ui:open", () => setInspectorOpen(true));',
|
|
666
|
-
' }',
|
|
667
728
|
' }',
|
|
668
729
|
'}',
|
|
730
|
+
'window.addEventListener("nudge-ui:open", () => {',
|
|
731
|
+
' if (!__nudge_ui_renderer && (__nudge_ui_explicit_direct || __nudge_ui_direct_tab)) window.location.assign(createNudgeUiEditorUrl(window.location.href));',
|
|
732
|
+
'});',
|
|
669
733
|
].join("\n");
|
|
670
734
|
}
|
|
671
735
|
return null;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ReactElement } from "react";
|
|
2
2
|
import type { AgentClientSnapshot } from "./client.js";
|
|
3
3
|
export declare const MCP_DOCS_URL = "https://github.com/charlessmart/nudge-ui#connect-a-coding-agent";
|
|
4
|
-
/**
|
|
5
|
-
export declare function createMcpSetupCommand(
|
|
4
|
+
/** Uses the guided installer so browser diagnostics never become shell arguments. */
|
|
5
|
+
export declare function createMcpSetupCommand(): string;
|
|
6
6
|
export interface McpConnectionContentProps {
|
|
7
7
|
readonly projectId: string;
|
|
8
8
|
readonly origin: string;
|
|
@@ -8,32 +8,9 @@ import { Button } from "../ui/Button.js";
|
|
|
8
8
|
import { SegmentedControl } from "../ui/SegmentedControl.js";
|
|
9
9
|
import { StatusCallout } from "../ui/StatusCallout.js";
|
|
10
10
|
export const MCP_DOCS_URL = "https://github.com/charlessmart/nudge-ui#connect-a-coding-agent";
|
|
11
|
-
/**
|
|
12
|
-
function
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
function shellArgument(value) {
|
|
16
|
-
// Keep the usual command readable while quoting values that could be
|
|
17
|
-
// interpreted as shell syntax when add-mcp runs the nested command.
|
|
18
|
-
return /^[A-Za-z0-9_./:@%+=,-]+$/.test(value) ? value : shellQuote(value);
|
|
19
|
-
}
|
|
20
|
-
/** Builds the project-local add-mcp command shown by the connection panel. */
|
|
21
|
-
export function createMcpSetupCommand(projectId, origin) {
|
|
22
|
-
const serverCommand = [
|
|
23
|
-
"npx -y @nudge-ui/mcp@latest",
|
|
24
|
-
"--project-id",
|
|
25
|
-
shellArgument(projectId),
|
|
26
|
-
"--origin",
|
|
27
|
-
shellArgument(origin),
|
|
28
|
-
"--workspace-root .",
|
|
29
|
-
].join(" ");
|
|
30
|
-
return `npx add-mcp ${shellQuote(serverCommand)} --name nudge_ui`;
|
|
31
|
-
}
|
|
32
|
-
function formatMcpSetupCommand(command) {
|
|
33
|
-
return command
|
|
34
|
-
.replace(" --project-id ", "\n --project-id ")
|
|
35
|
-
.replace(" --origin ", "\n --origin ")
|
|
36
|
-
.replace(" --workspace-root ", "\n --workspace-root ");
|
|
11
|
+
/** Uses the guided installer so browser diagnostics never become shell arguments. */
|
|
12
|
+
export function createMcpSetupCommand() {
|
|
13
|
+
return "npx nudge-ui agent setup";
|
|
37
14
|
}
|
|
38
15
|
function portalContainer() {
|
|
39
16
|
return typeof document !== "undefined"
|
|
@@ -52,17 +29,16 @@ export function McpConnectionContent({ projectId, origin, snapshot, onConnect, o
|
|
|
52
29
|
const [copiedListener, setCopiedListener] = useState(false);
|
|
53
30
|
const [copyError, setCopyError] = useState();
|
|
54
31
|
const [checkError, setCheckError] = useState();
|
|
55
|
-
const setupCommand =
|
|
56
|
-
const displaySetupCommand = useMemo(() => formatMcpSetupCommand(setupCommand), [setupCommand]);
|
|
32
|
+
const setupCommand = createMcpSetupCommand();
|
|
57
33
|
const setupPrompt = useMemo(() => [
|
|
58
|
-
"Please
|
|
34
|
+
"Please set up the Nudge coding-agent integration for this application.",
|
|
59
35
|
"",
|
|
60
|
-
"Run this command from the
|
|
36
|
+
"Run this command from the application directory and select this coding agent:",
|
|
61
37
|
setupCommand,
|
|
62
38
|
"",
|
|
63
|
-
"
|
|
39
|
+
"Start the application normally. Reload the agent if needed, then listen to Nudge.",
|
|
64
40
|
].join("\n"), [setupCommand]);
|
|
65
|
-
const listenerInstruction = "
|
|
41
|
+
const listenerInstruction = "Listen to Nudge for this application in the current worktree. Use nudge_list_sessions if selection is ambiguous, then call nudge_listen. After applying each prompt, call nudge_report_status and listen again until I ask you to stop.";
|
|
66
42
|
const status = getAgentConnectionStatus(snapshot);
|
|
67
43
|
const canConnect = snapshot.companionReachable
|
|
68
44
|
&& !snapshot.paired
|
|
@@ -118,10 +94,10 @@ export function McpConnectionContent({ projectId, origin, snapshot, onConnect, o
|
|
|
118
94
|
setCopyError("The listener instruction could not be copied. Select it and copy it manually.");
|
|
119
95
|
}
|
|
120
96
|
}
|
|
121
|
-
return (_jsxs(_Fragment, { children: [_jsxs(StatusCallout, { className: "mcp-connection__status", tone: status.tone, "data-test": "mcp-connection-status", children: [_jsx("span", { className: "mcp-connection__status-label", role: "status", children: status.label }), snapshot.error ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.error })) : null, checkError ? _jsx("span", { role: "alert", children: checkError }) : null, snapshot.request?.summary ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.request.summary })) : null, snapshot.request?.error ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.request.error })) : null] }), _jsxs("ol", { className: "mcp-connection__timeline", "data-test": "mcp-connection-timeline", children: [_jsxs(TimelineStep, { number: 1, complete: snapshot.
|
|
97
|
+
return (_jsxs(_Fragment, { children: [_jsxs(StatusCallout, { className: "mcp-connection__status", tone: status.tone, "data-test": "mcp-connection-status", children: [_jsx("span", { className: "mcp-connection__status-label", role: "status", children: status.label }), snapshot.error ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.error })) : null, checkError ? _jsx("span", { role: "alert", children: checkError }) : null, snapshot.request?.summary ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.request.summary })) : null, snapshot.request?.error ? (_jsx("span", { className: "mcp-connection__status-detail", children: snapshot.request.error })) : null] }), _jsxs("ol", { className: "mcp-connection__timeline", "data-test": "mcp-connection-timeline", children: [_jsxs(TimelineStep, { number: 1, complete: snapshot.listenerActive || snapshot.request !== null, title: "Set up your agent once", children: [_jsx(SegmentedControl, { "aria-label": "MCP setup method", className: "mcp-connection__setup-tabs", "data-test": "mcp-setup-tabs", value: setupMethod, options: [
|
|
122
98
|
{ value: "ai", label: "AI instructions", testId: "mcp-setup-tab-ai" },
|
|
123
99
|
{ value: "terminal", label: "Terminal command", testId: "mcp-setup-tab-terminal" },
|
|
124
|
-
], onChange: setSetupMethod }), setupMethod === "terminal" ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "mcp-connection__copy", children: "Run this command from your
|
|
100
|
+
], onChange: setSetupMethod }), setupMethod === "terminal" ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "mcp-connection__copy", children: "Run this command from the application directory, then start your app normally." }), _jsx("pre", { className: "mcp-connection__command", children: _jsx("code", { "data-test": "mcp-setup-command", children: setupCommand }) }), _jsx("div", { className: "mcp-connection__command-actions", children: _jsxs(Button, { variant: "secondary", "data-test": "mcp-copy-command", type: "button", onClick: () => void handleCopyCommand(), children: [copiedCommand ? _jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) : _jsx(IconClipboard, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), copiedCommand ? "Copied" : "Copy command"] }) })] })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "mcp-connection__copy", children: "Copy these setup instructions into your coding agent." }), _jsx("pre", { className: "mcp-connection__command", children: _jsx("code", { "data-test": "mcp-setup-prompt", children: setupPrompt }) }), _jsx("div", { className: "mcp-connection__command-actions", children: _jsxs(Button, { variant: "secondary", "data-test": "mcp-copy-setup-prompt", type: "button", onClick: () => void handleCopyPrompt(), children: [copiedPrompt ? _jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) : _jsx(IconClipboard, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), copiedPrompt ? "Copied" : "Copy prompt"] }) })] }))] }), _jsxs(TimelineStep, { number: 2, complete: snapshot.listenerActive, title: "Listen in this agent session", children: [_jsx("p", { className: "mcp-connection__copy", children: "Tell your coding agent: \u201CListen to Nudge.\u201D Reload the agent if Nudge tools are unavailable." }), _jsx("div", { className: "mcp-connection__command-actions", children: _jsxs(Button, { variant: "secondary", "data-test": "mcp-copy-listener", type: "button", onClick: () => void handleCopyListener(), children: [copiedListener ? _jsx(IconCheck, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) : _jsx(IconClipboard, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), copiedListener ? "Copied" : "Copy listening instruction"] }) })] })] }), _jsxs("details", { className: "mcp-connection__details", children: [_jsx("summary", { children: "Connection details and recovery" }), _jsxs("p", { className: "mcp-connection__copy", children: ["The page connects automatically when the development integration is available. To diagnose setup, run ", _jsx("code", { children: "nudge-ui agent doctor" }), " from the application directory."] }), _jsxs("dl", { className: "mcp-connection__diagnostics", "data-test": "mcp-connection-diagnostics", children: [_jsxs("div", { className: "mcp-connection__diagnostic", children: [_jsx("dt", { children: "Project ID" }), _jsx("dd", { "data-test": "mcp-project-id", children: _jsx("code", { children: projectId }) })] }), _jsxs("div", { className: "mcp-connection__diagnostic", children: [_jsx("dt", { children: "Origin" }), _jsx("dd", { "data-test": "mcp-origin", children: _jsx("code", { children: origin }) })] })] }), _jsxs("div", { className: "mcp-connection__actions", children: [canConnect ? (_jsxs(Button, { variant: "primary", "data-test": "mcp-connect", type: "button", onClick: () => void onConnect(), children: [_jsx(IconPlugConnected, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), "Connect"] })) : null, _jsxs(Button, { variant: "quiet", "data-test": "mcp-check-again", type: "button", disabled: checking || snapshot.state === "disabled" || snapshot.state === "pairing", onClick: () => void handleCheckAgain(), children: [_jsx(IconRefresh, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), checking ? "Checking…" : "Check again"] }), canDisconnect ? (_jsxs(Button, { variant: "danger", "data-test": "mcp-disconnect", type: "button", onClick: onDisconnect, children: [_jsx(IconUnlink, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), snapshot.paired ? "Disconnect" : "Forget connection"] })) : null] })] }), copyError ? _jsx("p", { className: "mcp-connection__error", role: "alert", children: copyError }) : null, _jsxs("a", { className: "mcp-connection__docs", "data-test": "mcp-docs-link", href: MCP_DOCS_URL, target: "_blank", rel: "noreferrer", children: [_jsx(IconExternalLink, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }), "Read the MCP connection guide"] })] }));
|
|
125
101
|
}
|
|
126
102
|
/**
|
|
127
103
|
* Explains and controls the project-scoped MCP companion connection.
|
|
@@ -131,5 +107,5 @@ export function McpConnectionContent({ projectId, origin, snapshot, onConnect, o
|
|
|
131
107
|
*/
|
|
132
108
|
export function McpConnectionDialog({ open, projectId, origin, snapshot, onOpenChange, onConnect, onDisconnect, onCheckAgain, }) {
|
|
133
109
|
const closeRef = useRef(null);
|
|
134
|
-
return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { className: "mcp-connection__backdrop", "data-test": "mcp-connection-backdrop" }), _jsxs(Dialog.Popup, { className: "mcp-connection__popup", "data-test": "mcp-connection-dialog", initialFocus: closeRef, children: [_jsxs("div", { className: "mcp-connection__header", children: [_jsxs("div", { children: [_jsx(Dialog.Title, { className: "mcp-connection__title", children: "Connect MCP" }), _jsx(Dialog.Description, { className: "mcp-connection__description", children: "
|
|
110
|
+
return (_jsx(Dialog.Root, { open: open, onOpenChange: onOpenChange, children: _jsxs(Dialog.Portal, { container: portalContainer(), children: [_jsx(Dialog.Backdrop, { className: "mcp-connection__backdrop", "data-test": "mcp-connection-backdrop" }), _jsxs(Dialog.Popup, { className: "mcp-connection__popup", "data-test": "mcp-connection-dialog", initialFocus: closeRef, children: [_jsxs("div", { className: "mcp-connection__header", children: [_jsxs("div", { children: [_jsx(Dialog.Title, { className: "mcp-connection__title", children: "Connect MCP" }), _jsx(Dialog.Description, { className: "mcp-connection__description", children: "Set up your coding agent once, then ask it to listen to Nudge in this worktree." })] }), _jsx(Dialog.Close, { ref: closeRef, className: "icon-button icon-button--quiet mcp-connection__close", "aria-label": "Close MCP connection", "data-test": "mcp-connection-close", type: "button", children: _jsx(IconX, { size: "var(--icon-size-small)", stroke: 1.8, "aria-hidden": "true" }) })] }), _jsx(McpConnectionContent, { projectId: projectId, origin: origin, snapshot: snapshot, onConnect: onConnect, onDisconnect: onDisconnect, onCheckAgain: onCheckAgain })] })] }) }));
|
|
135
111
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { type AgentBridgeTransport, type AgentClientOptions, type AgentRequestStatus, type AgentStatusSnapshot, type PromptDispatchResponse } from "./protocol.js";
|
|
1
|
+
import { type AgentBridgeTransport, type AgentClientOptions, type AgentSketchAttachment, type AgentSketchMetadata, type AgentRequestStatus, type AgentStatusSnapshot, type PromptDispatchResponse } from "./protocol.js";
|
|
2
2
|
export type AgentClientState = "disabled" | "disconnected" | "available" | "pairing" | "connected" | "working" | "completed" | "failed" | "interrupted";
|
|
3
3
|
export interface AgentRequestSnapshot {
|
|
4
4
|
readonly requestId: string;
|
|
5
5
|
readonly changeRevision?: number;
|
|
6
|
+
readonly clientDispatchId?: string;
|
|
7
|
+
readonly sketches?: readonly AgentSketchMetadata[];
|
|
6
8
|
readonly status: AgentRequestStatus;
|
|
7
9
|
readonly summary?: string;
|
|
8
10
|
readonly error?: string;
|
|
@@ -20,7 +22,7 @@ export interface AgentClientSnapshot {
|
|
|
20
22
|
readonly error?: string;
|
|
21
23
|
}
|
|
22
24
|
/** Creates a stable numeric revision for one prompt snapshot. */
|
|
23
|
-
export declare function createPromptRevision(changes: readonly unknown[], structuralChanges?: readonly unknown[]): number;
|
|
25
|
+
export declare function createPromptRevision(changes: readonly unknown[], structuralChanges?: readonly unknown[], sketches?: readonly unknown[]): number;
|
|
24
26
|
/**
|
|
25
27
|
* Browser-side companion client.
|
|
26
28
|
*
|
|
@@ -50,6 +52,8 @@ export declare class AgentClient {
|
|
|
50
52
|
private companionReachable;
|
|
51
53
|
private paired;
|
|
52
54
|
private lastError;
|
|
55
|
+
private readonly autoConnect;
|
|
56
|
+
private autoConnectSuppressed;
|
|
53
57
|
constructor(options: AgentClientOptions);
|
|
54
58
|
/** Installs the controller callback used to acknowledge Canvas commands. */
|
|
55
59
|
setCanvasCommandHandler(handler: AgentClientOptions["canvasCommandHandler"]): void;
|
|
@@ -72,7 +76,10 @@ export declare class AgentClient {
|
|
|
72
76
|
* synchronously, before the transport promise resolves, so the UI cannot
|
|
73
77
|
* issue a second prompt while the first one is in flight.
|
|
74
78
|
*/
|
|
75
|
-
dispatchPrompt(prompt: string, changeRevision?: number
|
|
79
|
+
dispatchPrompt(prompt: string, changeRevision?: number, options?: {
|
|
80
|
+
readonly clientDispatchId?: string;
|
|
81
|
+
readonly attachments?: readonly AgentSketchAttachment[];
|
|
82
|
+
}): Promise<PromptDispatchResponse | null>;
|
|
76
83
|
/** Interrupts local request state and asks the companion to revoke pairing. */
|
|
77
84
|
disconnect(reason?: string): void;
|
|
78
85
|
private discover;
|
|
@@ -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 { 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",
|
|
@@ -13,6 +13,7 @@ const DISABLED_SNAPSHOT = Object.freeze({
|
|
|
13
13
|
});
|
|
14
14
|
const DEFAULT_DISCOVERY_INTERVAL_MS = 2_000;
|
|
15
15
|
const SESSION_STORAGE_PREFIX = "nudge-ui-agent-session:";
|
|
16
|
+
const AUTO_CONNECT_DISABLED_PREFIX = "nudge-ui-agent-auto-connect-disabled:";
|
|
16
17
|
function randomId(prefix) {
|
|
17
18
|
const random = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
18
19
|
? crypto.randomUUID()
|
|
@@ -68,6 +69,31 @@ function clearStoredSession(projectId) {
|
|
|
68
69
|
// Ignore storage failures; disconnect still clears the live connection.
|
|
69
70
|
}
|
|
70
71
|
}
|
|
72
|
+
function autoConnectDisabled(projectId) {
|
|
73
|
+
if (typeof localStorage === "undefined")
|
|
74
|
+
return false;
|
|
75
|
+
try {
|
|
76
|
+
return localStorage.getItem(`${AUTO_CONNECT_DISABLED_PREFIX}${encodeURIComponent(projectId)}`) === "true";
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function setAutoConnectDisabled(projectId, disabled) {
|
|
83
|
+
if (typeof localStorage === "undefined")
|
|
84
|
+
return;
|
|
85
|
+
try {
|
|
86
|
+
const key = `${AUTO_CONNECT_DISABLED_PREFIX}${encodeURIComponent(projectId)}`;
|
|
87
|
+
if (disabled)
|
|
88
|
+
localStorage.setItem(key, "true");
|
|
89
|
+
else
|
|
90
|
+
localStorage.removeItem(key);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Privacy settings can disable storage. The current document still keeps
|
|
94
|
+
// the explicit disconnect preference in memory.
|
|
95
|
+
}
|
|
96
|
+
}
|
|
71
97
|
function statusForConnection(status) {
|
|
72
98
|
if (status.connection === "working" || status.request?.status === "working")
|
|
73
99
|
return "working";
|
|
@@ -90,10 +116,10 @@ function hashRevision(value) {
|
|
|
90
116
|
return (hash >>> 0) & 0x7fff_ffff;
|
|
91
117
|
}
|
|
92
118
|
/** Creates a stable numeric revision for one prompt snapshot. */
|
|
93
|
-
export function createPromptRevision(changes, structuralChanges = []) {
|
|
119
|
+
export function createPromptRevision(changes, structuralChanges = [], sketches = []) {
|
|
94
120
|
let serialized;
|
|
95
121
|
try {
|
|
96
|
-
serialized = JSON.stringify({ changes, structuralChanges }) ?? "";
|
|
122
|
+
serialized = JSON.stringify({ changes, structuralChanges, sketches }) ?? "";
|
|
97
123
|
}
|
|
98
124
|
catch {
|
|
99
125
|
serialized = `${changes.length}:${structuralChanges.length}`;
|
|
@@ -129,12 +155,16 @@ export class AgentClient {
|
|
|
129
155
|
companionReachable = false;
|
|
130
156
|
paired = false;
|
|
131
157
|
lastError;
|
|
158
|
+
autoConnect;
|
|
159
|
+
autoConnectSuppressed = false;
|
|
132
160
|
constructor(options) {
|
|
133
161
|
this.projectId = options.projectId;
|
|
134
162
|
this.origin = options.origin ?? (typeof window !== "undefined" ? window.location.origin : "http://localhost");
|
|
135
163
|
this.transport = options.transport ?? new HttpAgentBridgeTransport(options.endpoint);
|
|
136
164
|
this.discoveryIntervalMs = options.discoveryIntervalMs ?? DEFAULT_DISCOVERY_INTERVAL_MS;
|
|
137
165
|
this.canvasCommandHandler = options.canvasCommandHandler;
|
|
166
|
+
this.autoConnect = options.autoConnect ?? shouldAutoConnectAgentBridge();
|
|
167
|
+
this.autoConnectSuppressed = this.autoConnect && autoConnectDisabled(this.projectId);
|
|
138
168
|
// The public demo renders the real inspector but intentionally keeps the
|
|
139
169
|
// local MCP/agent bridge inert.
|
|
140
170
|
this.enabled = isNudgeUiDev() && !isDemoRuntime();
|
|
@@ -269,6 +299,8 @@ export class AgentClient {
|
|
|
269
299
|
async connect() {
|
|
270
300
|
if (!this.enabled || !this.started || this.pairingAbort || this.paired || !this.companionReachable)
|
|
271
301
|
return false;
|
|
302
|
+
this.autoConnectSuppressed = false;
|
|
303
|
+
setAutoConnectDisabled(this.projectId, false);
|
|
272
304
|
if (this.connection === "working" || this.activeRequest?.status === "working")
|
|
273
305
|
return false;
|
|
274
306
|
const idleListenerActive = this.listenerActive;
|
|
@@ -351,7 +383,7 @@ export class AgentClient {
|
|
|
351
383
|
* synchronously, before the transport promise resolves, so the UI cannot
|
|
352
384
|
* issue a second prompt while the first one is in flight.
|
|
353
385
|
*/
|
|
354
|
-
async dispatchPrompt(prompt, changeRevision) {
|
|
386
|
+
async dispatchPrompt(prompt, changeRevision, options = {}) {
|
|
355
387
|
if (!this.enabled || !this.started || !this.paired || !this.listenerActive || !this.sessionToken)
|
|
356
388
|
return null;
|
|
357
389
|
if (!prompt || this.activeRequest?.status === "working")
|
|
@@ -362,12 +394,17 @@ export class AgentClient {
|
|
|
362
394
|
sessionToken: this.sessionToken,
|
|
363
395
|
prompt,
|
|
364
396
|
...(changeRevision === undefined ? {} : { changeRevision }),
|
|
397
|
+
...(options.clientDispatchId === undefined ? {} : { clientDispatchId: options.clientDispatchId }),
|
|
398
|
+
...(options.attachments === undefined ? {} : { attachments: options.attachments }),
|
|
365
399
|
};
|
|
400
|
+
const sketches = options.attachments?.map(({ data: _data, ...metadata }) => metadata);
|
|
366
401
|
this.activeRequest = {
|
|
367
402
|
localRequestId,
|
|
368
403
|
requestId: localRequestId,
|
|
369
404
|
prompt,
|
|
370
405
|
...(changeRevision === undefined ? {} : { changeRevision }),
|
|
406
|
+
...(options.clientDispatchId === undefined ? {} : { clientDispatchId: options.clientDispatchId }),
|
|
407
|
+
...(sketches === undefined ? {} : { sketches }),
|
|
371
408
|
status: "working",
|
|
372
409
|
};
|
|
373
410
|
this.connection = "working";
|
|
@@ -381,7 +418,12 @@ export class AgentClient {
|
|
|
381
418
|
return null;
|
|
382
419
|
if (response?.request?.requestId) {
|
|
383
420
|
this.activeRequest = this.activeRequest && this.activeRequest.localRequestId === localRequestId
|
|
384
|
-
? {
|
|
421
|
+
? {
|
|
422
|
+
...this.activeRequest,
|
|
423
|
+
requestId: response.request.requestId,
|
|
424
|
+
...(response.request.clientDispatchId === undefined ? {} : { clientDispatchId: response.request.clientDispatchId }),
|
|
425
|
+
...(response.request.sketches === undefined ? {} : { sketches: response.request.sketches }),
|
|
426
|
+
}
|
|
385
427
|
: this.activeRequest;
|
|
386
428
|
}
|
|
387
429
|
if (response?.status && response.status !== "working") {
|
|
@@ -405,6 +447,9 @@ export class AgentClient {
|
|
|
405
447
|
}
|
|
406
448
|
/** Interrupts local request state and asks the companion to revoke pairing. */
|
|
407
449
|
disconnect(reason = "browser_disconnected") {
|
|
450
|
+
this.autoConnectSuppressed = true;
|
|
451
|
+
if (this.autoConnect)
|
|
452
|
+
setAutoConnectDisabled(this.projectId, true);
|
|
408
453
|
const token = this.sessionToken;
|
|
409
454
|
const active = this.activeRequest;
|
|
410
455
|
if (active?.status === "working") {
|
|
@@ -454,7 +499,21 @@ export class AgentClient {
|
|
|
454
499
|
else {
|
|
455
500
|
this.companionReachable = true;
|
|
456
501
|
if (this.connection !== "paired" && this.connection !== "working" && !this.pairingAbort) {
|
|
457
|
-
|
|
502
|
+
// Discovery is unauthenticated. Another browser tab can own the
|
|
503
|
+
// pairing, so do not adopt `paired` until this tab restores the
|
|
504
|
+
// shared token. The next poll runs restore first in case a sibling
|
|
505
|
+
// tab has just stored that token.
|
|
506
|
+
this.applyStatus(status.paired ? {
|
|
507
|
+
...status,
|
|
508
|
+
connection: status.listenerActive ? "listening" : "offline",
|
|
509
|
+
paired: false,
|
|
510
|
+
request: null,
|
|
511
|
+
} : status);
|
|
512
|
+
if (this.autoConnect
|
|
513
|
+
&& !status.paired
|
|
514
|
+
&& !this.autoConnectSuppressed
|
|
515
|
+
&& !autoConnectDisabled(this.projectId))
|
|
516
|
+
void this.connect();
|
|
458
517
|
}
|
|
459
518
|
}
|
|
460
519
|
}
|
|
@@ -491,7 +550,7 @@ export class AgentClient {
|
|
|
491
550
|
return;
|
|
492
551
|
this.discoveryTimer = setTimeout(() => {
|
|
493
552
|
this.discoveryTimer = null;
|
|
494
|
-
void this.
|
|
553
|
+
void this.restoreOrDiscover();
|
|
495
554
|
}, this.discoveryIntervalMs);
|
|
496
555
|
}
|
|
497
556
|
scheduleReconnect() {
|
|
@@ -538,6 +597,12 @@ export class AgentClient {
|
|
|
538
597
|
},
|
|
539
598
|
};
|
|
540
599
|
}
|
|
600
|
+
// Every tab with the shared browser session receives the SSE command, but
|
|
601
|
+
// only the Canvas lease owner may answer it. A read-only tab reports this
|
|
602
|
+
// local sentinel; leave the command pending for the owning tab instead of
|
|
603
|
+
// racing its acknowledgement with a false failure.
|
|
604
|
+
if (!acknowledgement.ok && acknowledgement.error?.code === "workspace-locked")
|
|
605
|
+
return;
|
|
541
606
|
if (acknowledgement.commandId !== command.commandId) {
|
|
542
607
|
acknowledgement = {
|
|
543
608
|
commandId: command.commandId,
|
|
@@ -570,7 +635,7 @@ export class AgentClient {
|
|
|
570
635
|
this.discoveryTimer = null;
|
|
571
636
|
}
|
|
572
637
|
if (status.request) {
|
|
573
|
-
this.applyRequestStatus(status.request.status, status.request.requestId, status.request.summary, status.request.error, status.request.changeRevision);
|
|
638
|
+
this.applyRequestStatus(status.request.status, status.request.requestId, status.request.summary, status.request.error, status.request.changeRevision, status.request.clientDispatchId, status.request.sketches);
|
|
574
639
|
}
|
|
575
640
|
else if (status.listenerActive && status.paired && this.activeRequest?.status !== "working") {
|
|
576
641
|
// A re-armed MCP listen call is the lifecycle boundary for the previous
|
|
@@ -598,7 +663,7 @@ export class AgentClient {
|
|
|
598
663
|
this.paired = false;
|
|
599
664
|
this.publish();
|
|
600
665
|
}
|
|
601
|
-
applyRequestStatus(status, requestId, summary, error, changeRevision) {
|
|
666
|
+
applyRequestStatus(status, requestId, summary, error, changeRevision, clientDispatchId, sketches) {
|
|
602
667
|
const active = this.activeRequest;
|
|
603
668
|
if (active && active.requestId !== requestId && active.localRequestId !== requestId) {
|
|
604
669
|
// A fresh server id can arrive before the POST response. Associate it
|
|
@@ -613,6 +678,8 @@ export class AgentClient {
|
|
|
613
678
|
requestId,
|
|
614
679
|
prompt: "",
|
|
615
680
|
...(changeRevision === undefined ? {} : { changeRevision }),
|
|
681
|
+
...(clientDispatchId === undefined ? {} : { clientDispatchId }),
|
|
682
|
+
...(sketches === undefined ? {} : { sketches }),
|
|
616
683
|
status,
|
|
617
684
|
...(summary === undefined ? {} : { summary }),
|
|
618
685
|
...(error === undefined ? {} : { error }),
|
|
@@ -624,6 +691,8 @@ export class AgentClient {
|
|
|
624
691
|
requestId,
|
|
625
692
|
status,
|
|
626
693
|
...(changeRevision === undefined ? {} : { changeRevision }),
|
|
694
|
+
...(clientDispatchId === undefined ? {} : { clientDispatchId }),
|
|
695
|
+
...(sketches === undefined ? {} : { sketches }),
|
|
627
696
|
...(summary === undefined ? {} : { summary }),
|
|
628
697
|
...(error === undefined ? {} : { error }),
|
|
629
698
|
};
|
|
@@ -659,6 +728,8 @@ export class AgentClient {
|
|
|
659
728
|
request: request ? {
|
|
660
729
|
requestId: request.requestId,
|
|
661
730
|
...(request.changeRevision === undefined ? {} : { changeRevision: request.changeRevision }),
|
|
731
|
+
...(request.clientDispatchId === undefined ? {} : { clientDispatchId: request.clientDispatchId }),
|
|
732
|
+
...(request.sketches === undefined ? {} : { sketches: request.sketches }),
|
|
662
733
|
status: request.status,
|
|
663
734
|
...(request.summary === undefined ? {} : { summary: request.summary }),
|
|
664
735
|
...(request.error === undefined ? {} : { error: request.error }),
|
|
@@ -15,20 +15,20 @@ export function getAgentConnectionStatus(snapshot) {
|
|
|
15
15
|
if (snapshot.paired && !snapshot.listenerActive) {
|
|
16
16
|
return {
|
|
17
17
|
kind: "connected-not-listening",
|
|
18
|
-
label: "
|
|
18
|
+
label: "Project connected · Ask agent to listen",
|
|
19
19
|
tone: "accent",
|
|
20
20
|
action: { kind: "setup", label: "Set up" },
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
if (!snapshot.companionReachable) {
|
|
24
|
-
return { kind: "not-found", label: "
|
|
24
|
+
return { kind: "not-found", label: "Project bridge unavailable", tone: "neutral" };
|
|
25
25
|
}
|
|
26
26
|
if (snapshot.listenerActive) {
|
|
27
27
|
return { kind: "listening", label: "Agent listening", tone: "accent" };
|
|
28
28
|
}
|
|
29
29
|
return {
|
|
30
30
|
kind: "ready-to-connect",
|
|
31
|
-
label: "
|
|
31
|
+
label: "Project available · Connect this page",
|
|
32
32
|
tone: "accent",
|
|
33
33
|
action: { kind: "connect", label: "Connect" },
|
|
34
34
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
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;
|
|
2
4
|
/**
|
|
3
5
|
* HTTP/SSE browser adapter for the companion's canonical endpoints.
|
|
4
6
|
*
|