@adverant-nexus/cli 2.0.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/ARCHITECTURE.md +1366 -0
- package/LICENSE +21 -0
- package/README.md +593 -0
- package/completions/_nexus +452 -0
- package/completions/nexus.bash +329 -0
- package/completions/nexus.fish +187 -0
- package/dist/cli.d.ts +35 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +135 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agent/index.d.ts +9 -0
- package/dist/commands/agent/index.d.ts.map +1 -0
- package/dist/commands/agent/index.js +20 -0
- package/dist/commands/agent/index.js.map +1 -0
- package/dist/commands/agent/list.d.ts +9 -0
- package/dist/commands/agent/list.d.ts.map +1 -0
- package/dist/commands/agent/list.js +122 -0
- package/dist/commands/agent/list.js.map +1 -0
- package/dist/commands/agent/run.d.ts +10 -0
- package/dist/commands/agent/run.d.ts.map +1 -0
- package/dist/commands/agent/run.js +167 -0
- package/dist/commands/agent/run.js.map +1 -0
- package/dist/commands/agent/status.d.ts +9 -0
- package/dist/commands/agent/status.d.ts.map +1 -0
- package/dist/commands/agent/status.js +157 -0
- package/dist/commands/agent/status.js.map +1 -0
- package/dist/commands/compute/agent.d.ts +21 -0
- package/dist/commands/compute/agent.d.ts.map +1 -0
- package/dist/commands/compute/agent.js +158 -0
- package/dist/commands/compute/agent.js.map +1 -0
- package/dist/commands/compute/index.d.ts +10 -0
- package/dist/commands/compute/index.d.ts.map +1 -0
- package/dist/commands/compute/index.js +27 -0
- package/dist/commands/compute/index.js.map +1 -0
- package/dist/commands/compute/lib/hardware-detection.d.ts +52 -0
- package/dist/commands/compute/lib/hardware-detection.d.ts.map +1 -0
- package/dist/commands/compute/lib/hardware-detection.js +432 -0
- package/dist/commands/compute/lib/hardware-detection.js.map +1 -0
- package/dist/commands/compute/lib/local-compute-agent.d.ts +143 -0
- package/dist/commands/compute/lib/local-compute-agent.d.ts.map +1 -0
- package/dist/commands/compute/lib/local-compute-agent.js +1162 -0
- package/dist/commands/compute/lib/local-compute-agent.js.map +1 -0
- package/dist/commands/compute/lib/local-compute-client.d.ts +89 -0
- package/dist/commands/compute/lib/local-compute-client.d.ts.map +1 -0
- package/dist/commands/compute/lib/local-compute-client.js +388 -0
- package/dist/commands/compute/lib/local-compute-client.js.map +1 -0
- package/dist/commands/compute/list.d.ts +14 -0
- package/dist/commands/compute/list.d.ts.map +1 -0
- package/dist/commands/compute/list.js +164 -0
- package/dist/commands/compute/list.js.map +1 -0
- package/dist/commands/compute/logs.d.ts +14 -0
- package/dist/commands/compute/logs.d.ts.map +1 -0
- package/dist/commands/compute/logs.js +105 -0
- package/dist/commands/compute/logs.js.map +1 -0
- package/dist/commands/compute/resources.d.ts +14 -0
- package/dist/commands/compute/resources.d.ts.map +1 -0
- package/dist/commands/compute/resources.js +163 -0
- package/dist/commands/compute/resources.js.map +1 -0
- package/dist/commands/compute/status.d.ts +13 -0
- package/dist/commands/compute/status.d.ts.map +1 -0
- package/dist/commands/compute/status.js +223 -0
- package/dist/commands/compute/status.js.map +1 -0
- package/dist/commands/compute/submit.d.ts +19 -0
- package/dist/commands/compute/submit.d.ts.map +1 -0
- package/dist/commands/compute/submit.js +99 -0
- package/dist/commands/compute/submit.js.map +1 -0
- package/dist/commands/deploy.d.ts +11 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +117 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/dynamic/graphrag-commands.d.ts +16 -0
- package/dist/commands/dynamic/graphrag-commands.d.ts.map +1 -0
- package/dist/commands/dynamic/graphrag-commands.js +562 -0
- package/dist/commands/dynamic/graphrag-commands.js.map +1 -0
- package/dist/commands/dynamic/index.d.ts +10 -0
- package/dist/commands/dynamic/index.d.ts.map +1 -0
- package/dist/commands/dynamic/index.js +17 -0
- package/dist/commands/dynamic/index.js.map +1 -0
- package/dist/commands/dynamic/mageagent-commands.d.ts +8 -0
- package/dist/commands/dynamic/mageagent-commands.d.ts.map +1 -0
- package/dist/commands/dynamic/mageagent-commands.js +86 -0
- package/dist/commands/dynamic/mageagent-commands.js.map +1 -0
- package/dist/commands/dynamic/mcp-tool-mapper.d.ts +97 -0
- package/dist/commands/dynamic/mcp-tool-mapper.d.ts.map +1 -0
- package/dist/commands/dynamic/mcp-tool-mapper.js +328 -0
- package/dist/commands/dynamic/mcp-tool-mapper.js.map +1 -0
- package/dist/commands/dynamic/nexus-commands.d.ts +78 -0
- package/dist/commands/dynamic/nexus-commands.d.ts.map +1 -0
- package/dist/commands/dynamic/nexus-commands.js +426 -0
- package/dist/commands/dynamic/nexus-commands.js.map +1 -0
- package/dist/commands/dynamic/sandbox-commands.d.ts +8 -0
- package/dist/commands/dynamic/sandbox-commands.d.ts.map +1 -0
- package/dist/commands/dynamic/sandbox-commands.js +80 -0
- package/dist/commands/dynamic/sandbox-commands.js.map +1 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +128 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +10 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +81 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +10 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +83 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logs.d.ts +11 -0
- package/dist/commands/logs.d.ts.map +1 -0
- package/dist/commands/logs.js +79 -0
- package/dist/commands/logs.js.map +1 -0
- package/dist/commands/nexus/index.d.ts +69 -0
- package/dist/commands/nexus/index.d.ts.map +1 -0
- package/dist/commands/nexus/index.js +336 -0
- package/dist/commands/nexus/index.js.map +1 -0
- package/dist/commands/plugin/disable.d.ts +8 -0
- package/dist/commands/plugin/disable.d.ts.map +1 -0
- package/dist/commands/plugin/disable.js +31 -0
- package/dist/commands/plugin/disable.js.map +1 -0
- package/dist/commands/plugin/enable.d.ts +8 -0
- package/dist/commands/plugin/enable.d.ts.map +1 -0
- package/dist/commands/plugin/enable.js +37 -0
- package/dist/commands/plugin/enable.js.map +1 -0
- package/dist/commands/plugin/index.d.ts +8 -0
- package/dist/commands/plugin/index.d.ts.map +1 -0
- package/dist/commands/plugin/index.js +23 -0
- package/dist/commands/plugin/index.js.map +1 -0
- package/dist/commands/plugin/info.d.ts +8 -0
- package/dist/commands/plugin/info.d.ts.map +1 -0
- package/dist/commands/plugin/info.js +129 -0
- package/dist/commands/plugin/info.js.map +1 -0
- package/dist/commands/plugin/init.d.ts +8 -0
- package/dist/commands/plugin/init.d.ts.map +1 -0
- package/dist/commands/plugin/init.js +83 -0
- package/dist/commands/plugin/init.js.map +1 -0
- package/dist/commands/plugin/install.d.ts +8 -0
- package/dist/commands/plugin/install.d.ts.map +1 -0
- package/dist/commands/plugin/install.js +56 -0
- package/dist/commands/plugin/install.js.map +1 -0
- package/dist/commands/plugin/list.d.ts +8 -0
- package/dist/commands/plugin/list.d.ts.map +1 -0
- package/dist/commands/plugin/list.js +72 -0
- package/dist/commands/plugin/list.js.map +1 -0
- package/dist/commands/plugin/uninstall.d.ts +8 -0
- package/dist/commands/plugin/uninstall.d.ts.map +1 -0
- package/dist/commands/plugin/uninstall.js +49 -0
- package/dist/commands/plugin/uninstall.js.map +1 -0
- package/dist/commands/register.d.ts +10 -0
- package/dist/commands/register.d.ts.map +1 -0
- package/dist/commands/register.js +85 -0
- package/dist/commands/register.js.map +1 -0
- package/dist/commands/services/health.d.ts +7 -0
- package/dist/commands/services/health.d.ts.map +1 -0
- package/dist/commands/services/health.js +73 -0
- package/dist/commands/services/health.js.map +1 -0
- package/dist/commands/services/index.d.ts +16 -0
- package/dist/commands/services/index.d.ts.map +1 -0
- package/dist/commands/services/index.js +26 -0
- package/dist/commands/services/index.js.map +1 -0
- package/dist/commands/services/info.d.ts +7 -0
- package/dist/commands/services/info.d.ts.map +1 -0
- package/dist/commands/services/info.js +68 -0
- package/dist/commands/services/info.js.map +1 -0
- package/dist/commands/services/list.d.ts +9 -0
- package/dist/commands/services/list.d.ts.map +1 -0
- package/dist/commands/services/list.js +43 -0
- package/dist/commands/services/list.js.map +1 -0
- package/dist/commands/services/logs.d.ts +7 -0
- package/dist/commands/services/logs.d.ts.map +1 -0
- package/dist/commands/services/logs.js +107 -0
- package/dist/commands/services/logs.js.map +1 -0
- package/dist/commands/services/ports.d.ts +7 -0
- package/dist/commands/services/ports.d.ts.map +1 -0
- package/dist/commands/services/ports.js +65 -0
- package/dist/commands/services/ports.js.map +1 -0
- package/dist/commands/services/restart.d.ts +7 -0
- package/dist/commands/services/restart.d.ts.map +1 -0
- package/dist/commands/services/restart.js +67 -0
- package/dist/commands/services/restart.js.map +1 -0
- package/dist/commands/services/start.d.ts +7 -0
- package/dist/commands/services/start.d.ts.map +1 -0
- package/dist/commands/services/start.js +67 -0
- package/dist/commands/services/start.js.map +1 -0
- package/dist/commands/services/status.d.ts +9 -0
- package/dist/commands/services/status.d.ts.map +1 -0
- package/dist/commands/services/status.js +119 -0
- package/dist/commands/services/status.js.map +1 -0
- package/dist/commands/services/stop.d.ts +7 -0
- package/dist/commands/services/stop.d.ts.map +1 -0
- package/dist/commands/services/stop.js +67 -0
- package/dist/commands/services/stop.js.map +1 -0
- package/dist/commands/session/delete.d.ts +9 -0
- package/dist/commands/session/delete.d.ts.map +1 -0
- package/dist/commands/session/delete.js +51 -0
- package/dist/commands/session/delete.js.map +1 -0
- package/dist/commands/session/export.d.ts +9 -0
- package/dist/commands/session/export.d.ts.map +1 -0
- package/dist/commands/session/export.js +36 -0
- package/dist/commands/session/export.js.map +1 -0
- package/dist/commands/session/import.d.ts +9 -0
- package/dist/commands/session/import.d.ts.map +1 -0
- package/dist/commands/session/import.js +54 -0
- package/dist/commands/session/import.js.map +1 -0
- package/dist/commands/session/index.d.ts +9 -0
- package/dist/commands/session/index.d.ts.map +1 -0
- package/dist/commands/session/index.js +28 -0
- package/dist/commands/session/index.js.map +1 -0
- package/dist/commands/session/list.d.ts +9 -0
- package/dist/commands/session/list.d.ts.map +1 -0
- package/dist/commands/session/list.js +88 -0
- package/dist/commands/session/list.js.map +1 -0
- package/dist/commands/session/load.d.ts +9 -0
- package/dist/commands/session/load.d.ts.map +1 -0
- package/dist/commands/session/load.js +71 -0
- package/dist/commands/session/load.js.map +1 -0
- package/dist/commands/session/resume.d.ts +9 -0
- package/dist/commands/session/resume.d.ts.map +1 -0
- package/dist/commands/session/resume.js +49 -0
- package/dist/commands/session/resume.js.map +1 -0
- package/dist/commands/session/save.d.ts +9 -0
- package/dist/commands/session/save.d.ts.map +1 -0
- package/dist/commands/session/save.js +69 -0
- package/dist/commands/session/save.js.map +1 -0
- package/dist/commands/workspace/git-commit.d.ts +9 -0
- package/dist/commands/workspace/git-commit.d.ts.map +1 -0
- package/dist/commands/workspace/git-commit.js +156 -0
- package/dist/commands/workspace/git-commit.js.map +1 -0
- package/dist/commands/workspace/git-status.d.ts +9 -0
- package/dist/commands/workspace/git-status.d.ts.map +1 -0
- package/dist/commands/workspace/git-status.js +192 -0
- package/dist/commands/workspace/git-status.js.map +1 -0
- package/dist/commands/workspace/index.d.ts +9 -0
- package/dist/commands/workspace/index.d.ts.map +1 -0
- package/dist/commands/workspace/index.js +24 -0
- package/dist/commands/workspace/index.js.map +1 -0
- package/dist/commands/workspace/info.d.ts +10 -0
- package/dist/commands/workspace/info.d.ts.map +1 -0
- package/dist/commands/workspace/info.js +191 -0
- package/dist/commands/workspace/info.js.map +1 -0
- package/dist/commands/workspace/init.d.ts +9 -0
- package/dist/commands/workspace/init.d.ts.map +1 -0
- package/dist/commands/workspace/init.js +193 -0
- package/dist/commands/workspace/init.js.map +1 -0
- package/dist/commands/workspace/validate.d.ts +9 -0
- package/dist/commands/workspace/validate.d.ts.map +1 -0
- package/dist/commands/workspace/validate.js +140 -0
- package/dist/commands/workspace/validate.js.map +1 -0
- package/dist/core/agent-client.d.ts +49 -0
- package/dist/core/agent-client.d.ts.map +1 -0
- package/dist/core/agent-client.js +289 -0
- package/dist/core/agent-client.js.map +1 -0
- package/dist/core/config/config-manager.d.ts +75 -0
- package/dist/core/config/config-manager.d.ts.map +1 -0
- package/dist/core/config/config-manager.js +379 -0
- package/dist/core/config/config-manager.js.map +1 -0
- package/dist/core/config/profile-manager.d.ts +76 -0
- package/dist/core/config/profile-manager.d.ts.map +1 -0
- package/dist/core/config/profile-manager.js +250 -0
- package/dist/core/config/profile-manager.js.map +1 -0
- package/dist/core/config/workspace-detector.d.ts +66 -0
- package/dist/core/config/workspace-detector.d.ts.map +1 -0
- package/dist/core/config/workspace-detector.js +291 -0
- package/dist/core/config/workspace-detector.js.map +1 -0
- package/dist/core/discovery/docker-parser.d.ts +27 -0
- package/dist/core/discovery/docker-parser.d.ts.map +1 -0
- package/dist/core/discovery/docker-parser.js +361 -0
- package/dist/core/discovery/docker-parser.js.map +1 -0
- package/dist/core/discovery/index.d.ts +11 -0
- package/dist/core/discovery/index.d.ts.map +1 -0
- package/dist/core/discovery/index.js +16 -0
- package/dist/core/discovery/index.js.map +1 -0
- package/dist/core/discovery/mcp-discovery.d.ts +41 -0
- package/dist/core/discovery/mcp-discovery.d.ts.map +1 -0
- package/dist/core/discovery/mcp-discovery.js +439 -0
- package/dist/core/discovery/mcp-discovery.js.map +1 -0
- package/dist/core/discovery/openapi-parser.d.ts +95 -0
- package/dist/core/discovery/openapi-parser.d.ts.map +1 -0
- package/dist/core/discovery/openapi-parser.js +353 -0
- package/dist/core/discovery/openapi-parser.js.map +1 -0
- package/dist/core/discovery/plugin-discovery.d.ts +63 -0
- package/dist/core/discovery/plugin-discovery.d.ts.map +1 -0
- package/dist/core/discovery/plugin-discovery.js +333 -0
- package/dist/core/discovery/plugin-discovery.js.map +1 -0
- package/dist/core/discovery/service-discovery.d.ts +107 -0
- package/dist/core/discovery/service-discovery.d.ts.map +1 -0
- package/dist/core/discovery/service-discovery.js +349 -0
- package/dist/core/discovery/service-discovery.js.map +1 -0
- package/dist/core/nexus-client.d.ts +119 -0
- package/dist/core/nexus-client.d.ts.map +1 -0
- package/dist/core/nexus-client.js +292 -0
- package/dist/core/nexus-client.js.map +1 -0
- package/dist/core/nexus-tool-executor.d.ts +102 -0
- package/dist/core/nexus-tool-executor.d.ts.map +1 -0
- package/dist/core/nexus-tool-executor.js +386 -0
- package/dist/core/nexus-tool-executor.js.map +1 -0
- package/dist/core/react-handler.d.ts +17 -0
- package/dist/core/react-handler.d.ts.map +1 -0
- package/dist/core/react-handler.js +296 -0
- package/dist/core/react-handler.js.map +1 -0
- package/dist/core/router/command-registry.d.ts +61 -0
- package/dist/core/router/command-registry.d.ts.map +1 -0
- package/dist/core/router/command-registry.js +138 -0
- package/dist/core/router/command-registry.js.map +1 -0
- package/dist/core/router/command-router.d.ts +33 -0
- package/dist/core/router/command-router.d.ts.map +1 -0
- package/dist/core/router/command-router.js +111 -0
- package/dist/core/router/command-router.js.map +1 -0
- package/dist/core/router/index.d.ts +9 -0
- package/dist/core/router/index.d.ts.map +1 -0
- package/dist/core/router/index.js +8 -0
- package/dist/core/router/index.js.map +1 -0
- package/dist/core/session/context-manager.d.ts +69 -0
- package/dist/core/session/context-manager.d.ts.map +1 -0
- package/dist/core/session/context-manager.js +109 -0
- package/dist/core/session/context-manager.js.map +1 -0
- package/dist/core/session/history-manager.d.ts +72 -0
- package/dist/core/session/history-manager.d.ts.map +1 -0
- package/dist/core/session/history-manager.js +175 -0
- package/dist/core/session/history-manager.js.map +1 -0
- package/dist/core/session/index.d.ts +9 -0
- package/dist/core/session/index.d.ts.map +1 -0
- package/dist/core/session/index.js +9 -0
- package/dist/core/session/index.js.map +1 -0
- package/dist/core/session/session-manager.d.ts +50 -0
- package/dist/core/session/session-manager.d.ts.map +1 -0
- package/dist/core/session/session-manager.js +200 -0
- package/dist/core/session/session-manager.js.map +1 -0
- package/dist/core/session/session-storage.d.ts +59 -0
- package/dist/core/session/session-storage.d.ts.map +1 -0
- package/dist/core/session/session-storage.js +226 -0
- package/dist/core/session/session-storage.js.map +1 -0
- package/dist/core/transport/http-client.d.ts +27 -0
- package/dist/core/transport/http-client.d.ts.map +1 -0
- package/dist/core/transport/http-client.js +182 -0
- package/dist/core/transport/http-client.js.map +1 -0
- package/dist/core/transport/index.d.ts +11 -0
- package/dist/core/transport/index.d.ts.map +1 -0
- package/dist/core/transport/index.js +10 -0
- package/dist/core/transport/index.js.map +1 -0
- package/dist/core/transport/mcp-client.d.ts +69 -0
- package/dist/core/transport/mcp-client.d.ts.map +1 -0
- package/dist/core/transport/mcp-client.js +252 -0
- package/dist/core/transport/mcp-client.js.map +1 -0
- package/dist/core/transport/stream-handler.d.ts +51 -0
- package/dist/core/transport/stream-handler.d.ts.map +1 -0
- package/dist/core/transport/stream-handler.js +264 -0
- package/dist/core/transport/stream-handler.js.map +1 -0
- package/dist/core/transport/websocket-client.d.ts +66 -0
- package/dist/core/transport/websocket-client.d.ts.map +1 -0
- package/dist/core/transport/websocket-client.js +232 -0
- package/dist/core/transport/websocket-client.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +106 -0
- package/dist/index.js.map +1 -0
- package/dist/output/formatters/index.d.ts +9 -0
- package/dist/output/formatters/index.d.ts.map +1 -0
- package/dist/output/formatters/index.js +9 -0
- package/dist/output/formatters/index.js.map +1 -0
- package/dist/output/formatters/json-formatter.d.ts +71 -0
- package/dist/output/formatters/json-formatter.d.ts.map +1 -0
- package/dist/output/formatters/json-formatter.js +236 -0
- package/dist/output/formatters/json-formatter.js.map +1 -0
- package/dist/output/formatters/stream-formatter.d.ts +107 -0
- package/dist/output/formatters/stream-formatter.d.ts.map +1 -0
- package/dist/output/formatters/stream-formatter.js +247 -0
- package/dist/output/formatters/stream-formatter.js.map +1 -0
- package/dist/output/formatters/table-formatter.d.ts +63 -0
- package/dist/output/formatters/table-formatter.d.ts.map +1 -0
- package/dist/output/formatters/table-formatter.js +294 -0
- package/dist/output/formatters/table-formatter.js.map +1 -0
- package/dist/output/formatters/text-formatter.d.ts +63 -0
- package/dist/output/formatters/text-formatter.d.ts.map +1 -0
- package/dist/output/formatters/text-formatter.js +202 -0
- package/dist/output/formatters/text-formatter.js.map +1 -0
- package/dist/output/formatters/yaml-formatter.d.ts +71 -0
- package/dist/output/formatters/yaml-formatter.d.ts.map +1 -0
- package/dist/output/formatters/yaml-formatter.js +239 -0
- package/dist/output/formatters/yaml-formatter.js.map +1 -0
- package/dist/output/index.d.ts +7 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/output/index.js +7 -0
- package/dist/output/index.js.map +1 -0
- package/dist/output/output-manager.d.ts +156 -0
- package/dist/output/output-manager.d.ts.map +1 -0
- package/dist/output/output-manager.js +339 -0
- package/dist/output/output-manager.js.map +1 -0
- package/dist/output/renderers/index.d.ts +6 -0
- package/dist/output/renderers/index.d.ts.map +1 -0
- package/dist/output/renderers/index.js +6 -0
- package/dist/output/renderers/index.js.map +1 -0
- package/dist/output/renderers/progress-renderer.d.ts +56 -0
- package/dist/output/renderers/progress-renderer.d.ts.map +1 -0
- package/dist/output/renderers/progress-renderer.js +306 -0
- package/dist/output/renderers/progress-renderer.js.map +1 -0
- package/dist/output/renderers/terminal-renderer.d.ts +117 -0
- package/dist/output/renderers/terminal-renderer.d.ts.map +1 -0
- package/dist/output/renderers/terminal-renderer.js +294 -0
- package/dist/output/renderers/terminal-renderer.js.map +1 -0
- package/dist/plugins/index.d.ts +12 -0
- package/dist/plugins/index.d.ts.map +1 -0
- package/dist/plugins/index.js +18 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/plugins/plugin-loader.d.ts +37 -0
- package/dist/plugins/plugin-loader.d.ts.map +1 -0
- package/dist/plugins/plugin-loader.js +155 -0
- package/dist/plugins/plugin-loader.js.map +1 -0
- package/dist/plugins/plugin-manager.d.ts +78 -0
- package/dist/plugins/plugin-manager.d.ts.map +1 -0
- package/dist/plugins/plugin-manager.js +310 -0
- package/dist/plugins/plugin-manager.js.map +1 -0
- package/dist/plugins/plugin-sdk.d.ts +136 -0
- package/dist/plugins/plugin-sdk.d.ts.map +1 -0
- package/dist/plugins/plugin-sdk.js +260 -0
- package/dist/plugins/plugin-sdk.js.map +1 -0
- package/dist/plugins/plugin-storage.d.ts +109 -0
- package/dist/plugins/plugin-storage.d.ts.map +1 -0
- package/dist/plugins/plugin-storage.js +207 -0
- package/dist/plugins/plugin-storage.js.map +1 -0
- package/dist/plugins/plugin-validator.d.ts +44 -0
- package/dist/plugins/plugin-validator.d.ts.map +1 -0
- package/dist/plugins/plugin-validator.js +272 -0
- package/dist/plugins/plugin-validator.js.map +1 -0
- package/dist/plugins/template-generator.d.ts +35 -0
- package/dist/plugins/template-generator.d.ts.map +1 -0
- package/dist/plugins/template-generator.js +438 -0
- package/dist/plugins/template-generator.js.map +1 -0
- package/dist/repl/completer.d.ts +58 -0
- package/dist/repl/completer.d.ts.map +1 -0
- package/dist/repl/completer.js +198 -0
- package/dist/repl/completer.js.map +1 -0
- package/dist/repl/evaluator.d.ts +55 -0
- package/dist/repl/evaluator.d.ts.map +1 -0
- package/dist/repl/evaluator.js +299 -0
- package/dist/repl/evaluator.js.map +1 -0
- package/dist/repl/index.d.ts +10 -0
- package/dist/repl/index.d.ts.map +1 -0
- package/dist/repl/index.js +10 -0
- package/dist/repl/index.js.map +1 -0
- package/dist/repl/renderer.d.ts +77 -0
- package/dist/repl/renderer.d.ts.map +1 -0
- package/dist/repl/renderer.js +291 -0
- package/dist/repl/renderer.js.map +1 -0
- package/dist/repl/repl.d.ts +89 -0
- package/dist/repl/repl.d.ts.map +1 -0
- package/dist/repl/repl.js +339 -0
- package/dist/repl/repl.js.map +1 -0
- package/dist/templates/python.d.ts +15 -0
- package/dist/templates/python.d.ts.map +1 -0
- package/dist/templates/python.js +375 -0
- package/dist/templates/python.js.map +1 -0
- package/dist/templates/typescript.d.ts +15 -0
- package/dist/templates/typescript.d.ts.map +1 -0
- package/dist/templates/typescript.js +274 -0
- package/dist/templates/typescript.js.map +1 -0
- package/dist/types/agent.d.ts +109 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +7 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/api.d.ts +125 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +53 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/command.d.ts +101 -0
- package/dist/types/command.d.ts.map +1 -0
- package/dist/types/command.js +7 -0
- package/dist/types/command.js.map +1 -0
- package/dist/types/compute.d.ts +317 -0
- package/dist/types/compute.d.ts.map +1 -0
- package/dist/types/compute.js +19 -0
- package/dist/types/compute.js.map +1 -0
- package/dist/types/config.d.ts +71 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +7 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/errors.d.ts +56 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +64 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/index.d.ts +16 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/nexus.d.ts +254 -0
- package/dist/types/nexus.d.ts.map +1 -0
- package/dist/types/nexus.js +7 -0
- package/dist/types/nexus.js.map +1 -0
- package/dist/types/output.d.ts +107 -0
- package/dist/types/output.d.ts.map +1 -0
- package/dist/types/output.js +7 -0
- package/dist/types/output.js.map +1 -0
- package/dist/types/plugin.d.ts +118 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +7 -0
- package/dist/types/plugin.js.map +1 -0
- package/dist/types/service.d.ts +94 -0
- package/dist/types/service.d.ts.map +1 -0
- package/dist/types/service.js +15 -0
- package/dist/types/service.js.map +1 -0
- package/dist/types/session.d.ts +80 -0
- package/dist/types/session.d.ts.map +1 -0
- package/dist/types/session.js +7 -0
- package/dist/types/session.js.map +1 -0
- package/dist/types/transport.d.ts +96 -0
- package/dist/types/transport.d.ts.map +1 -0
- package/dist/types/transport.js +7 -0
- package/dist/types/transport.js.map +1 -0
- package/dist/utils/config.d.ts +47 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +166 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/error-handler.d.ts +82 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +238 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +9 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +61 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +200 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/prompt.d.ts +57 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +141 -0
- package/dist/utils/prompt.js.map +1 -0
- package/dist/utils/spinner.d.ts +47 -0
- package/dist/utils/spinner.d.ts.map +1 -0
- package/dist/utils/spinner.js +123 -0
- package/dist/utils/spinner.js.map +1 -0
- package/dist/utils/validation.d.ts +97 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +289 -0
- package/dist/utils/validation.js.map +1 -0
- package/docs/COMPLETIONS.md +528 -0
- package/docs/REPL.md +466 -0
- package/docs/graphrag-commands.md +387 -0
- package/package.json +154 -0
- package/scripts/install-completions.sh +356 -0
- package/scripts/prepublish.js +522 -0
- package/scripts/verify-package.js +505 -0
|
@@ -0,0 +1,1162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Compute Agent
|
|
3
|
+
*
|
|
4
|
+
* Daemon process that registers with HPC Gateway and executes ML jobs locally.
|
|
5
|
+
* Supports Apple Silicon (MPS), NVIDIA CUDA, and CPU-only execution.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
import { io } from 'socket.io-client';
|
|
9
|
+
import express from 'express';
|
|
10
|
+
import { Server as SocketIOServer } from 'socket.io';
|
|
11
|
+
import EventEmitter from 'eventemitter3';
|
|
12
|
+
import os from 'os';
|
|
13
|
+
import fs from 'fs/promises';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
16
|
+
import { detectHardware } from './hardware-detection.js';
|
|
17
|
+
/**
|
|
18
|
+
* Path to agent PID file
|
|
19
|
+
*/
|
|
20
|
+
const NEXUS_DIR = path.join(os.homedir(), '.nexus');
|
|
21
|
+
const AGENT_PID_FILE = path.join(NEXUS_DIR, 'compute-agent.pid');
|
|
22
|
+
const AGENT_LOG_FILE = path.join(NEXUS_DIR, 'compute-agent.log');
|
|
23
|
+
export class LocalComputeAgent extends EventEmitter {
|
|
24
|
+
config;
|
|
25
|
+
hardware = null;
|
|
26
|
+
agentId = null;
|
|
27
|
+
gatewaySocket = null;
|
|
28
|
+
localServer = null;
|
|
29
|
+
localApp = null;
|
|
30
|
+
localIO = null;
|
|
31
|
+
jobQueue = [];
|
|
32
|
+
currentJob = null;
|
|
33
|
+
completedJobs = new Map();
|
|
34
|
+
jobLogs = new Map();
|
|
35
|
+
// Kernel session management
|
|
36
|
+
kernelSessions = new Map();
|
|
37
|
+
kernelProcesses = new Map();
|
|
38
|
+
kernelOutputBuffers = new Map();
|
|
39
|
+
reconnectAttempts = 0;
|
|
40
|
+
reconnectTimer = null;
|
|
41
|
+
heartbeatTimer = null;
|
|
42
|
+
idleTimer = null;
|
|
43
|
+
running = false;
|
|
44
|
+
jobsCompleted = 0;
|
|
45
|
+
jobsFailed = 0;
|
|
46
|
+
totalComputeTime = 0;
|
|
47
|
+
startTime = null;
|
|
48
|
+
constructor(config = {}) {
|
|
49
|
+
super();
|
|
50
|
+
this.config = {
|
|
51
|
+
name: config.name || os.hostname(),
|
|
52
|
+
gatewayUrl: config.gatewayUrl || 'https://api.adverant.ai/hpc',
|
|
53
|
+
maxMemoryPercent: config.maxMemoryPercent ?? 75,
|
|
54
|
+
allowRemoteJobs: config.allowRemoteJobs ?? false,
|
|
55
|
+
idleTimeoutMinutes: config.idleTimeoutMinutes ?? 30,
|
|
56
|
+
apiPort: config.apiPort ?? 9200,
|
|
57
|
+
reconnectInterval: config.reconnectInterval ?? 5000,
|
|
58
|
+
maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Start the local compute agent
|
|
63
|
+
*/
|
|
64
|
+
async start() {
|
|
65
|
+
if (this.running) {
|
|
66
|
+
throw new Error('Agent is already running');
|
|
67
|
+
}
|
|
68
|
+
// Ensure .nexus directory exists
|
|
69
|
+
await fs.mkdir(NEXUS_DIR, { recursive: true });
|
|
70
|
+
// Check if another agent is already running
|
|
71
|
+
if (await this.isAnotherAgentRunning()) {
|
|
72
|
+
throw new Error('Another compute agent is already running. Stop it first with: nexus compute agent stop');
|
|
73
|
+
}
|
|
74
|
+
// Detect hardware
|
|
75
|
+
this.hardware = await detectHardware();
|
|
76
|
+
this.agentId = uuidv4();
|
|
77
|
+
this.running = true;
|
|
78
|
+
this.startTime = new Date();
|
|
79
|
+
// Write PID file
|
|
80
|
+
await fs.writeFile(AGENT_PID_FILE, process.pid.toString());
|
|
81
|
+
// Start local API server
|
|
82
|
+
await this.startLocalServer();
|
|
83
|
+
// Connect to HPC Gateway
|
|
84
|
+
await this.connectToGateway();
|
|
85
|
+
// Start heartbeat
|
|
86
|
+
this.startHeartbeat();
|
|
87
|
+
// Reset idle timer
|
|
88
|
+
this.resetIdleTimer();
|
|
89
|
+
this.emit('connected', { agentId: this.agentId });
|
|
90
|
+
// Setup graceful shutdown
|
|
91
|
+
this.setupShutdownHandlers();
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Stop the agent gracefully
|
|
95
|
+
*/
|
|
96
|
+
async stop() {
|
|
97
|
+
if (!this.running) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.running = false;
|
|
101
|
+
// Shutdown all kernels
|
|
102
|
+
await this.shutdownAllKernels();
|
|
103
|
+
// Cancel current job if running
|
|
104
|
+
if (this.currentJob) {
|
|
105
|
+
await this.cancelJob(this.currentJob.job.id);
|
|
106
|
+
}
|
|
107
|
+
// Clear timers
|
|
108
|
+
if (this.heartbeatTimer) {
|
|
109
|
+
clearInterval(this.heartbeatTimer);
|
|
110
|
+
this.heartbeatTimer = null;
|
|
111
|
+
}
|
|
112
|
+
if (this.idleTimer) {
|
|
113
|
+
clearTimeout(this.idleTimer);
|
|
114
|
+
this.idleTimer = null;
|
|
115
|
+
}
|
|
116
|
+
if (this.reconnectTimer) {
|
|
117
|
+
clearTimeout(this.reconnectTimer);
|
|
118
|
+
this.reconnectTimer = null;
|
|
119
|
+
}
|
|
120
|
+
// Disconnect from gateway
|
|
121
|
+
if (this.gatewaySocket) {
|
|
122
|
+
this.gatewaySocket.emit('agent:disconnect', { agentId: this.agentId });
|
|
123
|
+
this.gatewaySocket.disconnect();
|
|
124
|
+
this.gatewaySocket = null;
|
|
125
|
+
}
|
|
126
|
+
// Stop local server
|
|
127
|
+
if (this.localIO) {
|
|
128
|
+
this.localIO.close();
|
|
129
|
+
this.localIO = null;
|
|
130
|
+
}
|
|
131
|
+
if (this.localServer) {
|
|
132
|
+
await new Promise((resolve) => {
|
|
133
|
+
this.localServer.close(() => resolve());
|
|
134
|
+
});
|
|
135
|
+
this.localServer = null;
|
|
136
|
+
}
|
|
137
|
+
// Remove PID file
|
|
138
|
+
await fs.unlink(AGENT_PID_FILE).catch(() => { });
|
|
139
|
+
this.emit('disconnected', { reason: 'shutdown' });
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Submit a job for execution
|
|
143
|
+
*/
|
|
144
|
+
async submitJob(job) {
|
|
145
|
+
const fullJob = {
|
|
146
|
+
...job,
|
|
147
|
+
id: uuidv4(),
|
|
148
|
+
status: 'queued',
|
|
149
|
+
submittedAt: new Date(),
|
|
150
|
+
logs: [],
|
|
151
|
+
};
|
|
152
|
+
this.jobQueue.push({
|
|
153
|
+
job: fullJob,
|
|
154
|
+
priority: 1,
|
|
155
|
+
submittedAt: new Date(),
|
|
156
|
+
});
|
|
157
|
+
this.jobLogs.set(fullJob.id, []);
|
|
158
|
+
// Process queue if not busy
|
|
159
|
+
if (!this.currentJob) {
|
|
160
|
+
this.processQueue();
|
|
161
|
+
}
|
|
162
|
+
this.resetIdleTimer();
|
|
163
|
+
return fullJob;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get job by ID
|
|
167
|
+
*/
|
|
168
|
+
getJob(jobId) {
|
|
169
|
+
// Check current job
|
|
170
|
+
if (this.currentJob?.job.id === jobId) {
|
|
171
|
+
return { ...this.currentJob.job, logs: this.jobLogs.get(jobId) || [] };
|
|
172
|
+
}
|
|
173
|
+
// Check queue
|
|
174
|
+
const queued = this.jobQueue.find((q) => q.job.id === jobId);
|
|
175
|
+
if (queued) {
|
|
176
|
+
return queued.job;
|
|
177
|
+
}
|
|
178
|
+
// Check completed
|
|
179
|
+
return this.completedJobs.get(jobId) || null;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* List all jobs
|
|
183
|
+
*/
|
|
184
|
+
listJobs(options) {
|
|
185
|
+
const jobs = [];
|
|
186
|
+
// Add current job
|
|
187
|
+
if (this.currentJob) {
|
|
188
|
+
jobs.push({ ...this.currentJob.job, logs: this.jobLogs.get(this.currentJob.job.id) || [] });
|
|
189
|
+
}
|
|
190
|
+
// Add queued jobs
|
|
191
|
+
jobs.push(...this.jobQueue.map((q) => q.job));
|
|
192
|
+
// Add completed jobs
|
|
193
|
+
jobs.push(...Array.from(this.completedJobs.values()));
|
|
194
|
+
// Filter by status
|
|
195
|
+
let filtered = options?.status
|
|
196
|
+
? jobs.filter((j) => j.status === options.status)
|
|
197
|
+
: jobs;
|
|
198
|
+
// Sort by submission time (newest first)
|
|
199
|
+
filtered.sort((a, b) => b.submittedAt.getTime() - a.submittedAt.getTime());
|
|
200
|
+
// Limit results
|
|
201
|
+
if (options?.limit) {
|
|
202
|
+
filtered = filtered.slice(0, options.limit);
|
|
203
|
+
}
|
|
204
|
+
return filtered;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Cancel a job
|
|
208
|
+
*/
|
|
209
|
+
async cancelJob(jobId) {
|
|
210
|
+
// Check if it's the current job
|
|
211
|
+
if (this.currentJob?.job.id === jobId) {
|
|
212
|
+
try {
|
|
213
|
+
process.kill(this.currentJob.pid, 'SIGTERM');
|
|
214
|
+
// Wait for process to exit
|
|
215
|
+
await new Promise((resolve) => {
|
|
216
|
+
const check = setInterval(() => {
|
|
217
|
+
try {
|
|
218
|
+
process.kill(this.currentJob.pid, 0);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
clearInterval(check);
|
|
222
|
+
resolve();
|
|
223
|
+
}
|
|
224
|
+
}, 100);
|
|
225
|
+
// Force kill after 5 seconds
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
try {
|
|
228
|
+
process.kill(this.currentJob.pid, 'SIGKILL');
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Already dead
|
|
232
|
+
}
|
|
233
|
+
}, 5000);
|
|
234
|
+
});
|
|
235
|
+
this.currentJob.job.status = 'cancelled';
|
|
236
|
+
this.completedJobs.set(jobId, this.currentJob.job);
|
|
237
|
+
this.currentJob = null;
|
|
238
|
+
this.emit('job:cancelled', { jobId });
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Check queue
|
|
246
|
+
const queueIndex = this.jobQueue.findIndex((q) => q.job.id === jobId);
|
|
247
|
+
if (queueIndex >= 0) {
|
|
248
|
+
const [removed] = this.jobQueue.splice(queueIndex, 1);
|
|
249
|
+
removed.job.status = 'cancelled';
|
|
250
|
+
this.completedJobs.set(jobId, removed.job);
|
|
251
|
+
this.emit('job:cancelled', { jobId });
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Get job logs
|
|
258
|
+
*/
|
|
259
|
+
getJobLogs(jobId, tail) {
|
|
260
|
+
const logs = this.jobLogs.get(jobId) || [];
|
|
261
|
+
if (tail && tail > 0) {
|
|
262
|
+
return logs.slice(-tail);
|
|
263
|
+
}
|
|
264
|
+
return logs;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get agent status for local CLI display
|
|
268
|
+
*/
|
|
269
|
+
async getStatus() {
|
|
270
|
+
// Check if agent is running by reading PID file
|
|
271
|
+
let pid;
|
|
272
|
+
let uptime;
|
|
273
|
+
let isRunning = false;
|
|
274
|
+
try {
|
|
275
|
+
const pidStr = await fs.readFile(AGENT_PID_FILE, 'utf-8');
|
|
276
|
+
pid = parseInt(pidStr.trim(), 10);
|
|
277
|
+
// Check if process is still running
|
|
278
|
+
process.kill(pid, 0);
|
|
279
|
+
isRunning = true;
|
|
280
|
+
if (this.startTime) {
|
|
281
|
+
const uptimeMs = Date.now() - this.startTime.getTime();
|
|
282
|
+
const hours = Math.floor(uptimeMs / (1000 * 60 * 60));
|
|
283
|
+
const minutes = Math.floor((uptimeMs % (1000 * 60 * 60)) / (1000 * 60));
|
|
284
|
+
uptime = `${hours}h ${minutes}m`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
isRunning = false;
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
running: isRunning || this.running,
|
|
292
|
+
pid,
|
|
293
|
+
uptime,
|
|
294
|
+
jobsCompleted: this.jobsCompleted,
|
|
295
|
+
jobsRunning: this.currentJob ? 1 : 0,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get full agent status for HPC Gateway
|
|
300
|
+
*/
|
|
301
|
+
getFullStatus() {
|
|
302
|
+
return {
|
|
303
|
+
id: this.agentId || '',
|
|
304
|
+
name: this.config.name,
|
|
305
|
+
status: this.currentJob ? 'busy' : 'idle',
|
|
306
|
+
registeredAt: this.startTime || new Date(),
|
|
307
|
+
lastHeartbeat: new Date(),
|
|
308
|
+
currentJob: this.currentJob?.job || null,
|
|
309
|
+
jobsCompleted: this.jobsCompleted,
|
|
310
|
+
jobsFailed: this.jobsFailed,
|
|
311
|
+
totalComputeTime: this.totalComputeTime,
|
|
312
|
+
hardware: this.hardware,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Start agent as a background daemon
|
|
317
|
+
*/
|
|
318
|
+
async startDaemon() {
|
|
319
|
+
// Fork the process to run in background
|
|
320
|
+
const { fork } = await import('child_process');
|
|
321
|
+
const scriptPath = new URL(import.meta.url).pathname;
|
|
322
|
+
const child = fork(scriptPath, ['--daemon-mode'], {
|
|
323
|
+
detached: true,
|
|
324
|
+
stdio: 'ignore',
|
|
325
|
+
});
|
|
326
|
+
child.unref();
|
|
327
|
+
// Write PID file
|
|
328
|
+
await fs.mkdir(NEXUS_DIR, { recursive: true });
|
|
329
|
+
await fs.writeFile(AGENT_PID_FILE, child.pid?.toString() || '');
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Check if another agent is already running
|
|
333
|
+
*/
|
|
334
|
+
async isAnotherAgentRunning() {
|
|
335
|
+
try {
|
|
336
|
+
const pidStr = await fs.readFile(AGENT_PID_FILE, 'utf-8');
|
|
337
|
+
const pid = parseInt(pidStr.trim(), 10);
|
|
338
|
+
if (pid === process.pid) {
|
|
339
|
+
return false;
|
|
340
|
+
}
|
|
341
|
+
// Check if process is running
|
|
342
|
+
try {
|
|
343
|
+
process.kill(pid, 0);
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// Process not running, stale PID file
|
|
348
|
+
await fs.unlink(AGENT_PID_FILE).catch(() => { });
|
|
349
|
+
return false;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Start local API server
|
|
358
|
+
*/
|
|
359
|
+
async startLocalServer() {
|
|
360
|
+
this.localApp = express();
|
|
361
|
+
this.localApp.use(express.json());
|
|
362
|
+
// Health check
|
|
363
|
+
this.localApp.get('/health', (_req, res) => {
|
|
364
|
+
res.json({ status: 'ok', agentId: this.agentId });
|
|
365
|
+
});
|
|
366
|
+
// Status
|
|
367
|
+
this.localApp.get('/status', (_req, res) => {
|
|
368
|
+
res.json(this.getFullStatus());
|
|
369
|
+
});
|
|
370
|
+
// Submit job
|
|
371
|
+
this.localApp.post('/jobs', async (req, res) => {
|
|
372
|
+
try {
|
|
373
|
+
const job = await this.submitJob(req.body);
|
|
374
|
+
res.json(job);
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
res.status(400).json({
|
|
378
|
+
error: error instanceof Error ? error.message : String(error),
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
// List jobs
|
|
383
|
+
this.localApp.get('/jobs', (req, res) => {
|
|
384
|
+
const jobs = this.listJobs({
|
|
385
|
+
status: req.query.status,
|
|
386
|
+
limit: req.query.limit ? parseInt(req.query.limit, 10) : undefined,
|
|
387
|
+
});
|
|
388
|
+
res.json({ jobs });
|
|
389
|
+
});
|
|
390
|
+
// Get job
|
|
391
|
+
this.localApp.get('/jobs/:jobId', (req, res) => {
|
|
392
|
+
const job = this.getJob(req.params.jobId);
|
|
393
|
+
if (job) {
|
|
394
|
+
res.json(job);
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
res.status(404).json({ error: 'Job not found' });
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
// Get job logs
|
|
401
|
+
this.localApp.get('/jobs/:jobId/logs', (req, res) => {
|
|
402
|
+
const logs = this.getJobLogs(req.params.jobId, req.query.tail ? parseInt(req.query.tail, 10) : undefined);
|
|
403
|
+
res.json({ logs });
|
|
404
|
+
});
|
|
405
|
+
// Cancel job
|
|
406
|
+
this.localApp.post('/jobs/:jobId/cancel', async (req, res) => {
|
|
407
|
+
const success = await this.cancelJob(req.params.jobId);
|
|
408
|
+
if (success) {
|
|
409
|
+
res.json({ success: true });
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
res.status(404).json({ error: 'Job not found or already completed' });
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
// Shutdown
|
|
416
|
+
this.localApp.post('/shutdown', async (_req, res) => {
|
|
417
|
+
res.json({ success: true });
|
|
418
|
+
setTimeout(() => this.stop(), 100);
|
|
419
|
+
});
|
|
420
|
+
// ========================================
|
|
421
|
+
// Jupyter Kernel Endpoints
|
|
422
|
+
// ========================================
|
|
423
|
+
// Create kernel
|
|
424
|
+
this.localApp.post('/kernels', async (req, res) => {
|
|
425
|
+
try {
|
|
426
|
+
const { language = 'python' } = req.body;
|
|
427
|
+
const kernel = await this.createKernel(language);
|
|
428
|
+
res.json(kernel);
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
res.status(500).json({
|
|
432
|
+
error: error instanceof Error ? error.message : String(error),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
// List kernels
|
|
437
|
+
this.localApp.get('/kernels', (_req, res) => {
|
|
438
|
+
const kernels = this.listKernels();
|
|
439
|
+
res.json({ kernels });
|
|
440
|
+
});
|
|
441
|
+
// Get kernel
|
|
442
|
+
this.localApp.get('/kernels/:kernelId', (req, res) => {
|
|
443
|
+
const kernel = this.getKernelSession(req.params.kernelId);
|
|
444
|
+
if (kernel) {
|
|
445
|
+
res.json(kernel);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
res.status(404).json({ error: 'Kernel not found' });
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
// Execute code
|
|
452
|
+
this.localApp.post('/kernels/:kernelId/execute', async (req, res) => {
|
|
453
|
+
try {
|
|
454
|
+
const result = await this.executeCode({
|
|
455
|
+
...req.body,
|
|
456
|
+
kernelId: req.params.kernelId,
|
|
457
|
+
});
|
|
458
|
+
res.json(result);
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
res.status(500).json({
|
|
462
|
+
error: error instanceof Error ? error.message : String(error),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
});
|
|
466
|
+
// Execute code (auto-create kernel if needed)
|
|
467
|
+
this.localApp.post('/execute', async (req, res) => {
|
|
468
|
+
try {
|
|
469
|
+
const result = await this.executeCode(req.body);
|
|
470
|
+
res.json(result);
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
res.status(500).json({
|
|
474
|
+
error: error instanceof Error ? error.message : String(error),
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
// Shutdown kernel
|
|
479
|
+
this.localApp.delete('/kernels/:kernelId', async (req, res) => {
|
|
480
|
+
try {
|
|
481
|
+
await this.shutdownKernel(req.params.kernelId);
|
|
482
|
+
res.json({ success: true });
|
|
483
|
+
}
|
|
484
|
+
catch (error) {
|
|
485
|
+
res.status(404).json({
|
|
486
|
+
error: error instanceof Error ? error.message : String(error),
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
// Interrupt kernel execution
|
|
491
|
+
this.localApp.post('/kernels/:kernelId/interrupt', async (req, res) => {
|
|
492
|
+
try {
|
|
493
|
+
const success = this.interruptKernel(req.params.kernelId);
|
|
494
|
+
if (success) {
|
|
495
|
+
res.json({ success: true });
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
res.status(404).json({ error: 'Kernel not found or not running' });
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
res.status(500).json({
|
|
503
|
+
error: error instanceof Error ? error.message : String(error),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
// Start server
|
|
508
|
+
return new Promise((resolve, reject) => {
|
|
509
|
+
this.localServer = this.localApp.listen(this.config.apiPort, () => {
|
|
510
|
+
// Setup Socket.IO for log streaming
|
|
511
|
+
this.localIO = new SocketIOServer(this.localServer);
|
|
512
|
+
this.localIO.on('connection', (socket) => {
|
|
513
|
+
// Job log subscriptions
|
|
514
|
+
socket.on('subscribe:logs', ({ jobId }) => {
|
|
515
|
+
socket.join(`logs:${jobId}`);
|
|
516
|
+
});
|
|
517
|
+
// Kernel output subscriptions
|
|
518
|
+
socket.on('subscribe:kernel', ({ kernelId }) => {
|
|
519
|
+
socket.join(`kernel:${kernelId}`);
|
|
520
|
+
});
|
|
521
|
+
socket.on('unsubscribe:kernel', ({ kernelId }) => {
|
|
522
|
+
socket.leave(`kernel:${kernelId}`);
|
|
523
|
+
});
|
|
524
|
+
});
|
|
525
|
+
resolve();
|
|
526
|
+
});
|
|
527
|
+
this.localServer.on('error', (error) => {
|
|
528
|
+
if (error.code === 'EADDRINUSE') {
|
|
529
|
+
reject(new Error(`Port ${this.config.apiPort} is already in use`));
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
reject(error);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Connect to HPC Gateway
|
|
539
|
+
*/
|
|
540
|
+
async connectToGateway() {
|
|
541
|
+
if (!this.hardware) {
|
|
542
|
+
throw new Error('Hardware not detected');
|
|
543
|
+
}
|
|
544
|
+
const wsUrl = this.config.gatewayUrl
|
|
545
|
+
.replace('http://', 'ws://')
|
|
546
|
+
.replace('https://', 'wss://');
|
|
547
|
+
return new Promise((resolve, reject) => {
|
|
548
|
+
this.gatewaySocket = io(wsUrl, {
|
|
549
|
+
transports: ['websocket'],
|
|
550
|
+
reconnection: false,
|
|
551
|
+
timeout: 10000,
|
|
552
|
+
});
|
|
553
|
+
const timeout = setTimeout(() => {
|
|
554
|
+
reject(new Error('Gateway connection timeout'));
|
|
555
|
+
}, 10000);
|
|
556
|
+
this.gatewaySocket.on('connect', () => {
|
|
557
|
+
clearTimeout(timeout);
|
|
558
|
+
this.reconnectAttempts = 0;
|
|
559
|
+
// Register with gateway
|
|
560
|
+
const registration = {
|
|
561
|
+
type: 'local-compute',
|
|
562
|
+
name: this.config.name,
|
|
563
|
+
hostname: os.hostname(),
|
|
564
|
+
capabilities: {
|
|
565
|
+
gpuType: this.hardware.gpu?.type || 'none',
|
|
566
|
+
gpuMemory: this.hardware.gpu?.memory || 0,
|
|
567
|
+
cpuCores: this.hardware.cpu.cores,
|
|
568
|
+
ramTotal: this.hardware.memory.total,
|
|
569
|
+
frameworks: this.hardware.frameworks
|
|
570
|
+
.filter((f) => f.available)
|
|
571
|
+
.map((f) => f.name.toLowerCase()),
|
|
572
|
+
metalVersion: this.hardware.gpu?.api === 'Metal 3' ? 3 : undefined,
|
|
573
|
+
computeCapability: this.hardware.gpu?.computeCapability,
|
|
574
|
+
neuralEngine: this.hardware.gpu?.neuralEngine,
|
|
575
|
+
neuralEngineTops: this.hardware.gpu?.neuralEngineTops,
|
|
576
|
+
},
|
|
577
|
+
config: {
|
|
578
|
+
maxMemoryPercent: this.config.maxMemoryPercent,
|
|
579
|
+
allowRemoteJobs: this.config.allowRemoteJobs,
|
|
580
|
+
idleTimeoutMinutes: this.config.idleTimeoutMinutes,
|
|
581
|
+
},
|
|
582
|
+
};
|
|
583
|
+
this.gatewaySocket.emit('agent:register', registration);
|
|
584
|
+
resolve();
|
|
585
|
+
});
|
|
586
|
+
this.gatewaySocket.on('connect_error', (error) => {
|
|
587
|
+
clearTimeout(timeout);
|
|
588
|
+
this.emit('error', {
|
|
589
|
+
code: 'CONNECTION_ERROR',
|
|
590
|
+
message: error.message,
|
|
591
|
+
});
|
|
592
|
+
this.scheduleReconnect();
|
|
593
|
+
});
|
|
594
|
+
this.gatewaySocket.on('disconnect', (reason) => {
|
|
595
|
+
this.emit('disconnected', { reason });
|
|
596
|
+
if (this.running && reason !== 'io client disconnect') {
|
|
597
|
+
this.scheduleReconnect();
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
// Handle remote job assignment (if allowRemoteJobs is true)
|
|
601
|
+
this.gatewaySocket.on('job:assign', async (data) => {
|
|
602
|
+
if (this.config.allowRemoteJobs) {
|
|
603
|
+
try {
|
|
604
|
+
const job = await this.submitJob(data);
|
|
605
|
+
this.gatewaySocket.emit('job:accepted', { jobId: job.id });
|
|
606
|
+
}
|
|
607
|
+
catch (error) {
|
|
608
|
+
this.gatewaySocket.emit('job:rejected', {
|
|
609
|
+
error: error instanceof Error ? error.message : String(error),
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
this.gatewaySocket.emit('job:rejected', {
|
|
615
|
+
error: 'Remote jobs not allowed',
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Schedule reconnection attempt
|
|
623
|
+
*/
|
|
624
|
+
scheduleReconnect() {
|
|
625
|
+
if (!this.running)
|
|
626
|
+
return;
|
|
627
|
+
if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
|
|
628
|
+
this.emit('error', {
|
|
629
|
+
code: 'MAX_RECONNECT_ATTEMPTS',
|
|
630
|
+
message: 'Maximum reconnection attempts reached',
|
|
631
|
+
});
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
const delay = Math.min(this.config.reconnectInterval * Math.pow(2, this.reconnectAttempts), 60000);
|
|
635
|
+
this.reconnectAttempts++;
|
|
636
|
+
this.emit('reconnecting', { attempt: this.reconnectAttempts, delay });
|
|
637
|
+
this.reconnectTimer = setTimeout(() => {
|
|
638
|
+
this.connectToGateway().catch(() => {
|
|
639
|
+
// Error handled in connect
|
|
640
|
+
});
|
|
641
|
+
}, delay);
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Start heartbeat to gateway
|
|
645
|
+
*/
|
|
646
|
+
startHeartbeat() {
|
|
647
|
+
this.heartbeatTimer = setInterval(() => {
|
|
648
|
+
if (this.gatewaySocket?.connected) {
|
|
649
|
+
this.gatewaySocket.emit('agent:heartbeat', {
|
|
650
|
+
agentId: this.agentId,
|
|
651
|
+
status: this.currentJob ? 'busy' : 'idle',
|
|
652
|
+
currentJobId: this.currentJob?.job.id,
|
|
653
|
+
jobsCompleted: this.jobsCompleted,
|
|
654
|
+
jobsFailed: this.jobsFailed,
|
|
655
|
+
});
|
|
656
|
+
this.emit('heartbeat', { timestamp: new Date() });
|
|
657
|
+
}
|
|
658
|
+
}, 30000);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Reset idle timer
|
|
662
|
+
*/
|
|
663
|
+
resetIdleTimer() {
|
|
664
|
+
if (this.idleTimer) {
|
|
665
|
+
clearTimeout(this.idleTimer);
|
|
666
|
+
}
|
|
667
|
+
if (this.config.idleTimeoutMinutes > 0 && !this.currentJob && this.jobQueue.length === 0) {
|
|
668
|
+
this.idleTimer = setTimeout(() => {
|
|
669
|
+
console.log('Idle timeout reached, shutting down...');
|
|
670
|
+
this.stop();
|
|
671
|
+
}, this.config.idleTimeoutMinutes * 60 * 1000);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Process job queue
|
|
676
|
+
*/
|
|
677
|
+
async processQueue() {
|
|
678
|
+
if (this.currentJob || this.jobQueue.length === 0) {
|
|
679
|
+
this.resetIdleTimer();
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
// Get highest priority job
|
|
683
|
+
this.jobQueue.sort((a, b) => b.priority - a.priority);
|
|
684
|
+
const queuedJob = this.jobQueue.shift();
|
|
685
|
+
if (!queuedJob) {
|
|
686
|
+
this.resetIdleTimer();
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
await this.executeJob(queuedJob.job);
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Execute a job
|
|
693
|
+
*/
|
|
694
|
+
async executeJob(job) {
|
|
695
|
+
job.status = 'running';
|
|
696
|
+
job.startedAt = new Date();
|
|
697
|
+
this.emit('job:started', { jobId: job.id });
|
|
698
|
+
// Build environment (filter out undefined values)
|
|
699
|
+
const env = Object.fromEntries(Object.entries({ ...process.env, ...job.environment })
|
|
700
|
+
.filter((entry) => entry[1] !== undefined));
|
|
701
|
+
// Set framework-specific environment
|
|
702
|
+
if (this.hardware?.gpu?.type.includes('Apple M')) {
|
|
703
|
+
env.PYTORCH_ENABLE_MPS_FALLBACK = '1';
|
|
704
|
+
}
|
|
705
|
+
// Determine command
|
|
706
|
+
let command;
|
|
707
|
+
let args;
|
|
708
|
+
if (job.scriptPath) {
|
|
709
|
+
if (job.scriptPath.endsWith('.py')) {
|
|
710
|
+
command = 'python3';
|
|
711
|
+
args = [job.scriptPath];
|
|
712
|
+
}
|
|
713
|
+
else {
|
|
714
|
+
command = 'bash';
|
|
715
|
+
args = [job.scriptPath];
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
command = 'python3';
|
|
720
|
+
args = ['-c', job.script];
|
|
721
|
+
}
|
|
722
|
+
// Spawn process
|
|
723
|
+
const childProcess = spawn(command, args, {
|
|
724
|
+
cwd: job.workingDir,
|
|
725
|
+
env: env,
|
|
726
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
727
|
+
});
|
|
728
|
+
this.currentJob = {
|
|
729
|
+
pid: childProcess.pid,
|
|
730
|
+
job,
|
|
731
|
+
startTime: new Date(),
|
|
732
|
+
logBuffer: [],
|
|
733
|
+
};
|
|
734
|
+
const logs = this.jobLogs.get(job.id) || [];
|
|
735
|
+
// Capture stdout
|
|
736
|
+
childProcess.stdout?.on('data', (data) => {
|
|
737
|
+
const line = data.toString();
|
|
738
|
+
logs.push(line);
|
|
739
|
+
this.currentJob.logBuffer.push(line);
|
|
740
|
+
this.emit('job:log', { jobId: job.id, line, timestamp: new Date() });
|
|
741
|
+
// Broadcast to WebSocket clients
|
|
742
|
+
if (this.localIO) {
|
|
743
|
+
this.localIO.to(`logs:${job.id}`).emit('log', { line });
|
|
744
|
+
}
|
|
745
|
+
});
|
|
746
|
+
// Capture stderr
|
|
747
|
+
childProcess.stderr?.on('data', (data) => {
|
|
748
|
+
const line = `[stderr] ${data.toString()}`;
|
|
749
|
+
logs.push(line);
|
|
750
|
+
this.currentJob.logBuffer.push(line);
|
|
751
|
+
this.emit('job:log', { jobId: job.id, line, timestamp: new Date() });
|
|
752
|
+
if (this.localIO) {
|
|
753
|
+
this.localIO.to(`logs:${job.id}`).emit('log', { line });
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
// Handle completion
|
|
757
|
+
return new Promise((resolve) => {
|
|
758
|
+
childProcess.on('exit', (code, signal) => {
|
|
759
|
+
const endTime = new Date();
|
|
760
|
+
const durationSeconds = (endTime.getTime() - this.currentJob.startTime.getTime()) / 1000;
|
|
761
|
+
job.completedAt = endTime;
|
|
762
|
+
job.exitCode = code ?? (signal ? 128 : 1);
|
|
763
|
+
job.logs = [...logs];
|
|
764
|
+
const metrics = {
|
|
765
|
+
peakMemoryGb: 0, // Would need to track during execution
|
|
766
|
+
cpuUtilization: 0,
|
|
767
|
+
durationSeconds,
|
|
768
|
+
};
|
|
769
|
+
job.metrics = metrics;
|
|
770
|
+
if (code === 0) {
|
|
771
|
+
job.status = 'completed';
|
|
772
|
+
this.jobsCompleted++;
|
|
773
|
+
this.emit('job:completed', { jobId: job.id, exitCode: code, metrics });
|
|
774
|
+
if (this.localIO) {
|
|
775
|
+
this.localIO.to(`logs:${job.id}`).emit('job:completed', { exitCode: code });
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
job.status = 'failed';
|
|
780
|
+
job.error = signal
|
|
781
|
+
? `Process killed by signal: ${signal}`
|
|
782
|
+
: `Process exited with code: ${code}`;
|
|
783
|
+
this.jobsFailed++;
|
|
784
|
+
this.emit('job:failed', { jobId: job.id, error: job.error });
|
|
785
|
+
if (this.localIO) {
|
|
786
|
+
this.localIO.to(`logs:${job.id}`).emit('job:failed', { error: job.error });
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
this.totalComputeTime += durationSeconds;
|
|
790
|
+
this.completedJobs.set(job.id, job);
|
|
791
|
+
this.currentJob = null;
|
|
792
|
+
// Report to gateway
|
|
793
|
+
if (this.gatewaySocket?.connected) {
|
|
794
|
+
this.gatewaySocket.emit('job:complete', {
|
|
795
|
+
agentId: this.agentId,
|
|
796
|
+
jobId: job.id,
|
|
797
|
+
status: job.status,
|
|
798
|
+
exitCode: job.exitCode,
|
|
799
|
+
metrics,
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
// Process next job
|
|
803
|
+
this.processQueue();
|
|
804
|
+
resolve();
|
|
805
|
+
});
|
|
806
|
+
childProcess.on('error', (error) => {
|
|
807
|
+
job.status = 'failed';
|
|
808
|
+
job.error = error.message;
|
|
809
|
+
job.completedAt = new Date();
|
|
810
|
+
this.jobsFailed++;
|
|
811
|
+
this.emit('job:failed', { jobId: job.id, error: error.message });
|
|
812
|
+
this.completedJobs.set(job.id, job);
|
|
813
|
+
this.currentJob = null;
|
|
814
|
+
this.processQueue();
|
|
815
|
+
resolve();
|
|
816
|
+
});
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
// ========================================
|
|
820
|
+
// Kernel Management Methods
|
|
821
|
+
// ========================================
|
|
822
|
+
/**
|
|
823
|
+
* Create a new kernel session
|
|
824
|
+
*/
|
|
825
|
+
async createKernel(language = 'python') {
|
|
826
|
+
const kernelId = uuidv4();
|
|
827
|
+
const session = {
|
|
828
|
+
id: kernelId,
|
|
829
|
+
language,
|
|
830
|
+
createdAt: new Date(),
|
|
831
|
+
lastActivity: new Date(),
|
|
832
|
+
status: 'starting',
|
|
833
|
+
executionCount: 0,
|
|
834
|
+
};
|
|
835
|
+
this.kernelSessions.set(kernelId, session);
|
|
836
|
+
this.kernelOutputBuffers.set(kernelId, []);
|
|
837
|
+
// For Python, we use a persistent Python subprocess
|
|
838
|
+
// that can execute multiple code blocks
|
|
839
|
+
const pythonProcess = spawn('python3', ['-u', '-i', '-q'], {
|
|
840
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
841
|
+
env: {
|
|
842
|
+
...process.env,
|
|
843
|
+
PYTHONUNBUFFERED: '1',
|
|
844
|
+
// Enable Apple MPS if available
|
|
845
|
+
PYTORCH_ENABLE_MPS_FALLBACK: '1',
|
|
846
|
+
},
|
|
847
|
+
});
|
|
848
|
+
this.kernelProcesses.set(kernelId, pythonProcess);
|
|
849
|
+
// Initialize the Python environment
|
|
850
|
+
const initCode = `
|
|
851
|
+
import sys
|
|
852
|
+
import json
|
|
853
|
+
import traceback
|
|
854
|
+
|
|
855
|
+
# Set up output capture
|
|
856
|
+
class OutputCapture:
|
|
857
|
+
def __init__(self, stream_type):
|
|
858
|
+
self.stream_type = stream_type
|
|
859
|
+
self.original = getattr(sys, stream_type)
|
|
860
|
+
|
|
861
|
+
def write(self, text):
|
|
862
|
+
self.original.write(text)
|
|
863
|
+
self.original.flush()
|
|
864
|
+
|
|
865
|
+
def flush(self):
|
|
866
|
+
self.original.flush()
|
|
867
|
+
|
|
868
|
+
sys.stdout = OutputCapture('stdout')
|
|
869
|
+
sys.stderr = OutputCapture('stderr')
|
|
870
|
+
|
|
871
|
+
# Import common ML libraries if available
|
|
872
|
+
try:
|
|
873
|
+
import numpy as np
|
|
874
|
+
except ImportError:
|
|
875
|
+
pass
|
|
876
|
+
try:
|
|
877
|
+
import torch
|
|
878
|
+
if torch.backends.mps.is_available():
|
|
879
|
+
print("MPS (Apple Silicon GPU) is available")
|
|
880
|
+
except ImportError:
|
|
881
|
+
pass
|
|
882
|
+
try:
|
|
883
|
+
import mlx
|
|
884
|
+
print("MLX (Apple ML framework) is available")
|
|
885
|
+
except ImportError:
|
|
886
|
+
pass
|
|
887
|
+
|
|
888
|
+
print("__KERNEL_READY__")
|
|
889
|
+
`;
|
|
890
|
+
pythonProcess.stdin?.write(initCode);
|
|
891
|
+
pythonProcess.stdin?.write('\n');
|
|
892
|
+
// Wait for kernel to be ready
|
|
893
|
+
await new Promise((resolve, reject) => {
|
|
894
|
+
const timeout = setTimeout(() => {
|
|
895
|
+
reject(new Error('Kernel startup timeout'));
|
|
896
|
+
}, 30000);
|
|
897
|
+
const checkReady = (data) => {
|
|
898
|
+
if (data.toString().includes('__KERNEL_READY__')) {
|
|
899
|
+
clearTimeout(timeout);
|
|
900
|
+
pythonProcess.stdout?.off('data', checkReady);
|
|
901
|
+
session.status = 'idle';
|
|
902
|
+
this.kernelSessions.set(kernelId, session);
|
|
903
|
+
resolve();
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
pythonProcess.stdout?.on('data', checkReady);
|
|
907
|
+
pythonProcess.on('error', (err) => {
|
|
908
|
+
clearTimeout(timeout);
|
|
909
|
+
reject(err);
|
|
910
|
+
});
|
|
911
|
+
});
|
|
912
|
+
// Broadcast via WebSocket
|
|
913
|
+
if (this.localIO) {
|
|
914
|
+
this.localIO.emit('kernel:created', { kernelId, session });
|
|
915
|
+
}
|
|
916
|
+
return session;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Execute code in a kernel
|
|
920
|
+
*/
|
|
921
|
+
async executeCode(request) {
|
|
922
|
+
const startTime = Date.now();
|
|
923
|
+
let kernelId = request.kernelId;
|
|
924
|
+
// Auto-create kernel if not specified
|
|
925
|
+
if (!kernelId || !this.kernelSessions.has(kernelId)) {
|
|
926
|
+
const session = await this.createKernel('python');
|
|
927
|
+
kernelId = session.id;
|
|
928
|
+
}
|
|
929
|
+
const session = this.kernelSessions.get(kernelId);
|
|
930
|
+
const process = this.kernelProcesses.get(kernelId);
|
|
931
|
+
if (!session || !process) {
|
|
932
|
+
throw new Error('Kernel not found');
|
|
933
|
+
}
|
|
934
|
+
// Update session status
|
|
935
|
+
session.status = 'busy';
|
|
936
|
+
session.lastActivity = new Date();
|
|
937
|
+
this.kernelSessions.set(kernelId, session);
|
|
938
|
+
if (this.localIO) {
|
|
939
|
+
this.localIO.emit('kernel:status', { kernelId, status: 'busy' });
|
|
940
|
+
}
|
|
941
|
+
const outputs = [];
|
|
942
|
+
let errorResult;
|
|
943
|
+
// Generate execution marker
|
|
944
|
+
const execId = uuidv4().slice(0, 8);
|
|
945
|
+
const startMarker = `__EXEC_START_${execId}__`;
|
|
946
|
+
const endMarker = `__EXEC_END_${execId}__`;
|
|
947
|
+
const errorMarker = `__EXEC_ERROR_${execId}__`;
|
|
948
|
+
// Wrap code to capture output and detect completion
|
|
949
|
+
const wrappedCode = `
|
|
950
|
+
print("${startMarker}")
|
|
951
|
+
try:
|
|
952
|
+
exec(${JSON.stringify(request.code)})
|
|
953
|
+
print("${endMarker}")
|
|
954
|
+
except Exception as e:
|
|
955
|
+
import traceback
|
|
956
|
+
print("${errorMarker}")
|
|
957
|
+
print(json.dumps({
|
|
958
|
+
"name": type(e).__name__,
|
|
959
|
+
"value": str(e),
|
|
960
|
+
"traceback": traceback.format_exc().split("\\n")
|
|
961
|
+
}))
|
|
962
|
+
print("${endMarker}")
|
|
963
|
+
`;
|
|
964
|
+
return new Promise((resolve) => {
|
|
965
|
+
let outputBuffer = '';
|
|
966
|
+
let inExecution = false;
|
|
967
|
+
let gotError = false;
|
|
968
|
+
const handleOutput = (data, isStderr) => {
|
|
969
|
+
const text = data.toString();
|
|
970
|
+
outputBuffer += text;
|
|
971
|
+
// Check for start marker
|
|
972
|
+
if (outputBuffer.includes(startMarker)) {
|
|
973
|
+
inExecution = true;
|
|
974
|
+
outputBuffer = outputBuffer.split(startMarker)[1] || '';
|
|
975
|
+
}
|
|
976
|
+
// Check for error marker
|
|
977
|
+
if (outputBuffer.includes(errorMarker)) {
|
|
978
|
+
gotError = true;
|
|
979
|
+
const parts = outputBuffer.split(errorMarker);
|
|
980
|
+
outputBuffer = parts[1] || '';
|
|
981
|
+
}
|
|
982
|
+
// Check for end marker
|
|
983
|
+
if (inExecution && outputBuffer.includes(endMarker)) {
|
|
984
|
+
const outputText = outputBuffer.split(endMarker)[0];
|
|
985
|
+
// Parse error if present
|
|
986
|
+
if (gotError) {
|
|
987
|
+
try {
|
|
988
|
+
const errorData = JSON.parse(outputText.trim());
|
|
989
|
+
errorResult = {
|
|
990
|
+
name: errorData.name,
|
|
991
|
+
value: errorData.value,
|
|
992
|
+
traceback: errorData.traceback,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
catch {
|
|
996
|
+
errorResult = {
|
|
997
|
+
name: 'ExecutionError',
|
|
998
|
+
value: outputText,
|
|
999
|
+
traceback: [],
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
else if (outputText.trim()) {
|
|
1004
|
+
outputs.push({
|
|
1005
|
+
type: 'stream',
|
|
1006
|
+
name: isStderr ? 'stderr' : 'stdout',
|
|
1007
|
+
text: outputText,
|
|
1008
|
+
});
|
|
1009
|
+
// Broadcast output
|
|
1010
|
+
if (this.localIO) {
|
|
1011
|
+
this.localIO.emit('kernel:output', {
|
|
1012
|
+
kernelId,
|
|
1013
|
+
output: outputs[outputs.length - 1],
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
// Cleanup listeners
|
|
1018
|
+
process.stdout?.off('data', stdoutHandler);
|
|
1019
|
+
process.stderr?.off('data', stderrHandler);
|
|
1020
|
+
// Update session
|
|
1021
|
+
session.status = 'idle';
|
|
1022
|
+
session.executionCount++;
|
|
1023
|
+
session.lastActivity = new Date();
|
|
1024
|
+
this.kernelSessions.set(kernelId, session);
|
|
1025
|
+
const result = {
|
|
1026
|
+
executionCount: session.executionCount,
|
|
1027
|
+
status: errorResult ? 'error' : 'ok',
|
|
1028
|
+
outputs,
|
|
1029
|
+
error: errorResult,
|
|
1030
|
+
duration: Date.now() - startTime,
|
|
1031
|
+
};
|
|
1032
|
+
if (this.localIO) {
|
|
1033
|
+
this.localIO.emit('kernel:status', { kernelId, status: 'idle' });
|
|
1034
|
+
this.localIO.emit('kernel:result', { kernelId, result });
|
|
1035
|
+
}
|
|
1036
|
+
resolve(result);
|
|
1037
|
+
}
|
|
1038
|
+
else if (inExecution && !gotError) {
|
|
1039
|
+
// Stream intermediate output
|
|
1040
|
+
const output = {
|
|
1041
|
+
type: 'stream',
|
|
1042
|
+
name: isStderr ? 'stderr' : 'stdout',
|
|
1043
|
+
text,
|
|
1044
|
+
};
|
|
1045
|
+
outputs.push(output);
|
|
1046
|
+
if (this.localIO) {
|
|
1047
|
+
this.localIO.emit('kernel:output', { kernelId, output });
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
const stdoutHandler = (data) => handleOutput(data, false);
|
|
1052
|
+
const stderrHandler = (data) => handleOutput(data, true);
|
|
1053
|
+
process.stdout?.on('data', stdoutHandler);
|
|
1054
|
+
process.stderr?.on('data', stderrHandler);
|
|
1055
|
+
// Send code to kernel
|
|
1056
|
+
process.stdin?.write(wrappedCode);
|
|
1057
|
+
process.stdin?.write('\n');
|
|
1058
|
+
// Timeout after 5 minutes
|
|
1059
|
+
setTimeout(() => {
|
|
1060
|
+
process.stdout?.off('data', stdoutHandler);
|
|
1061
|
+
process.stderr?.off('data', stderrHandler);
|
|
1062
|
+
session.status = 'idle';
|
|
1063
|
+
this.kernelSessions.set(kernelId, session);
|
|
1064
|
+
resolve({
|
|
1065
|
+
executionCount: session.executionCount,
|
|
1066
|
+
status: 'error',
|
|
1067
|
+
outputs,
|
|
1068
|
+
error: {
|
|
1069
|
+
name: 'TimeoutError',
|
|
1070
|
+
value: 'Execution timed out after 5 minutes',
|
|
1071
|
+
traceback: [],
|
|
1072
|
+
},
|
|
1073
|
+
duration: Date.now() - startTime,
|
|
1074
|
+
});
|
|
1075
|
+
}, 300000);
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
1079
|
+
* Shutdown a kernel
|
|
1080
|
+
*/
|
|
1081
|
+
async shutdownKernel(kernelId) {
|
|
1082
|
+
const session = this.kernelSessions.get(kernelId);
|
|
1083
|
+
const process = this.kernelProcesses.get(kernelId);
|
|
1084
|
+
if (!session || !process) {
|
|
1085
|
+
throw new Error('Kernel not found');
|
|
1086
|
+
}
|
|
1087
|
+
// Send quit command
|
|
1088
|
+
process.stdin?.write('quit()\n');
|
|
1089
|
+
// Wait for process to exit or force kill
|
|
1090
|
+
await new Promise((resolve) => {
|
|
1091
|
+
const timeout = setTimeout(() => {
|
|
1092
|
+
process.kill('SIGKILL');
|
|
1093
|
+
resolve();
|
|
1094
|
+
}, 5000);
|
|
1095
|
+
process.on('exit', () => {
|
|
1096
|
+
clearTimeout(timeout);
|
|
1097
|
+
resolve();
|
|
1098
|
+
});
|
|
1099
|
+
});
|
|
1100
|
+
// Cleanup
|
|
1101
|
+
this.kernelSessions.delete(kernelId);
|
|
1102
|
+
this.kernelProcesses.delete(kernelId);
|
|
1103
|
+
this.kernelOutputBuffers.delete(kernelId);
|
|
1104
|
+
if (this.localIO) {
|
|
1105
|
+
this.localIO.emit('kernel:shutdown', { kernelId });
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Get kernel session info
|
|
1110
|
+
*/
|
|
1111
|
+
getKernelSession(kernelId) {
|
|
1112
|
+
return this.kernelSessions.get(kernelId) || null;
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* List all active kernels
|
|
1116
|
+
*/
|
|
1117
|
+
listKernels() {
|
|
1118
|
+
return Array.from(this.kernelSessions.values());
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Interrupt kernel execution
|
|
1122
|
+
*/
|
|
1123
|
+
interruptKernel(kernelId) {
|
|
1124
|
+
const process = this.kernelProcesses.get(kernelId);
|
|
1125
|
+
if (!process) {
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
1128
|
+
try {
|
|
1129
|
+
process.kill('SIGINT');
|
|
1130
|
+
const session = this.kernelSessions.get(kernelId);
|
|
1131
|
+
if (session) {
|
|
1132
|
+
session.status = 'idle';
|
|
1133
|
+
this.kernelSessions.set(kernelId, session);
|
|
1134
|
+
}
|
|
1135
|
+
return true;
|
|
1136
|
+
}
|
|
1137
|
+
catch {
|
|
1138
|
+
return false;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Shutdown all kernels (called on agent stop)
|
|
1143
|
+
*/
|
|
1144
|
+
async shutdownAllKernels() {
|
|
1145
|
+
const kernelIds = Array.from(this.kernelSessions.keys());
|
|
1146
|
+
await Promise.all(kernelIds.map((id) => this.shutdownKernel(id).catch(() => { })));
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Setup graceful shutdown handlers
|
|
1150
|
+
*/
|
|
1151
|
+
setupShutdownHandlers() {
|
|
1152
|
+
const shutdown = async () => {
|
|
1153
|
+
console.log('\nShutting down compute agent...');
|
|
1154
|
+
await this.stop();
|
|
1155
|
+
process.exit(0);
|
|
1156
|
+
};
|
|
1157
|
+
process.on('SIGINT', shutdown);
|
|
1158
|
+
process.on('SIGTERM', shutdown);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
export default LocalComputeAgent;
|
|
1162
|
+
//# sourceMappingURL=local-compute-agent.js.map
|