diff --git a/.github/workflows/ae-see-suite-mcp.yml b/.github/workflows/ae-see-suite-mcp.yml new file mode 100644 index 0000000..bd4d308 --- /dev/null +++ b/.github/workflows/ae-see-suite-mcp.yml @@ -0,0 +1,31 @@ +name: AE See-Suite MCP + +on: + pull_request: + paths: + - "scripts/ae-see-suite-mcp-*.mjs" + - ".github/workflows/ae-see-suite-mcp.yml" + push: + branches: [main] + paths: + - "scripts/ae-see-suite-mcp-*.mjs" + - ".github/workflows/ae-see-suite-mcp.yml" + +jobs: + ae-see-suite-mcp-check: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Syntax check AE See-Suite MCP tools + run: node --check ./scripts/ae-see-suite-mcp-tools.mjs + + - name: Syntax check AE See-Suite MCP server + run: node --check ./scripts/ae-see-suite-mcp-server.mjs diff --git a/.github/workflows/frontend-proof.yml b/.github/workflows/frontend-proof.yml index 9126617..d0e219b 100644 --- a/.github/workflows/frontend-proof.yml +++ b/.github/workflows/frontend-proof.yml @@ -28,18 +28,25 @@ jobs: run: npm install - name: Build frontend - run: npm run build -w @ae-see-suite/web + shell: bash + run: | + mkdir -p frontend/proof/logs + set +e + npm run build -w @ae-see-suite/web 2>&1 | tee frontend/proof/logs/build-web.log + status=${PIPESTATUS[0]} + set -e + exit "$status" - name: Install Playwright Chromium run: npx playwright install chromium --with-deps - name: Capture anchor visual states working-directory: frontend - run: npm run proof:visual -- --states=01,06,26,37,61 --label=ci-anchors + run: npm run proof:visual -- --states=01,06,22,26,37,61 --label=ci-anchors - name: Produce non-strict pixel receipt working-directory: frontend - run: npm run proof:pixel -- --states=01,06,26,37,61 + run: npm run proof:pixel -- --states=01,06,22,26,37,61 - name: Summarize proof working-directory: frontend diff --git a/.github/workflows/project-green.yml b/.github/workflows/project-green.yml index 4607bd2..08d34f4 100644 --- a/.github/workflows/project-green.yml +++ b/.github/workflows/project-green.yml @@ -9,6 +9,8 @@ jobs: syntax-and-build: runs-on: ubuntu-latest timeout-minutes: 25 + env: + DATABASE_URL: postgresql://ae:ae@localhost:5432/ae_see_suite steps: - name: Checkout uses: actions/checkout@v4 @@ -30,8 +32,26 @@ jobs: - name: Build API workspace run: npm run build -w @ae-see-suite/api + - name: Test API workspace + run: npm run test -w @ae-see-suite/api + - name: Build frontend workspace - run: npm run build -w @ae-see-suite/web + shell: bash + run: | + mkdir -p frontend/proof/logs + set +e + npm run build -w @ae-see-suite/web 2>&1 | tee frontend/proof/logs/project-green-build-web.log + status=${PIPESTATUS[0]} + set -e + exit "$status" - name: AtomSmasher runtime syntax run: node --check ./scripts/v4/atomsmasher-runtime.mjs && node --check ./scripts/v4/atomsmasher-api-routes.mjs + + - name: Upload green logs + uses: actions/upload-artifact@v4 + if: always() + with: + name: project-green-logs + path: frontend/proof/logs/ + if-no-files-found: warn diff --git a/docs/ae-see-suite/MCP_SETUP.md b/docs/ae-see-suite/MCP_SETUP.md new file mode 100644 index 0000000..d394b51 --- /dev/null +++ b/docs/ae-see-suite/MCP_SETUP.md @@ -0,0 +1,87 @@ +# AE See-Suite MCP Setup + +This repo now includes a dedicated AE See-Suite MCP lane for build/proof control of the living dashboard. + +## Server + +```bash +node ./scripts/ae-see-suite-mcp-server.mjs +``` + +The server registers a focused AE See-Suite tool pack. It does not replace the broader OrangeBOX MCP server; it gives Claude Code/Codex a smaller surface specifically for the living dashboard work. + +## Tools + +- `ae_see_suite_help` +- `ae_see_suite_build_frontend` +- `ae_see_suite_proof_anchors` +- `ae_see_suite_proof_72` +- `ae_see_suite_state_open_command` + +## Product law encoded in MCP + +- One AppShell. +- Many semantic states. +- Do not build 72 pages. +- Do not use mockup images as UI. +- Use the 72-state atlas as proof/QA, not production logic. + +## Anchor state proof target + +The canonical anchor states are: + +- `01` Calm Overview +- `06` Alert / Critical Latency +- `22` Temporal Memory Expanded +- `26` Command Palette +- `37` Agent Queue +- `61` Living Canvas + +Run anchor proof through MCP with: + +```txt +ae_see_suite_proof_anchors +``` + +Equivalent shell path: + +```bash +npm run build:web +npm run frontend:proof:visual -- --states=01,06,22,26,37,61 --label=anchor-pass +npm run frontend:proof:pixel -- --states=01,06,22,26,37,61 +``` + +Run the full atlas proof with: + +```txt +ae_see_suite_proof_72 +``` + +Equivalent shell path: + +```bash +npm run build:web +npm run frontend:proof:visual:72 +``` + +## Example Claude Desktop / MCP config shape + +Use the local repo path that exists on the machine running the MCP client. + +```json +{ + "mcpServers": { + "ae-see-suite": { + "command": "node", + "args": ["C:/AtomEons/orangebox-delta/scripts/ae-see-suite-mcp-server.mjs"], + "env": { + "ORANGEBOX_URL": "http://127.0.0.1:8787" + } + } + } +} +``` + +## Operating note + +The MCP tools call the existing OrangeBOX receipted executor for builds/proofs. The OrangeBOX service must be running at `ORANGEBOX_URL` for build/proof tools to execute. The state URL helper does not require execution; it returns direct browser URLs and acceptance checks. diff --git a/frontend/package.json b/frontend/package.json index 007eaa7..ec649fe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -7,9 +7,9 @@ "dev": "vite --host 127.0.0.1", "build": "tsc --noEmit && vite build", "preview": "vite preview --host 127.0.0.1", - "proof:visual": "node ./scripts/visual-proof.mjs --states=01,06,26,37,61 --label=anchors", + "proof:visual": "node ./scripts/visual-proof.mjs --states=01,06,22,26,37,61 --label=anchors", "proof:visual:72": "node ./scripts/visual-proof.mjs --states=all --label=72-state", - "proof:pixel": "node ./scripts/pixel-compare.mjs --states=01,06,26,37,61", + "proof:pixel": "node ./scripts/pixel-compare.mjs --states=01,06,22,26,37,61", "proof:summary": "node ./scripts/proof-summary.mjs" }, "dependencies": { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5178941..bb8b1e7 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -11,6 +11,8 @@ import "./styles/motion.css"; import "./styles/command-palette.css"; import "./styles/drawers.css"; import "./styles/canvas.css"; +import "./styles/temporal.css"; +import "./styles/product-surfaces.css"; import "./styles/animations.css"; import "./styles/responsive.css"; import "./styles/visual-atlas.css"; diff --git a/frontend/src/components/overlays/ProductSurfaceOverlay.tsx b/frontend/src/components/overlays/ProductSurfaceOverlay.tsx new file mode 100644 index 0000000..807294e --- /dev/null +++ b/frontend/src/components/overlays/ProductSurfaceOverlay.tsx @@ -0,0 +1,33 @@ +import { AnimatePresence } from "motion/react"; +import { useAppStore } from "../../store/useAppStore"; +import { SettingsDrawer } from "../surfaces/SettingsDrawer"; +import { NotificationsDrawer } from "../surfaces/NotificationsDrawer"; +import { ArtifactInspectorDrawer } from "../surfaces/ArtifactInspectorDrawer"; +import { ProductModalStack } from "../surfaces/ProductModalStack"; + +const productDrawers = new Set(["settings", "notifications", "artifact-inspector"]); +const productModals = new Set(["add-widget", "context-picker", "model-selector", "export", "approval", "shortcuts", "compare", "stop-confirm"]); + +export function isProductSurfaceDrawer(drawer?: string) { + return Boolean(drawer && productDrawers.has(drawer)); +} + +export function isProductSurfaceModal(modal?: string) { + return Boolean(modal && productModals.has(modal)); +} + +export function ProductSurfaceOverlay() { + const activeDrawer = useAppStore((s) => s.activeDrawer); + const activeModal = useAppStore((s) => s.activeModal); + + return ( + <> + + {activeDrawer === "settings" ? : null} + {activeDrawer === "notifications" ? : null} + {activeDrawer === "artifact-inspector" ? : null} + + {isProductSurfaceModal(activeModal) ? : null} + + ); +} diff --git a/frontend/src/components/overlays/StateWorkbenchOverlay.tsx b/frontend/src/components/overlays/StateWorkbenchOverlay.tsx index e31d800..1ba2371 100644 --- a/frontend/src/components/overlays/StateWorkbenchOverlay.tsx +++ b/frontend/src/components/overlays/StateWorkbenchOverlay.tsx @@ -2,34 +2,16 @@ import { AnimatePresence, motion } from "motion/react"; import { X } from "lucide-react"; import { getMockupStateSpec } from "../../engine/mockupStateBank"; import { useAppStore } from "../../store/useAppStore"; +import { isProductSurfaceDrawer, isProductSurfaceModal } from "./ProductSurfaceOverlay"; const drawerTitles: Record = { - settings: "Settings", - notifications: "Notifications", - "artifact-inspector": "Artifact Inspector", performance: "Performance Overlay", }; -const modalTitles: Record = { - "add-widget": "Add Widget", - "context-picker": "Context Picker", - "model-selector": "Model Selector", - export: "Export Workspace", - approval: "Deployment Approval", - shortcuts: "Keyboard Shortcuts", - compare: "Artifact Branch Compare", - "stop-confirm": "Stop Active Run?", -}; +const modalTitles: Record = {}; function linesForSurface(label: string) { - if (label.includes("Settings")) return ["Motion intensity", "Memory scope", "Backend: mock / remote", "Visual priority tuning"]; - if (label.includes("Notifications")) return ["Critical latency", "Tool result returned", "Artifact created", "Agent task completed"]; - if (label.includes("Artifact")) return ["Deployment Report", "Primary branch", "Related panels: pipeline, model, causality", "Export ready"]; - if (label.includes("Context")) return ["System Health", "Project Nexus", "Causal Insights", "Temporal Memory"]; - if (label.includes("Model")) return ["GPT-5.5 frontier", "Deep mode", "Latency budget: medium", "Tool policy: approved"]; - if (label.includes("Approval")) return ["Canary 5%", "Risk: medium", "Rollback gate: ready", "Human approval required"]; - if (label.includes("Shortcuts")) return ["Cmd/Ctrl+K command palette", "Cmd/Ctrl+L composer focus", "Cmd/Ctrl+Enter run", "Escape closes overlays"]; - if (label.includes("Compare")) return ["Branch A: concise", "Branch B: evidence-rich", "Branch C: deployment-safe", "Recommended: Branch B"]; + if (label.includes("Performance")) return ["FPS", "Memory", "Render cost", "Stream health"]; return ["Workspace snapshot", "Timeline range", "Artifacts and memory", "Ready"]; } @@ -41,8 +23,8 @@ export function StateWorkbenchOverlay() { const setModalOpen = useAppStore((s) => s.setModalOpen); const mockup = getMockupStateSpec(activeMockupStateId); - const drawerTitle = activeDrawer ? drawerTitles[activeDrawer] : undefined; - const modalTitle = activeModal ? modalTitles[activeModal] : undefined; + const drawerTitle = activeDrawer && !isProductSurfaceDrawer(activeDrawer) ? drawerTitles[activeDrawer] : undefined; + const modalTitle = activeModal && !isProductSurfaceModal(activeModal) ? modalTitles[activeModal] : undefined; return ( <> diff --git a/frontend/src/components/overlays/TemporalMemoryExpandedOverlay.tsx b/frontend/src/components/overlays/TemporalMemoryExpandedOverlay.tsx new file mode 100644 index 0000000..f96f0ca --- /dev/null +++ b/frontend/src/components/overlays/TemporalMemoryExpandedOverlay.tsx @@ -0,0 +1,72 @@ +import { AnimatePresence, motion } from "motion/react"; +import { Clock, FastForward, Play, RotateCcw, Search, X } from "lucide-react"; +import { useAppStore } from "../../store/useAppStore"; + +export function TemporalMemoryExpandedOverlay() { + const memoryPanel = useAppStore((s) => s.panels.find((panel) => panel.id === "memory-ribbon")); + const timeline = useAppStore((s) => s.timeline); + const focusPanel = useAppStore((s) => s.focusPanel); + const setContextPanels = useAppStore((s) => s.setContextPanels); + const setComposerValue = useAppStore((s) => s.setComposerValue); + const expandPanel = useAppStore((s) => s.expandPanel); + const activeMockupStateId = useAppStore((s) => s.activeMockupStateId); + + const open = Boolean(memoryPanel?.expanded); + const events = timeline.slice(-12).reverse(); + + return ( + + {open ? ( + +
+
+ Temporal Memory + Timeline scrub, snapshots, rewind, and event replay +
+ +
+ +
+ + Search memory, timeline, decisions, artifacts... +
+ +
+ + + +
+ +
+ {events.map((event) => ( + + ))} +
+
+ ) : null} +
+ ); +} diff --git a/frontend/src/components/overlays/TemporalMemoryRibbon.tsx b/frontend/src/components/overlays/TemporalMemoryRibbon.tsx index 4690f76..76c4713 100644 --- a/frontend/src/components/overlays/TemporalMemoryRibbon.tsx +++ b/frontend/src/components/overlays/TemporalMemoryRibbon.tsx @@ -3,10 +3,21 @@ import { useAppStore } from "../../store/useAppStore"; export function TemporalMemoryRibbon() { const timeline = useAppStore((s) => s.timeline); - const setDrawerOpen = useAppStore((s) => s.setDrawerOpen); + const expandPanel = useAppStore((s) => s.expandPanel); + const setMode = useAppStore((s) => s.setMode); + const setContextPanels = useAppStore((s) => s.setContextPanels); const events = timeline.slice(-7); + return ( - - - - diff --git a/frontend/src/components/surfaces/ArtifactInspectorDrawer.tsx b/frontend/src/components/surfaces/ArtifactInspectorDrawer.tsx new file mode 100644 index 0000000..8905338 --- /dev/null +++ b/frontend/src/components/surfaces/ArtifactInspectorDrawer.tsx @@ -0,0 +1,55 @@ +import { Download, FileText, Layers } from "lucide-react"; +import { useAppStore } from "../../store/useAppStore"; +import type { PanelId } from "../../types/app"; +import { ProductDrawerShell } from "./SurfaceShell"; + +function panelLabel(id: PanelId) { + const labels: Record = { + "system-health": "System Health", + "project-nexus": "Project Nexus", + "realtime-insights": "Real-Time Insights", + "model-performance": "Model Performance", + "data-stream": "Data Stream", + "pipeline-orchestrator": "Pipeline", + "activity-feed": "Activity", + "smart-suggestions": "Suggestions", + causality: "Causality", + "memory-ribbon": "Temporal Memory", + }; + return labels[id] ?? id; +} + +export function ArtifactInspectorDrawer() { + const artifacts = useAppStore((s) => s.artifacts); + const activeArtifactId = useAppStore((s) => s.activeArtifactId); + const setWorkspaceView = useAppStore((s) => s.setWorkspaceView); + const setActiveArtifact = useAppStore((s) => s.setActiveArtifact); + const artifact = artifacts.find((item) => item.id === activeArtifactId) ?? artifacts[artifacts.length - 1]; + + return ( + }> + {artifact ? ( + <> +
+ {artifact.kind} + {artifact.title} + {artifact.content.slice(0, 360)} +
+
+ {artifact.relatedPanelIds.map((panelId) => ( + + ))} +
+
+ + +
+ + ) : ( +
No artifact yet. Run a generate command to create a canvas object.
+ )} +
+ ); +} diff --git a/frontend/src/components/surfaces/NotificationsDrawer.tsx b/frontend/src/components/surfaces/NotificationsDrawer.tsx new file mode 100644 index 0000000..81b4a3f --- /dev/null +++ b/frontend/src/components/surfaces/NotificationsDrawer.tsx @@ -0,0 +1,25 @@ +import { Bell } from "lucide-react"; +import { useAppStore } from "../../store/useAppStore"; +import { ProductDrawerShell } from "./SurfaceShell"; + +export function NotificationsDrawer() { + const eventToasts = useAppStore((s) => s.eventToasts); + const timeline = useAppStore((s) => s.timeline); + const items = eventToasts.length + ? eventToasts.map((toast) => ({ id: toast.id, title: toast.title, description: toast.description, severity: toast.severity, label: "toast" })) + : timeline.slice(-8).reverse().map((event) => ({ id: event.id, title: event.title, description: event.description, severity: event.severity, label: event.type })); + + return ( + }> +
+ {items.map((item) => ( +
+ {item.label} + {item.title} + {item.description ? {item.description} : null} +
+ ))} +
+
+ ); +} diff --git a/frontend/src/components/surfaces/ProductModalStack.tsx b/frontend/src/components/surfaces/ProductModalStack.tsx new file mode 100644 index 0000000..eb22821 --- /dev/null +++ b/frontend/src/components/surfaces/ProductModalStack.tsx @@ -0,0 +1,196 @@ +import { + Boxes, + ClipboardCheck, + Cpu, + Download, + GitCompare, + Keyboard, + Plus, + ShieldAlert, + Sparkles, + Square, + X, +} from "lucide-react"; +import { useAppStore } from "../../store/useAppStore"; +import type { ModalId, PanelId } from "../../types/app"; +import { ProductModalShell } from "./SurfaceShell"; + +function panelLabel(id: PanelId) { + const labels: Record = { + "system-health": "System Health", + "project-nexus": "Project Nexus", + "realtime-insights": "Real-Time Insights", + "model-performance": "Model Performance", + "data-stream": "Data Stream", + "pipeline-orchestrator": "Pipeline", + "activity-feed": "Activity", + "smart-suggestions": "Suggestions", + causality: "Causality", + "memory-ribbon": "Temporal Memory", + }; + return labels[id] ?? id; +} + +function AddWidgetModal() { + const setComposerValue = useAppStore((s) => s.setComposerValue); + const setModalOpen = useAppStore((s) => s.setModalOpen); + const templates = ["Latency causes", "Model drift", "Pipeline stage timing", "Cost pressure", "Agent work queue"]; + + return ( + }> +
+ {templates.map((template) => ( + + ))} +
+
+ ); +} + +function ContextPickerModal() { + const panels = useAppStore((s) => s.panels); + const contextPanelIds = useAppStore((s) => s.composer.contextPanelIds); + const setContextPanels = useAppStore((s) => s.setContextPanels); + const focusPanel = useAppStore((s) => s.focusPanel); + + function toggle(panelId: PanelId) { + const next = contextPanelIds.includes(panelId) + ? contextPanelIds.filter((id) => id !== panelId) + : [...contextPanelIds, panelId].slice(0, 6); + setContextPanels(next); + } + + return ( + }> +
+ {panels.map((panel) => ( + + ))} +
+
+ ); +} + +function ModelSelectorModal() { + const composer = useAppStore((s) => s.composer); + const setSelectedModel = useAppStore((s) => s.setSelectedModel); + const setSelectedMode = useAppStore((s) => s.setSelectedMode); + const models = ["GPT-5.5", "Local DeepSeek", "Qwen Coder", "Fast Router"]; + const modes: Array = ["fast", "deep", "creative", "code", "agent"]; + + return ( + }> +
Models
+
+ {models.map((model) => )} +
+
Modes
+
+ {modes.map((mode) => )} +
+
+ ); +} + +function ExportModal() { + const artifacts = useAppStore((s) => s.artifacts); + const timeline = useAppStore((s) => s.timeline); + + return ( + }> +
+
Artifacts{artifacts.length}
+
Timeline events{timeline.length}
+
FormatReport bundle
+
+
+
+ ); +} + +function ApprovalModal() { + const metrics = useAppStore((s) => s.metrics); + const setModalOpen = useAppStore((s) => s.setModalOpen); + const setDrawerOpen = useAppStore((s) => s.setDrawerOpen); + + return ( + }> +
+
Latency{Math.round(metrics.latencyMs)}ms
+
Pipeline{Math.round(metrics.pipelineSuccess)}%
+
Risk{metrics.latencyMs > 110 ? "high" : "medium"}
+
+
+ +
+
+ ); +} + +function ShortcutsModal() { + const shortcuts = [ + ["Cmd/Ctrl + K", "Command palette"], + ["Cmd/Ctrl + L", "Focus composer"], + ["Cmd/Ctrl + Enter", "Run command"], + ["Escape", "Close overlays / collapse panels"], + ]; + return ( + }> +
+ {shortcuts.map(([key, label]) =>
{key}{label}
)} +
+
+ ); +} + +function CompareModal() { + const artifacts = useAppStore((s) => s.artifacts); + const first = artifacts[0]; + const second = artifacts[1] ?? artifacts[0]; + + return ( + }> +
+ {[first, second].map((artifact, index) => ( +
+ Branch {index + 1} + {artifact?.title ?? "No branch"} + {artifact?.content.slice(0, 300) ?? "Generate an artifact to compare branches."} +
+ ))} +
+
+ ); +} + +function StopConfirmModal() { + const cancelActiveTasks = useAppStore((s) => s.cancelActiveTasks); + const setModalOpen = useAppStore((s) => s.setModalOpen); + const tasks = useAppStore((s) => s.tasks.filter((task) => ["queued", "planning", "running", "waiting"].includes(task.status))); + + return ( + }> +
Active tasks{tasks.length}Stopping cancels running work while preserving timeline, artifacts, and context.
+
+
+ ); +} + +export function ProductModalStack({ modalId }: { modalId: ModalId }) { + if (modalId === "add-widget") return ; + if (modalId === "context-picker") return ; + if (modalId === "model-selector") return ; + if (modalId === "export") return ; + if (modalId === "approval") return ; + if (modalId === "shortcuts") return ; + if (modalId === "compare") return ; + if (modalId === "stop-confirm") return ; + return null; +} diff --git a/frontend/src/components/surfaces/SettingsDrawer.tsx b/frontend/src/components/surfaces/SettingsDrawer.tsx new file mode 100644 index 0000000..47d88af --- /dev/null +++ b/frontend/src/components/surfaces/SettingsDrawer.tsx @@ -0,0 +1,42 @@ +import { Cpu, Settings, SlidersHorizontal } from "lucide-react"; +import { useAppStore } from "../../store/useAppStore"; +import { ProductDrawerShell } from "./SurfaceShell"; + +export function SettingsDrawer() { + const mode = useAppStore((s) => s.mode); + const composer = useAppStore((s) => s.composer); + const performanceOverlayOpen = useAppStore((s) => s.performanceOverlayOpen); + const setPerformanceOverlayOpen = useAppStore((s) => s.setPerformanceOverlayOpen); + const layoutEditMode = useAppStore((s) => s.layoutEditMode); + const setLayoutEditMode = useAppStore((s) => s.setLayoutEditMode); + + const rows = [ + ["System mode", mode], + ["Selected model", composer.selectedModel], + ["Run mode", composer.selectedMode], + ["Tools", composer.selectedTools.join(" / ")], + ]; + + return ( + }> +
+ {rows.map(([label, value]) => ( +
+ {label} + {value} +
+ ))} +
+
+ + +
+
+ ); +} diff --git a/frontend/src/components/surfaces/SurfaceShell.tsx b/frontend/src/components/surfaces/SurfaceShell.tsx new file mode 100644 index 0000000..c276413 --- /dev/null +++ b/frontend/src/components/surfaces/SurfaceShell.tsx @@ -0,0 +1,85 @@ +import type { ReactNode } from "react"; +import { motion } from "motion/react"; +import { X } from "lucide-react"; +import { useAppStore } from "../../store/useAppStore"; +import type { ModalId } from "../../types/app"; + +export function ProductDrawerShell({ + title, + subtitle, + icon, + children, +}: { + title: string; + subtitle: string; + icon: ReactNode; + children: ReactNode; +}) { + const setDrawerOpen = useAppStore((s) => s.setDrawerOpen); + + return ( + +
+ {icon} +
+ {title} + {subtitle} +
+ +
+
{children}
+
+ ); +} + +export function ProductModalShell({ + modalId, + title, + subtitle, + icon, + children, +}: { + modalId: ModalId; + title: string; + subtitle: string; + icon: ReactNode; + children: ReactNode; +}) { + const setModalOpen = useAppStore((s) => s.setModalOpen); + + return ( + +
+ {icon} +
+ {title} + {subtitle} +
+ +
+
{children}
+
+ ); +} diff --git a/frontend/src/engine/commandPaletteRegistry.ts b/frontend/src/engine/commandPaletteRegistry.ts index d288d6b..0e25377 100644 --- a/frontend/src/engine/commandPaletteRegistry.ts +++ b/frontend/src/engine/commandPaletteRegistry.ts @@ -1,4 +1,4 @@ -import type { PanelId, SystemMode } from "../types/app"; +import type { DrawerId, ModalId, PanelId, SystemMode } from "../types/app"; import { handleSubmitCommand } from "./taskRunner"; import { useAppStore } from "../store/useAppStore"; import { mockupStateBank } from "./mockupStateBank"; @@ -46,6 +46,36 @@ function setModeAction(mode: SystemMode, title: string): CommandPaletteAction { }; } +function openDrawerAction(id: DrawerId, title: string, subtitle: string, keywords: string[], kind: CommandPaletteActionKind = "workspace"): CommandPaletteAction { + return { + id: `open-drawer-${id}`, + kind, + title, + subtitle, + keywords: ["drawer", "open", title, ...(keywords ?? [])], + run: () => { + const store = useAppStore.getState(); + store.setDrawerOpen(id); + store.setCommandPaletteOpen(false); + }, + }; +} + +function openModalAction(id: ModalId, title: string, subtitle: string, keywords: string[], kind: CommandPaletteActionKind = "workspace"): CommandPaletteAction { + return { + id: `open-modal-${id}`, + kind, + title, + subtitle, + keywords: ["modal", "open", title, ...(keywords ?? [])], + run: () => { + const store = useAppStore.getState(); + store.setModalOpen(id); + store.setCommandPaletteOpen(false); + }, + }; +} + export function getCommandPaletteActions(): CommandPaletteAction[] { const store = useAppStore.getState(); return [ @@ -96,6 +126,18 @@ export function getCommandPaletteActions(): CommandPaletteAction[] { s.setCommandPaletteOpen(false); }, }, + { + id: "open-dashboard", + kind: "workspace", + title: "Return to Dashboard", + subtitle: "Bring floating panels back to the front", + keywords: ["dashboard", "return", "panels", "system"], + run: () => { + const s = useAppStore.getState(); + s.setWorkspaceView("dashboard"); + s.setCommandPaletteOpen(false); + }, + }, { id: "open-agent-queue", kind: "agent", @@ -120,6 +162,30 @@ export function getCommandPaletteActions(): CommandPaletteAction[] { s.setCommandPaletteOpen(false); }, }, + openDrawerAction("settings", "Open Settings", "Motion, memory, backend, and operator preferences", ["settings", "preferences", "motion", "backend"], "workspace"), + openDrawerAction("notifications", "Open Notifications", "Alerts, tool results, task completions, and workspace events", ["notifications", "alerts", "events", "toast"], "debug"), + openDrawerAction("artifact-inspector", "Open Artifact Inspector", "Inspect generated work and source context", ["artifact", "inspector", "export", "canvas"], "workspace"), + openModalAction("add-widget", "Add Widget", "Create a semantic panel from a metric, memory, or command", ["widget", "panel", "create", "add"], "workspace"), + openModalAction("context-picker", "Open Context Picker", "Attach panels and memory to the next command", ["context", "picker", "attach", "panels"], "run"), + openModalAction("model-selector", "Open Model Selector", "Choose model and reasoning mode", ["model", "selector", "mode", "router"], "run"), + openModalAction("export", "Export Workspace", "Package artifacts, timeline, and current state", ["export", "download", "bundle"], "workspace"), + openModalAction("approval", "Open Deployment Approval", "Review rollout risk and approval gates", ["approval", "deploy", "canary", "risk"], "tool"), + openModalAction("shortcuts", "Open Keyboard Shortcuts", "Show fast control commands", ["keyboard", "shortcuts", "hotkeys"], "workspace"), + openModalAction("compare", "Open Branch Compare", "Compare generated artifact branches", ["compare", "branch", "artifact", "diff"], "workspace"), + openModalAction("stop-confirm", "Confirm Stop Active Run", "Cancel active agent work with a clear checkpoint", ["stop", "cancel", "run", "task"], "debug"), + { + id: "expand-temporal-memory", + kind: "memory", + title: "Expand Temporal Memory", + subtitle: "Open the large timeline scrub and replay surface", + keywords: ["temporal", "memory", "timeline", "state", "22", "expand"], + run: () => { + const s = useAppStore.getState(); + s.expandPanel("memory-ribbon", true); + s.setMode("reviewing"); + s.setCommandPaletteOpen(false); + }, + }, { id: "toggle-performance", kind: "debug", diff --git a/frontend/src/engine/remoteLLMAdapter.ts b/frontend/src/engine/remoteLLMAdapter.ts index c31c03d..bb48eaa 100644 --- a/frontend/src/engine/remoteLLMAdapter.ts +++ b/frontend/src/engine/remoteLLMAdapter.ts @@ -1,29 +1,60 @@ import type { LLMAdapter, LLMStreamEvent } from "../types/llm"; import { useAppStore } from "../store/useAppStore"; import { serializeWorkspaceForLLM } from "./workspaceSerializer"; +import { mockLLMAdapter } from "./mockLLMAdapter"; const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? ""; +const ORANGEBOX_RUN_PATH = "/api/v4/see-suite/agent/run"; + +async function* fallbackToAelidVisualMode(input: Parameters[0], reason: string): AsyncIterable { + yield { + type: "state", + mode: "reviewing", + energy: 0.62, + contextPanelIds: ["project-nexus", "system-health", "memory-ribbon"], + }; + + yield { + type: "token", + token: `OrangeBOX is not connected (${reason}). AELID is running in visual/local mode. `, + }; + + for await (const event of mockLLMAdapter.run(input)) { + yield event; + } +} export const remoteLLMAdapter: LLMAdapter = { async *run(input) { const state = useAppStore.getState(); const lastUser = [...input.messages].reverse().find((message) => message.role === "user"); const base = API_BASE_URL || window.location.origin; - const response = await fetch(`${base}/api/v4/see-suite/agent/run`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - workspaceId: state.workspaceId, - sessionId: "session-local-dev", - command: lastUser?.content ?? "", - messages: input.messages, - workspace: serializeWorkspaceForLLM(state), - }), - signal: input.abortSignal, - }); + + let response: Response; + + try { + response = await fetch(`${base}${ORANGEBOX_RUN_PATH}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + module: "AELID", + moduleName: "AtomEons Living Intelligence Dashboard", + workspaceId: state.workspaceId, + sessionId: "session-local-dev", + command: lastUser?.content ?? "", + messages: input.messages, + workspace: serializeWorkspaceForLLM(state), + }), + signal: input.abortSignal, + }); + } catch (error) { + const reason = error instanceof Error ? error.message : "fetch failed"; + yield* fallbackToAelidVisualMode(input, reason); + return; + } if (!response.ok || !response.body) { - yield { type: "error", error: `Remote run failed: ${response.status}` }; + yield* fallbackToAelidVisualMode(input, `HTTP ${response.status}`); return; } @@ -44,7 +75,7 @@ export const remoteLLMAdapter: LLMAdapter = { try { yield JSON.parse(line.replace(/^data:\s*/, "")) as LLMStreamEvent; } catch { - yield { type: "error", error: "Failed to parse stream event." }; + yield { type: "error", error: "Failed to parse OrangeBOX stream event." }; } } } diff --git a/frontend/src/hooks/useStartupStateCleanup.ts b/frontend/src/hooks/useStartupStateCleanup.ts new file mode 100644 index 0000000..bffdf59 --- /dev/null +++ b/frontend/src/hooks/useStartupStateCleanup.ts @@ -0,0 +1,15 @@ +import { useEffect } from "react"; +import { useAppStore } from "../store/useAppStore"; + +export function useStartupStateCleanup() { + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const requestedState = params.get("state") ?? params.get("mockupState"); + if (requestedState) return; + + const state = useAppStore.getState(); + if (state.mode === "calm" && !state.activeMockupStateId && state.activeCausalPath) { + state.setActiveCausalPath(undefined); + } + }, []); +} diff --git a/frontend/src/store/useAppStore.ts b/frontend/src/store/useAppStore.ts index db9b074..64c9f7f 100644 --- a/frontend/src/store/useAppStore.ts +++ b/frontend/src/store/useAppStore.ts @@ -120,7 +120,7 @@ export const useAppStore = create((set, get) => ({ focusPanelId: undefined, panels: seedPanels, agents: seedAgents, - activeCausalPath: createLatencyCausalPath(), + activeCausalPath: undefined, timeline: persisted?.timeline ?? seedTimeline, messages: persisted?.messages ?? seedMessages, tasks: [], @@ -145,10 +145,9 @@ export const useAppStore = create((set, get) => ({ pendingApprovals: [], eventToasts: [], performanceOverlayOpen: false, - chatDockExpanded: false, + chatDockExpanded: true, layoutEditMode: false, activeMockupStateId: undefined, - setWorkspaceId: (workspaceId) => set({ workspaceId }), setMode: (mode) => set({ mode }), setWorkspaceView: (workspaceView) => set({ workspaceView }), @@ -161,8 +160,8 @@ export const useAppStore = create((set, get) => ({ panels: state.panels.map((panel) => ({ ...panel, dimmed: false, highlighted: false })), }; } - const focusedPanel = state.panels.find((panel) => panel.id === panelId); - const related = new Set(focusedPanel ? [focusedPanel.id, ...focusedPanel.connectedTo] : [panelId]); + const focused = state.panels.find((panel) => panel.id === panelId); + const related = new Set(focused ? [focused.id, ...focused.connectedTo] : [panelId]); return { focusPanelId: panelId, panels: state.panels.map((panel) => ({ @@ -170,10 +169,7 @@ export const useAppStore = create((set, get) => ({ dimmed: !related.has(panel.id), highlighted: related.has(panel.id) && panel.id !== panelId, })), - composer: { - ...state.composer, - contextPanelIds: Array.from(related).slice(0, 4), - }, + composer: { ...state.composer, contextPanelIds: Array.from(related).slice(0, 4) }, }; }), clearFocus: () => @@ -183,271 +179,154 @@ export const useAppStore = create((set, get) => ({ })), expandPanel: (panelId, expanded) => set((state) => ({ - panels: state.panels.map((panel) => ({ ...panel, expanded: panel.id === panelId ? expanded : false })), + panels: state.panels.map((panel) => (panel.id === panelId ? { ...panel, expanded } : panel)), focusPanelId: expanded ? panelId : state.focusPanelId, })), - collapseAllPanels: () => - set((state) => ({ - panels: state.panels.map((panel) => ({ ...panel, expanded: false })), - })), - setPanelSeverity: (panelId, severity) => - set((state) => ({ - panels: state.panels.map((panel) => (panel.id === panelId ? { ...panel, severity } : panel)), - })), - setComposerValue: (value) => - set((state) => ({ - composer: { ...state.composer, value, slashMenuOpen: value.startsWith("/") }, - mode: state.composer.isFocused ? "listening" : state.mode, - })), - setComposerFocus: (isFocused) => - set((state) => ({ - composer: { ...state.composer, isFocused }, - mode: isFocused && state.mode === "calm" ? "listening" : !isFocused && state.mode === "listening" ? "calm" : state.mode, - })), + collapseAllPanels: () => set((state) => ({ panels: state.panels.map((panel) => ({ ...panel, expanded: false })) })), + setPanelSeverity: (panelId, severity) => set((state) => ({ panels: state.panels.map((panel) => (panel.id === panelId ? { ...panel, severity } : panel)) })), + setComposerValue: (value) => set((state) => ({ composer: { ...state.composer, value, slashMenuOpen: value.startsWith("/") } })), + setComposerFocus: (isFocused) => set((state) => ({ composer: { ...state.composer, isFocused }, mode: isFocused ? "listening" : state.mode })), setSelectedMode: (selectedMode) => set((state) => ({ composer: { ...state.composer, selectedMode } })), setSelectedModel: (selectedModel) => set((state) => ({ composer: { ...state.composer, selectedModel } })), toggleTool: (tool) => - set((state) => { - const selectedTools = state.composer.selectedTools.includes(tool) - ? state.composer.selectedTools.filter((item) => item !== tool) - : [...state.composer.selectedTools, tool]; - return { composer: { ...state.composer, selectedTools } }; - }), + set((state) => ({ + composer: { + ...state.composer, + selectedTools: state.composer.selectedTools.includes(tool) + ? state.composer.selectedTools.filter((item) => item !== tool) + : [...state.composer.selectedTools, tool], + }, + })), setContextPanels: (contextPanelIds) => set((state) => ({ composer: { ...state.composer, contextPanelIds } })), setSlashMenuOpen: (slashMenuOpen) => set((state) => ({ composer: { ...state.composer, slashMenuOpen } })), setPlanPreview: (planPreview) => set((state) => ({ composer: { ...state.composer, planPreview } })), - addMessage: (message) => set((state) => ({ messages: [...state.messages, message] })), - updateMessage: (id, patch) => - set((state) => ({ - messages: state.messages.map((message) => (message.id === id ? { ...message, ...patch } : message)), - })), + addMessage: (message) => set((state) => ({ messages: [...state.messages, message].slice(-100) })), + updateMessage: (id, patch) => set((state) => ({ messages: state.messages.map((message) => (message.id === id ? { ...message, ...patch } : message)) })), addTimelineEvent: (event) => set((state) => ({ timeline: [...state.timeline, event].slice(-120) })), - addTask: (task) => set((state) => ({ tasks: [...state.tasks, task].slice(-40) })), - updateTask: (id, patch) => + addTask: (task) => set((state) => ({ tasks: [...state.tasks, task].slice(-80) })), + updateTask: (id, patch) => set((state) => ({ tasks: state.tasks.map((task) => (task.id === id ? { ...task, ...patch } : task)) })), + completeTask: (id) => set((state) => ({ - tasks: state.tasks.map((task) => (task.id === id ? { ...task, ...patch } : task)), + tasks: state.tasks.map((task) => (task.id === id ? { ...task, status: "complete", progress: 100, completedAt: Date.now() } : task)), + agents: state.agents.map((agent) => (agent.taskId === id ? { ...agent, state: "complete", taskId: undefined, energy: 0.68 } : agent)), })), - completeTask: (id) => - set((state) => { - const completedTask = state.tasks.find((task) => task.id === id); - return { - tasks: state.tasks.map((task) => - task.id === id ? { ...task, status: "complete", progress: 100, completedAt: Date.now() } : task, - ), - agents: completedTask - ? state.agents.map((agent) => - agent.id === completedTask.assignedAgentId ? { ...agent, state: "complete", taskId: undefined, energy: 0.68 } : agent, - ) - : state.agents, - }; - }), cancelActiveTasks: () => set((state) => ({ - mode: state.activeCausalPath ? "alert" : "calm", - energy: state.activeCausalPath ? 0.82 : 0.55, + mode: "calm", + energy: 0.55, tasks: state.tasks.map((task) => ["queued", "planning", "running", "waiting"].includes(task.status) - ? { ...task, status: "cancelled", progress: Math.min(task.progress, 99), completedAt: Date.now() } + ? { ...task, status: "cancelled", completedAt: Date.now() } : task, ), - agents: state.agents.map((agent) => ({ - ...agent, - state: agent.state === "working" || agent.state === "thinking" || agent.state === "blocked" ? "idle" : agent.state, - taskId: undefined, - energy: Math.max(0.42, agent.energy * 0.7), - })), + agents: state.agents.map((agent) => ({ ...agent, state: agent.state === "blocked" ? "blocked" : "idle", taskId: undefined, energy: 0.48 })), })), - updateAgent: (id, patch) => - set((state) => ({ agents: state.agents.map((agent) => (agent.id === id ? { ...agent, ...patch } : agent)) })), + updateAgent: (id, patch) => set((state) => ({ agents: state.agents.map((agent) => (agent.id === id ? { ...agent, ...patch } : agent)) })), setActiveCausalPath: (activeCausalPath) => set({ activeCausalPath, mode: activeCausalPath ? "alert" : "calm" }), triggerLatencyScenario: () => set((state) => { - const activeCausalPath = createLatencyCausalPath(); - const activeIds: PanelId[] = ["realtime-insights", "data-stream", "model-performance", "system-health", "activity-feed", "causality"]; + const path = createLatencyCausalPath(); return { - activeCausalPath, mode: "alert", energy: 1, - composer: { ...state.composer, contextPanelIds: activeIds.slice(0, 4) }, + activeCausalPath: path, panels: state.panels.map((panel) => { - if (panel.id === "realtime-insights") return { ...panel, dimmed: false, severity: "warning" }; - if (panel.id === "system-health") return { ...panel, dimmed: false, severity: "critical" }; - if (panel.id === "data-stream") return { ...panel, dimmed: false, severity: "warning" }; - return { ...panel, dimmed: !activeIds.includes(panel.id), highlighted: activeIds.includes(panel.id) }; - }), - agents: state.agents.map((agent) => { - if (agent.id === "watcher") return { ...agent, state: "working", energy: 1, connectedPanel: "realtime-insights" }; - if (agent.id === "analyst") return { ...agent, state: "thinking", energy: 0.92, connectedPanel: "causality" }; - return { ...agent, energy: Math.max(0.42, agent.energy * 0.76) }; + if (["realtime-insights", "data-stream", "system-health"].includes(panel.id)) return { ...panel, severity: "warning", dimmed: false }; + if (panel.id === "causality") return { ...panel, severity: "critical", dimmed: false }; + return { ...panel, dimmed: !["model-performance", "activity-feed", "smart-suggestions"].includes(panel.id) }; }), + agents: state.agents.map((agent) => + agent.id === "watcher" || agent.id === "analyst" ? { ...agent, state: "working", energy: 1, connectedPanel: "causality" } : { ...agent, energy: 0.48 }, + ), timeline: [ ...state.timeline, - createTimelineEvent({ - title: "Latency anomaly activated", - description: "Causal path generated across gateway, model, resource, and user impact signals.", - type: "alert", - severity: "warning", - relatedPanelIds: activeIds, - }), - ], - eventToasts: [ - ...state.eventToasts.slice(-4), - { id: createId("toast"), title: "Causal path generated", description: activeCausalPath.title, severity: "warning", createdAt: Date.now() }, - ], + createTimelineEvent({ title: "Latency anomaly traced", description: path.title, type: "alert", severity: "warning", relatedPanelIds: ["causality", "data-stream", "system-health"] }), + ].slice(-120), }; }), clearCausality: () => set((state) => ({ - activeCausalPath: undefined, mode: "calm", energy: 0.55, + activeCausalPath: undefined, panels: state.panels.map((panel) => ({ ...panel, dimmed: false, highlighted: false, severity: undefined })), - agents: state.agents.map((agent) => ({ - ...agent, - state: agent.id === "watcher" || agent.id === "analyst" ? "watching" : "idle", - energy: 0.55, - taskId: undefined, - })), + agents: state.agents.map((agent) => ({ ...agent, state: agent.id === "watcher" ? "watching" : "idle", taskId: undefined, energy: 0.55 })), })), tickMetrics: () => set((state) => { - const alertActive = state.mode === "alert"; - const thinkingActive = ["thinking", "analyzing", "generating"].includes(state.mode); - const metrics: SystemMetrics = { - cpu: jitterMetric(state.metrics.cpu + (alertActive ? 1.2 : thinkingActive ? 0.5 : 0), alertActive ? 6 : 3, 12, 98), - memory: jitterMetric(state.metrics.memory + (thinkingActive ? 0.4 : 0), 3, 20, 96), - gpu: jitterMetric(state.metrics.gpu + (state.mode === "generating" ? 1 : 0), 5, 10, 98), - network: jitterMetric(state.metrics.network + (alertActive ? 1.4 : 0), 6, 18, 99), - latencyMs: jitterMetric(state.metrics.latencyMs + (alertActive ? 8 : -1.5), alertActive ? 28 : 8, 24, alertActive ? 220 : 120), - throughputTbs: jitterMetric(state.metrics.throughputTbs, 0.16, 1.2, 3.8), - eventsPerSecondM: jitterMetric(state.metrics.eventsPerSecondM, 0.24, 0.8, 3.4), - modelAccuracy: jitterMetric(state.metrics.modelAccuracy, 0.18, 88, 98), - precision: jitterMetric(state.metrics.precision, 0.22, 86, 98), - recall: jitterMetric(state.metrics.recall, 0.22, 86, 98), - f1: jitterMetric(state.metrics.f1, 0.18, 86, 98), - activeStreams: Math.round(jitterMetric(state.metrics.activeStreams, 3, 14, 42)), - pipelineSuccess: jitterMetric(state.metrics.pipelineSuccess - (alertActive ? 0.12 : 0), 0.25, 92, 99.8), + const metrics = { + ...state.metrics, + cpu: jitterMetric(state.metrics.cpu + (state.mode === "alert" ? 1.2 : 0), 5, 12, 98), + memory: jitterMetric(state.metrics.memory, 3, 20, 96), + gpu: jitterMetric(state.metrics.gpu + (state.mode === "generating" ? 1.3 : 0), 5, 10, 99), + network: jitterMetric(state.metrics.network + (state.mode === "alert" ? 1.8 : 0), 5, 18, 99), + latencyMs: Math.round(jitterMetric(state.metrics.latencyMs + (state.mode === "alert" ? 3.8 : -0.6), state.mode === "alert" ? 18 : 5, 28, state.mode === "alert" ? 260 : 90)), }; - return { metrics, metricHistory: [...state.metricHistory.slice(-48), { timestamp: Date.now(), system: metrics }] }; - }), - ingestMetricSnapshot: (incoming) => - set((state) => { - const metrics = { ...state.metrics, ...incoming }; - return { metrics, metricHistory: [...state.metricHistory.slice(-48), { timestamp: Date.now(), system: metrics }] }; + return { metrics, metricHistory: [...state.metricHistory.slice(-40), { timestamp: Date.now(), system: metrics }] }; }), + ingestMetricSnapshot: (incoming) => set((state) => { + const metrics = { ...state.metrics, ...incoming }; + return { metrics, metricHistory: [...state.metricHistory.slice(-40), { timestamp: Date.now(), system: metrics }] }; + }), setDrawerOpen: (activeDrawer) => set({ activeDrawer }), setModalOpen: (activeModal) => set({ activeModal }), - setCommandPaletteOpen: (commandPaletteOpen) => - set({ commandPaletteOpen, commandPaletteQuery: commandPaletteOpen ? get().commandPaletteQuery : "" }), + setCommandPaletteOpen: (commandPaletteOpen) => set({ commandPaletteOpen, commandPaletteQuery: commandPaletteOpen ? get().commandPaletteQuery : "" }), setCommandPaletteQuery: (commandPaletteQuery) => set({ commandPaletteQuery }), - addPendingApproval: (approval) => - set((state) => ({ - pendingApprovals: [...state.pendingApprovals.filter((item) => item.id !== approval.id), approval], - activeDrawer: "tool-approval", - })), - resolveApproval: (id, status) => - set((state) => ({ - pendingApprovals: state.pendingApprovals.map((approval) => (approval.id === id ? { ...approval, status } : approval)), - })), - addEventToast: (toast) => - set((state) => ({ eventToasts: [...state.eventToasts.slice(-4), { ...toast, id: createId("toast"), createdAt: Date.now() }] })), + addPendingApproval: (approval) => set((state) => ({ pendingApprovals: [...state.pendingApprovals.filter((item) => item.id !== approval.id), approval], activeDrawer: "tool-approval" })), + resolveApproval: (id, status) => set((state) => ({ pendingApprovals: state.pendingApprovals.map((approval) => (approval.id === id ? { ...approval, status } : approval)) })), + addEventToast: (toast) => set((state) => ({ eventToasts: [...state.eventToasts.slice(-4), { ...toast, id: createId("toast"), createdAt: Date.now() }] })), dismissEventToast: (id) => set((state) => ({ eventToasts: state.eventToasts.filter((toast) => toast.id !== id) })), setPerformanceOverlayOpen: (performanceOverlayOpen) => set({ performanceOverlayOpen }), setChatDockExpanded: (chatDockExpanded) => set({ chatDockExpanded }), - setLayoutEditMode: (layoutEditMode) => set({ layoutEditMode, mode: layoutEditMode ? "reviewing" : get().mode }), + setLayoutEditMode: (layoutEditMode) => set({ layoutEditMode }), setActiveMockupState: (activeMockupStateId) => set({ activeMockupStateId }), - resetToBaseline: () => - set(() => { - const metrics = baselineMetrics(); - return { - mode: "calm", - workspaceView: "dashboard", - energy: 0.78, - focusPanelId: undefined, - panels: baselinePanels(), - agents: baselineAgents(), - activeCausalPath: undefined, - timeline: baselineTimeline(), - messages: baselineMessages(), - tasks: [], - metrics, - metricHistory: [{ timestamp: Date.now(), system: metrics }], - composer: { - value: "", - isFocused: false, - selectedModel: "GPT-5.5", - selectedMode: "deep", - selectedTools: defaultSelectedTools, - contextPanelIds: ["project-nexus", "system-health"], - slashMenuOpen: false, - planPreview: undefined, - }, - artifacts: [], - activeArtifactId: undefined, - activeDrawer: undefined, - activeModal: undefined, - commandPaletteOpen: false, - commandPaletteQuery: "", - pendingApprovals: [], - eventToasts: [], - performanceOverlayOpen: false, - chatDockExpanded: false, - layoutEditMode: false, - activeMockupStateId: undefined, - }; - }), - resetToDemoBaseline: () => - set(() => { - const metrics = baselineMetrics(); - return { - workspaceView: "dashboard", - mode: "calm", - energy: 0.78, - focusPanelId: undefined, - panels: demoPanels(), - agents: baselineAgents(), - activeCausalPath: createLatencyCausalPath(), - tasks: [], - timeline: baselineTimeline(), - messages: baselineMessages(), - metrics, - metricHistory: [{ timestamp: Date.now(), system: metrics }], - composer: { - value: "", - isFocused: false, - selectedModel: "GPT-5.5", - selectedMode: "deep", - selectedTools: defaultSelectedTools, - contextPanelIds: ["project-nexus", "system-health"], - slashMenuOpen: false, - planPreview: undefined, - }, - artifacts: [], - activeArtifactId: undefined, - activeDrawer: undefined, - activeModal: undefined, - commandPaletteOpen: false, - commandPaletteQuery: "", - pendingApprovals: [], - eventToasts: [], - performanceOverlayOpen: false, - chatDockExpanded: false, - layoutEditMode: false, - activeMockupStateId: undefined, - }; - }), - addArtifact: (artifact) => - set((state) => ({ - artifacts: [ - ...state.artifacts.filter((item) => item.id !== artifact.id), - { ...artifact, createdAt: artifact.createdAt ?? Date.now(), updatedAt: artifact.updatedAt ?? Date.now() }, - ], - activeArtifactId: artifact.id, - workspaceView: "canvas", - })), - updateArtifact: (id, patch) => - set((state) => ({ - artifacts: state.artifacts.map((artifact) => (artifact.id === id ? { ...artifact, ...patch, updatedAt: Date.now() } : artifact)), - })), + resetToBaseline: () => set({ + mode: "calm", + workspaceView: "dashboard", + energy: 0.55, + focusPanelId: undefined, + panels: baselinePanels(), + agents: baselineAgents(), + activeCausalPath: undefined, + tasks: [], + metrics: baselineMetrics(), + metricHistory: [{ timestamp: Date.now(), system: baselineMetrics() }], + activeDrawer: undefined, + activeModal: undefined, + commandPaletteOpen: false, + commandPaletteQuery: "", + pendingApprovals: [], + eventToasts: [], + performanceOverlayOpen: false, + layoutEditMode: false, + activeMockupStateId: undefined, + }), + resetToDemoBaseline: () => set({ + mode: "calm", + workspaceView: "dashboard", + energy: 0.72, + panels: demoPanels(), + agents: baselineAgents(), + timeline: baselineTimeline(), + messages: baselineMessages(), + tasks: [], + metrics: baselineMetrics(), + metricHistory: [{ timestamp: Date.now(), system: baselineMetrics() }], + activeCausalPath: undefined, + activeDrawer: undefined, + activeModal: undefined, + commandPaletteOpen: false, + commandPaletteQuery: "", + pendingApprovals: [], + eventToasts: [], + performanceOverlayOpen: false, + layoutEditMode: false, + }), + addArtifact: (artifact) => set((state) => ({ + artifacts: [...state.artifacts.filter((item) => item.id !== artifact.id), artifact], + activeArtifactId: artifact.id, + workspaceView: "canvas", + })), + updateArtifact: (id, patch) => set((state) => ({ artifacts: state.artifacts.map((artifact) => (artifact.id === id ? { ...artifact, ...patch, updatedAt: Date.now() } : artifact)) })), setActiveArtifact: (activeArtifactId) => set({ activeArtifactId, workspaceView: activeArtifactId ? "canvas" : "dashboard" }), })); diff --git a/frontend/src/styles/product-surfaces.css b/frontend/src/styles/product-surfaces.css new file mode 100644 index 0000000..147ad34 --- /dev/null +++ b/frontend/src/styles/product-surfaces.css @@ -0,0 +1 @@ +.product-surface-drawer{position:absolute;z-index:74;top:72px;right:24px;bottom:116px;width:min(520px,calc(100vw - 310px));border-radius:30px;overflow:hidden;pointer-events:auto}.product-surface-modal{position:absolute;z-index:86;left:50%;top:50%;width:min(760px,calc(100vw - 140px));max-height:min(680px,calc(100vh - 140px));transform:translate(-50%,-50%);border-radius:30px;overflow:hidden;pointer-events:auto}.product-surface__header{min-height:72px;display:grid;grid-template-columns:42px 1fr 36px;align-items:center;gap:12px;padding:14px 18px;border-bottom:1px solid rgba(142,227,255,.12)}.product-surface__icon{width:40px;height:40px;display:grid;place-items:center;border-radius:16px;color:var(--cyan);background:rgba(47,252,255,.08);border:1px solid rgba(47,252,255,.12)}.product-surface__header strong,.product-surface__header em{display:block}.product-surface__header strong{color:white;font-size:16px}.product-surface__header em{margin-top:4px;color:var(--text-muted);font-size:12px;font-style:normal}.product-surface__header button{width:34px;height:34px;display:grid;place-items:center;border:1px solid rgba(142,227,255,.14);border-radius:999px;color:var(--text-secondary);background:rgba(255,255,255,.035);cursor:pointer}.product-surface__body{max-height:calc(100% - 72px);overflow:auto;padding:14px}.surface-stat-grid,.surface-card-list--grid,.surface-compare-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}.surface-card-list,.surface-action-list{display:grid;gap:10px}.surface-action-list{margin-top:14px}.surface-stat-grid article,.surface-hero-card,.surface-card,.surface-template-card,.surface-compare-grid article{border:1px solid rgba(142,227,255,.1);border-radius:20px;background:rgba(255,255,255,.025);padding:12px}.surface-stat-grid em,.surface-hero-card em,.surface-card i,.surface-compare-grid em,.surface-section-title{display:block;color:var(--text-muted);font-size:10px;font-style:normal;text-transform:uppercase;letter-spacing:.06em}.surface-stat-grid strong,.surface-hero-card strong,.surface-card strong,.surface-template-card strong,.surface-compare-grid strong{display:block;margin-top:4px;color:white;font-size:13px}.surface-hero-card span,.surface-card span,.surface-template-card span,.surface-compare-grid span{display:block;margin-top:7px;color:var(--text-secondary);font-size:12px;line-height:1.45}.surface-action-list button,.surface-chip-row button,.surface-pill-grid button{min-height:34px;display:inline-flex;align-items:center;gap:8px;border:1px solid rgba(142,227,255,.12);border-radius:999px;color:var(--text-secondary);background:rgba(255,255,255,.035);padding:0 12px;cursor:pointer;font-size:12px}.surface-action-list button:hover,.surface-chip-row button:hover,.surface-pill-grid button:hover,.surface-pill-grid button.is-active,.surface-template-card.is-active{color:white;border-color:rgba(47,252,255,.32);background:rgba(47,252,255,.08)}.surface-template-card{min-height:110px;display:grid;gap:5px;color:var(--text-secondary);cursor:pointer;text-align:left}.surface-template-card svg{color:var(--cyan)}.surface-card--warning{border-color:rgba(255,191,72,.24)}.surface-card--critical{border-color:rgba(255,59,95,.28)}.surface-card--success{border-color:rgba(56,255,179,.24)}.surface-chip-row,.surface-pill-grid{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.surface-section-title{margin:12px 0 8px}.surface-empty{border:1px solid rgba(142,227,255,.08);border-radius:20px;color:var(--text-muted);background:rgba(255,255,255,.025);padding:18px;text-align:center;font-size:12px}@media(max-width:1040px){.product-surface-drawer{left:90px;right:12px;width:auto}.product-surface-modal{width:calc(100vw - 120px)}.surface-card-list--grid,.surface-stat-grid,.surface-compare-grid{grid-template-columns:1fr}} \ No newline at end of file diff --git a/frontend/src/styles/temporal.css b/frontend/src/styles/temporal.css new file mode 100644 index 0000000..4dbb825 --- /dev/null +++ b/frontend/src/styles/temporal.css @@ -0,0 +1,182 @@ +.temporal-expanded { + position: absolute; + z-index: 56; + left: 50%; + bottom: 180px; + width: min(980px, calc(100vw - 420px)); + max-height: min(520px, calc(100vh - 270px)); + transform: translateX(-50%); + border-radius: 30px; + padding: 16px; + overflow: hidden; + pointer-events: auto; +} + +.temporal-expanded__header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 18px; + padding-bottom: 12px; + border-bottom: 1px solid rgba(142, 227, 255, 0.12); +} + +.temporal-expanded__header strong, +.temporal-expanded__header span { + display: block; +} + +.temporal-expanded__header strong { + display: inline-flex; + align-items: center; + gap: 8px; + color: white; + font-size: 18px; + letter-spacing: -0.03em; +} + +.temporal-expanded__header span { + margin-top: 5px; + color: var(--text-muted); + font-size: 12px; +} + +.temporal-expanded__header button { + width: 34px; + height: 34px; + display: grid; + place-items: center; + border: 1px solid rgba(142, 227, 255, 0.14); + border-radius: 999px; + color: var(--text-secondary); + background: rgba(255, 255, 255, 0.035); + cursor: pointer; +} + +.temporal-expanded__search { + height: 42px; + display: grid; + grid-template-columns: 26px 1fr; + align-items: center; + gap: 8px; + margin: 14px 0; + border: 1px solid rgba(142, 227, 255, 0.12); + border-radius: 999px; + color: var(--cyan); + background: rgba(255, 255, 255, 0.035); + padding: 0 12px; +} + +.temporal-expanded__search span { + color: rgba(214, 231, 248, 0.5); + font-size: 12px; +} + +.temporal-expanded__scrub { + display: flex; + gap: 8px; + margin-bottom: 14px; +} + +.temporal-expanded__scrub button { + height: 32px; + display: inline-flex; + align-items: center; + gap: 7px; + border: 1px solid rgba(142, 227, 255, 0.12); + border-radius: 999px; + color: var(--text-secondary); + background: rgba(255, 255, 255, 0.035); + padding: 0 11px; + cursor: pointer; + font-size: 12px; +} + +.temporal-expanded__track { + max-height: 330px; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; + overflow: auto; + padding-right: 4px; +} + +.temporal-expanded__event { + min-height: 88px; + display: grid; + gap: 4px; + border: 1px solid rgba(142, 227, 255, 0.1); + border-radius: 20px; + color: var(--text-secondary); + background: + radial-gradient(circle at 0% 0%, rgba(47, 252, 255, 0.08), transparent 34%), + rgba(255, 255, 255, 0.025); + padding: 12px; + cursor: pointer; + text-align: left; +} + +.temporal-expanded__event:hover { + border-color: rgba(47, 252, 255, 0.28); + background: + radial-gradient(circle at 0% 0%, rgba(47, 252, 255, 0.14), transparent 34%), + rgba(47, 252, 255, 0.045); +} + +.temporal-expanded__event i, +.temporal-expanded__event em { + color: var(--text-muted); + font-size: 10px; + font-style: normal; +} + +.temporal-expanded__event strong { + color: white; + font-size: 12px; +} + +.temporal-expanded__event span { + color: var(--text-secondary); + font-size: 11px; + line-height: 1.4; +} + +.temporal-expanded__event--warning { + border-color: rgba(255, 191, 72, 0.22); + background: + radial-gradient(circle at 0% 0%, rgba(255, 191, 72, 0.12), transparent 34%), + rgba(255, 255, 255, 0.025); +} + +.temporal-expanded__event--critical { + border-color: rgba(255, 59, 95, 0.28); + background: + radial-gradient(circle at 0% 0%, rgba(255, 59, 95, 0.13), transparent 34%), + rgba(255, 255, 255, 0.025); +} + +.temporal-expanded__event--success { + border-color: rgba(56, 255, 179, 0.24); + background: + radial-gradient(circle at 0% 0%, rgba(56, 255, 179, 0.11), transparent 34%), + rgba(255, 255, 255, 0.025); +} + +.temporal-expanded--state-22 { + border-color: rgba(255, 191, 72, 0.36); + background: + radial-gradient(circle at 50% 0%, rgba(255, 191, 72, 0.12), transparent 38%), + radial-gradient(circle at 12% 80%, rgba(47, 252, 255, 0.08), transparent 32%), + rgba(7, 13, 31, 0.72); +} + +@media (max-width: 1180px) { + .temporal-expanded { + left: calc(50% + 36px); + width: calc(100vw - 170px); + } + + .temporal-expanded__track { + grid-template-columns: 1fr; + } +} diff --git a/package.json b/package.json index facfb8a..42521b8 100644 --- a/package.json +++ b/package.json @@ -10,21 +10,21 @@ "scripts": { "start": "node ./scripts/orangebox-command-server.mjs", "check": "node --check ./scripts/orangebox-command-server.mjs && node --check ./scripts/orangebox-mcp-server.mjs && node --check ./src/app.js", - "check:all": "npm.cmd run check && npm.cmd run build:web && npm.cmd run build:api", - "dev:web": "npm.cmd run dev -w @ae-see-suite/web", - "dev:api": "npm.cmd run dev -w @ae-see-suite/api", - "build:web": "npm.cmd run build -w @ae-see-suite/web", - "build:api": "npm.cmd run build -w @ae-see-suite/api", - "build": "npm.cmd run build:web && npm.cmd run build:api", - "test:api": "npm.cmd run test -w @ae-see-suite/api", - "frontend:proof:visual": "npm.cmd run proof:visual -w @ae-see-suite/web", - "frontend:proof:visual:72": "npm.cmd run proof:visual:72 -w @ae-see-suite/web", - "frontend:proof:pixel": "npm.cmd run proof:pixel -w @ae-see-suite/web", - "frontend:proof:summary": "npm.cmd run proof:summary -w @ae-see-suite/web", - "frontend:proof": "npm.cmd run build:web && npm.cmd run frontend:proof:visual && npm.cmd run frontend:proof:pixel && npm.cmd run frontend:proof:summary", - "proof:see-suite-react": "npm.cmd run build:web && node ./scripts/v4/react-see-suite-proof.mjs --receipt", + "check:all": "npm run check && npm run build:web && npm run build:api", + "dev:web": "npm run dev -w @ae-see-suite/web", + "dev:api": "npm run dev -w @ae-see-suite/api", + "build:web": "npm run build -w @ae-see-suite/web", + "build:api": "npm run build -w @ae-see-suite/api", + "build": "npm run build:web && npm run build:api", + "test:api": "npm run test -w @ae-see-suite/api", + "frontend:proof:visual": "npm run proof:visual -w @ae-see-suite/web", + "frontend:proof:visual:72": "npm run proof:visual:72 -w @ae-see-suite/web", + "frontend:proof:pixel": "npm run proof:pixel -w @ae-see-suite/web", + "frontend:proof:summary": "npm run proof:summary -w @ae-see-suite/web", + "frontend:proof": "npm run build:web && npm run frontend:proof:visual && npm run frontend:proof:pixel && npm run frontend:proof:summary", + "proof:see-suite-react": "npm run build:web && node ./scripts/v4/react-see-suite-proof.mjs --receipt", "proof:see-suite-visual-compare": "node ./scripts/v4/react-see-suite-visual-contact-sheet.mjs", - "proof:see-suite-72-state": "npm.cmd run build:web && node ./scripts/v4/react-see-suite-72-state-proof.mjs", + "proof:see-suite-72-state": "npm run build:web && node ./scripts/v4/react-see-suite-72-state-proof.mjs", "proof:see-suite-pixel-compare": "node ./scripts/v4/react-see-suite-pixel-compare.mjs", "mcp": "node ./scripts/orangebox-mcp-server.mjs", "knowledge": "node ./scripts/orangebox-knowledge-v2.mjs", @@ -60,7 +60,7 @@ "aecode:operator": "node ./scripts/v4/gauntlet-engine.mjs operator status --json", "system:proof": "node ./scripts/v4/gauntlet-engine.mjs system proof orangebox-main-system-v0 --json", "system:full-green": "node ./scripts/v4/gauntlet-engine.mjs system full-green orangebox-main-system-v0 --execute --create --operator-approved --json", - "machine:test-drive": "npm.cmd run innovation:activate && npm.cmd run ten:finish && npm.cmd run control:big && npm.cmd run inference:doctor && npm.cmd run four:doctor && npm.cmd run system:doctor && npm.cmd run creations:doctor && npm.cmd run aecode:format && npm.cmd run aecode:schemas && npm.cmd run aecode:compile && npm.cmd run aecode:mission-run && npm.cmd run aecode:artifact-generate && npm.cmd run aecode:operator && npm.cmd run system:proof", + "machine:test-drive": "npm run innovation:activate && npm run ten:finish && npm run control:big && npm run inference:doctor && npm run four:doctor && npm run system:doctor && npm run creations:doctor && npm run aecode:format && npm run aecode:schemas && npm run aecode:compile && npm run aecode:mission-run && npm run aecode:artifact-generate && npm run aecode:operator && npm run system:proof", "inference:doctor": "node ./scripts/v4/inference-acceleration-doctor.mjs --json --receipt", "creations:doctor": "node ./scripts/v4/orangebox-creations-doctor.mjs --json --receipt", "aecode:format": "node ./scripts/v4/aecode-format-doctor.mjs --json --receipt", @@ -103,7 +103,7 @@ "control:adapters": "node ./control-plane/run-bun.mjs ./control-plane/adapter-doctor.ts --receipt", "control:topology": "node ./control-plane/run-bun.mjs ./control-plane/topology-doctor.ts --receipt", "control:doctor": "node ./control-plane/run-bun.mjs ./control-plane/doctor.ts --receipt", - "control:big": "npm.cmd run control:topology && npm.cmd run control:adapters && npm.cmd run control:real-smoke && npm.cmd run control:llama-gen && npm.cmd run control:doctor && npm.cmd run control:smoke", + "control:big": "npm run control:topology && npm run control:adapters && npm run control:real-smoke && npm run control:llama-gen && npm run control:doctor && npm run control:smoke", "desktop": "tauri dev", "desktop:build": "tauri build", "tauri": "tauri" diff --git a/scripts/ae-see-suite-mcp-server.mjs b/scripts/ae-see-suite-mcp-server.mjs new file mode 100644 index 0000000..dc9eab9 --- /dev/null +++ b/scripts/ae-see-suite-mcp-server.mjs @@ -0,0 +1,83 @@ +import { McpServer } from "file:///C:/AtomEons/agent-stack/npm-tools/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js"; +import { StdioServerTransport } from "file:///C:/AtomEons/agent-stack/npm-tools/node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js"; +import { z } from "file:///C:/AtomEons/agent-stack/npm-tools/node_modules/zod/index.js"; +import { registerAeSeeSuiteTools } from "./ae-see-suite-mcp-tools.mjs"; + +const orangeboxUrl = process.env.ORANGEBOX_URL || "http://127.0.0.1:8787"; + +function textContent(value) { + const text = typeof value === "string" ? value : JSON.stringify(value, null, 2); + return { content: [{ type: "text", text }] }; +} + +async function orangebox(path, { method = "GET", body = null, timeoutMs = 120000 } = {}) { + const ac = new AbortController(); + const timer = setTimeout(() => ac.abort(), timeoutMs); + try { + const res = await fetch(`${orangeboxUrl}${path}`, { + method, + headers: { "content-type": "application/json" }, + body: body ? JSON.stringify(body) : undefined, + signal: ac.signal + }); + const text = await res.text(); + let json = null; + try { json = JSON.parse(text); } catch {} + if (!res.ok) { + return { status: "FAILED", code: res.status, error: json?.error || text || res.statusText }; + } + return json ?? text; + } catch (error) { + return { status: error.name === "AbortError" ? "TIMEOUT" : "FAILED", error: error.message, orangeboxUrl, path }; + } finally { + clearTimeout(timer); + } +} + +function summarize(value, limit = 900) { + const text = typeof value === "string" ? value : JSON.stringify(value ?? {}, null, 2); + return text.length > limit ? `${text.slice(0, limit)}\n[truncated ${text.length - limit} chars]` : text; +} + +async function postWorkEvent(event) { + try { + await orangebox("/api/mcp/event", { + method: "POST", + body: { source: "ae-see-suite-mcp", ...event }, + timeoutMs: 3000 + }); + } catch {} +} + +function resultStatus(result) { + try { + const text = result?.content?.[0]?.text || ""; + const parsed = JSON.parse(text); + return parsed.status || parsed.recommendation?.status || parsed.event?.status || "VERIFIED"; + } catch { + return "VERIFIED"; + } +} + +function trackedTool(tool, handler) { + return async (args = {}) => { + const id = `${Date.now()}-${tool}-${Math.random().toString(16).slice(2)}`; + const started = Date.now(); + await postWorkEvent({ id, tool, phase: "start", status: "Running", summary: summarize(args) }); + try { + const result = await handler(args); + await postWorkEvent({ id, tool, phase: "end", status: "VERIFIED", resultStatus: resultStatus(result), durationMs: Date.now() - started, summary: summarize(args, 500) }); + return result; + } catch (error) { + await postWorkEvent({ id, tool, phase: "error", status: "FAILED", durationMs: Date.now() - started, summary: summarize(args, 500), error: error.message }); + throw error; + } + }; +} + +const server = new McpServer({ name: "ae-see-suite", version: "0.1.0" }); + +registerAeSeeSuiteTools({ server, z, textContent, orangebox, trackedTool }); + +const transport = new StdioServerTransport(); +await server.connect(transport); diff --git a/scripts/ae-see-suite-mcp-tools.mjs b/scripts/ae-see-suite-mcp-tools.mjs new file mode 100644 index 0000000..bd92a96 --- /dev/null +++ b/scripts/ae-see-suite-mcp-tools.mjs @@ -0,0 +1,103 @@ +export function registerAeSeeSuiteTools({ server, z, textContent, orangebox, trackedTool }) { + const frontendBuild = "npm run build:web"; + + async function runCommand(command, timeoutMs = 240000) { + return orangebox("/api/v4/mcp/code-execute", { + method: "POST", + body: { command, timeoutMs, receipt: true }, + timeoutMs + }); + } + + server.registerTool( + "ae_see_suite_help", + { + title: "AE See-Suite Help", + description: "Show the AE See-Suite living dashboard build contract, state atlas workflow, and proof commands.", + inputSchema: {} + }, + trackedTool("ae_see_suite_help", async () => textContent({ + status: "VERIFIED", + product: "AE See-Suite living AI operating environment", + law: "One AppShell, many semantic states. Never build 72 pages or use mockup images as UI.", + anchors: ["01 Calm", "06 Alert", "22 Temporal Memory", "26 Command Palette", "37 Agent Queue", "61 Living Canvas"], + commands: { + build: frontendBuild, + proofAnchors: "npm run build:web && npm run frontend:proof:visual -- --states=01,06,22,26,37,61 --label=mcp-anchors", + proof72: "npm run build:web && npm run frontend:proof:visual:72" + }, + nextGoal: "Convert state atlas surfaces into real product behavior driven by Zustand state, user actions, and backend stream events." + })) + ); + + server.registerTool( + "ae_see_suite_build_frontend", + { + title: "Build AE See-Suite Frontend", + description: "Run the frontend TypeScript/Vite build through the receipted OrangeBOX executor.", + inputSchema: {} + }, + trackedTool("ae_see_suite_build_frontend", async () => textContent(await runCommand(frontendBuild, 240000))) + ); + + server.registerTool( + "ae_see_suite_proof_anchors", + { + title: "Proof AE See-Suite Anchor States", + description: "Build and capture the highest-value AE See-Suite anchor states: 01, 06, 22, 26, 37, 61.", + inputSchema: { + states: z.string().optional().describe("Comma-separated state ids. Default: 01,06,22,26,37,61"), + label: z.string().optional().describe("Visual proof label. Default: mcp-anchors") + } + }, + trackedTool("ae_see_suite_proof_anchors", async ({ states = "01,06,22,26,37,61", label = "mcp-anchors" }) => { + const command = `npm run build:web && npm run frontend:proof:visual -- --states=${states} --label=${label}`; + return textContent(await runCommand(command, 420000)); + }) + ); + + server.registerTool( + "ae_see_suite_proof_72", + { + title: "Proof AE See-Suite 72-State Atlas", + description: "Build and capture all 72 AE See-Suite state atlas entries.", + inputSchema: { + label: z.string().optional().describe("Visual proof label. Default: mcp-72-state") + } + }, + trackedTool("ae_see_suite_proof_72", async ({ label = "mcp-72-state" }) => { + const command = `npm run build:web && npm run frontend:proof:visual:72 -- --label=${label}`; + return textContent(await runCommand(command, 900000)); + }) + ); + + server.registerTool( + "ae_see_suite_state_open_command", + { + title: "AE See-Suite State URL Helper", + description: "Return direct local URLs and acceptance checks for a state atlas id.", + inputSchema: { + state: z.string().describe("State id such as 01, 06, 22, 26, 37, or 61."), + baseUrl: z.string().optional().describe("Frontend base URL. Default http://localhost:5173") + } + }, + trackedTool("ae_see_suite_state_open_command", async ({ state, baseUrl = "http://localhost:5173" }) => { + const normalized = String(state).padStart(2, "0"); + const acceptance = { + "01": ["calm shell", "no forced incident", "panels readable", "chat dock available"], + "06": ["alert mode", "causal path visible", "watcher/analyst active", "remediation suggestions"], + "22": ["temporal expanded overlay", "timeline events visible", "scrub controls visible", "composer can receive timeline prompt"], + "26": ["command palette open", "dashboard recessed", "actions searchable"], + "37": ["agent queue drawer open", "tasks/agents visible", "dashboard remains behind drawer"], + "61": ["living canvas primary", "artifact branches visible", "chat dock remains command surface"] + }; + return textContent({ + status: "VERIFIED", + state: normalized, + url: `${baseUrl}/?state=${normalized}`, + acceptance: acceptance[normalized] ?? ["same AppShell", "state reachable", "no mockup image as UI"], + note: "Use this as a local browser target or proof-run target." + }); + }) + ); +}