@nimblebrain/synapse 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/{chunk-Q7OSHSGZ.cjs → chunk-ABJP7PL4.cjs} +24 -2
- package/dist/chunk-ABJP7PL4.cjs.map +1 -0
- package/dist/{chunk-JZC3VC2C.js → chunk-BWSAQV3P.js} +24 -2
- package/dist/chunk-BWSAQV3P.js.map +1 -0
- package/dist/codegen/cli.cjs +1 -1
- package/dist/codegen/cli.js +1 -1
- package/dist/codegen/index.d.cts +1 -1
- package/dist/codegen/index.d.ts +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/react/index.cjs +12 -9
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +19 -2
- package/dist/react/index.d.ts +19 -2
- package/dist/react/index.js +12 -10
- package/dist/react/index.js.map +1 -1
- package/dist/{server-SEI7XI3B.cjs → server-3BDZ5S72.cjs} +26 -26
- package/dist/server-3BDZ5S72.cjs.map +1 -0
- package/dist/{server-RUCX2TIB.js → server-NNW54YW5.js} +26 -26
- package/dist/server-NNW54YW5.js.map +1 -0
- package/dist/synapse-runtime.iife.global.js +1 -1
- package/dist/{types-BP0SNrpo.d.cts → types-BL15lUqi.d.cts} +56 -1
- package/dist/{types-BP0SNrpo.d.ts → types-BL15lUqi.d.ts} +56 -1
- package/dist/vite/index.cjs +28 -15
- package/dist/vite/index.cjs.map +1 -1
- package/dist/vite/index.js +28 -15
- package/dist/vite/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-JZC3VC2C.js.map +0 -1
- package/dist/chunk-Q7OSHSGZ.cjs.map +0 -1
- package/dist/server-RUCX2TIB.js.map +0 -1
- package/dist/server-SEI7XI3B.cjs.map +0 -1
package/dist/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/provider.tsx","../../src/react/hooks.ts"],"names":["useRef"
|
|
1
|
+
{"version":3,"sources":["../../src/react/provider.tsx","../../src/react/hooks.ts"],"names":["useRef"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, type ReactNode, useContext, useRef } from \"react\";\nimport { createSynapse } from \"../core.js\";\nimport type { Synapse, SynapseOptions } from \"../types.js\";\n\nconst SynapseContext = createContext<Synapse | null>(null);\n\nexport interface SynapseProviderProps extends SynapseOptions {\n children: ReactNode;\n}\n\nexport function SynapseProvider({ children, ...options }: SynapseProviderProps) {\n // Use a ref so the same Synapse instance survives StrictMode's\n // unmount/remount cycle. We intentionally do NOT destroy on unmount\n // because StrictMode re-mounts immediately and the transport must\n // stay alive. The instance is GC'd when the provider is truly removed.\n const ref = useRef<Synapse | null>(null);\n\n if (ref.current === null || ref.current.destroyed) {\n ref.current = createSynapse(options);\n }\n\n return <SynapseContext.Provider value={ref.current}>{children}</SynapseContext.Provider>;\n}\n\nexport function useSynapseContext(): Synapse {\n const ctx = useContext(SynapseContext);\n if (!ctx) {\n throw new Error(\n \"useSynapse must be used within a <SynapseProvider>. \" +\n \"Wrap your app component tree with <SynapseProvider>.\",\n );\n }\n return ctx;\n}\n","import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport type {\n ActionReducer,\n AgentAction,\n DataChangedEvent,\n Store,\n StoreDispatch,\n Synapse,\n SynapseTheme,\n ToolCallResult,\n} from \"../types.js\";\nimport { SynapseProvider, useSynapseContext } from \"./provider.js\";\n\n// Re-export provider components\nexport { SynapseProvider };\n\nexport function useSynapse(): Synapse {\n return useSynapseContext();\n}\n\nexport function useCallTool<TOutput = unknown>(\n toolName: string,\n): {\n call: (args?: Record<string, unknown>) => Promise<ToolCallResult<TOutput>>;\n isPending: boolean;\n error: Error | null;\n data: TOutput | null;\n} {\n const synapse = useSynapseContext();\n const [isPending, setIsPending] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n const [data, setData] = useState<TOutput | null>(null);\n const callIdRef = useRef(0);\n\n const call = useCallback(\n async (args?: Record<string, unknown>): Promise<ToolCallResult<TOutput>> => {\n const id = ++callIdRef.current;\n setIsPending(true);\n setError(null);\n\n try {\n const result = await synapse.callTool<Record<string, unknown>, TOutput>(toolName, args);\n // Stale guard: only update if this is still the latest call\n if (id === callIdRef.current) {\n setData(result.data);\n setIsPending(false);\n }\n return result;\n } catch (err) {\n if (id === callIdRef.current) {\n const e = err instanceof Error ? err : new Error(String(err));\n setError(e);\n setIsPending(false);\n }\n throw err;\n }\n },\n [synapse, toolName],\n );\n\n return { call, isPending, error, data };\n}\n\nexport function useDataSync(callback: (event: DataChangedEvent) => void): void {\n const synapse = useSynapseContext();\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n return synapse.onDataChanged((event) => callbackRef.current(event));\n }, [synapse]);\n}\n\n/**\n * Subscribe to agent actions — typed, declarative commands from the server.\n *\n * Actions are emitted by tools as deterministic side effects (e.g., \"navigate\n * to the board I just created\"). The UI decides how to handle each action type.\n *\n * @example\n * ```tsx\n * useAgentAction((action) => {\n * if (action.type === \"navigate\") {\n * const { entity, id } = action.payload as NavigatePayload;\n * if (entity === \"board\") setSelectedBoardId(id);\n * }\n * });\n * ```\n */\nexport function useAgentAction(callback: (action: AgentAction) => void): void {\n const synapse = useSynapseContext();\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n return synapse.onAction((action) => callbackRef.current(action));\n }, [synapse]);\n}\n\nexport function useTheme(): SynapseTheme {\n const synapse = useSynapseContext();\n const [theme, setTheme] = useState<SynapseTheme>(() => synapse.getTheme());\n\n useEffect(() => {\n // Sync in case theme changed between render and effect\n setTheme(synapse.getTheme());\n return synapse.onThemeChanged(setTheme);\n }, [synapse]);\n\n return theme;\n}\n\nexport function useAction(): (action: string, params?: Record<string, unknown>) => void {\n const synapse = useSynapseContext();\n return useCallback(\n (action: string, params?: Record<string, unknown>) => synapse.action(action, params),\n [synapse],\n );\n}\n\nexport function useChat(): (\n message: string,\n context?: { action?: string; entity?: string },\n) => void {\n const synapse = useSynapseContext();\n return useCallback(\n (message: string, context?: { action?: string; entity?: string }) =>\n synapse.chat(message, context),\n [synapse],\n );\n}\n\nexport function useVisibleState(): (state: Record<string, unknown>, summary?: string) => void {\n const synapse = useSynapseContext();\n return useCallback(\n (state: Record<string, unknown>, summary?: string) => synapse.setVisibleState(state, summary),\n [synapse],\n );\n}\n\nexport function useStore<TState, TActions extends Record<string, ActionReducer<TState, any>>>(\n store: Store<TState, TActions>,\n): {\n state: TState;\n dispatch: StoreDispatch<TActions>;\n} {\n const state = useSyncExternalStore(\n (onStoreChange) => store.subscribe(onStoreChange),\n () => store.getState(),\n () => store.getState(),\n );\n\n return { state, dispatch: store.dispatch };\n}\n"]}
|
|
@@ -37,19 +37,19 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
37
37
|
|
|
38
38
|
// Minimal NimbleBrain bridge host \u2014 just enough to make Synapse work
|
|
39
39
|
var tokens = darkMode ? {
|
|
40
|
-
"--
|
|
41
|
-
"--
|
|
42
|
-
"--
|
|
43
|
-
"--
|
|
44
|
-
"--
|
|
45
|
-
"--
|
|
40
|
+
"--color-background-primary": "#0f172a", "--color-text-primary": "#e2e8f0",
|
|
41
|
+
"--color-background-secondary": "#1e293b", "--color-text-primary": "#e2e8f0",
|
|
42
|
+
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
43
|
+
"--color-text-secondary": "#94a3b8", "--color-border-primary": "#334155",
|
|
44
|
+
"--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
|
|
45
|
+
"--border-radius-sm": "0.5rem",
|
|
46
46
|
} : {
|
|
47
|
-
"--
|
|
48
|
-
"--
|
|
49
|
-
"--
|
|
50
|
-
"--
|
|
51
|
-
"--
|
|
52
|
-
"--
|
|
47
|
+
"--color-background-primary": "#ffffff", "--color-text-primary": "#1a1a1a",
|
|
48
|
+
"--color-background-secondary": "#f9fafb", "--color-text-primary": "#1a1a1a",
|
|
49
|
+
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
50
|
+
"--color-text-secondary": "#6b7280", "--color-border-primary": "#e5e7eb",
|
|
51
|
+
"--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
|
|
52
|
+
"--border-radius-sm": "0.5rem",
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
function post(msg) { iframe.contentWindow.postMessage(msg, "*"); }
|
|
@@ -126,19 +126,19 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
126
126
|
darkMode = !darkMode;
|
|
127
127
|
document.body.style.background = darkMode ? "#0f172a" : "#f1f5f9";
|
|
128
128
|
tokens = darkMode ? {
|
|
129
|
-
"--
|
|
130
|
-
"--
|
|
131
|
-
"--
|
|
132
|
-
"--
|
|
133
|
-
"--
|
|
134
|
-
"--
|
|
129
|
+
"--color-background-primary": "#0f172a", "--color-text-primary": "#e2e8f0",
|
|
130
|
+
"--color-background-secondary": "#1e293b", "--color-text-primary": "#e2e8f0",
|
|
131
|
+
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
132
|
+
"--color-text-secondary": "#94a3b8", "--color-border-primary": "#334155",
|
|
133
|
+
"--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
|
|
134
|
+
"--border-radius-sm": "0.5rem",
|
|
135
135
|
} : {
|
|
136
|
-
"--
|
|
137
|
-
"--
|
|
138
|
-
"--
|
|
139
|
-
"--
|
|
140
|
-
"--
|
|
141
|
-
"--
|
|
136
|
+
"--color-background-primary": "#ffffff", "--color-text-primary": "#1a1a1a",
|
|
137
|
+
"--color-background-secondary": "#f9fafb", "--color-text-primary": "#1a1a1a",
|
|
138
|
+
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
139
|
+
"--color-text-secondary": "#6b7280", "--color-border-primary": "#e5e7eb",
|
|
140
|
+
"--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
|
|
141
|
+
"--border-radius-sm": "0.5rem",
|
|
142
142
|
};
|
|
143
143
|
post({ jsonrpc: "2.0", method: "ui/themeChanged", params: { mode: darkMode ? "dark" : "light", tokens: tokens } });
|
|
144
144
|
});
|
|
@@ -202,5 +202,5 @@ async function startPreview(options) {
|
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
exports.startPreview = startPreview;
|
|
205
|
-
//# sourceMappingURL=server-
|
|
206
|
-
//# sourceMappingURL=server-
|
|
205
|
+
//# sourceMappingURL=server-3BDZ5S72.cjs.map
|
|
206
|
+
//# sourceMappingURL=server-3BDZ5S72.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/preview/server.ts"],"names":["spawn","resolve","existsSync","join","createServer"],"mappings":"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","file":"server-3BDZ5S72.cjs","sourcesContent":["/**\n * Synapse Preview — standalone dev harness for MCP apps with UIs.\n *\n * Starts the MCP server (HTTP mode) and a minimal bridge host page\n * that iframes the app UI and proxies tool calls to the server.\n *\n * Usage:\n * npx synapse preview --server \"uv run uvicorn mcp_hello.server:app --port 8001\" --ui ./ui\n * npx synapse preview --server \"node dist/index.js\" --ui ./ui --server-port 8001\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { join, resolve } from \"node:path\";\n\nexport interface PreviewOptions {\n /** Shell command to start the MCP server in HTTP mode */\n serverCmd: string;\n /** Port the MCP server listens on (default: 8001) */\n serverPort: number;\n /** Path to the UI directory (must have package.json with dev script) */\n uiDir: string;\n /** Port for the UI Vite dev server (default: 5173) */\n uiPort: number;\n /** Port for the preview harness (default: 5180) */\n previewPort: number;\n}\n\nconst HOST_HTML = (uiPort: number, serverPort: number) => `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <title>Synapse Preview</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: #0f172a; color: #e2e8f0; }\n header { padding: 12px 20px; background: #1e293b; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 12px; }\n header h1 { font-size: 14px; font-weight: 500; }\n header .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }\n header .info { font-size: 12px; color: #94a3b8; margin-left: auto; }\n .theme-toggle { background: #334155; border: none; color: #e2e8f0; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; }\n iframe { width: 100%; height: calc(100vh - 45px); border: none; }\n </style>\n</head>\n<body>\n <header>\n <span class=\"dot\"></span>\n <h1>Synapse Preview</h1>\n <button class=\"theme-toggle\" id=\"toggle\">Toggle Dark/Light</button>\n <span class=\"info\">MCP: localhost:${serverPort} | UI: localhost:${uiPort}</span>\n </header>\n <iframe id=\"app\" src=\"http://localhost:${uiPort}\"></iframe>\n\n <script>\n var iframe = document.getElementById(\"app\");\n var darkMode = true;\n\n // Minimal NimbleBrain bridge host — just enough to make Synapse work\n var tokens = darkMode ? {\n \"--color-background-primary\": \"#0f172a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-background-secondary\": \"#1e293b\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-border-primary\": \"#334155\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-background-secondary\": \"#f9fafb\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-border-primary\": \"#e5e7eb\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n };\n\n function post(msg) { iframe.contentWindow.postMessage(msg, \"*\"); }\n\n window.addEventListener(\"message\", async function (event) {\n if (event.source !== iframe.contentWindow) return;\n var msg = event.data;\n if (!msg || typeof msg !== \"object\") return;\n\n // ext-apps handshake\n if (msg.method === \"ui/initialize\" && msg.id) {\n post({\n jsonrpc: \"2.0\", id: msg.id,\n result: {\n protocolVersion: \"2026-01-26\",\n serverInfo: { name: \"nimblebrain\", version: \"preview\" },\n capabilities: { openLinks: {}, serverTools: {} },\n hostContext: { theme: darkMode ? \"dark\" : \"light\", primaryColor: \"#6366f1\", tokens: tokens }\n }\n });\n return;\n }\n\n if (msg.method === \"ui/notifications/initialized\") return;\n\n // Tool call proxy — forward to the MCP server\n if (msg.method === \"tools/call\" && msg.id) {\n try {\n var resp = await fetch(\"http://localhost:${serverPort}/mcp\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\", id: msg.id,\n method: \"tools/call\",\n params: { name: msg.params.name, arguments: msg.params.arguments || {} }\n })\n });\n var result = await resp.json();\n // Forward the JSON-RPC response back to the iframe\n post(result);\n } catch (err) {\n post({ jsonrpc: \"2.0\", id: msg.id, error: { code: -32000, message: err.message || \"Server error\" } });\n }\n return;\n }\n\n // ui/chat — log to console\n if (msg.method === \"ui/chat\") {\n console.log(\"[chat]\", msg.params?.message);\n return;\n }\n\n // ui/action — log to console\n if (msg.method === \"ui/action\") {\n console.log(\"[action]\", msg.params?.action, msg.params);\n return;\n }\n\n // ui/keydown — ignore in preview\n if (msg.method === \"ui/keydown\") return;\n\n // ui/stateChanged — log\n if (msg.method === \"ui/stateChanged\") {\n console.log(\"[state]\", msg.params?.state);\n post({ jsonrpc: \"2.0\", method: \"ui/stateAcknowledged\", params: { truncated: false } });\n return;\n }\n\n console.log(\"[bridge] unhandled:\", msg.method, msg);\n });\n\n // Theme toggle\n document.getElementById(\"toggle\").addEventListener(\"click\", function () {\n darkMode = !darkMode;\n document.body.style.background = darkMode ? \"#0f172a\" : \"#f1f5f9\";\n tokens = darkMode ? {\n \"--color-background-primary\": \"#0f172a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-background-secondary\": \"#1e293b\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-border-primary\": \"#334155\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-background-secondary\": \"#f9fafb\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-border-primary\": \"#e5e7eb\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n };\n post({ jsonrpc: \"2.0\", method: \"ui/themeChanged\", params: { mode: darkMode ? \"dark\" : \"light\", tokens: tokens } });\n });\n </script>\n</body>\n</html>`;\n\nexport async function startPreview(options: PreviewOptions): Promise<void> {\n const { serverCmd, serverPort, uiDir, uiPort, previewPort } = options;\n const children: ChildProcess[] = [];\n\n console.log(`\\n Synapse Preview\\n`);\n\n // 1. Start MCP server\n console.log(` [server] ${serverCmd}`);\n const serverProc = spawn(serverCmd, {\n shell: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n children.push(serverProc);\n serverProc.stdout?.on(\"data\", (d: Buffer) => process.stdout.write(` [server] ${d}`));\n serverProc.stderr?.on(\"data\", (d: Buffer) => process.stderr.write(` [server] ${d}`));\n\n // 2. Start UI Vite dev server\n const resolvedUi = resolve(uiDir);\n if (!existsSync(join(resolvedUi, \"package.json\"))) {\n console.error(` [ui] Error: no package.json found at ${resolvedUi}`);\n process.exit(1);\n }\n console.log(` [ui] cd ${resolvedUi} && npx vite --port ${uiPort}`);\n const uiProc = spawn(\"npx\", [\"vite\", \"--port\", String(uiPort)], {\n cwd: resolvedUi,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n children.push(uiProc);\n uiProc.stdout?.on(\"data\", (d: Buffer) => process.stdout.write(` [ui] ${d}`));\n uiProc.stderr?.on(\"data\", (d: Buffer) => process.stderr.write(` [ui] ${d}`));\n\n // 3. Start preview host\n const html = HOST_HTML(uiPort, serverPort);\n const host = createServer((_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(html);\n });\n host.listen(previewPort, () => {\n console.log(`\\n Preview: http://localhost:${previewPort}`);\n console.log(` UI: http://localhost:${uiPort}`);\n console.log(` Server: http://localhost:${serverPort}`);\n console.log(`\\n Press Ctrl+C to stop.\\n`);\n });\n\n // Shutdown\n const shutdown = () => {\n console.log(\"\\n Shutting down...\");\n host.close();\n for (const child of children) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n"]}
|
|
@@ -35,19 +35,19 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
35
35
|
|
|
36
36
|
// Minimal NimbleBrain bridge host \u2014 just enough to make Synapse work
|
|
37
37
|
var tokens = darkMode ? {
|
|
38
|
-
"--
|
|
39
|
-
"--
|
|
40
|
-
"--
|
|
41
|
-
"--
|
|
42
|
-
"--
|
|
43
|
-
"--
|
|
38
|
+
"--color-background-primary": "#0f172a", "--color-text-primary": "#e2e8f0",
|
|
39
|
+
"--color-background-secondary": "#1e293b", "--color-text-primary": "#e2e8f0",
|
|
40
|
+
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
41
|
+
"--color-text-secondary": "#94a3b8", "--color-border-primary": "#334155",
|
|
42
|
+
"--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
|
|
43
|
+
"--border-radius-sm": "0.5rem",
|
|
44
44
|
} : {
|
|
45
|
-
"--
|
|
46
|
-
"--
|
|
47
|
-
"--
|
|
48
|
-
"--
|
|
49
|
-
"--
|
|
50
|
-
"--
|
|
45
|
+
"--color-background-primary": "#ffffff", "--color-text-primary": "#1a1a1a",
|
|
46
|
+
"--color-background-secondary": "#f9fafb", "--color-text-primary": "#1a1a1a",
|
|
47
|
+
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
48
|
+
"--color-text-secondary": "#6b7280", "--color-border-primary": "#e5e7eb",
|
|
49
|
+
"--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
|
|
50
|
+
"--border-radius-sm": "0.5rem",
|
|
51
51
|
};
|
|
52
52
|
|
|
53
53
|
function post(msg) { iframe.contentWindow.postMessage(msg, "*"); }
|
|
@@ -124,19 +124,19 @@ var HOST_HTML = (uiPort, serverPort) => `<!DOCTYPE html>
|
|
|
124
124
|
darkMode = !darkMode;
|
|
125
125
|
document.body.style.background = darkMode ? "#0f172a" : "#f1f5f9";
|
|
126
126
|
tokens = darkMode ? {
|
|
127
|
-
"--
|
|
128
|
-
"--
|
|
129
|
-
"--
|
|
130
|
-
"--
|
|
131
|
-
"--
|
|
132
|
-
"--
|
|
127
|
+
"--color-background-primary": "#0f172a", "--color-text-primary": "#e2e8f0",
|
|
128
|
+
"--color-background-secondary": "#1e293b", "--color-text-primary": "#e2e8f0",
|
|
129
|
+
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
130
|
+
"--color-text-secondary": "#94a3b8", "--color-border-primary": "#334155",
|
|
131
|
+
"--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
|
|
132
|
+
"--border-radius-sm": "0.5rem",
|
|
133
133
|
} : {
|
|
134
|
-
"--
|
|
135
|
-
"--
|
|
136
|
-
"--
|
|
137
|
-
"--
|
|
138
|
-
"--
|
|
139
|
-
"--
|
|
134
|
+
"--color-background-primary": "#ffffff", "--color-text-primary": "#1a1a1a",
|
|
135
|
+
"--color-background-secondary": "#f9fafb", "--color-text-primary": "#1a1a1a",
|
|
136
|
+
"--color-text-accent": "#6366f1", "--nb-color-accent-foreground": "#ffffff",
|
|
137
|
+
"--color-text-secondary": "#6b7280", "--color-border-primary": "#e5e7eb",
|
|
138
|
+
"--color-ring-primary": "#6366f1", "--nb-color-danger": "#ef4444",
|
|
139
|
+
"--border-radius-sm": "0.5rem",
|
|
140
140
|
};
|
|
141
141
|
post({ jsonrpc: "2.0", method: "ui/themeChanged", params: { mode: darkMode ? "dark" : "light", tokens: tokens } });
|
|
142
142
|
});
|
|
@@ -200,5 +200,5 @@ async function startPreview(options) {
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
export { startPreview };
|
|
203
|
-
//# sourceMappingURL=server-
|
|
204
|
-
//# sourceMappingURL=server-
|
|
203
|
+
//# sourceMappingURL=server-NNW54YW5.js.map
|
|
204
|
+
//# sourceMappingURL=server-NNW54YW5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/preview/server.ts"],"names":[],"mappings":"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","file":"server-NNW54YW5.js","sourcesContent":["/**\n * Synapse Preview — standalone dev harness for MCP apps with UIs.\n *\n * Starts the MCP server (HTTP mode) and a minimal bridge host page\n * that iframes the app UI and proxies tool calls to the server.\n *\n * Usage:\n * npx synapse preview --server \"uv run uvicorn mcp_hello.server:app --port 8001\" --ui ./ui\n * npx synapse preview --server \"node dist/index.js\" --ui ./ui --server-port 8001\n */\n\nimport { type ChildProcess, spawn } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { join, resolve } from \"node:path\";\n\nexport interface PreviewOptions {\n /** Shell command to start the MCP server in HTTP mode */\n serverCmd: string;\n /** Port the MCP server listens on (default: 8001) */\n serverPort: number;\n /** Path to the UI directory (must have package.json with dev script) */\n uiDir: string;\n /** Port for the UI Vite dev server (default: 5173) */\n uiPort: number;\n /** Port for the preview harness (default: 5180) */\n previewPort: number;\n}\n\nconst HOST_HTML = (uiPort: number, serverPort: number) => `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\" />\n <title>Synapse Preview</title>\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif; background: #0f172a; color: #e2e8f0; }\n header { padding: 12px 20px; background: #1e293b; border-bottom: 1px solid #334155; display: flex; align-items: center; gap: 12px; }\n header h1 { font-size: 14px; font-weight: 500; }\n header .dot { width: 8px; height: 8px; border-radius: 50%; background: #22c55e; }\n header .info { font-size: 12px; color: #94a3b8; margin-left: auto; }\n .theme-toggle { background: #334155; border: none; color: #e2e8f0; padding: 4px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; }\n iframe { width: 100%; height: calc(100vh - 45px); border: none; }\n </style>\n</head>\n<body>\n <header>\n <span class=\"dot\"></span>\n <h1>Synapse Preview</h1>\n <button class=\"theme-toggle\" id=\"toggle\">Toggle Dark/Light</button>\n <span class=\"info\">MCP: localhost:${serverPort} | UI: localhost:${uiPort}</span>\n </header>\n <iframe id=\"app\" src=\"http://localhost:${uiPort}\"></iframe>\n\n <script>\n var iframe = document.getElementById(\"app\");\n var darkMode = true;\n\n // Minimal NimbleBrain bridge host — just enough to make Synapse work\n var tokens = darkMode ? {\n \"--color-background-primary\": \"#0f172a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-background-secondary\": \"#1e293b\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-border-primary\": \"#334155\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-background-secondary\": \"#f9fafb\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-border-primary\": \"#e5e7eb\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n };\n\n function post(msg) { iframe.contentWindow.postMessage(msg, \"*\"); }\n\n window.addEventListener(\"message\", async function (event) {\n if (event.source !== iframe.contentWindow) return;\n var msg = event.data;\n if (!msg || typeof msg !== \"object\") return;\n\n // ext-apps handshake\n if (msg.method === \"ui/initialize\" && msg.id) {\n post({\n jsonrpc: \"2.0\", id: msg.id,\n result: {\n protocolVersion: \"2026-01-26\",\n serverInfo: { name: \"nimblebrain\", version: \"preview\" },\n capabilities: { openLinks: {}, serverTools: {} },\n hostContext: { theme: darkMode ? \"dark\" : \"light\", primaryColor: \"#6366f1\", tokens: tokens }\n }\n });\n return;\n }\n\n if (msg.method === \"ui/notifications/initialized\") return;\n\n // Tool call proxy — forward to the MCP server\n if (msg.method === \"tools/call\" && msg.id) {\n try {\n var resp = await fetch(\"http://localhost:${serverPort}/mcp\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n jsonrpc: \"2.0\", id: msg.id,\n method: \"tools/call\",\n params: { name: msg.params.name, arguments: msg.params.arguments || {} }\n })\n });\n var result = await resp.json();\n // Forward the JSON-RPC response back to the iframe\n post(result);\n } catch (err) {\n post({ jsonrpc: \"2.0\", id: msg.id, error: { code: -32000, message: err.message || \"Server error\" } });\n }\n return;\n }\n\n // ui/chat — log to console\n if (msg.method === \"ui/chat\") {\n console.log(\"[chat]\", msg.params?.message);\n return;\n }\n\n // ui/action — log to console\n if (msg.method === \"ui/action\") {\n console.log(\"[action]\", msg.params?.action, msg.params);\n return;\n }\n\n // ui/keydown — ignore in preview\n if (msg.method === \"ui/keydown\") return;\n\n // ui/stateChanged — log\n if (msg.method === \"ui/stateChanged\") {\n console.log(\"[state]\", msg.params?.state);\n post({ jsonrpc: \"2.0\", method: \"ui/stateAcknowledged\", params: { truncated: false } });\n return;\n }\n\n console.log(\"[bridge] unhandled:\", msg.method, msg);\n });\n\n // Theme toggle\n document.getElementById(\"toggle\").addEventListener(\"click\", function () {\n darkMode = !darkMode;\n document.body.style.background = darkMode ? \"#0f172a\" : \"#f1f5f9\";\n tokens = darkMode ? {\n \"--color-background-primary\": \"#0f172a\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-background-secondary\": \"#1e293b\", \"--color-text-primary\": \"#e2e8f0\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#94a3b8\", \"--color-border-primary\": \"#334155\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n } : {\n \"--color-background-primary\": \"#ffffff\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-background-secondary\": \"#f9fafb\", \"--color-text-primary\": \"#1a1a1a\",\n \"--color-text-accent\": \"#6366f1\", \"--nb-color-accent-foreground\": \"#ffffff\",\n \"--color-text-secondary\": \"#6b7280\", \"--color-border-primary\": \"#e5e7eb\",\n \"--color-ring-primary\": \"#6366f1\", \"--nb-color-danger\": \"#ef4444\",\n \"--border-radius-sm\": \"0.5rem\",\n };\n post({ jsonrpc: \"2.0\", method: \"ui/themeChanged\", params: { mode: darkMode ? \"dark\" : \"light\", tokens: tokens } });\n });\n </script>\n</body>\n</html>`;\n\nexport async function startPreview(options: PreviewOptions): Promise<void> {\n const { serverCmd, serverPort, uiDir, uiPort, previewPort } = options;\n const children: ChildProcess[] = [];\n\n console.log(`\\n Synapse Preview\\n`);\n\n // 1. Start MCP server\n console.log(` [server] ${serverCmd}`);\n const serverProc = spawn(serverCmd, {\n shell: true,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n children.push(serverProc);\n serverProc.stdout?.on(\"data\", (d: Buffer) => process.stdout.write(` [server] ${d}`));\n serverProc.stderr?.on(\"data\", (d: Buffer) => process.stderr.write(` [server] ${d}`));\n\n // 2. Start UI Vite dev server\n const resolvedUi = resolve(uiDir);\n if (!existsSync(join(resolvedUi, \"package.json\"))) {\n console.error(` [ui] Error: no package.json found at ${resolvedUi}`);\n process.exit(1);\n }\n console.log(` [ui] cd ${resolvedUi} && npx vite --port ${uiPort}`);\n const uiProc = spawn(\"npx\", [\"vite\", \"--port\", String(uiPort)], {\n cwd: resolvedUi,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n children.push(uiProc);\n uiProc.stdout?.on(\"data\", (d: Buffer) => process.stdout.write(` [ui] ${d}`));\n uiProc.stderr?.on(\"data\", (d: Buffer) => process.stderr.write(` [ui] ${d}`));\n\n // 3. Start preview host\n const html = HOST_HTML(uiPort, serverPort);\n const host = createServer((_req: IncomingMessage, res: ServerResponse) => {\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(html);\n });\n host.listen(previewPort, () => {\n console.log(`\\n Preview: http://localhost:${previewPort}`);\n console.log(` UI: http://localhost:${uiPort}`);\n console.log(` Server: http://localhost:${serverPort}`);\n console.log(`\\n Press Ctrl+C to stop.\\n`);\n });\n\n // Shutdown\n const shutdown = () => {\n console.log(\"\\n Shutting down...\");\n host.close();\n for (const child of children) {\n try {\n child.kill(\"SIGTERM\");\n } catch {\n /* already dead */\n }\n }\n process.exit(0);\n };\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(){'use strict';var
|
|
1
|
+
(function(){'use strict';var C={mode:"light",primaryColor:"#6366f1",tokens:{}};function E(r){let e=r,n=A(e?.serverInfo),o=typeof n?.name=="string"?n.name:"unknown",i=typeof e?.protocolVersion=="string"?e.protocolVersion:"unknown",s=A(e?.hostContext),c=H(s?.theme);return {isNimbleBrain:o==="nimblebrain",serverName:o,protocolVersion:i,theme:c}}function H(r){let e=A(r);if(!e)return {...C};let n=e.mode==="light"||e.mode==="dark"?e.mode:C.mode,o=typeof e.primaryColor=="string"?e.primaryColor:C.primaryColor,i=e.tokens!==null&&typeof e.tokens=="object"&&!Array.isArray(e.tokens)?e.tokens:{};return {mode:n,primaryColor:o,tokens:i}}function A(r){if(r!==null&&typeof r=="object"&&!Array.isArray(r))return r}var S=class{listener;destroyed=false;constructor(e,n){let o=n??null;this.listener=i=>{this.destroyed||this.shouldForward(i,o)&&(i.preventDefault(),e.send("ui/keydown",{key:i.key,ctrlKey:i.ctrlKey,metaKey:i.metaKey,shiftKey:i.shiftKey,altKey:i.altKey}));},document.addEventListener("keydown",this.listener);}destroy(){this.destroyed||(this.destroyed=true,document.removeEventListener("keydown",this.listener));}shouldForward(e,n){return n&&n.length===0?false:n?n.some(o=>e.key.toLowerCase()===o.key.toLowerCase()&&(o.ctrl===void 0||e.ctrlKey===o.ctrl)&&(o.meta===void 0||e.metaKey===o.meta)&&(o.shift===void 0||e.shiftKey===o.shift)&&(o.alt===void 0||e.altKey===o.alt)):!!(e.key==="Escape"||e.ctrlKey||e.metaKey)}};function M(r){return r==null?{data:null,isError:false}:I(r)?N(r):{data:r,isError:false}}function I(r){return r===null||typeof r!="object"||Array.isArray(r)?false:Array.isArray(r.content)}function L(r){if(r===null||typeof r!="object"||Array.isArray(r))return false;let e=r;return e.type==="text"&&typeof e.text=="string"}function N(r){let e=r.isError===true,n=r.content;if(n.length===0)return {data:null,isError:e};let o=n.find(L);if(!o)return {data:n,isError:e};try{return {data:JSON.parse(o.text),isError:e}}catch{return {data:o.text,isError:e}}}var b=class{counter=0;destroyed=false;pending=new Map;handlers=new Map;listener;constructor(){this.listener=e=>this.handleMessage(e),window.addEventListener("message",this.listener);}send(e,n){if(this.destroyed)return;let o={jsonrpc:"2.0",method:e,...n!==void 0&&{params:n}};window.parent.postMessage(o,"*");}request(e,n){if(this.destroyed)return Promise.reject(new Error("Transport destroyed"));let o=`syn-${++this.counter}`,i={jsonrpc:"2.0",method:e,id:o,...n!==void 0&&{params:n}};return new Promise((s,c)=>{this.pending.set(o,{resolve:s,reject:c}),window.parent.postMessage(i,"*");})}onMessage(e,n){return this.handlers.has(e)||this.handlers.set(e,new Set),this.handlers.get(e)?.add(n),()=>{let o=this.handlers.get(e);o&&(o.delete(n),o.size===0&&this.handlers.delete(e));}}destroy(){if(this.destroyed)return;this.destroyed=true,window.removeEventListener("message",this.listener);let e=new Error("Transport destroyed");for(let n of this.pending.values())n.reject(e);this.pending.clear(),this.handlers.clear();}handleMessage(e){if(this.destroyed)return;let n=e.data;if(!(!n||n.jsonrpc!=="2.0")){if("id"in n&&n.id&&!("method"in n)){let o=n,i=this.pending.get(o.id);if(!i)return;if(this.pending.delete(o.id),o.error){let s=new Error(o.error.message);s.code=o.error.code,s.data=o.error.data,i.reject(s);}else i.resolve(o.result);return}if("method"in n&&!("id"in n&&n.id)){let o=n,i=this.handlers.get(o.method);if(i)for(let s of i)s(o.params);}}}};function j(r){let{name:e,version:n,internal:o=false,forwardKeys:i}=r,s=new b,c=null,u={mode:"light",primaryColor:"#6366f1",tokens:{}},w=false,y=null,v=null,R=s.request("ui/initialize",{protocolVersion:"2026-01-26",clientInfo:{name:e,version:n},capabilities:{}}).then(t=>{c=E(t),u=c.theme,s.send("ui/notifications/initialized",{}),v=new S(s,i);}),d=s.onMessage("ui/notifications/host-context-changed",t=>{if(!t)return;let a=t.theme==="dark"?"dark":"light",l=t.tokens&&typeof t.tokens=="object"?t.tokens:u.tokens;u={mode:a,primaryColor:u.primaryColor,tokens:l};for(let g of p)g(u);}),f=s.onMessage("ui/themeChanged",t=>{if(!t)return;let a=t.mode==="dark"||t.mode==="light"?t.mode:u.mode,l=t.tokens&&typeof t.tokens=="object"?t.tokens:u.tokens;u={mode:a,primaryColor:u.primaryColor,tokens:l};for(let g of p)g(u);}),p=new Set,m=new Set,h=new Set,k=s.onMessage("ui/datachanged",t=>{if(!t)return;let a={source:"agent",server:t.server??"",tool:t.tool??""};for(let l of m)l(a);}),K=s.onMessage("ui/action",t=>{if(!t||typeof t.type!="string")return;let a={type:t.type,payload:t.payload??{},requiresConfirmation:t.requiresConfirmation===true,label:typeof t.label=="string"?t.label:void 0};for(let l of h)l(a);}),T=()=>c?.isNimbleBrain===true;return {get ready(){return R},get isNimbleBrainHost(){return T()},get destroyed(){return w},async callTool(t,a){let l={name:t,arguments:a??{}};o&&(l.server=e);let g=await s.request("tools/call",l);return M(g)},onDataChanged(t){return m.add(t),()=>{m.delete(t);}},onAction(t){return h.add(t),()=>{h.delete(t);}},getTheme(){return {...u}},onThemeChanged(t){return p.add(t),()=>{p.delete(t);}},action(t,a){T()&&s.send("ui/action",{action:t,...a});},chat(t,a){T()&&s.send("ui/chat",{message:t,context:a});},setVisibleState(t,a){T()&&(y&&clearTimeout(y),y=setTimeout(()=>{s.send("ui/stateChanged",{state:t,...a!==void 0&&{summary:a}}),y=null;},250));},downloadFile(t,a,l){if(!T())return;let g=typeof a=="string"?a:"[Blob content not serializable]";s.send("ui/downloadFile",{data:g,filename:t,mimeType:l??"application/octet-stream"});},openLink(t){T()?s.send("ui/openLink",{url:t}):window.open(t,"_blank","noopener");},_onMessage(t,a){return s.onMessage(t,a)},_request(t,a){return s.request(t,a)},destroy(){w||(w=true,y&&clearTimeout(y),v?.destroy(),d(),f(),k(),K(),p.clear(),m.clear(),h.clear(),s.destroy());}}}function x(r,e){let n=structuredClone(e.initialState),o=new Set,i=false,s=null,c={};for(let d of Object.keys(e.actions))c[d]=f=>{i||(n=e.actions[d](n,f),u());};function u(){for(let d of o)d(n);e.visibleToAgent&&w(),e.persist&&y();}function w(){let d=e.summarize?.(n);r.setVisibleState(n,d);}function y(){s&&clearTimeout(s),s=setTimeout(()=>{r._request("ui/persistState",{state:n,version:e.version}).catch(()=>{}),s=null;},500);}let v;e.persist&&(v=r._onMessage("ui/stateLoaded",d=>{if(!d?.state)return;let f=d.state,p=d.version??1,m=e.version??1;if(e.migrations&&p<m){let h=p-1;for(let k=h;k<e.migrations.length;k++)f=e.migrations[k](f);}R.hydrate(f);}));let R={getState(){return n},subscribe(d){return o.add(d),()=>{o.delete(d);}},dispatch:c,hydrate(d){n=d;for(let f of o)f(n);},destroy(){i||(i=true,s&&clearTimeout(s),o.clear(),v?.());}};return R}window.NbSynapse={createSynapse:j,createStore:x};})();
|
|
@@ -21,6 +21,49 @@ interface DataChangedEvent {
|
|
|
21
21
|
server: string;
|
|
22
22
|
tool: string;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Built-in action types that Synapse handles natively.
|
|
26
|
+
*
|
|
27
|
+
* - `navigate` — select/focus a resource in the UI (e.g., a board, document, record)
|
|
28
|
+
* - `notify` — display a transient message (toast/banner)
|
|
29
|
+
* - `refresh` — force a full data refresh (heavier than datachanged)
|
|
30
|
+
* - `confirm` — request user confirmation before the agent proceeds
|
|
31
|
+
*
|
|
32
|
+
* Apps may also receive custom string types for domain-specific actions.
|
|
33
|
+
*/
|
|
34
|
+
type BuiltinActionType = "navigate" | "notify" | "refresh" | "confirm";
|
|
35
|
+
/**
|
|
36
|
+
* A typed, declarative action sent from the agent/server to the UI.
|
|
37
|
+
*
|
|
38
|
+
* Actions are deterministic side effects of tool execution — the tool decides
|
|
39
|
+
* what action to emit, not the LLM. The UI decides how to handle it.
|
|
40
|
+
*
|
|
41
|
+
* This mirrors Studio's ClientAction pattern, adapted for iframe postMessage.
|
|
42
|
+
*/
|
|
43
|
+
interface AgentAction<TPayload = Record<string, unknown>> {
|
|
44
|
+
/** Discriminator — a BuiltinActionType or custom string. */
|
|
45
|
+
type: BuiltinActionType | (string & {});
|
|
46
|
+
/** Typed payload — shape depends on `type`. */
|
|
47
|
+
payload: TPayload;
|
|
48
|
+
/** If true, the UI should confirm with the user before executing. */
|
|
49
|
+
requiresConfirmation?: boolean;
|
|
50
|
+
/** Human-readable label for confirmation dialogs or logs. */
|
|
51
|
+
label?: string;
|
|
52
|
+
}
|
|
53
|
+
/** Payload for the built-in "navigate" action. */
|
|
54
|
+
interface NavigatePayload {
|
|
55
|
+
/** Entity type (e.g., "board", "document", "task"). */
|
|
56
|
+
entity: string;
|
|
57
|
+
/** Entity ID to select/focus. */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Optional sub-view or section within the entity. */
|
|
60
|
+
view?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Payload for the built-in "notify" action. */
|
|
63
|
+
interface NotifyPayload {
|
|
64
|
+
message: string;
|
|
65
|
+
level?: "info" | "success" | "warning" | "error";
|
|
66
|
+
}
|
|
24
67
|
interface ToolCallResult<T = unknown> {
|
|
25
68
|
data: T;
|
|
26
69
|
isError: boolean;
|
|
@@ -30,6 +73,16 @@ interface Synapse {
|
|
|
30
73
|
readonly isNimbleBrainHost: boolean;
|
|
31
74
|
callTool<TInput = Record<string, unknown>, TOutput = unknown>(name: string, args?: TInput): Promise<ToolCallResult<TOutput>>;
|
|
32
75
|
onDataChanged(callback: (event: DataChangedEvent) => void): () => void;
|
|
76
|
+
/**
|
|
77
|
+
* Subscribe to agent actions — typed, declarative commands from the server.
|
|
78
|
+
*
|
|
79
|
+
* Actions are deterministic side effects of tool execution. The server/tool
|
|
80
|
+
* decides what action to emit; the UI decides how to handle it.
|
|
81
|
+
*
|
|
82
|
+
* The callback receives an AgentAction with a `type` discriminator and typed
|
|
83
|
+
* `payload`. Apps should handle known types and ignore unknown ones.
|
|
84
|
+
*/
|
|
85
|
+
onAction(callback: (action: AgentAction) => void): () => void;
|
|
33
86
|
getTheme(): SynapseTheme;
|
|
34
87
|
onThemeChanged(callback: (theme: SynapseTheme) => void): () => void;
|
|
35
88
|
action(action: string, params?: Record<string, unknown>): void;
|
|
@@ -44,6 +97,8 @@ interface Synapse {
|
|
|
44
97
|
_onMessage(method: string, callback: (params: Record<string, unknown> | undefined) => void): () => void;
|
|
45
98
|
/** @internal — used by createStore for ui/persistState */
|
|
46
99
|
_request(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
100
|
+
/** True after destroy() has been called. */
|
|
101
|
+
readonly destroyed: boolean;
|
|
47
102
|
destroy(): void;
|
|
48
103
|
}
|
|
49
104
|
interface VisibleState {
|
|
@@ -93,4 +148,4 @@ interface HostInfo {
|
|
|
93
148
|
theme: SynapseTheme;
|
|
94
149
|
}
|
|
95
150
|
|
|
96
|
-
export type { ActionReducer as A, DataChangedEvent as D, HostInfo as H, KeyForwardConfig as K, SynapseOptions as S, ToolCallResult as T, VisibleState as V, Synapse as a, StoreConfig as b, Store as c,
|
|
151
|
+
export type { ActionReducer as A, BuiltinActionType as B, DataChangedEvent as D, HostInfo as H, KeyForwardConfig as K, NavigatePayload as N, SynapseOptions as S, ToolCallResult as T, VisibleState as V, Synapse as a, StoreConfig as b, Store as c, AgentAction as d, NotifyPayload as e, StateAcknowledgement as f, StoreDispatch as g, SynapseTheme as h, ToolDefinition as i };
|
|
@@ -21,6 +21,49 @@ interface DataChangedEvent {
|
|
|
21
21
|
server: string;
|
|
22
22
|
tool: string;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Built-in action types that Synapse handles natively.
|
|
26
|
+
*
|
|
27
|
+
* - `navigate` — select/focus a resource in the UI (e.g., a board, document, record)
|
|
28
|
+
* - `notify` — display a transient message (toast/banner)
|
|
29
|
+
* - `refresh` — force a full data refresh (heavier than datachanged)
|
|
30
|
+
* - `confirm` — request user confirmation before the agent proceeds
|
|
31
|
+
*
|
|
32
|
+
* Apps may also receive custom string types for domain-specific actions.
|
|
33
|
+
*/
|
|
34
|
+
type BuiltinActionType = "navigate" | "notify" | "refresh" | "confirm";
|
|
35
|
+
/**
|
|
36
|
+
* A typed, declarative action sent from the agent/server to the UI.
|
|
37
|
+
*
|
|
38
|
+
* Actions are deterministic side effects of tool execution — the tool decides
|
|
39
|
+
* what action to emit, not the LLM. The UI decides how to handle it.
|
|
40
|
+
*
|
|
41
|
+
* This mirrors Studio's ClientAction pattern, adapted for iframe postMessage.
|
|
42
|
+
*/
|
|
43
|
+
interface AgentAction<TPayload = Record<string, unknown>> {
|
|
44
|
+
/** Discriminator — a BuiltinActionType or custom string. */
|
|
45
|
+
type: BuiltinActionType | (string & {});
|
|
46
|
+
/** Typed payload — shape depends on `type`. */
|
|
47
|
+
payload: TPayload;
|
|
48
|
+
/** If true, the UI should confirm with the user before executing. */
|
|
49
|
+
requiresConfirmation?: boolean;
|
|
50
|
+
/** Human-readable label for confirmation dialogs or logs. */
|
|
51
|
+
label?: string;
|
|
52
|
+
}
|
|
53
|
+
/** Payload for the built-in "navigate" action. */
|
|
54
|
+
interface NavigatePayload {
|
|
55
|
+
/** Entity type (e.g., "board", "document", "task"). */
|
|
56
|
+
entity: string;
|
|
57
|
+
/** Entity ID to select/focus. */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Optional sub-view or section within the entity. */
|
|
60
|
+
view?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Payload for the built-in "notify" action. */
|
|
63
|
+
interface NotifyPayload {
|
|
64
|
+
message: string;
|
|
65
|
+
level?: "info" | "success" | "warning" | "error";
|
|
66
|
+
}
|
|
24
67
|
interface ToolCallResult<T = unknown> {
|
|
25
68
|
data: T;
|
|
26
69
|
isError: boolean;
|
|
@@ -30,6 +73,16 @@ interface Synapse {
|
|
|
30
73
|
readonly isNimbleBrainHost: boolean;
|
|
31
74
|
callTool<TInput = Record<string, unknown>, TOutput = unknown>(name: string, args?: TInput): Promise<ToolCallResult<TOutput>>;
|
|
32
75
|
onDataChanged(callback: (event: DataChangedEvent) => void): () => void;
|
|
76
|
+
/**
|
|
77
|
+
* Subscribe to agent actions — typed, declarative commands from the server.
|
|
78
|
+
*
|
|
79
|
+
* Actions are deterministic side effects of tool execution. The server/tool
|
|
80
|
+
* decides what action to emit; the UI decides how to handle it.
|
|
81
|
+
*
|
|
82
|
+
* The callback receives an AgentAction with a `type` discriminator and typed
|
|
83
|
+
* `payload`. Apps should handle known types and ignore unknown ones.
|
|
84
|
+
*/
|
|
85
|
+
onAction(callback: (action: AgentAction) => void): () => void;
|
|
33
86
|
getTheme(): SynapseTheme;
|
|
34
87
|
onThemeChanged(callback: (theme: SynapseTheme) => void): () => void;
|
|
35
88
|
action(action: string, params?: Record<string, unknown>): void;
|
|
@@ -44,6 +97,8 @@ interface Synapse {
|
|
|
44
97
|
_onMessage(method: string, callback: (params: Record<string, unknown> | undefined) => void): () => void;
|
|
45
98
|
/** @internal — used by createStore for ui/persistState */
|
|
46
99
|
_request(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
100
|
+
/** True after destroy() has been called. */
|
|
101
|
+
readonly destroyed: boolean;
|
|
47
102
|
destroy(): void;
|
|
48
103
|
}
|
|
49
104
|
interface VisibleState {
|
|
@@ -93,4 +148,4 @@ interface HostInfo {
|
|
|
93
148
|
theme: SynapseTheme;
|
|
94
149
|
}
|
|
95
150
|
|
|
96
|
-
export type { ActionReducer as A, DataChangedEvent as D, HostInfo as H, KeyForwardConfig as K, SynapseOptions as S, ToolCallResult as T, VisibleState as V, Synapse as a, StoreConfig as b, Store as c,
|
|
151
|
+
export type { ActionReducer as A, BuiltinActionType as B, DataChangedEvent as D, HostInfo as H, KeyForwardConfig as K, NavigatePayload as N, SynapseOptions as S, ToolCallResult as T, VisibleState as V, Synapse as a, StoreConfig as b, Store as c, AgentAction as d, NotifyPayload as e, StateAcknowledgement as f, StoreDispatch as g, SynapseTheme as h, ToolDefinition as i };
|
package/dist/vite/index.cjs
CHANGED
|
@@ -201,7 +201,7 @@ function previewHostHtml(appName) {
|
|
|
201
201
|
<button id="toggle">Toggle Theme</button>
|
|
202
202
|
<span class="url">Synapse Preview</span>
|
|
203
203
|
</header>
|
|
204
|
-
<iframe id="app"
|
|
204
|
+
<iframe id="app"></iframe>
|
|
205
205
|
|
|
206
206
|
<script>
|
|
207
207
|
var iframe = document.getElementById("app");
|
|
@@ -209,19 +209,19 @@ function previewHostHtml(appName) {
|
|
|
209
209
|
|
|
210
210
|
function getTokens(d) {
|
|
211
211
|
return d ? {
|
|
212
|
-
"--
|
|
213
|
-
"--
|
|
214
|
-
"--
|
|
215
|
-
"--
|
|
216
|
-
"--
|
|
217
|
-
"--
|
|
212
|
+
"--color-background-primary":"#0f172a","--color-text-primary":"#e2e8f0",
|
|
213
|
+
"--color-background-secondary":"#1e293b","--color-text-primary":"#e2e8f0",
|
|
214
|
+
"--color-text-accent":"#6366f1","--nb-color-accent-foreground":"#fff",
|
|
215
|
+
"--color-text-secondary":"#94a3b8","--color-border-primary":"#334155",
|
|
216
|
+
"--color-ring-primary":"#6366f1","--nb-color-danger":"#ef4444",
|
|
217
|
+
"--border-radius-sm":"0.5rem","--font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
|
|
218
218
|
} : {
|
|
219
|
-
"--
|
|
220
|
-
"--
|
|
221
|
-
"--
|
|
222
|
-
"--
|
|
223
|
-
"--
|
|
224
|
-
"--
|
|
219
|
+
"--color-background-primary":"#ffffff","--color-text-primary":"#0f172a",
|
|
220
|
+
"--color-background-secondary":"#f8fafc","--color-text-primary":"#0f172a",
|
|
221
|
+
"--color-text-accent":"#6366f1","--nb-color-accent-foreground":"#fff",
|
|
222
|
+
"--color-text-secondary":"#64748b","--color-border-primary":"#e2e8f0",
|
|
223
|
+
"--color-ring-primary":"#6366f1","--nb-color-danger":"#ef4444",
|
|
224
|
+
"--border-radius-sm":"0.5rem","--font-sans":"-apple-system,BlinkMacSystemFont,sans-serif"
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
|
|
@@ -246,14 +246,23 @@ function previewHostHtml(appName) {
|
|
|
246
246
|
|
|
247
247
|
// Tool calls \u2014 proxy via Vite middleware
|
|
248
248
|
if (msg.method === "tools/call" && msg.id) {
|
|
249
|
+
var originalId = msg.id;
|
|
249
250
|
try {
|
|
250
251
|
var r = await fetch("/__mcp", {
|
|
251
252
|
method:"POST", headers:{"Content-Type":"application/json"},
|
|
252
253
|
body: JSON.stringify({jsonrpc:"2.0",id:msg.id,method:"tools/call",params:{name:msg.params.name,arguments:msg.params.arguments||{}}})
|
|
253
254
|
});
|
|
254
|
-
|
|
255
|
+
var response = await r.json();
|
|
256
|
+
response.id = originalId;
|
|
257
|
+
post(response);
|
|
258
|
+
// Notify the app that data changed so hooks auto-refresh
|
|
259
|
+
// Only for mutating operations (not list/search/get which are read-only)
|
|
260
|
+
var tn = msg.params.name || "";
|
|
261
|
+
if (!response.error && !tn.startsWith("list_") && !tn.startsWith("search_") && !tn.startsWith("get_") && !tn.startsWith("query_")) {
|
|
262
|
+
post({jsonrpc:"2.0",method:"ui/datachanged",params:{tool:tn,server:"preview"}});
|
|
263
|
+
}
|
|
255
264
|
} catch(err) {
|
|
256
|
-
post({jsonrpc:"2.0",id:
|
|
265
|
+
post({jsonrpc:"2.0",id:originalId,error:{code:-32000,message:err.message}});
|
|
257
266
|
}
|
|
258
267
|
return;
|
|
259
268
|
}
|
|
@@ -271,6 +280,10 @@ function previewHostHtml(appName) {
|
|
|
271
280
|
document.body.style.background = dark ? "#0f172a" : "#f1f5f9";
|
|
272
281
|
post({jsonrpc:"2.0",method:"ui/themeChanged",params:{mode:dark?"dark":"light",tokens:getTokens(dark)}});
|
|
273
282
|
};
|
|
283
|
+
|
|
284
|
+
// Load the iframe AFTER the message listener is attached to avoid
|
|
285
|
+
// a race where the app sends ui/initialize before the bridge is ready.
|
|
286
|
+
iframe.src = "/";
|
|
274
287
|
</script>
|
|
275
288
|
</body>
|
|
276
289
|
</html>`;
|