@xyit/wellspring 0.0.0-stage → 1.1.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/LICENSE +21 -0
- package/README.md +853 -2
- package/dist/agent/pending-asks.d.ts +20 -0
- package/dist/agent/pending-asks.js +70 -0
- package/dist/agent/pending-asks.js.map +1 -0
- package/dist/agent/task-registry.d.ts +14 -0
- package/dist/agent/task-registry.js +80 -0
- package/dist/agent/task-registry.js.map +1 -0
- package/dist/agent/tool-context.d.ts +30 -0
- package/dist/agent/tool-context.js +10 -0
- package/dist/agent/tool-context.js.map +1 -0
- package/dist/agent/usage.d.ts +40 -0
- package/dist/agent/usage.js +122 -0
- package/dist/agent/usage.js.map +1 -0
- package/dist/api/host/agent.d.ts +165 -0
- package/dist/api/host/agent.js +5 -0
- package/dist/api/host/agent.js.map +1 -0
- package/dist/api/host/events.d.ts +2 -0
- package/dist/api/host/events.js +6 -0
- package/dist/api/host/events.js.map +1 -0
- package/dist/api/host/exec.d.ts +36 -0
- package/dist/api/host/exec.js +10 -0
- package/dist/api/host/exec.js.map +1 -0
- package/dist/api/host/fs.d.ts +24 -0
- package/dist/api/host/fs.js +7 -0
- package/dist/api/host/fs.js.map +1 -0
- package/dist/api/host/hooks.d.ts +210 -0
- package/dist/api/host/hooks.js +401 -0
- package/dist/api/host/hooks.js.map +1 -0
- package/dist/api/host/host.d.ts +36 -0
- package/dist/api/host/host.js +53 -0
- package/dist/api/host/host.js.map +1 -0
- package/dist/api/host/http.d.ts +58 -0
- package/dist/api/host/http.js +6 -0
- package/dist/api/host/http.js.map +1 -0
- package/dist/api/host/index.d.ts +15 -0
- package/dist/api/host/index.js +20 -0
- package/dist/api/host/index.js.map +1 -0
- package/dist/api/host/models.d.ts +65 -0
- package/dist/api/host/models.js +8 -0
- package/dist/api/host/models.js.map +1 -0
- package/dist/api/host/network.d.ts +22 -0
- package/dist/api/host/network.js +7 -0
- package/dist/api/host/network.js.map +1 -0
- package/dist/api/host/plugin.d.ts +138 -0
- package/dist/api/host/plugin.js +6 -0
- package/dist/api/host/plugin.js.map +1 -0
- package/dist/api/host/plugins.d.ts +49 -0
- package/dist/api/host/plugins.js +6 -0
- package/dist/api/host/plugins.js.map +1 -0
- package/dist/api/host/pty.d.ts +55 -0
- package/dist/api/host/pty.js +21 -0
- package/dist/api/host/pty.js.map +1 -0
- package/dist/api/host/session.d.ts +5 -0
- package/dist/api/host/session.js +52 -0
- package/dist/api/host/session.js.map +1 -0
- package/dist/api/host/version.d.ts +2 -0
- package/dist/api/host/version.js +3 -0
- package/dist/api/host/version.js.map +1 -0
- package/dist/api/host/workspace.d.ts +38 -0
- package/dist/api/host/workspace.js +9 -0
- package/dist/api/host/workspace.js.map +1 -0
- package/dist/api/plugin/http-client.d.ts +42 -0
- package/dist/api/plugin/http-client.js +364 -0
- package/dist/api/plugin/http-client.js.map +1 -0
- package/dist/api/plugin/http-protocol.d.ts +152 -0
- package/dist/api/plugin/http-protocol.js +49 -0
- package/dist/api/plugin/http-protocol.js.map +1 -0
- package/dist/api/result/result.d.ts +16 -0
- package/dist/api/result/result.js +43 -0
- package/dist/api/result/result.js.map +1 -0
- package/dist/commands/agent-turn.d.ts +63 -0
- package/dist/commands/agent-turn.js +687 -0
- package/dist/commands/agent-turn.js.map +1 -0
- package/dist/commands/ask-prompt.d.ts +6 -0
- package/dist/commands/ask-prompt.js +49 -0
- package/dist/commands/ask-prompt.js.map +1 -0
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +283 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/conversation.d.ts +51 -0
- package/dist/commands/conversation.js +630 -0
- package/dist/commands/conversation.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +77 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/exec-approval.d.ts +3 -0
- package/dist/commands/exec-approval.js +80 -0
- package/dist/commands/exec-approval.js.map +1 -0
- package/dist/commands/help.d.ts +2 -0
- package/dist/commands/help.js +35 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/key.d.ts +2 -0
- package/dist/commands/key.js +226 -0
- package/dist/commands/key.js.map +1 -0
- package/dist/commands/plugin.d.ts +2 -0
- package/dist/commands/plugin.js +190 -0
- package/dist/commands/plugin.js.map +1 -0
- package/dist/commands/session.d.ts +2 -0
- package/dist/commands/session.js +167 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/shared.d.ts +35 -0
- package/dist/commands/shared.js +66 -0
- package/dist/commands/shared.js.map +1 -0
- package/dist/commands/start.d.ts +2 -0
- package/dist/commands/start.js +75 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/stop.d.ts +2 -0
- package/dist/commands/stop.js +24 -0
- package/dist/commands/stop.js.map +1 -0
- package/dist/commands/version.d.ts +2 -0
- package/dist/commands/version.js +33 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/config/codes.d.ts +13 -0
- package/dist/config/codes.js +15 -0
- package/dist/config/codes.js.map +1 -0
- package/dist/config/credentials.d.ts +67 -0
- package/dist/config/credentials.js +214 -0
- package/dist/config/credentials.js.map +1 -0
- package/dist/config/defaults.d.ts +5 -0
- package/dist/config/defaults.js +31 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.js +10 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +10 -0
- package/dist/config/loader.js +68 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/providers.d.ts +30 -0
- package/dist/config/providers.js +97 -0
- package/dist/config/providers.js.map +1 -0
- package/dist/config/types.d.ts +75 -0
- package/dist/config/types.js +4 -0
- package/dist/config/types.js.map +1 -0
- package/dist/config/validate.d.ts +4 -0
- package/dist/config/validate.js +391 -0
- package/dist/config/validate.js.map +1 -0
- package/dist/display/markdown.d.ts +7 -0
- package/dist/display/markdown.js +116 -0
- package/dist/display/markdown.js.map +1 -0
- package/dist/display/tool-label.d.ts +6 -0
- package/dist/display/tool-label.js +81 -0
- package/dist/display/tool-label.js.map +1 -0
- package/dist/event/event.d.ts +27 -0
- package/dist/event/event.js +152 -0
- package/dist/event/event.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +308 -0
- package/dist/index.js.map +1 -0
- package/dist/input/codes.d.ts +15 -0
- package/dist/input/codes.js +20 -0
- package/dist/input/codes.js.map +1 -0
- package/dist/input/commands.d.ts +10 -0
- package/dist/input/commands.js +65 -0
- package/dist/input/commands.js.map +1 -0
- package/dist/input/help.d.ts +8 -0
- package/dist/input/help.js +51 -0
- package/dist/input/help.js.map +1 -0
- package/dist/input/index.d.ts +6 -0
- package/dist/input/index.js +10 -0
- package/dist/input/index.js.map +1 -0
- package/dist/input/parse-options.d.ts +11 -0
- package/dist/input/parse-options.js +51 -0
- package/dist/input/parse-options.js.map +1 -0
- package/dist/input/parse.d.ts +3 -0
- package/dist/input/parse.js +119 -0
- package/dist/input/parse.js.map +1 -0
- package/dist/input/types.d.ts +17 -0
- package/dist/input/types.js +2 -0
- package/dist/input/types.js.map +1 -0
- package/dist/logger/elog4js.d.ts +65 -0
- package/dist/logger/elog4js.js +305 -0
- package/dist/logger/elog4js.js.map +1 -0
- package/dist/logger/logger.d.ts +37 -0
- package/dist/logger/logger.js +196 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/network/index.d.ts +2 -0
- package/dist/network/index.js +6 -0
- package/dist/network/index.js.map +1 -0
- package/dist/network/openai.d.ts +117 -0
- package/dist/network/openai.js +443 -0
- package/dist/network/openai.js.map +1 -0
- package/dist/network/proxy.d.ts +9 -0
- package/dist/network/proxy.js +72 -0
- package/dist/network/proxy.js.map +1 -0
- package/dist/network/web.d.ts +34 -0
- package/dist/network/web.js +152 -0
- package/dist/network/web.js.map +1 -0
- package/dist/platform/clipboard.d.ts +15 -0
- package/dist/platform/clipboard.js +82 -0
- package/dist/platform/clipboard.js.map +1 -0
- package/dist/platform/exec.d.ts +28 -0
- package/dist/platform/exec.js +95 -0
- package/dist/platform/exec.js.map +1 -0
- package/dist/platform/file.d.ts +38 -0
- package/dist/platform/file.js +197 -0
- package/dist/platform/file.js.map +1 -0
- package/dist/platform/index.d.ts +7 -0
- package/dist/platform/index.js +12 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/notification.d.ts +14 -0
- package/dist/platform/notification.js +82 -0
- package/dist/platform/notification.js.map +1 -0
- package/dist/platform/open-external.d.ts +10 -0
- package/dist/platform/open-external.js +105 -0
- package/dist/platform/open-external.js.map +1 -0
- package/dist/platform/os.d.ts +20 -0
- package/dist/platform/os.js +86 -0
- package/dist/platform/os.js.map +1 -0
- package/dist/platform/path.d.ts +32 -0
- package/dist/platform/path.js +154 -0
- package/dist/platform/path.js.map +1 -0
- package/dist/plugin/access-guard.d.ts +1 -0
- package/dist/plugin/access-guard.js +40 -0
- package/dist/plugin/access-guard.js.map +1 -0
- package/dist/plugin/agent-service.d.ts +25 -0
- package/dist/plugin/agent-service.js +809 -0
- package/dist/plugin/agent-service.js.map +1 -0
- package/dist/plugin/agent-tools.d.ts +12 -0
- package/dist/plugin/agent-tools.js +31 -0
- package/dist/plugin/agent-tools.js.map +1 -0
- package/dist/plugin/capabilities.d.ts +26 -0
- package/dist/plugin/capabilities.js +192 -0
- package/dist/plugin/capabilities.js.map +1 -0
- package/dist/plugin/console-guard.d.ts +2 -0
- package/dist/plugin/console-guard.js +52 -0
- package/dist/plugin/console-guard.js.map +1 -0
- package/dist/plugin/data-access.d.ts +15 -0
- package/dist/plugin/data-access.js +57 -0
- package/dist/plugin/data-access.js.map +1 -0
- package/dist/plugin/fs.d.ts +3 -0
- package/dist/plugin/fs.js +93 -0
- package/dist/plugin/fs.js.map +1 -0
- package/dist/plugin/host-alias.d.ts +2 -0
- package/dist/plugin/host-alias.js +57 -0
- package/dist/plugin/host-alias.js.map +1 -0
- package/dist/plugin/index.d.ts +7 -0
- package/dist/plugin/index.js +11 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/install.d.ts +17 -0
- package/dist/plugin/install.js +163 -0
- package/dist/plugin/install.js.map +1 -0
- package/dist/plugin/loader.d.ts +45 -0
- package/dist/plugin/loader.js +739 -0
- package/dist/plugin/loader.js.map +1 -0
- package/dist/plugin/manage.d.ts +31 -0
- package/dist/plugin/manage.js +271 -0
- package/dist/plugin/manage.js.map +1 -0
- package/dist/plugin/models-service.d.ts +15 -0
- package/dist/plugin/models-service.js +317 -0
- package/dist/plugin/models-service.js.map +1 -0
- package/dist/plugin/network-service.d.ts +10 -0
- package/dist/plugin/network-service.js +87 -0
- package/dist/plugin/network-service.js.map +1 -0
- package/dist/plugin/package-manager.d.ts +46 -0
- package/dist/plugin/package-manager.js +148 -0
- package/dist/plugin/package-manager.js.map +1 -0
- package/dist/plugin/paths.d.ts +6 -0
- package/dist/plugin/paths.js +15 -0
- package/dist/plugin/paths.js.map +1 -0
- package/dist/plugin/registry.d.ts +17 -0
- package/dist/plugin/registry.js +82 -0
- package/dist/plugin/registry.js.map +1 -0
- package/dist/plugin/runtime.d.ts +9 -0
- package/dist/plugin/runtime.js +43 -0
- package/dist/plugin/runtime.js.map +1 -0
- package/dist/plugin/scaffold.d.ts +13 -0
- package/dist/plugin/scaffold.js +132 -0
- package/dist/plugin/scaffold.js.map +1 -0
- package/dist/plugin/state.d.ts +6 -0
- package/dist/plugin/state.js +38 -0
- package/dist/plugin/state.js.map +1 -0
- package/dist/plugin/zip.d.ts +7 -0
- package/dist/plugin/zip.js +138 -0
- package/dist/plugin/zip.js.map +1 -0
- package/dist/sandbox/audit.d.ts +12 -0
- package/dist/sandbox/audit.js +20 -0
- package/dist/sandbox/audit.js.map +1 -0
- package/dist/sandbox/backends/bwrap.d.ts +28 -0
- package/dist/sandbox/backends/bwrap.js +197 -0
- package/dist/sandbox/backends/bwrap.js.map +1 -0
- package/dist/sandbox/backends/direct.d.ts +7 -0
- package/dist/sandbox/backends/direct.js +32 -0
- package/dist/sandbox/backends/direct.js.map +1 -0
- package/dist/sandbox/bridge-state.d.ts +13 -0
- package/dist/sandbox/bridge-state.js +12 -0
- package/dist/sandbox/bridge-state.js.map +1 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +6 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/sandbox/manager.d.ts +8 -0
- package/dist/sandbox/manager.js +325 -0
- package/dist/sandbox/manager.js.map +1 -0
- package/dist/sandbox/memory-limit.d.ts +44 -0
- package/dist/sandbox/memory-limit.js +193 -0
- package/dist/sandbox/memory-limit.js.map +1 -0
- package/dist/sandbox/plugin-client.d.ts +80 -0
- package/dist/sandbox/plugin-client.js +1601 -0
- package/dist/sandbox/plugin-client.js.map +1 -0
- package/dist/sandbox/plugin-host.d.ts +1 -0
- package/dist/sandbox/plugin-host.js +1001 -0
- package/dist/sandbox/plugin-host.js.map +1 -0
- package/dist/sandbox/protocol.d.ts +43 -0
- package/dist/sandbox/protocol.js +96 -0
- package/dist/sandbox/protocol.js.map +1 -0
- package/dist/sandbox/rpc-timeouts.d.ts +28 -0
- package/dist/sandbox/rpc-timeouts.js +15 -0
- package/dist/sandbox/rpc-timeouts.js.map +1 -0
- package/dist/sandbox/supervisor.d.ts +31 -0
- package/dist/sandbox/supervisor.js +114 -0
- package/dist/sandbox/supervisor.js.map +1 -0
- package/dist/sandbox/types.d.ts +124 -0
- package/dist/sandbox/types.js +17 -0
- package/dist/sandbox/types.js.map +1 -0
- package/dist/service/control.d.ts +32 -0
- package/dist/service/control.js +117 -0
- package/dist/service/control.js.map +1 -0
- package/dist/session/compress.d.ts +3 -0
- package/dist/session/compress.js +42 -0
- package/dist/session/compress.js.map +1 -0
- package/dist/session/index.d.ts +4 -0
- package/dist/session/index.js +8 -0
- package/dist/session/index.js.map +1 -0
- package/dist/session/registry.d.ts +4 -0
- package/dist/session/registry.js +12 -0
- package/dist/session/registry.js.map +1 -0
- package/dist/session/replay.d.ts +10 -0
- package/dist/session/replay.js +109 -0
- package/dist/session/replay.js.map +1 -0
- package/dist/session/session.d.ts +40 -0
- package/dist/session/session.js +108 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/store.d.ts +58 -0
- package/dist/session/store.js +382 -0
- package/dist/session/store.js.map +1 -0
- package/dist/session/types.d.ts +56 -0
- package/dist/session/types.js +3 -0
- package/dist/session/types.js.map +1 -0
- package/dist/workspace/index.d.ts +7 -0
- package/dist/workspace/index.js +17 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/registry.d.ts +25 -0
- package/dist/workspace/registry.js +293 -0
- package/dist/workspace/registry.js.map +1 -0
- package/package.json +73 -5
- package/scripts/build-node-pty.mjs +55 -0
- package/scripts/rewrite-dts.mjs +36 -0
- package/scripts/run-tests.mjs +41 -0
- package/scripts/verify-sandbox.ts +98 -0
- package/src/agent/pending-asks.ts +88 -0
- package/src/agent/task-registry.ts +108 -0
- package/src/agent/tool-context.ts +47 -0
- package/src/agent/usage.ts +170 -0
- package/src/api/host/agent.ts +162 -0
- package/src/api/host/events.ts +7 -0
- package/src/api/host/exec.ts +47 -0
- package/src/api/host/fs.ts +33 -0
- package/src/api/host/hooks.ts +635 -0
- package/src/api/host/host.ts +99 -0
- package/src/api/host/http.ts +79 -0
- package/src/api/host/index.ts +20 -0
- package/src/api/host/models.ts +81 -0
- package/src/api/host/network.ts +32 -0
- package/src/api/host/plugin.ts +158 -0
- package/src/api/host/plugins.ts +59 -0
- package/src/api/host/pty.ts +73 -0
- package/src/api/host/session.ts +61 -0
- package/src/api/host/version.ts +2 -0
- package/src/api/host/workspace.ts +51 -0
- package/src/api/plugin/http-client.ts +492 -0
- package/src/api/plugin/http-protocol.ts +231 -0
- package/src/api/result/result.ts +52 -0
- package/src/commands/agent-turn.ts +817 -0
- package/src/commands/ask-prompt.ts +52 -0
- package/src/commands/config.ts +306 -0
- package/src/commands/conversation.ts +777 -0
- package/src/commands/doctor.ts +92 -0
- package/src/commands/exec-approval.ts +91 -0
- package/src/commands/help.ts +41 -0
- package/src/commands/key.ts +281 -0
- package/src/commands/plugin.ts +222 -0
- package/src/commands/session.ts +179 -0
- package/src/commands/shared.ts +103 -0
- package/src/commands/start.ts +88 -0
- package/src/commands/stop.ts +24 -0
- package/src/commands/version.ts +40 -0
- package/src/config/codes.ts +17 -0
- package/src/config/credentials.ts +302 -0
- package/src/config/defaults.ts +32 -0
- package/src/config/index.ts +10 -0
- package/src/config/loader.ts +80 -0
- package/src/config/providers.ts +133 -0
- package/src/config/types.ts +88 -0
- package/src/config/validate.ts +458 -0
- package/src/display/markdown.ts +137 -0
- package/src/display/tool-label.ts +87 -0
- package/src/event/event.ts +171 -0
- package/src/index.ts +344 -0
- package/src/input/codes.ts +21 -0
- package/src/input/commands.ts +76 -0
- package/src/input/help.ts +60 -0
- package/src/input/index.ts +10 -0
- package/src/input/parse-options.ts +66 -0
- package/src/input/parse.ts +138 -0
- package/src/input/types.ts +19 -0
- package/src/logger/elog4js.ts +366 -0
- package/src/logger/logger.ts +251 -0
- package/src/network/index.ts +6 -0
- package/src/network/openai.ts +571 -0
- package/src/network/proxy.ts +80 -0
- package/src/network/web.ts +206 -0
- package/src/platform/clipboard.ts +107 -0
- package/src/platform/exec.ts +128 -0
- package/src/platform/file.ts +235 -0
- package/src/platform/index.ts +12 -0
- package/src/platform/notification.ts +108 -0
- package/src/platform/open-external.ts +132 -0
- package/src/platform/os.ts +101 -0
- package/src/platform/path.ts +214 -0
- package/src/plugin/access-guard.ts +46 -0
- package/src/plugin/agent-service.ts +969 -0
- package/src/plugin/agent-tools.ts +43 -0
- package/src/plugin/capabilities.ts +240 -0
- package/src/plugin/console-guard.ts +66 -0
- package/src/plugin/data-access.ts +70 -0
- package/src/plugin/fs.ts +109 -0
- package/src/plugin/host-alias.ts +61 -0
- package/src/plugin/index.ts +11 -0
- package/src/plugin/install.ts +213 -0
- package/src/plugin/loader.ts +952 -0
- package/src/plugin/manage.ts +338 -0
- package/src/plugin/models-service.ts +383 -0
- package/src/plugin/network-service.ts +102 -0
- package/src/plugin/package-manager.ts +214 -0
- package/src/plugin/paths.ts +19 -0
- package/src/plugin/registry.ts +103 -0
- package/src/plugin/runtime.ts +57 -0
- package/src/plugin/scaffold.ts +158 -0
- package/src/plugin/state.ts +52 -0
- package/src/plugin/zip.ts +156 -0
- package/src/sandbox/audit.ts +38 -0
- package/src/sandbox/backends/bwrap.ts +251 -0
- package/src/sandbox/backends/direct.ts +43 -0
- package/src/sandbox/bridge-state.ts +29 -0
- package/src/sandbox/index.ts +5 -0
- package/src/sandbox/manager.ts +406 -0
- package/src/sandbox/memory-limit.ts +245 -0
- package/src/sandbox/plugin-client.ts +1881 -0
- package/src/sandbox/plugin-host.ts +1282 -0
- package/src/sandbox/protocol.ts +142 -0
- package/src/sandbox/pty-bridge.py +154 -0
- package/src/sandbox/rpc-timeouts.ts +43 -0
- package/src/sandbox/supervisor.ts +172 -0
- package/src/sandbox/types.ts +149 -0
- package/src/service/control.ts +155 -0
- package/src/session/compress.ts +47 -0
- package/src/session/index.ts +8 -0
- package/src/session/registry.ts +17 -0
- package/src/session/replay.ts +132 -0
- package/src/session/session.ts +134 -0
- package/src/session/store.ts +468 -0
- package/src/session/types.ts +61 -0
- package/src/workspace/index.ts +21 -0
- package/src/workspace/registry.ts +356 -0
|
@@ -0,0 +1,1282 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* sandbox 插件进程入口。
|
|
3
|
+
* 由宿主通过 bwrap/direct backend 启动;stdio 上跑 frame 协议,插件代码只通过 bridge 访问宿主。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Readable } from "node:stream"
|
|
7
|
+
import { pathToFileURL } from "node:url"
|
|
8
|
+
|
|
9
|
+
import type { Logger } from "pino"
|
|
10
|
+
|
|
11
|
+
import type { Host, HostOSInfo, HostPluginStatus, HostStatus } from "../api/host/host.ts"
|
|
12
|
+
import type { HostFileApi, HostFileEntry, HostFileStat } from "../api/host/fs.ts"
|
|
13
|
+
import type {
|
|
14
|
+
HostExecApi,
|
|
15
|
+
HostExecChunk,
|
|
16
|
+
HostExecRunOptions,
|
|
17
|
+
HostExecSession,
|
|
18
|
+
} from "../api/host/exec.ts"
|
|
19
|
+
import type {
|
|
20
|
+
HostModelProviderInput,
|
|
21
|
+
HostModelsApi,
|
|
22
|
+
HostModelsFetchResult,
|
|
23
|
+
HostModelsState,
|
|
24
|
+
HostModelsStateResult,
|
|
25
|
+
} from "../api/host/models.ts"
|
|
26
|
+
import type {
|
|
27
|
+
HostNetworkApi,
|
|
28
|
+
HostNetworkProxyInput,
|
|
29
|
+
HostNetworkResult,
|
|
30
|
+
HostNetworkState,
|
|
31
|
+
} from "../api/host/network.ts"
|
|
32
|
+
import type {
|
|
33
|
+
HostPluginInstallInput,
|
|
34
|
+
HostPluginsApi,
|
|
35
|
+
HostPluginListResult,
|
|
36
|
+
HostPluginMutationResult,
|
|
37
|
+
} from "../api/host/plugins.ts"
|
|
38
|
+
import type {
|
|
39
|
+
HostWorkspaceAddInput,
|
|
40
|
+
HostWorkspaceAddResult,
|
|
41
|
+
HostWorkspaceEntry,
|
|
42
|
+
HostWorkspaceListResult,
|
|
43
|
+
HostWorkspaceRemoveResult,
|
|
44
|
+
HostWorkspacesApi,
|
|
45
|
+
} from "../api/host/workspace.ts"
|
|
46
|
+
import type {
|
|
47
|
+
HostPtyApi,
|
|
48
|
+
HostPtyAvailability,
|
|
49
|
+
HostPtyChunk,
|
|
50
|
+
HostPtySession,
|
|
51
|
+
HostPtySpawnOptions,
|
|
52
|
+
} from "../api/host/pty.ts"
|
|
53
|
+
import type {
|
|
54
|
+
HostAgentApi,
|
|
55
|
+
HostAgentChatInput,
|
|
56
|
+
HostAgentStreamEvent,
|
|
57
|
+
HostWorkspaceCheck,
|
|
58
|
+
HostWorkspaceFilesResult,
|
|
59
|
+
} from "../api/host/agent.ts"
|
|
60
|
+
import type {
|
|
61
|
+
HostHttpApi,
|
|
62
|
+
HttpHandler,
|
|
63
|
+
HttpRequest,
|
|
64
|
+
HttpResponse,
|
|
65
|
+
PluginWebSocket,
|
|
66
|
+
WebSocketHandler,
|
|
67
|
+
} from "../api/host/http.ts"
|
|
68
|
+
import {
|
|
69
|
+
emitAfterStep,
|
|
70
|
+
emitAfterTurn,
|
|
71
|
+
emitAgentAsyncTaskComplete,
|
|
72
|
+
emitAgentAsk,
|
|
73
|
+
emitAgentCallTool,
|
|
74
|
+
emitAgentQueryTask,
|
|
75
|
+
emitAgentRead,
|
|
76
|
+
emitAgentRequestPermission,
|
|
77
|
+
emitAgentRunAsync,
|
|
78
|
+
emitAgentRunSync,
|
|
79
|
+
emitAgentSetGoal,
|
|
80
|
+
emitAgentSetTasks,
|
|
81
|
+
emitAgentSubmit,
|
|
82
|
+
emitBeforeStep,
|
|
83
|
+
emitBeforeTurn,
|
|
84
|
+
emitInitHooks,
|
|
85
|
+
emitOnMessage,
|
|
86
|
+
emitSessionStart,
|
|
87
|
+
emitShutdownHooks,
|
|
88
|
+
emitUserAnswer,
|
|
89
|
+
listHookKinds,
|
|
90
|
+
withHookOwner,
|
|
91
|
+
type HookKind,
|
|
92
|
+
} from "../api/host/hooks.ts"
|
|
93
|
+
import type {
|
|
94
|
+
AgentCallToolResult,
|
|
95
|
+
AgentTool,
|
|
96
|
+
HostOutput,
|
|
97
|
+
PluginApi,
|
|
98
|
+
PluginCommand,
|
|
99
|
+
PluginCommandContext,
|
|
100
|
+
PluginConfig,
|
|
101
|
+
PluginStatus,
|
|
102
|
+
WellspringPlugin,
|
|
103
|
+
} from "../api/host/plugin.ts"
|
|
104
|
+
import { eventBus } from "../event/event.ts"
|
|
105
|
+
import type { ResolvedPaths } from "../platform/path.ts"
|
|
106
|
+
import type { AppSetting } from "../config/types.ts"
|
|
107
|
+
import { ensureAccessGuard } from "../plugin/access-guard.ts"
|
|
108
|
+
import { ensureHostAlias } from "../plugin/host-alias.ts"
|
|
109
|
+
import { setHostBridge, type HostBridge } from "./bridge-state.ts"
|
|
110
|
+
import {
|
|
111
|
+
FrameDecoder,
|
|
112
|
+
encodeMessage,
|
|
113
|
+
nextRequestId,
|
|
114
|
+
type RpcMessage,
|
|
115
|
+
type RpcRequest,
|
|
116
|
+
} from "./protocol.ts"
|
|
117
|
+
import { RPC_TIMEOUT_MS } from "./rpc-timeouts.ts"
|
|
118
|
+
|
|
119
|
+
interface HostSnapshotData {
|
|
120
|
+
readonly status: HostStatus
|
|
121
|
+
readonly os: HostOSInfo
|
|
122
|
+
readonly paths: ResolvedPaths
|
|
123
|
+
readonly plugins: ReadonlyArray<{
|
|
124
|
+
readonly id: string
|
|
125
|
+
readonly name: string
|
|
126
|
+
readonly version: string
|
|
127
|
+
readonly deps: readonly string[]
|
|
128
|
+
readonly commands: readonly string[]
|
|
129
|
+
readonly status: HostPluginStatus
|
|
130
|
+
}>
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
interface ConfigurePayload {
|
|
134
|
+
readonly pluginId: string
|
|
135
|
+
readonly pluginName: string
|
|
136
|
+
readonly pluginVersion: string
|
|
137
|
+
readonly pluginEntry: string
|
|
138
|
+
readonly pluginDataDir: string
|
|
139
|
+
readonly pluginConfig: PluginConfig
|
|
140
|
+
readonly paths: ResolvedPaths
|
|
141
|
+
readonly config: AppSetting
|
|
142
|
+
readonly host: HostSnapshotData | null
|
|
143
|
+
readonly deferInit?: boolean
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
interface HostRequest {
|
|
147
|
+
readonly method: string
|
|
148
|
+
readonly params: Record<string, unknown>
|
|
149
|
+
readonly id: string
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const rawStdoutWrite = process.stdout.write.bind(process.stdout)
|
|
153
|
+
const rawStderrWrite = process.stderr.write.bind(process.stderr)
|
|
154
|
+
|
|
155
|
+
function writeFrame(message: RpcMessage): void {
|
|
156
|
+
rawStdoutWrite(encodeMessage(message))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const pending = new Map<
|
|
160
|
+
string,
|
|
161
|
+
{
|
|
162
|
+
resolve: (value: unknown) => void
|
|
163
|
+
reject: (error: Error) => void
|
|
164
|
+
timer: NodeJS.Timeout | null
|
|
165
|
+
}
|
|
166
|
+
>()
|
|
167
|
+
|
|
168
|
+
function bridgeRequest(
|
|
169
|
+
method: string,
|
|
170
|
+
params: unknown,
|
|
171
|
+
timeoutMs: number | null = RPC_TIMEOUT_MS.default,
|
|
172
|
+
): Promise<unknown> {
|
|
173
|
+
const id = nextRequestId("child")
|
|
174
|
+
return new Promise<unknown>((resolveRequest, rejectRequest) => {
|
|
175
|
+
// timeoutMs 为 null 时不挂定时器:长任务 RPC(例如 agent.stream)由运行过程自身结束
|
|
176
|
+
const timer =
|
|
177
|
+
timeoutMs === null
|
|
178
|
+
? null
|
|
179
|
+
: setTimeout(() => {
|
|
180
|
+
pending.delete(id)
|
|
181
|
+
rejectRequest(new Error(`host RPC 超时:${method}`))
|
|
182
|
+
}, timeoutMs)
|
|
183
|
+
timer?.unref()
|
|
184
|
+
const clearTimer = (): void => {
|
|
185
|
+
if (timer !== null) {
|
|
186
|
+
clearTimeout(timer)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
pending.set(id, {
|
|
190
|
+
resolve: (value) => {
|
|
191
|
+
clearTimer()
|
|
192
|
+
resolveRequest(value)
|
|
193
|
+
},
|
|
194
|
+
reject: (error) => {
|
|
195
|
+
clearTimer()
|
|
196
|
+
rejectRequest(error)
|
|
197
|
+
},
|
|
198
|
+
timer,
|
|
199
|
+
})
|
|
200
|
+
writeFrame({
|
|
201
|
+
v: 1,
|
|
202
|
+
type: "request",
|
|
203
|
+
id,
|
|
204
|
+
method,
|
|
205
|
+
params,
|
|
206
|
+
...(timeoutMs === null ? {} : { timeoutMs }),
|
|
207
|
+
})
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function bridgeNotify(method: string, params: unknown): void {
|
|
212
|
+
writeFrame({ v: 1, type: "event", method, params })
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let hostSnapshot: Host | null = null
|
|
216
|
+
|
|
217
|
+
const bridge: HostBridge = {
|
|
218
|
+
request: bridgeRequest,
|
|
219
|
+
notify: bridgeNotify,
|
|
220
|
+
getHostSnapshot: () => {
|
|
221
|
+
if (hostSnapshot === null) {
|
|
222
|
+
throw new Error("插件未获得 getHost 权限")
|
|
223
|
+
}
|
|
224
|
+
return hostSnapshot
|
|
225
|
+
},
|
|
226
|
+
shutdown: () => {
|
|
227
|
+
void bridgeRequest("host.shutdown", {}).catch(() => undefined)
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setHostBridge(bridge)
|
|
232
|
+
ensureHostAlias()
|
|
233
|
+
ensureAccessGuard()
|
|
234
|
+
|
|
235
|
+
interface PluginRuntime {
|
|
236
|
+
readonly owner: string
|
|
237
|
+
readonly plugin: WellspringPlugin
|
|
238
|
+
readonly commands: PluginCommand[]
|
|
239
|
+
readonly agentTools: AgentTool[]
|
|
240
|
+
hooks: readonly HookKind[]
|
|
241
|
+
readonly api: PluginApi
|
|
242
|
+
initialized: boolean
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const HOOK_API_NAME: Readonly<Record<HookKind, string>> = {
|
|
246
|
+
message: "onMessage",
|
|
247
|
+
sessionStart: "onSessionStart",
|
|
248
|
+
beforeTurn: "beforeTurn",
|
|
249
|
+
beforeStep: "beforeStep",
|
|
250
|
+
afterStep: "afterStep",
|
|
251
|
+
afterTurn: "afterTurn",
|
|
252
|
+
agentRunSync: "onAgentRunSync",
|
|
253
|
+
agentRunAsync: "onAgentRunAsync",
|
|
254
|
+
agentAsyncTaskComplete: "onAgentAsyncTaskComplete",
|
|
255
|
+
agentCallTool: "onAgentCallTool",
|
|
256
|
+
agentAsk: "onAgentAsk",
|
|
257
|
+
agentRead: "onAgentRead",
|
|
258
|
+
agentQueryTask: "onAgentQueryTask",
|
|
259
|
+
agentSubmit: "onAgentSubmit",
|
|
260
|
+
agentSetTasks: "onAgentSetTasks",
|
|
261
|
+
agentSetGoal: "onAgentSetGoal",
|
|
262
|
+
agentRequestPermission: "onAgentRequestPermission",
|
|
263
|
+
userAnswer: "onUserAnswer",
|
|
264
|
+
init: "onInit",
|
|
265
|
+
shutdown: "onShutdown",
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let runtime: PluginRuntime | null = null
|
|
269
|
+
|
|
270
|
+
const httpHandlers = new Map<string, HttpHandler>()
|
|
271
|
+
const agentStreamHandlers = new Map<string, (event: HostAgentStreamEvent) => void>()
|
|
272
|
+
const websocketHandlers = new Map<string, WebSocketHandler>()
|
|
273
|
+
const wsMessages = new Map<string, Array<(data: string) => void>>()
|
|
274
|
+
const wsCloses = new Map<string, Array<(code: number, reason: string) => void>>()
|
|
275
|
+
|
|
276
|
+
function remoteExec(): HostExecApi {
|
|
277
|
+
return {
|
|
278
|
+
async run(options: HostExecRunOptions): Promise<HostExecSession> {
|
|
279
|
+
return (await bridgeRequest("host.exec.run", {
|
|
280
|
+
command: options.command,
|
|
281
|
+
cwd: options.cwd,
|
|
282
|
+
env: options.env ?? {},
|
|
283
|
+
timeoutMs: options.timeoutMs,
|
|
284
|
+
})) as HostExecSession
|
|
285
|
+
},
|
|
286
|
+
async poll(sessionId: string): Promise<HostExecChunk> {
|
|
287
|
+
return (await bridgeRequest("host.exec.poll", { sessionId })) as HostExecChunk
|
|
288
|
+
},
|
|
289
|
+
async stop(sessionId: string): Promise<void> {
|
|
290
|
+
await bridgeRequest("host.exec.stop", { sessionId })
|
|
291
|
+
},
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function remotePty(): HostPtyApi {
|
|
296
|
+
return {
|
|
297
|
+
async available(): Promise<HostPtyAvailability> {
|
|
298
|
+
const result = (await bridgeRequest("host.pty.available", {})) as {
|
|
299
|
+
available?: boolean
|
|
300
|
+
backend?: string
|
|
301
|
+
reason?: string
|
|
302
|
+
}
|
|
303
|
+
const backend =
|
|
304
|
+
result.backend === "node-pty" || result.backend === "python-pty"
|
|
305
|
+
? result.backend
|
|
306
|
+
: undefined
|
|
307
|
+
return {
|
|
308
|
+
available: result.available === true,
|
|
309
|
+
backend,
|
|
310
|
+
reason: typeof result.reason === "string" ? result.reason : undefined,
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
async spawn(options: HostPtySpawnOptions): Promise<HostPtySession> {
|
|
314
|
+
return (await bridgeRequest("host.pty.spawn", {
|
|
315
|
+
file: options.file ?? "",
|
|
316
|
+
args: options.args ?? [],
|
|
317
|
+
cwd: options.cwd,
|
|
318
|
+
env: options.env ?? {},
|
|
319
|
+
cols: options.cols,
|
|
320
|
+
rows: options.rows,
|
|
321
|
+
})) as HostPtySession
|
|
322
|
+
},
|
|
323
|
+
async poll(sessionId: string): Promise<HostPtyChunk> {
|
|
324
|
+
return (await bridgeRequest("host.pty.poll", { sessionId })) as HostPtyChunk
|
|
325
|
+
},
|
|
326
|
+
async write(sessionId: string, data: string): Promise<void> {
|
|
327
|
+
await bridgeRequest("host.pty.write", { sessionId, data })
|
|
328
|
+
},
|
|
329
|
+
async resize(sessionId: string, cols: number, rows: number): Promise<void> {
|
|
330
|
+
await bridgeRequest("host.pty.resize", { sessionId, cols, rows })
|
|
331
|
+
},
|
|
332
|
+
async kill(sessionId: string): Promise<void> {
|
|
333
|
+
await bridgeRequest("host.pty.kill", { sessionId })
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function remoteAgent(): HostAgentApi {
|
|
339
|
+
return {
|
|
340
|
+
async chat(input: HostAgentChatInput) {
|
|
341
|
+
return await bridgeRequest(
|
|
342
|
+
"host.agent.chat",
|
|
343
|
+
input as unknown as Record<string, unknown>,
|
|
344
|
+
RPC_TIMEOUT_MS.agentChat,
|
|
345
|
+
)
|
|
346
|
+
},
|
|
347
|
+
async stream(input: HostAgentChatInput, onEvent: (event: HostAgentStreamEvent) => void) {
|
|
348
|
+
const streamId = nextRequestId("agent-stream")
|
|
349
|
+
agentStreamHandlers.set(streamId, onEvent)
|
|
350
|
+
try {
|
|
351
|
+
// 一轮 Agent 可能跑很久(多步工具调用),不设 RPC 上限
|
|
352
|
+
return await bridgeRequest(
|
|
353
|
+
"host.agent.stream",
|
|
354
|
+
{ streamId, input },
|
|
355
|
+
RPC_TIMEOUT_MS.agentStream,
|
|
356
|
+
)
|
|
357
|
+
} finally {
|
|
358
|
+
agentStreamHandlers.delete(streamId)
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
async validateWorkspace(path: string) {
|
|
362
|
+
return (await bridgeRequest("host.agent.validateWorkspace", {
|
|
363
|
+
path,
|
|
364
|
+
})) as HostWorkspaceCheck
|
|
365
|
+
},
|
|
366
|
+
async pickDirectory() {
|
|
367
|
+
return (await bridgeRequest("host.agent.pickDirectory", {})) as HostWorkspaceCheck
|
|
368
|
+
},
|
|
369
|
+
async searchWorkspaceFiles(root: string, query: string) {
|
|
370
|
+
return (await bridgeRequest("host.agent.searchWorkspaceFiles", {
|
|
371
|
+
root,
|
|
372
|
+
query,
|
|
373
|
+
})) as HostWorkspaceFilesResult
|
|
374
|
+
},
|
|
375
|
+
async interject(sessionId: string, message: string) {
|
|
376
|
+
return (await bridgeRequest("host.agent.interject", {
|
|
377
|
+
sessionId,
|
|
378
|
+
message,
|
|
379
|
+
})) as boolean
|
|
380
|
+
},
|
|
381
|
+
async stop(sessionId: string) {
|
|
382
|
+
return (await bridgeRequest("host.agent.stop", { sessionId })) as boolean
|
|
383
|
+
},
|
|
384
|
+
async respondApproval(approvalId: string, approved: boolean) {
|
|
385
|
+
return (await bridgeRequest("host.agent.respondApproval", {
|
|
386
|
+
approvalId,
|
|
387
|
+
approved,
|
|
388
|
+
})) as boolean
|
|
389
|
+
},
|
|
390
|
+
async respondAsk(askId: string, answer: string) {
|
|
391
|
+
return (await bridgeRequest("host.agent.respondAsk", {
|
|
392
|
+
askId,
|
|
393
|
+
answer,
|
|
394
|
+
})) as boolean
|
|
395
|
+
},
|
|
396
|
+
} as HostAgentApi
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function remoteModels(): HostModelsApi {
|
|
400
|
+
return {
|
|
401
|
+
async state(): Promise<HostModelsState> {
|
|
402
|
+
return (await bridgeRequest("host.models.state", {})) as HostModelsState
|
|
403
|
+
},
|
|
404
|
+
async setActive(
|
|
405
|
+
providerId: string,
|
|
406
|
+
model: string | null,
|
|
407
|
+
reasoning?: string | null,
|
|
408
|
+
): Promise<HostModelsState> {
|
|
409
|
+
return (await bridgeRequest("host.models.setActive", {
|
|
410
|
+
providerId,
|
|
411
|
+
model,
|
|
412
|
+
reasoning,
|
|
413
|
+
})) as HostModelsState
|
|
414
|
+
},
|
|
415
|
+
async saveProvider(input: HostModelProviderInput): Promise<HostModelsStateResult> {
|
|
416
|
+
return (await bridgeRequest(
|
|
417
|
+
"host.models.saveProvider",
|
|
418
|
+
input as unknown as Record<string, unknown>,
|
|
419
|
+
)) as HostModelsStateResult
|
|
420
|
+
},
|
|
421
|
+
async removeProvider(id: string): Promise<HostModelsStateResult> {
|
|
422
|
+
return (await bridgeRequest("host.models.removeProvider", {
|
|
423
|
+
id,
|
|
424
|
+
})) as HostModelsStateResult
|
|
425
|
+
},
|
|
426
|
+
async fetchModels(id: string, apiKey?: string): Promise<HostModelsFetchResult> {
|
|
427
|
+
const payload: Record<string, unknown> = { id }
|
|
428
|
+
if (apiKey !== undefined) {
|
|
429
|
+
payload["apiKey"] = apiKey
|
|
430
|
+
}
|
|
431
|
+
return (await bridgeRequest(
|
|
432
|
+
"host.models.fetchModels",
|
|
433
|
+
payload,
|
|
434
|
+
RPC_TIMEOUT_MS.modelsFetch,
|
|
435
|
+
)) as HostModelsFetchResult
|
|
436
|
+
},
|
|
437
|
+
async saveKey(id: string, apiKey: string): Promise<HostModelsStateResult> {
|
|
438
|
+
return (await bridgeRequest("host.models.saveKey", {
|
|
439
|
+
id,
|
|
440
|
+
apiKey,
|
|
441
|
+
})) as HostModelsStateResult
|
|
442
|
+
},
|
|
443
|
+
async clearKey(id: string): Promise<HostModelsState> {
|
|
444
|
+
return (await bridgeRequest("host.models.clearKey", { id })) as HostModelsState
|
|
445
|
+
},
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function remoteNetwork(): HostNetworkApi {
|
|
450
|
+
return {
|
|
451
|
+
async state(): Promise<HostNetworkState> {
|
|
452
|
+
return (await bridgeRequest("host.network.state", {})) as HostNetworkState
|
|
453
|
+
},
|
|
454
|
+
async saveProxy(input: HostNetworkProxyInput): Promise<HostNetworkResult> {
|
|
455
|
+
return (await bridgeRequest(
|
|
456
|
+
"host.network.saveProxy",
|
|
457
|
+
input as unknown as Record<string, unknown>,
|
|
458
|
+
)) as HostNetworkResult
|
|
459
|
+
},
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function remotePlugins(): HostPluginsApi {
|
|
464
|
+
return {
|
|
465
|
+
async list(): Promise<HostPluginListResult> {
|
|
466
|
+
return (await bridgeRequest("host.plugins.list", {})) as HostPluginListResult
|
|
467
|
+
},
|
|
468
|
+
async install(input: HostPluginInstallInput): Promise<HostPluginMutationResult> {
|
|
469
|
+
return (await bridgeRequest(
|
|
470
|
+
"host.plugins.install",
|
|
471
|
+
input as unknown as Record<string, unknown>,
|
|
472
|
+
RPC_TIMEOUT_MS.pluginsInstall,
|
|
473
|
+
)) as HostPluginMutationResult
|
|
474
|
+
},
|
|
475
|
+
async remove(id: string): Promise<HostPluginMutationResult> {
|
|
476
|
+
return (await bridgeRequest("host.plugins.remove", { id })) as HostPluginMutationResult
|
|
477
|
+
},
|
|
478
|
+
async enable(id: string): Promise<HostPluginMutationResult> {
|
|
479
|
+
return (await bridgeRequest("host.plugins.enable", { id })) as HostPluginMutationResult
|
|
480
|
+
},
|
|
481
|
+
async disable(id: string): Promise<HostPluginMutationResult> {
|
|
482
|
+
return (await bridgeRequest("host.plugins.disable", { id })) as HostPluginMutationResult
|
|
483
|
+
},
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function remoteWorkspaces(): HostWorkspacesApi {
|
|
488
|
+
return {
|
|
489
|
+
async list(): Promise<HostWorkspaceListResult> {
|
|
490
|
+
return (await bridgeRequest("host.workspaces.list", {})) as HostWorkspaceListResult
|
|
491
|
+
},
|
|
492
|
+
async add(input: HostWorkspaceAddInput): Promise<HostWorkspaceAddResult> {
|
|
493
|
+
return (await bridgeRequest(
|
|
494
|
+
"host.workspaces.add",
|
|
495
|
+
input as unknown as Record<string, unknown>,
|
|
496
|
+
)) as HostWorkspaceAddResult
|
|
497
|
+
},
|
|
498
|
+
async remove(id: string): Promise<HostWorkspaceRemoveResult> {
|
|
499
|
+
return (await bridgeRequest("host.workspaces.remove", {
|
|
500
|
+
id,
|
|
501
|
+
})) as HostWorkspaceRemoveResult
|
|
502
|
+
},
|
|
503
|
+
async rename(id: string, name: string): Promise<HostWorkspaceEntry | null> {
|
|
504
|
+
return (await bridgeRequest("host.workspaces.rename", {
|
|
505
|
+
id,
|
|
506
|
+
name,
|
|
507
|
+
})) as HostWorkspaceEntry | null
|
|
508
|
+
},
|
|
509
|
+
async touch(id: string): Promise<HostWorkspaceEntry | null> {
|
|
510
|
+
return (await bridgeRequest("host.workspaces.touch", {
|
|
511
|
+
id,
|
|
512
|
+
})) as HostWorkspaceEntry | null
|
|
513
|
+
},
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function remoteHttp(): HostHttpApi {
|
|
518
|
+
return {
|
|
519
|
+
async createServer(options, handler) {
|
|
520
|
+
const serverId = nextRequestId("http")
|
|
521
|
+
httpHandlers.set(serverId, handler)
|
|
522
|
+
if (typeof options.websocket === "function") {
|
|
523
|
+
websocketHandlers.set(serverId, options.websocket)
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
const ready = (await bridgeRequest("host.http.createServer", {
|
|
527
|
+
serverId,
|
|
528
|
+
host: options.host,
|
|
529
|
+
port: options.port,
|
|
530
|
+
tls: options.tls,
|
|
531
|
+
websocket: typeof options.websocket === "function",
|
|
532
|
+
})) as { serverId: string; host: string; port: number }
|
|
533
|
+
return {
|
|
534
|
+
serverId: ready.serverId,
|
|
535
|
+
host: ready.host,
|
|
536
|
+
port: ready.port,
|
|
537
|
+
async close() {
|
|
538
|
+
httpHandlers.delete(serverId)
|
|
539
|
+
await bridgeRequest("host.http.closeServer", { serverId })
|
|
540
|
+
},
|
|
541
|
+
}
|
|
542
|
+
} catch (error) {
|
|
543
|
+
httpHandlers.delete(serverId)
|
|
544
|
+
throw error
|
|
545
|
+
}
|
|
546
|
+
},
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
551
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function validatePlugin(value: unknown): WellspringPlugin {
|
|
555
|
+
if (!isObject(value)) {
|
|
556
|
+
throw new Error("setup() 必须返回 WellspringPlugin 对象")
|
|
557
|
+
}
|
|
558
|
+
const id = value["id"]
|
|
559
|
+
const name = value["name"]
|
|
560
|
+
const version = value["version"]
|
|
561
|
+
const status = value["status"]
|
|
562
|
+
if (typeof id !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(id)) {
|
|
563
|
+
throw new Error(`插件 id 不合法:${String(id)}`)
|
|
564
|
+
}
|
|
565
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
566
|
+
throw new Error(`插件 name 不合法:${String(name)}`)
|
|
567
|
+
}
|
|
568
|
+
if (typeof version !== "string" || version.trim() === "") {
|
|
569
|
+
throw new Error(`插件 version 不合法:${String(version)}`)
|
|
570
|
+
}
|
|
571
|
+
if (status !== "ok" && status !== "failed") {
|
|
572
|
+
throw new Error(`插件 status 必须是 ok / failed:${String(status)}`)
|
|
573
|
+
}
|
|
574
|
+
return value as unknown as WellspringPlugin
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function createLogger(): Logger {
|
|
578
|
+
const make = (bindings: Record<string, unknown> = {}): Record<string, unknown> => {
|
|
579
|
+
const log =
|
|
580
|
+
(level: string) =>
|
|
581
|
+
(first: unknown, second?: unknown): void => {
|
|
582
|
+
const fields = isObject(first) ? first : bindings
|
|
583
|
+
const message = isObject(first) ? String(second ?? "") : String(first ?? "")
|
|
584
|
+
bridgeNotify("host.log", { level, message, fields })
|
|
585
|
+
}
|
|
586
|
+
const logger: Record<string, unknown> = {
|
|
587
|
+
trace: log("trace"),
|
|
588
|
+
debug: log("debug"),
|
|
589
|
+
info: log("info"),
|
|
590
|
+
warn: log("warn"),
|
|
591
|
+
error: log("error"),
|
|
592
|
+
fatal: log("fatal"),
|
|
593
|
+
child: (childBindings: Record<string, unknown>) =>
|
|
594
|
+
make({ ...bindings, ...childBindings }),
|
|
595
|
+
}
|
|
596
|
+
return logger
|
|
597
|
+
}
|
|
598
|
+
return make() as unknown as Logger
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const logger = createLogger()
|
|
602
|
+
|
|
603
|
+
const output: HostOutput = {
|
|
604
|
+
info: (text) => bridgeNotify("host.output", { kind: "info", text }),
|
|
605
|
+
warn: (text) => bridgeNotify("host.output", { kind: "warn", text }),
|
|
606
|
+
error: (text) => bridgeNotify("host.output", { kind: "error", text }),
|
|
607
|
+
print: (text = "") => bridgeNotify("host.output", { kind: "print", text }),
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function createFileApi(scope: "fs" | "workspaceFs"): HostFileApi {
|
|
611
|
+
const call = (method: string, params: Record<string, unknown> = {}) =>
|
|
612
|
+
bridgeRequest(`host.${scope}.${method}`, params)
|
|
613
|
+
return {
|
|
614
|
+
root: scope,
|
|
615
|
+
async readText(path) {
|
|
616
|
+
const result = (await call("readText", { path })) as { text: string | null }
|
|
617
|
+
return result.text
|
|
618
|
+
},
|
|
619
|
+
async readBytes(path) {
|
|
620
|
+
const result = (await call("readBytes", { path })) as { data: string | null }
|
|
621
|
+
if (result.data === null) {
|
|
622
|
+
return null
|
|
623
|
+
}
|
|
624
|
+
return new Uint8Array(Buffer.from(result.data, "base64"))
|
|
625
|
+
},
|
|
626
|
+
async readJson<T = unknown>(path: string) {
|
|
627
|
+
const result = (await call("readJson", { path })) as { value: T | null }
|
|
628
|
+
return result.value
|
|
629
|
+
},
|
|
630
|
+
async writeText(path, text) {
|
|
631
|
+
await call("writeText", { path, text })
|
|
632
|
+
},
|
|
633
|
+
async writeBytes(path, bytes) {
|
|
634
|
+
await call("writeBytes", {
|
|
635
|
+
path,
|
|
636
|
+
data: Buffer.from(bytes).toString("base64"),
|
|
637
|
+
})
|
|
638
|
+
},
|
|
639
|
+
async writeJson(path, value) {
|
|
640
|
+
await call("writeJson", { path, value })
|
|
641
|
+
},
|
|
642
|
+
async list(path = "") {
|
|
643
|
+
const result = (await call("list", { path })) as { entries: HostFileEntry[] }
|
|
644
|
+
return result.entries
|
|
645
|
+
},
|
|
646
|
+
async stat(path) {
|
|
647
|
+
const result = (await call("stat", { path })) as { stat: HostFileStat | null }
|
|
648
|
+
return result.stat
|
|
649
|
+
},
|
|
650
|
+
async mkdir(path) {
|
|
651
|
+
await call("mkdir", { path })
|
|
652
|
+
},
|
|
653
|
+
async remove(path) {
|
|
654
|
+
await call("remove", { path })
|
|
655
|
+
},
|
|
656
|
+
async exists(path) {
|
|
657
|
+
const result = (await call("exists", { path })) as { exists: boolean }
|
|
658
|
+
return result.exists
|
|
659
|
+
},
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function patchConsole(): void {
|
|
664
|
+
const methods: Array<[string, string]> = [
|
|
665
|
+
["log", "info"],
|
|
666
|
+
["info", "info"],
|
|
667
|
+
["debug", "debug"],
|
|
668
|
+
["warn", "warn"],
|
|
669
|
+
["error", "error"],
|
|
670
|
+
["trace", "trace"],
|
|
671
|
+
["dir", "info"],
|
|
672
|
+
["table", "info"],
|
|
673
|
+
]
|
|
674
|
+
for (const [method, level] of methods) {
|
|
675
|
+
;(console as unknown as Record<string, (...args: unknown[]) => void>)[method] = (
|
|
676
|
+
...args: unknown[]
|
|
677
|
+
): void => {
|
|
678
|
+
bridgeNotify("host.console", { level, args: args.map((item) => String(item)) })
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
console.assert = (condition: unknown, ...args: unknown[]): void => {
|
|
682
|
+
if (!condition) {
|
|
683
|
+
bridgeNotify("host.console", { level: "error", args: args.map(String) })
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function patchProcessStreams(): void {
|
|
689
|
+
process.stdout.write = ((chunk: unknown): boolean => {
|
|
690
|
+
bridgeNotify("host.log", { level: "info", message: String(chunk), fields: {} })
|
|
691
|
+
return true
|
|
692
|
+
}) as typeof process.stdout.write
|
|
693
|
+
process.stderr.write = ((chunk: unknown): boolean => {
|
|
694
|
+
bridgeNotify("host.log", { level: "error", message: String(chunk), fields: {} })
|
|
695
|
+
return true
|
|
696
|
+
}) as typeof process.stderr.write
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function remotePluginData(targetId: string): HostFileApi {
|
|
700
|
+
const call = async (method: string, args: unknown[]): Promise<unknown> =>
|
|
701
|
+
await bridgeRequest("host.pluginData", { targetId, method, args })
|
|
702
|
+
return {
|
|
703
|
+
root: "",
|
|
704
|
+
async readText(path) {
|
|
705
|
+
return (await call("readText", [path])) as string | null
|
|
706
|
+
},
|
|
707
|
+
async readBytes(path) {
|
|
708
|
+
const value = (await call("readBytes", [path])) as number[] | null
|
|
709
|
+
return value === null ? null : Uint8Array.from(value)
|
|
710
|
+
},
|
|
711
|
+
async readJson<T = unknown>(path: string) {
|
|
712
|
+
return (await call("readJson", [path])) as T | null
|
|
713
|
+
},
|
|
714
|
+
async writeText(path, text) {
|
|
715
|
+
await call("writeText", [path, text])
|
|
716
|
+
},
|
|
717
|
+
async writeBytes(path, bytes) {
|
|
718
|
+
await call("writeBytes", [path, [...bytes]])
|
|
719
|
+
},
|
|
720
|
+
async writeJson(path, value) {
|
|
721
|
+
await call("writeJson", [path, value])
|
|
722
|
+
},
|
|
723
|
+
async list(path = "") {
|
|
724
|
+
return (await call("list", [path])) as HostFileEntry[]
|
|
725
|
+
},
|
|
726
|
+
async stat(path) {
|
|
727
|
+
return (await call("stat", [path])) as HostFileStat | null
|
|
728
|
+
},
|
|
729
|
+
async mkdir(path) {
|
|
730
|
+
await call("mkdir", [path])
|
|
731
|
+
},
|
|
732
|
+
async remove(path) {
|
|
733
|
+
await call("remove", [path])
|
|
734
|
+
},
|
|
735
|
+
async exists(path) {
|
|
736
|
+
return (await call("exists", [path])) as boolean
|
|
737
|
+
},
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
async function bootstrap(params: ConfigurePayload): Promise<{
|
|
742
|
+
plugin: { id: string; name: string; version: string; status: PluginStatus; deps: string[] }
|
|
743
|
+
commands: Array<{
|
|
744
|
+
name: string
|
|
745
|
+
summary: string
|
|
746
|
+
usage?: string
|
|
747
|
+
aliases?: readonly string[]
|
|
748
|
+
}>
|
|
749
|
+
agentTools: Array<{
|
|
750
|
+
name: string
|
|
751
|
+
introduction: { params: Record<string, unknown>; content: string }
|
|
752
|
+
}>
|
|
753
|
+
hooks: string[]
|
|
754
|
+
}> {
|
|
755
|
+
const hostData = params.host
|
|
756
|
+
hostSnapshot =
|
|
757
|
+
hostData === null
|
|
758
|
+
? null
|
|
759
|
+
: {
|
|
760
|
+
getStatus: () => hostData.status,
|
|
761
|
+
getOS: () => hostData.os,
|
|
762
|
+
getPaths: () => hostData.paths,
|
|
763
|
+
getPlugins: () =>
|
|
764
|
+
hostData.plugins.map((plugin) => ({
|
|
765
|
+
id: plugin.id,
|
|
766
|
+
name: plugin.name,
|
|
767
|
+
version: plugin.version,
|
|
768
|
+
deps: plugin.deps,
|
|
769
|
+
commands: plugin.commands,
|
|
770
|
+
getStatus: () => plugin.status,
|
|
771
|
+
})),
|
|
772
|
+
}
|
|
773
|
+
const pluginId = params.pluginId
|
|
774
|
+
const commands: PluginCommand[] = []
|
|
775
|
+
const agentTools: AgentTool[] = []
|
|
776
|
+
|
|
777
|
+
const api: PluginApi = {
|
|
778
|
+
apiVersion: 1,
|
|
779
|
+
plugin: { id: pluginId, name: params.pluginName, version: params.pluginVersion },
|
|
780
|
+
paths: params.paths,
|
|
781
|
+
config: params.config,
|
|
782
|
+
logger,
|
|
783
|
+
output,
|
|
784
|
+
pluginDataDir: params.pluginDataDir,
|
|
785
|
+
pluginConfig: params.pluginConfig,
|
|
786
|
+
fs: createFileApi("fs"),
|
|
787
|
+
workspaceFs: createFileApi("workspaceFs"),
|
|
788
|
+
registerCommand: (command: PluginCommand): void => {
|
|
789
|
+
commands.push(command)
|
|
790
|
+
},
|
|
791
|
+
registerAgentTool: (tool: AgentTool): void => {
|
|
792
|
+
agentTools.push(tool)
|
|
793
|
+
},
|
|
794
|
+
pluginData: async (targetId: string): Promise<HostFileApi | null> => {
|
|
795
|
+
return remotePluginData(targetId)
|
|
796
|
+
},
|
|
797
|
+
capabilities: {
|
|
798
|
+
async executeCode(input: unknown) {
|
|
799
|
+
// 沙箱自己的命令超时最长 600s,RPC 用更长的上限,避免宿主先放弃等待
|
|
800
|
+
return await bridgeRequest(
|
|
801
|
+
"host.capabilities.executeCode",
|
|
802
|
+
input as Record<string, unknown>,
|
|
803
|
+
RPC_TIMEOUT_MS.sandboxExec,
|
|
804
|
+
)
|
|
805
|
+
},
|
|
806
|
+
async startBackgroundCode(input: unknown) {
|
|
807
|
+
return await bridgeRequest(
|
|
808
|
+
"host.capabilities.startBackgroundCode",
|
|
809
|
+
input as Record<string, unknown>,
|
|
810
|
+
)
|
|
811
|
+
},
|
|
812
|
+
async queryTask(taskId?: string) {
|
|
813
|
+
return await bridgeRequest("host.capabilities.queryTask", { taskId })
|
|
814
|
+
},
|
|
815
|
+
async stopTask(taskId: string | undefined, mode: string) {
|
|
816
|
+
return await bridgeRequest("host.capabilities.stopTask", { taskId, mode })
|
|
817
|
+
},
|
|
818
|
+
async webSearch(query: string, count?: number) {
|
|
819
|
+
return await bridgeRequest("host.capabilities.webSearch", { query, count })
|
|
820
|
+
},
|
|
821
|
+
async webFetch(url: string) {
|
|
822
|
+
return await bridgeRequest("host.capabilities.webFetch", { url })
|
|
823
|
+
},
|
|
824
|
+
} as never,
|
|
825
|
+
http: remoteHttp(),
|
|
826
|
+
agent: remoteAgent(),
|
|
827
|
+
models: remoteModels(),
|
|
828
|
+
network: remoteNetwork(),
|
|
829
|
+
plugins: remotePlugins(),
|
|
830
|
+
workspaces: remoteWorkspaces(),
|
|
831
|
+
exec: remoteExec(),
|
|
832
|
+
pty: remotePty(),
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const module = (await withHookOwner(pluginId, async () => {
|
|
836
|
+
return (await import(pathToFileURL(params.pluginEntry).href)) as { setup?: unknown }
|
|
837
|
+
})) as { setup?: unknown }
|
|
838
|
+
|
|
839
|
+
if (typeof module.setup !== "function") {
|
|
840
|
+
throw new Error("入口需要导出 setup() 函数")
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
let created: WellspringPlugin | null = null
|
|
844
|
+
await withHookOwner(pluginId, async () => {
|
|
845
|
+
created = validatePlugin(await (module.setup as () => unknown)())
|
|
846
|
+
if (created.status !== "ok") {
|
|
847
|
+
throw new Error(`插件自报状态:${created.status}`)
|
|
848
|
+
}
|
|
849
|
+
commands.push(...(created.commands ?? []))
|
|
850
|
+
agentTools.push(...(created.agentTools ?? []))
|
|
851
|
+
})
|
|
852
|
+
if (created === null) {
|
|
853
|
+
throw new Error("插件未初始化")
|
|
854
|
+
}
|
|
855
|
+
const plugin: WellspringPlugin = created
|
|
856
|
+
const current: PluginRuntime = {
|
|
857
|
+
owner: pluginId,
|
|
858
|
+
plugin,
|
|
859
|
+
commands,
|
|
860
|
+
agentTools,
|
|
861
|
+
hooks: listHookKinds(pluginId),
|
|
862
|
+
api,
|
|
863
|
+
initialized: false,
|
|
864
|
+
}
|
|
865
|
+
runtime = current
|
|
866
|
+
if (params.deferInit !== true) {
|
|
867
|
+
await initializeRuntime(current)
|
|
868
|
+
}
|
|
869
|
+
return readyOf(current)
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
async function initializeRuntime(current: PluginRuntime): Promise<void> {
|
|
873
|
+
if (current.initialized) {
|
|
874
|
+
return
|
|
875
|
+
}
|
|
876
|
+
await withHookOwner(current.owner, async () => {
|
|
877
|
+
await current.plugin.init?.(current.api)
|
|
878
|
+
})
|
|
879
|
+
for (const tool of current.agentTools) {
|
|
880
|
+
if (typeof tool.name !== "string" || typeof tool.call !== "function") {
|
|
881
|
+
throw new Error("Agent 工具缺少 name / call")
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
current.initialized = true
|
|
885
|
+
current.hooks = listHookKinds(current.owner)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function readyOf(current: PluginRuntime): {
|
|
889
|
+
plugin: { id: string; name: string; version: string; status: PluginStatus; deps: string[] }
|
|
890
|
+
commands: Array<{
|
|
891
|
+
name: string
|
|
892
|
+
summary: string
|
|
893
|
+
usage?: string
|
|
894
|
+
aliases?: readonly string[]
|
|
895
|
+
}>
|
|
896
|
+
agentTools: Array<{
|
|
897
|
+
name: string
|
|
898
|
+
introduction: { params: Record<string, unknown>; content: string }
|
|
899
|
+
}>
|
|
900
|
+
hooks: string[]
|
|
901
|
+
} {
|
|
902
|
+
return {
|
|
903
|
+
plugin: {
|
|
904
|
+
id: current.plugin.id,
|
|
905
|
+
name: current.plugin.name,
|
|
906
|
+
version: current.plugin.version,
|
|
907
|
+
status: current.plugin.status,
|
|
908
|
+
deps: [...(current.plugin.deps ?? [])],
|
|
909
|
+
},
|
|
910
|
+
commands: current.commands.map((command) => ({
|
|
911
|
+
name: command.name,
|
|
912
|
+
summary: command.summary,
|
|
913
|
+
usage: command.usage,
|
|
914
|
+
aliases: command.aliases,
|
|
915
|
+
})),
|
|
916
|
+
agentTools: current.agentTools.map((tool) => ({
|
|
917
|
+
name: tool.name,
|
|
918
|
+
introduction: {
|
|
919
|
+
params: { ...tool.introduction.params },
|
|
920
|
+
content: tool.introduction.content,
|
|
921
|
+
},
|
|
922
|
+
})),
|
|
923
|
+
hooks: current.hooks.map((kind) => HOOK_API_NAME[kind]),
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
async function dispatchHook(name: string, payload: Record<string, unknown>): Promise<unknown> {
|
|
928
|
+
switch (name) {
|
|
929
|
+
case "onMessage":
|
|
930
|
+
await emitOnMessage(payload as never)
|
|
931
|
+
return { prompt: (payload["prompt"] as string[] | undefined) ?? [] }
|
|
932
|
+
case "onSessionStart":
|
|
933
|
+
await emitSessionStart(payload as never)
|
|
934
|
+
return { prompt: (payload["prompt"] as string[] | undefined) ?? [] }
|
|
935
|
+
case "beforeTurn":
|
|
936
|
+
await emitBeforeTurn(payload as never)
|
|
937
|
+
return {}
|
|
938
|
+
case "beforeStep":
|
|
939
|
+
await emitBeforeStep(payload as never)
|
|
940
|
+
return {}
|
|
941
|
+
case "afterStep":
|
|
942
|
+
await emitAfterStep(payload as never)
|
|
943
|
+
return {}
|
|
944
|
+
case "afterTurn":
|
|
945
|
+
await emitAfterTurn(payload as never)
|
|
946
|
+
return {}
|
|
947
|
+
case "onAgentRunSync":
|
|
948
|
+
await emitAgentRunSync(payload as never)
|
|
949
|
+
return {}
|
|
950
|
+
case "onAgentRunAsync":
|
|
951
|
+
await emitAgentRunAsync(payload as never)
|
|
952
|
+
return {}
|
|
953
|
+
case "onAgentAsyncTaskComplete":
|
|
954
|
+
await emitAgentAsyncTaskComplete(payload as never)
|
|
955
|
+
return {}
|
|
956
|
+
case "onAgentCallTool":
|
|
957
|
+
await emitAgentCallTool(payload as never)
|
|
958
|
+
return {}
|
|
959
|
+
case "onAgentAsk":
|
|
960
|
+
await emitAgentAsk(payload as never)
|
|
961
|
+
return {}
|
|
962
|
+
case "onAgentRead":
|
|
963
|
+
await emitAgentRead(payload as never)
|
|
964
|
+
return {}
|
|
965
|
+
case "onAgentQueryTask":
|
|
966
|
+
await emitAgentQueryTask(payload as never)
|
|
967
|
+
return {}
|
|
968
|
+
case "onAgentSubmit":
|
|
969
|
+
await emitAgentSubmit(payload as never)
|
|
970
|
+
return {}
|
|
971
|
+
case "onAgentSetTasks":
|
|
972
|
+
await emitAgentSetTasks(payload as never)
|
|
973
|
+
return {}
|
|
974
|
+
case "onAgentSetGoal":
|
|
975
|
+
await emitAgentSetGoal(payload as never)
|
|
976
|
+
return {}
|
|
977
|
+
case "onAgentRequestPermission":
|
|
978
|
+
await emitAgentRequestPermission(payload as never)
|
|
979
|
+
return {}
|
|
980
|
+
case "onUserAnswer":
|
|
981
|
+
await emitUserAnswer(payload as never)
|
|
982
|
+
return {}
|
|
983
|
+
case "onInit":
|
|
984
|
+
await emitInitHooks()
|
|
985
|
+
return {}
|
|
986
|
+
case "onShutdown":
|
|
987
|
+
await emitShutdownHooks()
|
|
988
|
+
return {}
|
|
989
|
+
default:
|
|
990
|
+
throw new Error(`未知钩子:${name}`)
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
async function runCommand(request: RpcRequest): Promise<unknown> {
|
|
995
|
+
if (runtime === null) {
|
|
996
|
+
throw new Error("插件尚未初始化")
|
|
997
|
+
}
|
|
998
|
+
const params = request.params as {
|
|
999
|
+
name: string
|
|
1000
|
+
argv: string[]
|
|
1001
|
+
cwd: string
|
|
1002
|
+
json: boolean
|
|
1003
|
+
stdin?: string
|
|
1004
|
+
}
|
|
1005
|
+
const command = runtime.commands.find((item) => item.name === params.name)
|
|
1006
|
+
if (command === undefined) {
|
|
1007
|
+
throw new Error(`命令不存在:${params.name}`)
|
|
1008
|
+
}
|
|
1009
|
+
const context: PluginCommandContext = {
|
|
1010
|
+
argv: params.argv,
|
|
1011
|
+
env: {},
|
|
1012
|
+
paths: runtime.api.paths,
|
|
1013
|
+
config: runtime.api.config,
|
|
1014
|
+
logger,
|
|
1015
|
+
output,
|
|
1016
|
+
pluginDataDir: runtime.api.pluginDataDir,
|
|
1017
|
+
pluginConfig: runtime.api.pluginConfig,
|
|
1018
|
+
fs: runtime.api.fs,
|
|
1019
|
+
workspaceFs: runtime.api.workspaceFs,
|
|
1020
|
+
cwd: params.cwd,
|
|
1021
|
+
json: params.json,
|
|
1022
|
+
stdin: Readable.from([params.stdin ?? ""]),
|
|
1023
|
+
stdout: process.stdout,
|
|
1024
|
+
stderr: process.stderr,
|
|
1025
|
+
}
|
|
1026
|
+
const code = await withHookOwner(runtime.owner, async () => await command.run(context))
|
|
1027
|
+
return { exitCode: typeof code === "number" ? code : 0 }
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
async function handleHostRequest(request: RpcRequest): Promise<void> {
|
|
1031
|
+
try {
|
|
1032
|
+
let result: unknown
|
|
1033
|
+
if (request.method === "plugin.command") {
|
|
1034
|
+
result = await runCommand(request)
|
|
1035
|
+
} else if (request.method === "plugin.agentTool") {
|
|
1036
|
+
if (runtime === null) {
|
|
1037
|
+
throw new Error("插件尚未初始化")
|
|
1038
|
+
}
|
|
1039
|
+
const params = request.params as { name: string; args: unknown }
|
|
1040
|
+
const tool = runtime.agentTools.find((item) => item.name === params.name)
|
|
1041
|
+
if (tool === undefined) {
|
|
1042
|
+
throw new Error(`Agent 工具不存在:${params.name}`)
|
|
1043
|
+
}
|
|
1044
|
+
result = await withHookOwner(
|
|
1045
|
+
runtime.owner,
|
|
1046
|
+
async () => (await tool.call(params.args)) as AgentCallToolResult,
|
|
1047
|
+
)
|
|
1048
|
+
} else if (request.method === "plugin.init") {
|
|
1049
|
+
if (runtime === null) {
|
|
1050
|
+
throw new Error("插件尚未初始化")
|
|
1051
|
+
}
|
|
1052
|
+
await initializeRuntime(runtime)
|
|
1053
|
+
result = readyOf(runtime)
|
|
1054
|
+
} else if (request.method === "plugin.websocket.connect") {
|
|
1055
|
+
if (runtime === null) {
|
|
1056
|
+
throw new Error("插件尚未初始化")
|
|
1057
|
+
}
|
|
1058
|
+
const params = request.params as {
|
|
1059
|
+
serverId: string
|
|
1060
|
+
connectionId: string
|
|
1061
|
+
request: HttpRequest
|
|
1062
|
+
}
|
|
1063
|
+
const handler = websocketHandlers.get(params.serverId)
|
|
1064
|
+
if (handler === undefined) {
|
|
1065
|
+
throw new Error("WebSocket handler 不存在")
|
|
1066
|
+
}
|
|
1067
|
+
const connectionId = params.connectionId
|
|
1068
|
+
const socket: PluginWebSocket = {
|
|
1069
|
+
connectionId,
|
|
1070
|
+
async send(data: string) {
|
|
1071
|
+
await bridgeRequest("host.websocket.send", { connectionId, data })
|
|
1072
|
+
},
|
|
1073
|
+
async close(code = 1000, reason = "") {
|
|
1074
|
+
await bridgeRequest("host.websocket.close", {
|
|
1075
|
+
connectionId,
|
|
1076
|
+
code,
|
|
1077
|
+
reason,
|
|
1078
|
+
})
|
|
1079
|
+
},
|
|
1080
|
+
onMessage(handler) {
|
|
1081
|
+
const list = wsMessages.get(connectionId) ?? []
|
|
1082
|
+
list.push(handler)
|
|
1083
|
+
wsMessages.set(connectionId, list)
|
|
1084
|
+
},
|
|
1085
|
+
onClose(handler) {
|
|
1086
|
+
const list = wsCloses.get(connectionId) ?? []
|
|
1087
|
+
list.push(handler)
|
|
1088
|
+
wsCloses.set(connectionId, list)
|
|
1089
|
+
},
|
|
1090
|
+
}
|
|
1091
|
+
await withHookOwner(runtime.owner, async () => handler(socket, params.request))
|
|
1092
|
+
result = {}
|
|
1093
|
+
} else if (request.method === "plugin.websocket.message") {
|
|
1094
|
+
const params = request.params as {
|
|
1095
|
+
connectionId: string
|
|
1096
|
+
data: string
|
|
1097
|
+
binary: boolean
|
|
1098
|
+
}
|
|
1099
|
+
const text = params.binary
|
|
1100
|
+
? Buffer.from(params.data, "base64").toString("utf8")
|
|
1101
|
+
: Buffer.from(params.data, "base64").toString("utf8")
|
|
1102
|
+
for (const handler of wsMessages.get(params.connectionId) ?? []) {
|
|
1103
|
+
handler(text)
|
|
1104
|
+
}
|
|
1105
|
+
result = {}
|
|
1106
|
+
} else if (request.method === "plugin.websocket.close") {
|
|
1107
|
+
const params = request.params as {
|
|
1108
|
+
connectionId: string
|
|
1109
|
+
code: number
|
|
1110
|
+
reason: string
|
|
1111
|
+
}
|
|
1112
|
+
for (const handler of wsCloses.get(params.connectionId) ?? []) {
|
|
1113
|
+
handler(params.code, params.reason)
|
|
1114
|
+
}
|
|
1115
|
+
wsMessages.delete(params.connectionId)
|
|
1116
|
+
wsCloses.delete(params.connectionId)
|
|
1117
|
+
result = {}
|
|
1118
|
+
} else if (request.method === "plugin.http") {
|
|
1119
|
+
if (runtime === null) {
|
|
1120
|
+
throw new Error("插件尚未初始化")
|
|
1121
|
+
}
|
|
1122
|
+
const params = request.params as {
|
|
1123
|
+
serverId: string
|
|
1124
|
+
requestId: string
|
|
1125
|
+
request: HttpRequest
|
|
1126
|
+
}
|
|
1127
|
+
const handler = httpHandlers.get(params.serverId)
|
|
1128
|
+
if (handler === undefined) {
|
|
1129
|
+
throw new Error(`HTTP handler 不存在:${params.serverId}`)
|
|
1130
|
+
}
|
|
1131
|
+
const requestId = params.requestId
|
|
1132
|
+
let flushed = false
|
|
1133
|
+
let ended = false
|
|
1134
|
+
const buffered: string[] = []
|
|
1135
|
+
const flush = (): void => {
|
|
1136
|
+
if (flushed) {
|
|
1137
|
+
return
|
|
1138
|
+
}
|
|
1139
|
+
flushed = true
|
|
1140
|
+
for (const chunk of buffered) {
|
|
1141
|
+
bridgeNotify("host.http.stream", { requestId, chunk })
|
|
1142
|
+
}
|
|
1143
|
+
buffered.length = 0
|
|
1144
|
+
if (ended) {
|
|
1145
|
+
bridgeNotify("host.http.stream", { requestId, end: true })
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const stream = {
|
|
1149
|
+
write(chunk: string) {
|
|
1150
|
+
if (flushed) {
|
|
1151
|
+
bridgeNotify("host.http.stream", { requestId, chunk })
|
|
1152
|
+
} else {
|
|
1153
|
+
buffered.push(chunk)
|
|
1154
|
+
}
|
|
1155
|
+
},
|
|
1156
|
+
end() {
|
|
1157
|
+
if (flushed) {
|
|
1158
|
+
bridgeNotify("host.http.stream", { requestId, end: true })
|
|
1159
|
+
} else {
|
|
1160
|
+
ended = true
|
|
1161
|
+
}
|
|
1162
|
+
},
|
|
1163
|
+
}
|
|
1164
|
+
const response = (await withHookOwner(runtime.owner, async () =>
|
|
1165
|
+
handler(params.request, stream),
|
|
1166
|
+
)) as HttpResponse
|
|
1167
|
+
if (response.stream === true) {
|
|
1168
|
+
setImmediate(flush)
|
|
1169
|
+
}
|
|
1170
|
+
result = response
|
|
1171
|
+
} else if (request.method === "plugin.hook") {
|
|
1172
|
+
const params = request.params as { hook: string; payload: Record<string, unknown> }
|
|
1173
|
+
if (runtime === null) {
|
|
1174
|
+
throw new Error("插件尚未初始化")
|
|
1175
|
+
}
|
|
1176
|
+
result = await withHookOwner(runtime.owner, async () =>
|
|
1177
|
+
dispatchHook(params.hook, params.payload),
|
|
1178
|
+
)
|
|
1179
|
+
} else if (request.method === "sandbox.ping") {
|
|
1180
|
+
result = { time: Date.now() }
|
|
1181
|
+
} else if (request.method === "sandbox.shutdown") {
|
|
1182
|
+
await emitShutdownHooks()
|
|
1183
|
+
result = {}
|
|
1184
|
+
writeFrame({ v: 1, type: "response", id: request.id, ok: true, result })
|
|
1185
|
+
setTimeout(() => process.exit(0), 10)
|
|
1186
|
+
return
|
|
1187
|
+
} else {
|
|
1188
|
+
throw new Error(`未知方法:${request.method}`)
|
|
1189
|
+
}
|
|
1190
|
+
writeFrame({ v: 1, type: "response", id: request.id, ok: true, result })
|
|
1191
|
+
} catch (error) {
|
|
1192
|
+
writeFrame({
|
|
1193
|
+
v: 1,
|
|
1194
|
+
type: "response",
|
|
1195
|
+
id: request.id,
|
|
1196
|
+
ok: false,
|
|
1197
|
+
error: {
|
|
1198
|
+
code:
|
|
1199
|
+
request.method === "plugin.hook"
|
|
1200
|
+
? "plugin.hook-failed"
|
|
1201
|
+
: "plugin.command-failed",
|
|
1202
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1203
|
+
},
|
|
1204
|
+
})
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
const configureResolvers: Array<(params: ConfigurePayload) => void> = []
|
|
1209
|
+
|
|
1210
|
+
async function main(): Promise<void> {
|
|
1211
|
+
const decoder = new FrameDecoder()
|
|
1212
|
+
process.stdin.on("data", async (chunk: Buffer) => {
|
|
1213
|
+
for (const message of decoder.push(chunk)) {
|
|
1214
|
+
if (message.type === "response") {
|
|
1215
|
+
const entry = pending.get(message.id)
|
|
1216
|
+
if (entry !== undefined) {
|
|
1217
|
+
pending.delete(message.id)
|
|
1218
|
+
if (message.ok) {
|
|
1219
|
+
entry.resolve(message.result)
|
|
1220
|
+
} else {
|
|
1221
|
+
entry.reject(new Error(message.error?.message ?? "host RPC 失败"))
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
continue
|
|
1225
|
+
}
|
|
1226
|
+
if (message.type === "event") {
|
|
1227
|
+
/*
|
|
1228
|
+
* 事件回调出错不能拖垮 stdin 读取
|
|
1229
|
+
*
|
|
1230
|
+
* 这里在 data 监听里同步执行插件侧回调;一旦抛出就是未捕获异常,
|
|
1231
|
+
* 插件可能直接挂掉或丢掉这一帧里剩下的事件——宿主还在跑、界面却再也
|
|
1232
|
+
* 收不到任何事件,表现就是「服务端一直有日志、页面卡住不动」。
|
|
1233
|
+
*/
|
|
1234
|
+
try {
|
|
1235
|
+
if (message.method === "sandbox.event") {
|
|
1236
|
+
const params = message.params as { name: string; data: unknown }
|
|
1237
|
+
eventBus.receive(params.name, params.data)
|
|
1238
|
+
} else if (message.method === "sandbox.agent.stream") {
|
|
1239
|
+
const params = message.params as {
|
|
1240
|
+
streamId: string
|
|
1241
|
+
event: HostAgentStreamEvent
|
|
1242
|
+
}
|
|
1243
|
+
agentStreamHandlers.get(params.streamId)?.(params.event)
|
|
1244
|
+
}
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
process.stderr.write(
|
|
1247
|
+
`事件处理失败(${message.method}):${error instanceof Error ? error.message : String(error)}\n`,
|
|
1248
|
+
)
|
|
1249
|
+
}
|
|
1250
|
+
continue
|
|
1251
|
+
}
|
|
1252
|
+
const request = message
|
|
1253
|
+
if (request.method === "sandbox.configure") {
|
|
1254
|
+
const params = request.params as ConfigurePayload
|
|
1255
|
+
try {
|
|
1256
|
+
patchConsole()
|
|
1257
|
+
patchProcessStreams()
|
|
1258
|
+
const ready = await bootstrap(params)
|
|
1259
|
+
writeFrame({ v: 1, type: "response", id: request.id, ok: true, result: ready })
|
|
1260
|
+
} catch (error) {
|
|
1261
|
+
writeFrame({
|
|
1262
|
+
v: 1,
|
|
1263
|
+
type: "response",
|
|
1264
|
+
id: request.id,
|
|
1265
|
+
ok: false,
|
|
1266
|
+
error: {
|
|
1267
|
+
code: "plugin.init-failed",
|
|
1268
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1269
|
+
},
|
|
1270
|
+
})
|
|
1271
|
+
setTimeout(() => process.exit(1), 10)
|
|
1272
|
+
}
|
|
1273
|
+
continue
|
|
1274
|
+
}
|
|
1275
|
+
void handleHostRequest(request)
|
|
1276
|
+
}
|
|
1277
|
+
})
|
|
1278
|
+
process.stdin.resume()
|
|
1279
|
+
await new Promise<void>(() => undefined)
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
await main()
|