project-graph-mcp 2.3.2 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/analysis/analysis-cache.ctx +9 -0
- package/src/analysis/analysis-cache.js +1 -1
- package/src/analysis/complexity.ctx +6 -0
- package/src/analysis/complexity.js +1 -1
- package/src/analysis/custom-rules.ctx +14 -0
- package/src/analysis/custom-rules.js +1 -1
- package/src/analysis/db-analysis.ctx +7 -0
- package/src/analysis/db-analysis.js +1 -1
- package/src/analysis/dead-code.ctx +6 -0
- package/src/analysis/dead-code.js +1 -1
- package/src/analysis/full-analysis.ctx +9 -0
- package/src/analysis/full-analysis.js +1 -1
- package/src/analysis/jsdoc-checker.ctx +10 -0
- package/src/analysis/jsdoc-checker.js +1 -1
- package/src/analysis/jsdoc-generator.ctx +9 -0
- package/src/analysis/jsdoc-generator.js +1 -1
- package/src/analysis/large-files.ctx +6 -0
- package/src/analysis/large-files.js +1 -1
- package/src/analysis/outdated-patterns.ctx +7 -0
- package/src/analysis/outdated-patterns.js +1 -1
- package/src/analysis/similar-functions.ctx +6 -0
- package/src/analysis/similar-functions.js +1 -1
- package/src/analysis/test-annotations.ctx +11 -0
- package/src/analysis/test-annotations.js +1 -1
- package/src/analysis/type-checker.ctx +6 -0
- package/src/analysis/type-checker.js +1 -1
- package/src/analysis/undocumented.ctx +8 -0
- package/src/analysis/undocumented.js +1 -1
- package/src/cli/cli-handlers.ctx +7 -0
- package/src/cli/cli-handlers.js +1 -1
- package/src/cli/cli.ctx +6 -0
- package/src/cli/cli.js +1 -1
- package/src/compact/ai-context.ctx +6 -0
- package/src/compact/ai-context.js +1 -1
- package/src/compact/compact-migrate.ctx +8 -0
- package/src/compact/compact-migrate.js +1 -1
- package/src/compact/compact.ctx +11 -0
- package/src/compact/compact.js +1 -1
- package/src/compact/compress.ctx +7 -0
- package/src/compact/compress.js +1 -1
- package/src/compact/ctx-resolver.ctx +2 -0
- package/src/compact/ctx-resolver.js +1 -1
- package/src/compact/ctx-to-jsdoc.ctx +11 -0
- package/src/compact/ctx-to-jsdoc.js +1 -1
- package/src/compact/doc-dialect.ctx +11 -0
- package/src/compact/doc-dialect.js +2 -2
- package/src/compact/expand.ctx +14 -0
- package/src/compact/expand.js +1 -1
- package/src/compact/framework-references.ctx +7 -0
- package/src/compact/framework-references.js +1 -1
- package/src/compact/instructions.ctx +6 -0
- package/src/compact/instructions.js +1 -1
- package/src/compact/jsdoc-builder.ctx +4 -0
- package/src/compact/jsdoc-builder.js +1 -1
- package/src/compact/mode-config.ctx +8 -0
- package/src/compact/mode-config.js +1 -1
- package/src/compact/split-declarations.ctx +6 -0
- package/src/compact/split-declarations.js +1 -1
- package/src/compact/validate-pipeline.ctx +12 -0
- package/src/compact/validate-pipeline.js +1 -1
- package/src/core/event-bus.ctx +9 -0
- package/src/core/event-bus.js +1 -1
- package/src/core/file-walker.ctx +1 -0
- package/src/core/file-walker.js +1 -1
- package/src/core/filters.ctx +12 -0
- package/src/core/filters.js +1 -1
- package/src/core/graph-builder.ctx +7 -0
- package/src/core/graph-builder.js +1 -1
- package/src/core/parser.ctx +12 -0
- package/src/core/parser.js +1 -1
- package/src/core/utils.ctx +1 -0
- package/src/core/utils.js +1 -1
- package/src/core/workspace.ctx +7 -0
- package/src/core/workspace.js +1 -1
- package/src/lang/lang-go.ctx +8 -0
- package/src/lang/lang-go.js +1 -1
- package/src/lang/lang-python.ctx +5 -0
- package/src/lang/lang-python.js +1 -1
- package/src/lang/lang-sql.ctx +10 -0
- package/src/lang/lang-sql.js +1 -1
- package/src/lang/lang-typescript.ctx +6 -0
- package/src/lang/lang-typescript.js +1 -1
- package/src/lang/lang-utils.ctx +5 -0
- package/src/lang/lang-utils.js +1 -1
- package/src/mcp/mcp-server.ctx +6 -0
- package/src/mcp/mcp-server.js +1 -1
- package/src/mcp/tool-defs.ctx +2 -0
- package/src/mcp/tool-defs.js +1 -1
- package/src/mcp/tools.ctx +13 -0
- package/src/mcp/tools.js +1 -1
- package/src/network/backend-lifecycle.ctx +10 -0
- package/src/network/backend-lifecycle.js +1 -1
- package/src/network/backend.ctx +5 -0
- package/src/network/backend.js +1 -1
- package/src/network/local-gateway.ctx +9 -0
- package/src/network/local-gateway.js +1 -1
- package/src/network/mdns.ctx +6 -0
- package/src/network/mdns.js +1 -1
- package/src/network/server.ctx +2 -0
- package/src/network/server.js +2 -2
- package/src/network/web-server.ctx +17 -0
- package/src/network/web-server.js +2 -2
- package/web/follow-controller.js +94 -25
- package/web/panels/dep-graph.js +207 -21
- package/project-graph-mcp-2.3.0.tgz +0 -0
- package/vendor/symbiote-node/CHANGELOG.md +0 -31
- package/vendor/symbiote-node/LICENSE +0 -21
- package/vendor/symbiote-node/README.md +0 -206
- package/vendor/symbiote-node/canvas/AutoLayout.js +0 -725
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.css.js +0 -73
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.js +0 -93
- package/vendor/symbiote-node/canvas/Breadcrumb/Breadcrumb.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/CanvasConnectionRenderer.js +0 -962
- package/vendor/symbiote-node/canvas/ConnectionRenderer.js +0 -1468
- package/vendor/symbiote-node/canvas/FlowSimulator.js +0 -323
- package/vendor/symbiote-node/canvas/ForceLayout.js +0 -189
- package/vendor/symbiote-node/canvas/ForceWorker.js +0 -1325
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.css.js +0 -97
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.js +0 -176
- package/vendor/symbiote-node/canvas/GraphTabs/GraphTabs.tpl.js +0 -12
- package/vendor/symbiote-node/canvas/LODManager.js +0 -88
- package/vendor/symbiote-node/canvas/Minimap/Minimap.css.js +0 -71
- package/vendor/symbiote-node/canvas/Minimap/Minimap.js +0 -207
- package/vendor/symbiote-node/canvas/Minimap/Minimap.tpl.js +0 -9
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.css.js +0 -261
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.js +0 -1840
- package/vendor/symbiote-node/canvas/NodeCanvas/NodeCanvas.tpl.js +0 -22
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.css.js +0 -97
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.js +0 -132
- package/vendor/symbiote-node/canvas/NodeSearch/NodeSearch.tpl.js +0 -21
- package/vendor/symbiote-node/canvas/NodeViewManager.js +0 -584
- package/vendor/symbiote-node/canvas/PinExpansion.js +0 -131
- package/vendor/symbiote-node/canvas/PseudoConnection.js +0 -80
- package/vendor/symbiote-node/canvas/SubgraphManager.js +0 -201
- package/vendor/symbiote-node/canvas/SubgraphRouter.js +0 -443
- package/vendor/symbiote-node/canvas/ViewportActions.js +0 -446
- package/vendor/symbiote-node/core/Connection.js +0 -45
- package/vendor/symbiote-node/core/Editor.js +0 -451
- package/vendor/symbiote-node/core/Frame.js +0 -31
- package/vendor/symbiote-node/core/GraphMermaid.js +0 -348
- package/vendor/symbiote-node/core/GraphText.js +0 -210
- package/vendor/symbiote-node/core/Node.js +0 -143
- package/vendor/symbiote-node/core/Portal.js +0 -104
- package/vendor/symbiote-node/core/Socket.js +0 -185
- package/vendor/symbiote-node/core/SubgraphNode.js +0 -125
- package/vendor/symbiote-node/engine/AgentUICommands.js +0 -100
- package/vendor/symbiote-node/engine/Executor.js +0 -371
- package/vendor/symbiote-node/engine/Graph.js +0 -314
- package/vendor/symbiote-node/engine/GraphServer.js +0 -353
- package/vendor/symbiote-node/engine/HandlerLoader.js +0 -145
- package/vendor/symbiote-node/engine/History.js +0 -83
- package/vendor/symbiote-node/engine/Lifecycle.js +0 -118
- package/vendor/symbiote-node/engine/Persistence.js +0 -84
- package/vendor/symbiote-node/engine/Registry.js +0 -264
- package/vendor/symbiote-node/engine/SocketTypes.js +0 -79
- package/vendor/symbiote-node/engine/cli.js +0 -404
- package/vendor/symbiote-node/engine/index.js +0 -56
- package/vendor/symbiote-node/engine/nanoid.js +0 -28
- package/vendor/symbiote-node/engine/package.json +0 -26
- package/vendor/symbiote-node/engine/packs/ai/beat-detect.handler.js +0 -215
- package/vendor/symbiote-node/engine/packs/ai/content-adapt.handler.js +0 -238
- package/vendor/symbiote-node/engine/packs/ai/face-detect.handler.js +0 -287
- package/vendor/symbiote-node/engine/packs/ai/grok-generate.handler.js +0 -565
- package/vendor/symbiote-node/engine/packs/ai/kling-lipsync.handler.js +0 -414
- package/vendor/symbiote-node/engine/packs/ai/lesson-generate.handler.js +0 -343
- package/vendor/symbiote-node/engine/packs/ai/opencode.handler.js +0 -164
- package/vendor/symbiote-node/engine/packs/ai/replicate-lipsync.handler.js +0 -341
- package/vendor/symbiote-node/engine/packs/ai/tts.handler.js +0 -241
- package/vendor/symbiote-node/engine/packs/ai/whisper.handler.js +0 -191
- package/vendor/symbiote-node/engine/packs/data/db-query.handler.js +0 -67
- package/vendor/symbiote-node/engine/packs/data/news-accumulate.handler.js +0 -281
- package/vendor/symbiote-node/engine/packs/data/personas.handler.js +0 -160
- package/vendor/symbiote-node/engine/packs/data/prompt-loader.handler.js +0 -193
- package/vendor/symbiote-node/engine/packs/data/roles.handler.js +0 -216
- package/vendor/symbiote-node/engine/packs/data/rss-feed.handler.js +0 -244
- package/vendor/symbiote-node/engine/packs/debug/inject.handler.js +0 -52
- package/vendor/symbiote-node/engine/packs/flow/agent.handler.js +0 -73
- package/vendor/symbiote-node/engine/packs/flow/if.handler.js +0 -107
- package/vendor/symbiote-node/engine/packs/flow/loop.handler.js +0 -58
- package/vendor/symbiote-node/engine/packs/flow/merge.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/flow/retry.handler.js +0 -65
- package/vendor/symbiote-node/engine/packs/flow/switch.handler.js +0 -64
- package/vendor/symbiote-node/engine/packs/flow/wait-all.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/io/http-request.handler.js +0 -82
- package/vendor/symbiote-node/engine/packs/io/read-file.handler.js +0 -60
- package/vendor/symbiote-node/engine/packs/io/write-file.handler.js +0 -63
- package/vendor/symbiote-node/engine/packs/transform/anchor-match.handler.js +0 -494
- package/vendor/symbiote-node/engine/packs/transform/effects-skeleton.handler.js +0 -417
- package/vendor/symbiote-node/engine/packs/transform/json-parse.handler.js +0 -43
- package/vendor/symbiote-node/engine/packs/transform/lipsync-select.handler.js +0 -339
- package/vendor/symbiote-node/engine/packs/transform/riopla-adapt.handler.js +0 -432
- package/vendor/symbiote-node/engine/packs/transform/set.handler.js +0 -57
- package/vendor/symbiote-node/engine/packs/transform/template-builder.handler.js +0 -134
- package/vendor/symbiote-node/engine/packs/transform/template.handler.js +0 -79
- package/vendor/symbiote-node/engine/packs/transform/timeline-build.handler.js +0 -399
- package/vendor/symbiote-node/engine/packs/util/delay.handler.js +0 -39
- package/vendor/symbiote-node/engine/packs/util/log.handler.js +0 -44
- package/vendor/symbiote-node/engine/packs/video-pack.js +0 -323
- package/vendor/symbiote-node/index.js +0 -103
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.css.js +0 -361
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.js +0 -332
- package/vendor/symbiote-node/inspector/InspectorPanel/InspectorPanel.tpl.js +0 -96
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.css.js +0 -104
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.js +0 -133
- package/vendor/symbiote-node/inspector/TemplatePreview/TemplatePreview.tpl.js +0 -33
- package/vendor/symbiote-node/interactions/ConnectFlow.js +0 -307
- package/vendor/symbiote-node/interactions/Drag.js +0 -102
- package/vendor/symbiote-node/interactions/Selector.js +0 -132
- package/vendor/symbiote-node/interactions/SnapGrid.js +0 -65
- package/vendor/symbiote-node/interactions/Zoom.js +0 -140
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.css.js +0 -88
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.js +0 -254
- package/vendor/symbiote-node/layout/ActionZone/ActionZone.tpl.js +0 -11
- package/vendor/symbiote-node/layout/Layout/Layout.css.js +0 -88
- package/vendor/symbiote-node/layout/Layout/Layout.js +0 -622
- package/vendor/symbiote-node/layout/Layout/Layout.tpl.js +0 -25
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.css.js +0 -293
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.js +0 -467
- package/vendor/symbiote-node/layout/LayoutNode/LayoutNode.tpl.js +0 -33
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.css.js +0 -46
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.js +0 -102
- package/vendor/symbiote-node/layout/LayoutPreview/LayoutPreview.tpl.js +0 -6
- package/vendor/symbiote-node/layout/LayoutRouter/LayoutRouter.js +0 -156
- package/vendor/symbiote-node/layout/LayoutRouter/routerSync.js +0 -250
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.css.js +0 -379
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.js +0 -263
- package/vendor/symbiote-node/layout/LayoutSidebar/LayoutSidebar.tpl.js +0 -20
- package/vendor/symbiote-node/layout/LayoutSidebar/SidebarSection.js +0 -183
- package/vendor/symbiote-node/layout/LayoutTree.js +0 -246
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.css.js +0 -43
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.js +0 -89
- package/vendor/symbiote-node/layout/PanelMenu/PanelMenu.tpl.js +0 -14
- package/vendor/symbiote-node/layout/index.js +0 -16
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.css.js +0 -61
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.js +0 -79
- package/vendor/symbiote-node/menu/ContextMenu/ContextMenu.tpl.js +0 -19
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.css.js +0 -41
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.js +0 -24
- package/vendor/symbiote-node/node/CtrlItem/CtrlItem.tpl.js +0 -16
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.css.js +0 -65
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.js +0 -29
- package/vendor/symbiote-node/node/GraphFrame/GraphFrame.tpl.js +0 -13
- package/vendor/symbiote-node/node/GraphNode/GraphNode.css.js +0 -683
- package/vendor/symbiote-node/node/GraphNode/GraphNode.js +0 -92
- package/vendor/symbiote-node/node/GraphNode/GraphNode.tpl.js +0 -17
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.js +0 -25
- package/vendor/symbiote-node/node/NodeSocket/NodeSocket.tpl.js +0 -7
- package/vendor/symbiote-node/node/PortItem/PortItem.css.js +0 -90
- package/vendor/symbiote-node/node/PortItem/PortItem.js +0 -87
- package/vendor/symbiote-node/node/PortItem/PortItem.tpl.js +0 -10
- package/vendor/symbiote-node/package.json +0 -59
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.css.js +0 -143
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.js +0 -131
- package/vendor/symbiote-node/palette/PaletteBrowser/PaletteBrowser.tpl.js +0 -16
- package/vendor/symbiote-node/plugins/History.js +0 -384
- package/vendor/symbiote-node/plugins/Readonly.js +0 -59
- package/vendor/symbiote-node/shapes/CircleShape.js +0 -80
- package/vendor/symbiote-node/shapes/CommentShape.js +0 -35
- package/vendor/symbiote-node/shapes/DiamondShape.js +0 -115
- package/vendor/symbiote-node/shapes/NodeShape.js +0 -80
- package/vendor/symbiote-node/shapes/PillShape.js +0 -91
- package/vendor/symbiote-node/shapes/RectShape.js +0 -72
- package/vendor/symbiote-node/shapes/SVGShape.js +0 -494
- package/vendor/symbiote-node/shapes/index.js +0 -53
- package/vendor/symbiote-node/themes/Palette.js +0 -32
- package/vendor/symbiote-node/themes/Skin.js +0 -113
- package/vendor/symbiote-node/themes/Theme.js +0 -84
- package/vendor/symbiote-node/themes/carbon.js +0 -137
- package/vendor/symbiote-node/themes/dark.js +0 -137
- package/vendor/symbiote-node/themes/ebook.js +0 -138
- package/vendor/symbiote-node/themes/grey.js +0 -137
- package/vendor/symbiote-node/themes/light.js +0 -137
- package/vendor/symbiote-node/themes/neon.js +0 -138
- package/vendor/symbiote-node/themes/pcb.js +0 -273
- package/vendor/symbiote-node/themes/synthwave.js +0 -137
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.css.js +0 -86
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.js +0 -128
- package/vendor/symbiote-node/toolbar/QuickToolbar/QuickToolbar.tpl.js +0 -29
|
@@ -1,353 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GraphServer.js - WebSocket + HTTP server for symbiote-node *
|
|
3
|
-
* Provides real-time graph synchronization between server and UI clients.
|
|
4
|
-
* Supports file-based workflow watching, handler hot-reload, and server-side execution.
|
|
5
|
-
*
|
|
6
|
-
* Protocol messages follow SPEC.md P23 Agent Bridge specification.
|
|
7
|
-
*
|
|
8
|
-
* @module symbiote-node/GraphServer */
|
|
9
|
-
|
|
10
|
-
import { createServer as createHttpServer } from 'node:http';
|
|
11
|
-
import { readFile, writeFile, watch as fsWatch } from 'node:fs/promises';
|
|
12
|
-
import { resolve, extname } from 'node:path';
|
|
13
|
-
import { WebSocketServer } from 'ws';
|
|
14
|
-
|
|
15
|
-
import { Graph } from './Graph.js';
|
|
16
|
-
import { Executor } from './Executor.js';
|
|
17
|
-
import { getNodeType, listDrivers } from './Registry.js';
|
|
18
|
-
import { loadHandlers, watchHandlers } from './HandlerLoader.js';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @typedef {object} ServerOptions
|
|
22
|
-
* @property {number} [port=3100] - HTTP/WebSocket port
|
|
23
|
-
* @property {string} [handlersDir] - Directory for .handler.js files
|
|
24
|
-
* @property {string} [workflowFile] - Path to .workflow.json
|
|
25
|
-
* @property {boolean} [watchFiles=true] - Enable file watching
|
|
26
|
-
* @property {boolean} [verbose=false] - Verbose logging
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Create an symbiote-node server instance * @param {ServerOptions} options
|
|
31
|
-
* @returns {Promise<{server: import('http').Server, wss: WebSocketServer, graph: Graph, close: () => Promise<void>}>}
|
|
32
|
-
*/
|
|
33
|
-
export async function createServer(options = {}) {
|
|
34
|
-
const {
|
|
35
|
-
port = 3100,
|
|
36
|
-
handlersDir,
|
|
37
|
-
workflowFile,
|
|
38
|
-
watchFiles = true,
|
|
39
|
-
verbose = false,
|
|
40
|
-
} = options;
|
|
41
|
-
|
|
42
|
-
let graph = new Graph();
|
|
43
|
-
const executor = new Executor();
|
|
44
|
-
const watchers = [];
|
|
45
|
-
const log = verbose ? console.log.bind(console) : () => { };
|
|
46
|
-
|
|
47
|
-
// Load initial workflow
|
|
48
|
-
if (workflowFile) {
|
|
49
|
-
try {
|
|
50
|
-
const json = await readFile(resolve(workflowFile), 'utf-8');
|
|
51
|
-
const data = JSON.parse(json);
|
|
52
|
-
graph = deserialize(data);
|
|
53
|
-
log(`📄 Loaded workflow: ${workflowFile} (${graph.nodes.size} nodes)`);
|
|
54
|
-
} catch (err) {
|
|
55
|
-
log(`⚠️ Could not load workflow: ${err.message}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Load handler files
|
|
60
|
-
if (handlersDir) {
|
|
61
|
-
const dir = resolve(handlersDir);
|
|
62
|
-
const registered = await loadHandlers(dir);
|
|
63
|
-
log(`🔧 Loaded ${registered.length} handler(s) from ${handlersDir}`);
|
|
64
|
-
|
|
65
|
-
if (watchFiles) {
|
|
66
|
-
const stopWatch = watchHandlers(dir, (type) => {
|
|
67
|
-
log(`♻️ Handler reloaded: ${type}`);
|
|
68
|
-
broadcast({ type: 'registry:add', payload: { type, category: type.split('/')[0] } });
|
|
69
|
-
});
|
|
70
|
-
watchers.push(stopWatch);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// ─── HTTP Server ────────────────────────────────────
|
|
75
|
-
|
|
76
|
-
const httpServer = createHttpServer(async (req, res) => {
|
|
77
|
-
const url = new URL(req.url, `http://localhost:${port}`);
|
|
78
|
-
|
|
79
|
-
// CORS headers
|
|
80
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
81
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
82
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
83
|
-
|
|
84
|
-
if (req.method === 'OPTIONS') {
|
|
85
|
-
res.writeHead(200);
|
|
86
|
-
res.end();
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
if (url.pathname === '/api/graph' && req.method === 'GET') {
|
|
92
|
-
const data = graph.toJSON();
|
|
93
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
94
|
-
res.end(JSON.stringify(data));
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (url.pathname === '/api/graph' && req.method === 'POST') {
|
|
99
|
-
const body = await readBody(req);
|
|
100
|
-
const data = JSON.parse(body);
|
|
101
|
-
graph = new Graph();
|
|
102
|
-
graph.fromJSON(data);
|
|
103
|
-
broadcast({ type: 'graph:update', payload: graph.toJSON() });
|
|
104
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
105
|
-
res.end(JSON.stringify({ ok: true }));
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (url.pathname === '/api/graph/execute' && req.method === 'POST') {
|
|
110
|
-
await executeGraph(res);
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (url.pathname === '/api/registry' && req.method === 'GET') {
|
|
115
|
-
const drivers = listDrivers();
|
|
116
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
117
|
-
res.end(JSON.stringify(drivers));
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Health check
|
|
122
|
-
if (url.pathname === '/api/health') {
|
|
123
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
124
|
-
res.end(JSON.stringify({ status: 'ok', nodes: graph.nodes.size }));
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
res.writeHead(404);
|
|
129
|
-
res.end('Not Found');
|
|
130
|
-
} catch (err) {
|
|
131
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
132
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
// ─── WebSocket Server ────────────────────────────────
|
|
137
|
-
|
|
138
|
-
const wss = new WebSocketServer({ server: httpServer });
|
|
139
|
-
/** @type {Set<import('ws').WebSocket>} */
|
|
140
|
-
const clients = new Set();
|
|
141
|
-
|
|
142
|
-
wss.on('connection', (ws) => {
|
|
143
|
-
clients.add(ws);
|
|
144
|
-
log(`🔌 Client connected (${clients.size} total)`);
|
|
145
|
-
|
|
146
|
-
// Send current state on connect
|
|
147
|
-
ws.send(JSON.stringify({ type: 'graph:update', payload: graph.toJSON() }));
|
|
148
|
-
|
|
149
|
-
ws.on('message', async (data) => {
|
|
150
|
-
try {
|
|
151
|
-
const msg = JSON.parse(data.toString());
|
|
152
|
-
await handleWsMessage(msg, ws);
|
|
153
|
-
} catch (err) {
|
|
154
|
-
ws.send(JSON.stringify({ type: 'error', payload: { message: err.message } }));
|
|
155
|
-
}
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
ws.on('close', () => {
|
|
159
|
-
clients.delete(ws);
|
|
160
|
-
log(`🔌 Client disconnected (${clients.size} total)`);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Broadcast message to all connected clients
|
|
166
|
-
* @param {object} msg
|
|
167
|
-
* @param {import('ws').WebSocket} [exclude] - Client to exclude
|
|
168
|
-
*/
|
|
169
|
-
function broadcast(msg, exclude) {
|
|
170
|
-
const json = JSON.stringify(msg);
|
|
171
|
-
for (const client of clients) {
|
|
172
|
-
if (client !== exclude && client.readyState === 1) {
|
|
173
|
-
client.send(json);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Handle incoming WebSocket message
|
|
180
|
-
* @param {{type: string, payload: object}} msg
|
|
181
|
-
* @param {import('ws').WebSocket} ws
|
|
182
|
-
*/
|
|
183
|
-
async function handleWsMessage(msg, ws) {
|
|
184
|
-
const { type, payload } = msg;
|
|
185
|
-
|
|
186
|
-
switch (type) {
|
|
187
|
-
case 'graph:action': {
|
|
188
|
-
const { action, nodeId, data } = payload;
|
|
189
|
-
|
|
190
|
-
switch (action) {
|
|
191
|
-
case 'addNode': {
|
|
192
|
-
const id = graph.addNode(data.type, data.params, data.options);
|
|
193
|
-
broadcast({ type: 'graph:update', payload: graph.toJSON() }, ws);
|
|
194
|
-
ws.send(JSON.stringify({ type: 'graph:actionResult', payload: { action, nodeId: id } }));
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
case 'removeNode': {
|
|
198
|
-
graph.removeNode(nodeId);
|
|
199
|
-
broadcast({ type: 'graph:update', payload: graph.toJSON() }, ws);
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
case 'connect': {
|
|
203
|
-
const { from, out, to, in: inp } = data;
|
|
204
|
-
graph.connect(from, out, to, inp);
|
|
205
|
-
broadcast({ type: 'graph:update', payload: graph.toJSON() }, ws);
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
208
|
-
case 'updateParams': {
|
|
209
|
-
graph.updateParams(nodeId, data.params);
|
|
210
|
-
broadcast({ type: 'graph:update', payload: graph.toJSON() }, ws);
|
|
211
|
-
break;
|
|
212
|
-
}
|
|
213
|
-
case 'execute': {
|
|
214
|
-
await executeAndStream();
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
default:
|
|
218
|
-
ws.send(JSON.stringify({ type: 'error', payload: { message: `Unknown action: ${action}` } }));
|
|
219
|
-
}
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Agent UI commands — forward to all other clients
|
|
224
|
-
case 'ui:layout':
|
|
225
|
-
case 'ui:focus':
|
|
226
|
-
case 'ui:select':
|
|
227
|
-
case 'ui:navigate':
|
|
228
|
-
case 'ui:playback':
|
|
229
|
-
case 'ui:notify':
|
|
230
|
-
case 'ui:cursor':
|
|
231
|
-
broadcast(msg, ws);
|
|
232
|
-
break;
|
|
233
|
-
|
|
234
|
-
default:
|
|
235
|
-
ws.send(JSON.stringify({ type: 'error', payload: { message: `Unknown message type: ${type}` } }));
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Execute graph and stream progress via WebSocket
|
|
241
|
-
*/
|
|
242
|
-
async function executeAndStream() {
|
|
243
|
-
const result = await executor.run(graph, {
|
|
244
|
-
onNodeStart: (nodeId) => {
|
|
245
|
-
broadcast({ type: 'node:progress', payload: { nodeId, progress: 0, phase: 'start' } });
|
|
246
|
-
},
|
|
247
|
-
onNodeComplete: (nodeId, output, timeMs) => {
|
|
248
|
-
const cached = !!(output && output._fromCache);
|
|
249
|
-
broadcast({ type: 'node:result', payload: { nodeId, status: 'done', cached, timeMs } });
|
|
250
|
-
},
|
|
251
|
-
onNodeSkipped: (nodeId) => {
|
|
252
|
-
broadcast({ type: 'node:result', payload: { nodeId, status: 'skipped' } });
|
|
253
|
-
},
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
broadcast({ type: 'graph:executed', payload: { totalTime: result.totalTime, log: result.log } });
|
|
257
|
-
|
|
258
|
-
// Save to workflow file if configured
|
|
259
|
-
if (workflowFile) {
|
|
260
|
-
try {
|
|
261
|
-
await writeFile(resolve(workflowFile), JSON.stringify(graph.toJSON(), null, 2));
|
|
262
|
-
} catch (err) {
|
|
263
|
-
log(`⚠️ Could not save workflow: ${err.message}`);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return result;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Execute graph via HTTP and return result
|
|
272
|
-
* @param {import('http').ServerResponse} res
|
|
273
|
-
*/
|
|
274
|
-
async function executeGraph(res) {
|
|
275
|
-
try {
|
|
276
|
-
const result = await executeAndStream();
|
|
277
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
278
|
-
res.end(JSON.stringify({
|
|
279
|
-
ok: true,
|
|
280
|
-
totalTime: result.totalTime,
|
|
281
|
-
outputs: result.outputs,
|
|
282
|
-
log: result.log,
|
|
283
|
-
}));
|
|
284
|
-
} catch (err) {
|
|
285
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
286
|
-
res.end(JSON.stringify({ error: err.message }));
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// ─── File Watching ────────────────────────────────
|
|
291
|
-
|
|
292
|
-
if (watchFiles && workflowFile) {
|
|
293
|
-
const wfPath = resolve(workflowFile);
|
|
294
|
-
let debounce = null;
|
|
295
|
-
const ac = new AbortController();
|
|
296
|
-
|
|
297
|
-
(async () => {
|
|
298
|
-
try {
|
|
299
|
-
const watcher = fsWatch(wfPath, { signal: ac.signal });
|
|
300
|
-
for await (const event of watcher) {
|
|
301
|
-
if (debounce) clearTimeout(debounce);
|
|
302
|
-
debounce = setTimeout(async () => {
|
|
303
|
-
try {
|
|
304
|
-
const json = await readFile(wfPath, 'utf-8');
|
|
305
|
-
const data = JSON.parse(json);
|
|
306
|
-
graph = deserialize(data);
|
|
307
|
-
broadcast({ type: 'graph:update', payload: data });
|
|
308
|
-
log(`📄 Workflow reloaded: ${workflowFile}`);
|
|
309
|
-
} catch (err) {
|
|
310
|
-
log(`⚠️ Workflow reload error: ${err.message}`);
|
|
311
|
-
}
|
|
312
|
-
}, 200);
|
|
313
|
-
}
|
|
314
|
-
} catch (err) {
|
|
315
|
-
if (err.name !== 'AbortError') log(`⚠️ Workflow watch error: ${err.message}`);
|
|
316
|
-
}
|
|
317
|
-
})();
|
|
318
|
-
|
|
319
|
-
watchers.push(() => ac.abort());
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// ─── Start & Close ────────────────────────────────
|
|
323
|
-
|
|
324
|
-
await new Promise((resolve) => httpServer.listen(port, resolve));
|
|
325
|
-
log(`🚀 symbiote-node server on http://localhost:${port}`);
|
|
326
|
-
async function close() {
|
|
327
|
-
for (const stop of watchers) {
|
|
328
|
-
if (typeof stop === 'function') stop();
|
|
329
|
-
}
|
|
330
|
-
for (const client of clients) {
|
|
331
|
-
client.close();
|
|
332
|
-
}
|
|
333
|
-
wss.close();
|
|
334
|
-
await new Promise((resolve) => httpServer.close(resolve));
|
|
335
|
-
log('🛑 Server stopped');
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
return { server: httpServer, wss, graph, executor, broadcast, close };
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* Read HTTP request body
|
|
343
|
-
* @param {import('http').IncomingMessage} req
|
|
344
|
-
* @returns {Promise<string>}
|
|
345
|
-
*/
|
|
346
|
-
function readBody(req) {
|
|
347
|
-
return new Promise((resolve, reject) => {
|
|
348
|
-
let body = '';
|
|
349
|
-
req.on('data', (chunk) => { body += chunk; });
|
|
350
|
-
req.on('end', () => resolve(body));
|
|
351
|
-
req.on('error', reject);
|
|
352
|
-
});
|
|
353
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HandlerLoader.js - File-based node handler loader
|
|
3
|
-
*
|
|
4
|
-
* Scans directories for .handler.js files and registers them
|
|
5
|
-
* as node types. Supports hot reload via fs.watch.
|
|
6
|
-
*
|
|
7
|
-
* Handler file convention:
|
|
8
|
-
* export default {
|
|
9
|
-
* type: 'category/name',
|
|
10
|
-
* category: 'category',
|
|
11
|
-
* icon: 'icon_name',
|
|
12
|
-
* driver: { inputs, outputs, params, ... },
|
|
13
|
-
* lifecycle: { validate, cacheKey, execute, postProcess },
|
|
14
|
-
* };
|
|
15
|
-
*
|
|
16
|
-
* @module symbiote-node/HandlerLoader */
|
|
17
|
-
|
|
18
|
-
import { readdir, stat } from 'node:fs/promises';
|
|
19
|
-
import { join, relative, extname } from 'node:path';
|
|
20
|
-
import { watch } from 'node:fs';
|
|
21
|
-
import { pathToFileURL } from 'node:url';
|
|
22
|
-
import { registerNodeType } from './Registry.js';
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Recursively find all .handler.js files in a directory
|
|
26
|
-
* @param {string} dir - Directory to scan
|
|
27
|
-
* @returns {Promise<string[]>} Absolute file paths
|
|
28
|
-
*/
|
|
29
|
-
async function findHandlerFiles(dir) {
|
|
30
|
-
const results = [];
|
|
31
|
-
let entries;
|
|
32
|
-
try {
|
|
33
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
34
|
-
} catch {
|
|
35
|
-
return results;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
for (const entry of entries) {
|
|
39
|
-
const fullPath = join(dir, entry.name);
|
|
40
|
-
if (entry.isDirectory()) {
|
|
41
|
-
const nested = await findHandlerFiles(fullPath);
|
|
42
|
-
results.push(...nested);
|
|
43
|
-
} else if (entry.name.endsWith('.handler.js')) {
|
|
44
|
-
results.push(fullPath);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return results;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Load a single handler file and register it
|
|
52
|
-
* @param {string} filePath - Absolute path to .handler.js file
|
|
53
|
-
* @returns {Promise<string|null>} Registered type name or null on error
|
|
54
|
-
*/
|
|
55
|
-
async function loadHandler(filePath) {
|
|
56
|
-
const fileUrl = pathToFileURL(filePath).href;
|
|
57
|
-
// Cache-bust for hot reload
|
|
58
|
-
const url = `${fileUrl}?t=${Date.now()}`;
|
|
59
|
-
|
|
60
|
-
const module = await import(url);
|
|
61
|
-
const handler = module.default;
|
|
62
|
-
|
|
63
|
-
if (!handler?.type) {
|
|
64
|
-
throw new Error(`Handler file ${filePath} missing 'type' field in default export`);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Build node type definition from handler
|
|
68
|
-
const nodeDef = {
|
|
69
|
-
type: handler.type,
|
|
70
|
-
category: handler.category || handler.type.split('/')[0],
|
|
71
|
-
icon: handler.icon,
|
|
72
|
-
driver: handler.driver || {},
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// Attach lifecycle hooks if present
|
|
76
|
-
if (handler.lifecycle) {
|
|
77
|
-
nodeDef.lifecycle = handler.lifecycle;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Attach process function if present (legacy mode)
|
|
81
|
-
if (handler.process) {
|
|
82
|
-
nodeDef.process = handler.process;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
registerNodeType(nodeDef);
|
|
86
|
-
return handler.type;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Scan a directory for .handler.js files and register them
|
|
91
|
-
* @param {string} dir - Directory to scan (e.g., 'nodes/')
|
|
92
|
-
* @returns {Promise<string[]>} List of registered type names
|
|
93
|
-
*/
|
|
94
|
-
export async function loadHandlers(dir) {
|
|
95
|
-
const files = await findHandlerFiles(dir);
|
|
96
|
-
const registered = [];
|
|
97
|
-
|
|
98
|
-
for (const file of files) {
|
|
99
|
-
try {
|
|
100
|
-
const type = await loadHandler(file);
|
|
101
|
-
if (type) registered.push(type);
|
|
102
|
-
} catch (err) {
|
|
103
|
-
console.error(`[symbiote-node] Failed to load handler ${relative(dir, file)}: ${err.message}`); }
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return registered;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Watch a directory for new/changed .handler.js files
|
|
111
|
-
* Auto-registers them on change.
|
|
112
|
-
*
|
|
113
|
-
* @param {string} dir - Directory to watch
|
|
114
|
-
* @param {object} [options={}]
|
|
115
|
-
* @param {function} [options.onRegister] - Callback(type, filePath)
|
|
116
|
-
* @param {function} [options.onError] - Callback(filePath, error)
|
|
117
|
-
* @returns {{close: function}} Watcher handle
|
|
118
|
-
*/
|
|
119
|
-
export function watchHandlers(dir, options = {}) {
|
|
120
|
-
const { onRegister, onError } = options;
|
|
121
|
-
|
|
122
|
-
const watcher = watch(dir, { recursive: true }, async (eventType, filename) => {
|
|
123
|
-
if (!filename?.endsWith('.handler.js')) return;
|
|
124
|
-
|
|
125
|
-
const filePath = join(dir, filename);
|
|
126
|
-
|
|
127
|
-
// Verify file exists (could be a delete event)
|
|
128
|
-
try {
|
|
129
|
-
await stat(filePath);
|
|
130
|
-
} catch {
|
|
131
|
-
return; // File deleted, ignore
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
const type = await loadHandler(filePath);
|
|
136
|
-
if (type && onRegister) onRegister(type, filePath);
|
|
137
|
-
} catch (err) {
|
|
138
|
-
if (onError) onError(filePath, err);
|
|
139
|
-
else console.error(`[symbiote-node] Watch error for ${filename}: ${err.message}`); }
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
return {
|
|
143
|
-
close: () => watcher.close(),
|
|
144
|
-
};
|
|
145
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* History.js - Snapshot-based undo/redo for graphs
|
|
3
|
-
*
|
|
4
|
-
* Stores deep-cloned snapshots of nodes and connections.
|
|
5
|
-
* Framework-agnostic — works with any graph data.
|
|
6
|
-
*
|
|
7
|
-
* @module agi-graph/History
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const MAX_HISTORY = 50;
|
|
11
|
-
|
|
12
|
-
export class History {
|
|
13
|
-
|
|
14
|
-
/** @type {Array<{nodes: object[], connections: object[]}>} */
|
|
15
|
-
_states = [];
|
|
16
|
-
|
|
17
|
-
/** @type {number} */
|
|
18
|
-
_index = -1;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Push a new state snapshot
|
|
22
|
-
* @param {object[]} nodes
|
|
23
|
-
* @param {object[]} connections
|
|
24
|
-
*/
|
|
25
|
-
push(nodes, connections) {
|
|
26
|
-
this._states.length = this._index + 1;
|
|
27
|
-
this._states.push({
|
|
28
|
-
nodes: JSON.parse(JSON.stringify(nodes)),
|
|
29
|
-
connections: JSON.parse(JSON.stringify(connections)),
|
|
30
|
-
});
|
|
31
|
-
if (this._states.length > MAX_HISTORY) {
|
|
32
|
-
this._states.shift();
|
|
33
|
-
}
|
|
34
|
-
this._index = this._states.length - 1;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Undo — return previous state
|
|
39
|
-
* @returns {{nodes: object[], connections: object[]}|null}
|
|
40
|
-
*/
|
|
41
|
-
undo() {
|
|
42
|
-
if (!this.canUndo) return null;
|
|
43
|
-
this._index--;
|
|
44
|
-
return this._clone(this._states[this._index]);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Redo — return next state
|
|
49
|
-
* @returns {{nodes: object[], connections: object[]}|null}
|
|
50
|
-
*/
|
|
51
|
-
redo() {
|
|
52
|
-
if (!this.canRedo) return null;
|
|
53
|
-
this._index++;
|
|
54
|
-
return this._clone(this._states[this._index]);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** @returns {boolean} */
|
|
58
|
-
get canUndo() { return this._index > 0; }
|
|
59
|
-
|
|
60
|
-
/** @returns {boolean} */
|
|
61
|
-
get canRedo() { return this._index < this._states.length - 1; }
|
|
62
|
-
|
|
63
|
-
/** @returns {number} */
|
|
64
|
-
get depth() { return this._states.length; }
|
|
65
|
-
|
|
66
|
-
/** @returns {number} */
|
|
67
|
-
get index() { return this._index; }
|
|
68
|
-
|
|
69
|
-
/** Clear all history */
|
|
70
|
-
clear() {
|
|
71
|
-
this._states = [];
|
|
72
|
-
this._index = -1;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* @param {{nodes: object[], connections: object[]}} state
|
|
77
|
-
* @returns {{nodes: object[], connections: object[]}}
|
|
78
|
-
* @private
|
|
79
|
-
*/
|
|
80
|
-
_clone(state) {
|
|
81
|
-
return JSON.parse(JSON.stringify(state));
|
|
82
|
-
}
|
|
83
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lifecycle.js - Node lifecycle pipeline
|
|
3
|
-
*
|
|
4
|
-
* Every node can define lifecycle hooks: validate, cacheKey, execute, postProcess.
|
|
5
|
-
* The lifecycle runner orchestrates these steps with cache awareness.
|
|
6
|
-
*
|
|
7
|
-
* @module agi-graph/Lifecycle
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {object} LifecycleHooks
|
|
12
|
-
* @property {function} [validate] - (inputs) => boolean — return false to abort
|
|
13
|
-
* @property {function} [cacheKey] - (inputs, params) => string — custom cache key
|
|
14
|
-
* @property {function} [execute] - (inputs, params) => outputs — main processing (async)
|
|
15
|
-
* @property {function} [postProcess] - (outputs) => outputs — transform before output
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @typedef {object} CacheState
|
|
20
|
-
* @property {'auto'|'freeze'|'force'} mode - Cache behavior mode
|
|
21
|
-
* @property {Map<string, {key: string, outputs: object}>} store - Cache storage
|
|
22
|
-
* @property {string} nodeId - Current node ID
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @typedef {object} LifecycleResult
|
|
27
|
-
* @property {object} outputs - Final outputs from the node
|
|
28
|
-
* @property {boolean} cached - Whether result came from cache
|
|
29
|
-
* @property {string|null} error - Error message if validation failed
|
|
30
|
-
* @property {string|null} cacheHash - Cache key used (for UI display)
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Default cache key: JSON hash of inputs + params
|
|
35
|
-
* @param {object} inputs
|
|
36
|
-
* @param {object} params
|
|
37
|
-
* @returns {string}
|
|
38
|
-
*/
|
|
39
|
-
function defaultCacheKey(inputs, params) {
|
|
40
|
-
return JSON.stringify({ i: inputs, p: params });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Run lifecycle pipeline for a node
|
|
45
|
-
*
|
|
46
|
-
* Flow:
|
|
47
|
-
* 1. validate(inputs) → false = abort with error
|
|
48
|
-
* 2. cacheKey(inputs, params) → compute hash
|
|
49
|
-
* 3. Check mode: freeze → return cached; force → skip; auto → check hash
|
|
50
|
-
* 4. execute(inputs, params) → outputs
|
|
51
|
-
* 5. postProcess(outputs) → final outputs
|
|
52
|
-
* 6. Store in cache
|
|
53
|
-
*
|
|
54
|
-
* @param {LifecycleHooks} hooks - Lifecycle hooks from driver
|
|
55
|
-
* @param {object} inputs - Resolved inputs from upstream
|
|
56
|
-
* @param {object} params - Node parameters
|
|
57
|
-
* @param {CacheState} cacheState - Cache state for this node
|
|
58
|
-
* @returns {Promise<LifecycleResult>}
|
|
59
|
-
*/
|
|
60
|
-
export async function runLifecycle(hooks, inputs, params, cacheState) {
|
|
61
|
-
const { mode = 'auto', store, nodeId } = cacheState;
|
|
62
|
-
|
|
63
|
-
// Step 1: Validate
|
|
64
|
-
if (hooks.validate) {
|
|
65
|
-
try {
|
|
66
|
-
const valid = hooks.validate(inputs);
|
|
67
|
-
if (valid === false) {
|
|
68
|
-
return { outputs: null, cached: false, error: 'Validation failed', cacheHash: null };
|
|
69
|
-
}
|
|
70
|
-
} catch (err) {
|
|
71
|
-
return { outputs: null, cached: false, error: `Validation error: ${err.message}`, cacheHash: null };
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Step 2: Compute cache key
|
|
76
|
-
const cacheKeyFn = hooks.cacheKey || defaultCacheKey;
|
|
77
|
-
const cacheHash = cacheKeyFn(inputs, params);
|
|
78
|
-
|
|
79
|
-
// Step 3: Check cache based on mode
|
|
80
|
-
const cached = store.get(nodeId);
|
|
81
|
-
|
|
82
|
-
if (mode === 'freeze' && cached) {
|
|
83
|
-
return { outputs: cached.outputs, cached: true, error: null, cacheHash: cached.key };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (mode === 'auto' && cached && cached.key === cacheHash) {
|
|
87
|
-
return { outputs: cached.outputs, cached: true, error: null, cacheHash };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// mode === 'force' → skip cache check entirely
|
|
91
|
-
|
|
92
|
-
// Step 4: Execute
|
|
93
|
-
const executeFn = hooks.execute;
|
|
94
|
-
if (!executeFn) {
|
|
95
|
-
return { outputs: null, cached: false, error: 'No execute hook defined', cacheHash };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
let outputs;
|
|
99
|
-
try {
|
|
100
|
-
outputs = await executeFn(inputs, params);
|
|
101
|
-
} catch (err) {
|
|
102
|
-
return { outputs: null, cached: false, error: `Execution error: ${err.message}`, cacheHash };
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Step 5: PostProcess
|
|
106
|
-
if (hooks.postProcess) {
|
|
107
|
-
try {
|
|
108
|
-
outputs = hooks.postProcess(outputs);
|
|
109
|
-
} catch (err) {
|
|
110
|
-
return { outputs: null, cached: false, error: `PostProcess error: ${err.message}`, cacheHash };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Step 6: Store in cache
|
|
115
|
-
store.set(nodeId, { key: cacheHash, outputs });
|
|
116
|
-
|
|
117
|
-
return { outputs, cached: false, error: null, cacheHash };
|
|
118
|
-
}
|