@xyit/wellspring 0.0.0-stage → 1.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/LICENSE +21 -0
- package/README.md +829 -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 +45 -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 +188 -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 +12 -0
- package/dist/plugin/install.js +114 -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 +265 -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/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 +126 -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/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 +55 -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 +218 -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 +149 -0
- package/src/plugin/loader.ts +952 -0
- package/src/plugin/manage.ts +329 -0
- package/src/plugin/models-service.ts +383 -0
- package/src/plugin/network-service.ts +102 -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 +152 -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,152 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 插件脚手架:生成可加载的最小插件目录。
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join, resolve } from "node:path"
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
ensureDir,
|
|
9
|
+
removePath,
|
|
10
|
+
statIfExists,
|
|
11
|
+
writeJsonAtomic,
|
|
12
|
+
writeTextAtomic,
|
|
13
|
+
} from "../platform/file.ts"
|
|
14
|
+
import { PLUGIN_NAME } from "./paths.ts"
|
|
15
|
+
|
|
16
|
+
export interface ScaffoldPluginOptions {
|
|
17
|
+
readonly name: string
|
|
18
|
+
/** 生成到哪个父目录,默认当前工作目录 */
|
|
19
|
+
readonly parentDir?: string
|
|
20
|
+
readonly force?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ScaffoldPluginResult {
|
|
24
|
+
readonly dir: string
|
|
25
|
+
readonly commandName: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 插件名 → 合法命令名;名字不适用时回落到 hello */
|
|
29
|
+
export function commandNameOf(pluginName: string): string {
|
|
30
|
+
let slug = pluginName
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.replace(/[^a-z0-9-]+/g, "-")
|
|
33
|
+
.replace(/-+/g, "-")
|
|
34
|
+
.replace(/^-+|-+$/g, "")
|
|
35
|
+
if (!/^[a-z][a-z0-9-]*$/.test(slug) || slug.length < 2) {
|
|
36
|
+
slug = "hello"
|
|
37
|
+
}
|
|
38
|
+
return slug.slice(0, 32).replace(/-+$/, "")
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function template(pluginName: string, commandName: string): string {
|
|
42
|
+
return [
|
|
43
|
+
'import { onMessage, onInit, onShutdown } from "wellspring/api/host/index.ts"',
|
|
44
|
+
'import type { WellspringPlugin } from "wellspring/api/host/index.ts"',
|
|
45
|
+
"",
|
|
46
|
+
"// 钩子可以放在模块顶层;平台在 import 时会把它们归属到当前插件",
|
|
47
|
+
"onMessage(({ phase, prompt }) => {",
|
|
48
|
+
' if (phase === "before") prompt.push("你是一个运行在 Wellspring 里的助手")',
|
|
49
|
+
"})",
|
|
50
|
+
"",
|
|
51
|
+
"export function setup() {",
|
|
52
|
+
" onInit(() => {",
|
|
53
|
+
" // wellspring start 时触发",
|
|
54
|
+
" })",
|
|
55
|
+
" onShutdown(() => {",
|
|
56
|
+
" // wellspring start 关闭时触发",
|
|
57
|
+
" })",
|
|
58
|
+
"",
|
|
59
|
+
" const plugin = {",
|
|
60
|
+
` id: ${JSON.stringify(pluginName)},`,
|
|
61
|
+
` name: ${JSON.stringify(pluginName)},`,
|
|
62
|
+
' version: "0.1.0",',
|
|
63
|
+
' status: "ok",',
|
|
64
|
+
" deps: [],",
|
|
65
|
+
" commands: [",
|
|
66
|
+
" {",
|
|
67
|
+
` name: ${JSON.stringify(commandName)},`,
|
|
68
|
+
` summary: ${JSON.stringify(`${pluginName} 示例命令`)},`,
|
|
69
|
+
" run: (ctx) => {",
|
|
70
|
+
' ctx.output.info("argv=" + ctx.argv.join(","))',
|
|
71
|
+
" return 0",
|
|
72
|
+
" },",
|
|
73
|
+
" },",
|
|
74
|
+
" ],",
|
|
75
|
+
" } satisfies WellspringPlugin",
|
|
76
|
+
"",
|
|
77
|
+
" return plugin",
|
|
78
|
+
"}",
|
|
79
|
+
"",
|
|
80
|
+
].join("\n")
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readmeTemplate(pluginName: string, commandName: string): string {
|
|
84
|
+
return [
|
|
85
|
+
`# ${pluginName}`,
|
|
86
|
+
"",
|
|
87
|
+
"## 开发",
|
|
88
|
+
"",
|
|
89
|
+
"编辑器 / tsc 解析 `wellspring/api/host/index.ts` 需要先把 Wellspring 链接进来:",
|
|
90
|
+
"",
|
|
91
|
+
"```bash",
|
|
92
|
+
"pnpm link /path/to/Wellspring",
|
|
93
|
+
"pnpm add -D typescript @types/node # 如果还没有",
|
|
94
|
+
"pnpm exec tsc --noEmit",
|
|
95
|
+
"```",
|
|
96
|
+
"",
|
|
97
|
+
"运行时不需要链接:通过 `wellspring plugin link <this-dir>` 加载时,平台会解析宿主 API。",
|
|
98
|
+
"",
|
|
99
|
+
"## 加载",
|
|
100
|
+
"",
|
|
101
|
+
"```bash",
|
|
102
|
+
`wellspring plugin link \"$PWD\"`,
|
|
103
|
+
"wellspring plugin list",
|
|
104
|
+
`wellspring ${commandName}`,
|
|
105
|
+
"```",
|
|
106
|
+
"",
|
|
107
|
+
].join("\n")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function scaffoldPlugin(
|
|
111
|
+
options: ScaffoldPluginOptions,
|
|
112
|
+
): Promise<ScaffoldPluginResult> {
|
|
113
|
+
const name = options.name.trim()
|
|
114
|
+
if (!PLUGIN_NAME.test(name)) {
|
|
115
|
+
throw new Error(`插件名不合法:${name}`)
|
|
116
|
+
}
|
|
117
|
+
const parentDir = resolve(options.parentDir ?? process.cwd())
|
|
118
|
+
const dir = join(parentDir, name)
|
|
119
|
+
const existing = await statIfExists(dir)
|
|
120
|
+
if (existing !== null) {
|
|
121
|
+
if (options.force !== true) {
|
|
122
|
+
throw new Error(`目录已存在:${dir}`)
|
|
123
|
+
}
|
|
124
|
+
await removePath(dir)
|
|
125
|
+
}
|
|
126
|
+
await ensureDir(dir)
|
|
127
|
+
|
|
128
|
+
const commandName = commandNameOf(name)
|
|
129
|
+
await writeJsonAtomic(join(dir, "package.json"), {
|
|
130
|
+
name,
|
|
131
|
+
version: "0.1.0",
|
|
132
|
+
type: "module",
|
|
133
|
+
main: "index.ts",
|
|
134
|
+
wellspring: { apiVersion: 1 },
|
|
135
|
+
})
|
|
136
|
+
await writeTextAtomic(join(dir, "index.ts"), template(name, commandName))
|
|
137
|
+
await writeJsonAtomic(join(dir, "tsconfig.json"), {
|
|
138
|
+
compilerOptions: {
|
|
139
|
+
target: "ES2024",
|
|
140
|
+
module: "NodeNext",
|
|
141
|
+
moduleResolution: "NodeNext",
|
|
142
|
+
strict: true,
|
|
143
|
+
noEmit: true,
|
|
144
|
+
allowImportingTsExtensions: true,
|
|
145
|
+
skipLibCheck: true,
|
|
146
|
+
types: ["node"],
|
|
147
|
+
},
|
|
148
|
+
include: ["index.ts"],
|
|
149
|
+
})
|
|
150
|
+
await writeTextAtomic(join(dir, "README.md"), readmeTemplate(name, commandName))
|
|
151
|
+
return { dir, commandName }
|
|
152
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 插件启用状态:<dataDir>/plugins-state.json。
|
|
3
|
+
* 以插件目录名为 key,禁用后 loader 不再 import 该插件。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { join } from "node:path"
|
|
7
|
+
|
|
8
|
+
import { readJsonIfExists, writeJsonAtomic } from "../platform/file.ts"
|
|
9
|
+
import { getPaths, type ResolvedPaths } from "../platform/path.ts"
|
|
10
|
+
|
|
11
|
+
interface PluginStateFile {
|
|
12
|
+
readonly version?: number
|
|
13
|
+
readonly disabled?: readonly string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function pluginStateFile(paths: ResolvedPaths = getPaths()): string {
|
|
17
|
+
return join(paths.dataDir, "plugins-state.json")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 读取被禁用的插件目录名集合 */
|
|
21
|
+
export async function readPluginState(
|
|
22
|
+
paths: ResolvedPaths = getPaths(),
|
|
23
|
+
): Promise<ReadonlySet<string>> {
|
|
24
|
+
const value = await readJsonIfExists<PluginStateFile>(pluginStateFile(paths))
|
|
25
|
+
if (value === null || !Array.isArray(value.disabled)) {
|
|
26
|
+
return new Set()
|
|
27
|
+
}
|
|
28
|
+
return new Set(value.disabled.filter((item): item is string => typeof item === "string"))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** 设置插件启用/禁用,返回是否发生变化 */
|
|
32
|
+
export async function setPluginEnabled(
|
|
33
|
+
name: string,
|
|
34
|
+
enabled: boolean,
|
|
35
|
+
paths: ResolvedPaths = getPaths(),
|
|
36
|
+
): Promise<boolean> {
|
|
37
|
+
const disabled = new Set(await readPluginState(paths))
|
|
38
|
+
const had = disabled.has(name)
|
|
39
|
+
if (enabled) {
|
|
40
|
+
disabled.delete(name)
|
|
41
|
+
} else {
|
|
42
|
+
disabled.add(name)
|
|
43
|
+
}
|
|
44
|
+
if (had === !enabled) {
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
await writeJsonAtomic(pluginStateFile(paths), {
|
|
48
|
+
version: 1,
|
|
49
|
+
disabled: [...disabled].sort(),
|
|
50
|
+
})
|
|
51
|
+
return true
|
|
52
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 最小 ZIP 读取器,只服务本地插件安装。
|
|
3
|
+
* 支持 store/deflate 两种压缩方式,拒绝加密、ZIP64、符号链接与路径穿越。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { inflateRawSync } from "node:zlib"
|
|
7
|
+
|
|
8
|
+
import { ensureDir, writeBytesAtomic } from "../platform/file.ts"
|
|
9
|
+
import { dirname, resolve, sep } from "node:path"
|
|
10
|
+
|
|
11
|
+
const EOCD_SIGNATURE = 0x06054b50
|
|
12
|
+
const CENTRAL_SIGNATURE = 0x02014b50
|
|
13
|
+
const LOCAL_SIGNATURE = 0x04034b50
|
|
14
|
+
const MAX_ENTRIES = 10_000
|
|
15
|
+
const MAX_ENTRY_BYTES = 100 * 1024 * 1024
|
|
16
|
+
const MAX_TOTAL_BYTES = 200 * 1024 * 1024
|
|
17
|
+
|
|
18
|
+
export interface ZipEntry {
|
|
19
|
+
readonly name: string
|
|
20
|
+
readonly data: Buffer
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fail(message: string): never {
|
|
24
|
+
throw new Error(`zip 包非法:${message}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function validateName(name: string): void {
|
|
28
|
+
if (name === "" || name.startsWith("/") || name.includes("\\") || /^[A-Za-z]:/.test(name)) {
|
|
29
|
+
fail(`非法路径:${name}`)
|
|
30
|
+
}
|
|
31
|
+
for (const segment of name.split("/")) {
|
|
32
|
+
if (segment === ".." || segment === ".") {
|
|
33
|
+
fail(`非法路径:${name}`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function findEocd(buffer: Buffer): number {
|
|
39
|
+
const minimum = Math.max(0, buffer.length - 65_557)
|
|
40
|
+
for (let offset = buffer.length - 22; offset >= minimum; offset -= 1) {
|
|
41
|
+
if (buffer.readUInt32LE(offset) === EOCD_SIGNATURE) {
|
|
42
|
+
return offset
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return fail("找不到中央目录")
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function readZip(buffer: Buffer): ZipEntry[] {
|
|
49
|
+
if (buffer.length < 22) {
|
|
50
|
+
fail("文件过短")
|
|
51
|
+
}
|
|
52
|
+
const eocd = findEocd(buffer)
|
|
53
|
+
const entries = buffer.readUInt16LE(eocd + 10)
|
|
54
|
+
const centralOffset = buffer.readUInt32LE(eocd + 16)
|
|
55
|
+
if (entries > MAX_ENTRIES) {
|
|
56
|
+
fail("文件条目过多")
|
|
57
|
+
}
|
|
58
|
+
if (centralOffset === 0xffffffff) {
|
|
59
|
+
fail("不支持 ZIP64")
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const result: ZipEntry[] = []
|
|
63
|
+
let total = 0
|
|
64
|
+
let offset = centralOffset
|
|
65
|
+
|
|
66
|
+
for (let index = 0; index < entries; index += 1) {
|
|
67
|
+
if (offset + 46 > buffer.length || buffer.readUInt32LE(offset) !== CENTRAL_SIGNATURE) {
|
|
68
|
+
fail("中央目录损坏")
|
|
69
|
+
}
|
|
70
|
+
const flags = buffer.readUInt16LE(offset + 8)
|
|
71
|
+
const method = buffer.readUInt16LE(offset + 10)
|
|
72
|
+
const compressedSize = buffer.readUInt32LE(offset + 20)
|
|
73
|
+
const uncompressedSize = buffer.readUInt32LE(offset + 24)
|
|
74
|
+
const nameLength = buffer.readUInt16LE(offset + 28)
|
|
75
|
+
const extraLength = buffer.readUInt16LE(offset + 30)
|
|
76
|
+
const commentLength = buffer.readUInt16LE(offset + 32)
|
|
77
|
+
const externalAttributes = buffer.readUInt32LE(offset + 38)
|
|
78
|
+
const localOffset = buffer.readUInt32LE(offset + 42)
|
|
79
|
+
const name = buffer.subarray(offset + 46, offset + 46 + nameLength).toString("utf8")
|
|
80
|
+
offset += 46 + nameLength + extraLength + commentLength
|
|
81
|
+
|
|
82
|
+
validateName(name)
|
|
83
|
+
if ((flags & 0x1) !== 0) {
|
|
84
|
+
fail(`不支持加密条目:${name}`)
|
|
85
|
+
}
|
|
86
|
+
if (compressedSize === 0xffffffff || uncompressedSize === 0xffffffff) {
|
|
87
|
+
fail("不支持 ZIP64 条目")
|
|
88
|
+
}
|
|
89
|
+
if (uncompressedSize > MAX_ENTRY_BYTES) {
|
|
90
|
+
fail(`单文件过大:${name}`)
|
|
91
|
+
}
|
|
92
|
+
total += uncompressedSize
|
|
93
|
+
if (total > MAX_TOTAL_BYTES) {
|
|
94
|
+
fail("解压后总大小超限")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (name.endsWith("/")) {
|
|
98
|
+
result.push({ name, data: Buffer.alloc(0) })
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
if (((externalAttributes >>> 16) & 0o170000) === 0o120000) {
|
|
102
|
+
fail(`不支持符号链接:${name}`)
|
|
103
|
+
}
|
|
104
|
+
if (
|
|
105
|
+
localOffset + 30 > buffer.length ||
|
|
106
|
+
buffer.readUInt32LE(localOffset) !== LOCAL_SIGNATURE
|
|
107
|
+
) {
|
|
108
|
+
fail(`本地文件头损坏:${name}`)
|
|
109
|
+
}
|
|
110
|
+
const localNameLength = buffer.readUInt16LE(localOffset + 26)
|
|
111
|
+
const localExtraLength = buffer.readUInt16LE(localOffset + 28)
|
|
112
|
+
const dataStart = localOffset + 30 + localNameLength + localExtraLength
|
|
113
|
+
const end = dataStart + compressedSize
|
|
114
|
+
if (end > buffer.length) {
|
|
115
|
+
fail(`数据越界:${name}`)
|
|
116
|
+
}
|
|
117
|
+
const compressed = buffer.subarray(dataStart, end)
|
|
118
|
+
|
|
119
|
+
let data: Buffer
|
|
120
|
+
if (method === 0) {
|
|
121
|
+
data = Buffer.from(compressed)
|
|
122
|
+
} else if (method === 8) {
|
|
123
|
+
try {
|
|
124
|
+
data = inflateRawSync(compressed)
|
|
125
|
+
} catch (error) {
|
|
126
|
+
fail(
|
|
127
|
+
`解压失败:${name}(${error instanceof Error ? error.message : String(error)})`,
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
} else {
|
|
131
|
+
fail(`不支持的压缩方式 ${method}:${name}`)
|
|
132
|
+
}
|
|
133
|
+
if (data.length !== uncompressedSize) {
|
|
134
|
+
fail(`解压大小不符:${name}`)
|
|
135
|
+
}
|
|
136
|
+
result.push({ name, data })
|
|
137
|
+
}
|
|
138
|
+
return result
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** 解压到目标目录;调用方负责临时目录与清理 */
|
|
142
|
+
export async function extractZip(buffer: Buffer, destination: string): Promise<void> {
|
|
143
|
+
const root = resolve(destination)
|
|
144
|
+
for (const entry of readZip(buffer)) {
|
|
145
|
+
const target = resolve(root, entry.name)
|
|
146
|
+
if (target !== root && !target.startsWith(`${root}${sep}`)) {
|
|
147
|
+
fail(`路径穿越:${entry.name}`)
|
|
148
|
+
}
|
|
149
|
+
if (entry.name.endsWith("/")) {
|
|
150
|
+
await ensureDir(target)
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
await ensureDir(dirname(target))
|
|
154
|
+
await writeBytesAtomic(target, entry.data)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* sandbox 审计日志:<logDir>/sandbox-audit.log,JSONL。
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { appendFile } from "node:fs/promises"
|
|
6
|
+
import { dirname, join } from "node:path"
|
|
7
|
+
|
|
8
|
+
import { ensureDir } from "../platform/file.ts"
|
|
9
|
+
import type { ResolvedPaths } from "../platform/path.ts"
|
|
10
|
+
|
|
11
|
+
export interface SandboxAuditEvent {
|
|
12
|
+
readonly type: string
|
|
13
|
+
readonly sandboxId: string
|
|
14
|
+
readonly pluginId?: string
|
|
15
|
+
readonly backend?: string
|
|
16
|
+
readonly decision?: "allowed" | "denied"
|
|
17
|
+
readonly reason?: string
|
|
18
|
+
readonly [key: string]: unknown
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let queue: Promise<void> = Promise.resolve()
|
|
22
|
+
|
|
23
|
+
export function auditSandbox(paths: ResolvedPaths, event: SandboxAuditEvent): void {
|
|
24
|
+
const file = join(paths.logDir, "sandbox-audit.log")
|
|
25
|
+
queue = queue
|
|
26
|
+
.then(async () => {
|
|
27
|
+
await ensureDir(dirname(file))
|
|
28
|
+
await appendFile(
|
|
29
|
+
file,
|
|
30
|
+
`${JSON.stringify({ time: new Date().toISOString(), ...event })}\n`,
|
|
31
|
+
)
|
|
32
|
+
})
|
|
33
|
+
.catch(() => undefined)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function flushSandboxAudit(): Promise<void> {
|
|
37
|
+
await queue
|
|
38
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Linux bwrap 后端。
|
|
3
|
+
* 由 bwrap 提供 mount / PID / network namespace;Node permission model 作为第二层。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawnSync } from "node:child_process"
|
|
7
|
+
import { existsSync } from "node:fs"
|
|
8
|
+
import { dirname, join, relative, resolve, sep } from "node:path"
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
AgentLaunchInput,
|
|
12
|
+
LaunchSpec,
|
|
13
|
+
PluginLaunchInput,
|
|
14
|
+
SandboxBackend,
|
|
15
|
+
SandboxBackendStatus,
|
|
16
|
+
} from "../types.ts"
|
|
17
|
+
|
|
18
|
+
const SYSTEM_DIRS = ["/usr", "/bin", "/sbin", "/lib", "/lib64", "/lib32"]
|
|
19
|
+
|
|
20
|
+
/** 沙箱内挂载 Node 运行时的目录 */
|
|
21
|
+
export const NODE_ROOT = "/opt/wellspring"
|
|
22
|
+
|
|
23
|
+
function existingSystemDirs(): string[] {
|
|
24
|
+
return SYSTEM_DIRS.filter((dir) => existsSync(dir))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface NodeRuntimeLayout {
|
|
28
|
+
/** 可整体挂载的 Node 前缀(含 bin/npm 与 lib/node_modules);只有裸二进制时为 null */
|
|
29
|
+
readonly prefix: string | null
|
|
30
|
+
/** 沙箱内的 node 路径 */
|
|
31
|
+
readonly nodePath: string
|
|
32
|
+
/** PATH 里的目录 */
|
|
33
|
+
readonly pathEntry: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 找到当前 node 的安装前缀。
|
|
38
|
+
*
|
|
39
|
+
* 只 bind 单个 node 二进制会丢掉同目录的 npm/npx(npm 在 lib/node_modules/npm 里),
|
|
40
|
+
* 所以优先把整个前缀挂到 /opt/wellspring;找不到 npm 的发行版才退回裸二进制。
|
|
41
|
+
*/
|
|
42
|
+
export function resolveNodeRuntime(
|
|
43
|
+
execPath: string = process.execPath,
|
|
44
|
+
exists: (path: string) => boolean = existsSync,
|
|
45
|
+
): NodeRuntimeLayout {
|
|
46
|
+
const prefix = resolve(dirname(execPath), "..")
|
|
47
|
+
const hasNpm =
|
|
48
|
+
exists(join(prefix, "bin", "npm")) ||
|
|
49
|
+
exists(join(prefix, "lib", "node_modules", "npm")) ||
|
|
50
|
+
exists(join(prefix, "share", "nodejs", "npm"))
|
|
51
|
+
if (hasNpm) {
|
|
52
|
+
return {
|
|
53
|
+
prefix,
|
|
54
|
+
nodePath: `${NODE_ROOT}/bin/node`,
|
|
55
|
+
pathEntry: `${NODE_ROOT}/bin`,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
prefix: null,
|
|
60
|
+
nodePath: `${NODE_ROOT}/node`,
|
|
61
|
+
pathEntry: NODE_ROOT,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function nodeRuntimeMount(runtime: NodeRuntimeLayout, execPath: string): string[] {
|
|
66
|
+
if (runtime.prefix !== null) {
|
|
67
|
+
return ["--dir", NODE_ROOT, "--ro-bind", runtime.prefix, NODE_ROOT]
|
|
68
|
+
}
|
|
69
|
+
return ["--dir", NODE_ROOT, "--ro-bind", execPath, `${NODE_ROOT}/node`]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function insideWorkspace(workspaceDir: string, cwd: string): string {
|
|
73
|
+
const root = resolve(workspaceDir)
|
|
74
|
+
const target = resolve(cwd)
|
|
75
|
+
if (target !== root && !target.startsWith(`${root}${sep}`)) {
|
|
76
|
+
throw new Error(`cwd 不在 workspace 内:${cwd}`)
|
|
77
|
+
}
|
|
78
|
+
const rel = relative(root, target)
|
|
79
|
+
return rel === "" ? "/workspace" : `/workspace/${rel.split(sep).join("/")}`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 纯函数,方便单测 */
|
|
83
|
+
export function buildBwrapLaunch(input: AgentLaunchInput): LaunchSpec {
|
|
84
|
+
const args: string[] = [
|
|
85
|
+
"--unshare-all",
|
|
86
|
+
"--die-with-parent",
|
|
87
|
+
"--new-session",
|
|
88
|
+
"--clearenv",
|
|
89
|
+
"--cap-drop",
|
|
90
|
+
"ALL",
|
|
91
|
+
"--proc",
|
|
92
|
+
"/proc",
|
|
93
|
+
"--dev",
|
|
94
|
+
"/dev",
|
|
95
|
+
"--tmpfs",
|
|
96
|
+
"/tmp",
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
if (input.network === true) {
|
|
100
|
+
args.push("--share-net")
|
|
101
|
+
}
|
|
102
|
+
for (const dir of existingSystemDirs()) {
|
|
103
|
+
args.push("--ro-bind-try", dir, dir)
|
|
104
|
+
}
|
|
105
|
+
// DNS、CA 证书与用户解析
|
|
106
|
+
args.push("--ro-bind-try", "/etc", "/etc")
|
|
107
|
+
// systemd-resolved 的 resolv.conf 指向 /run;先建目标目录再尝试绑定
|
|
108
|
+
if (existsSync("/run/systemd/resolve")) {
|
|
109
|
+
args.push("--dir", "/run", "--dir", "/run/systemd", "--dir", "/run/systemd/resolve")
|
|
110
|
+
args.push("--ro-bind-try", "/run/systemd/resolve", "/run/systemd/resolve")
|
|
111
|
+
}
|
|
112
|
+
const nodeRuntime = resolveNodeRuntime()
|
|
113
|
+
args.push(...nodeRuntimeMount(nodeRuntime, process.execPath))
|
|
114
|
+
const workspaceBind = input.access === "workspace-write" ? "--bind" : "--ro-bind"
|
|
115
|
+
args.push(workspaceBind, resolve(input.workspaceDir), "/workspace")
|
|
116
|
+
args.push("--bind", resolve(input.scratchDir), "/scratch")
|
|
117
|
+
args.push("--chdir", insideWorkspace(input.workspaceDir, input.cwd))
|
|
118
|
+
|
|
119
|
+
const env: Record<string, string> = { ...input.env }
|
|
120
|
+
env["PATH"] = `${nodeRuntime.pathEntry}:${env["PATH"] ?? "/usr/bin:/bin"}`
|
|
121
|
+
/*
|
|
122
|
+
* 不再限制 RLIMIT_AS(虚拟地址空间会直接打死 V8 / JVM / Go)。
|
|
123
|
+
* 物理内存由 SandboxManager 挂 cgroup v2 memory.max;这里只留 V8 堆上限,
|
|
124
|
+
* 且对所有命令生效,npx / npm 派生的 node 子进程也能继承。
|
|
125
|
+
*/
|
|
126
|
+
env["NODE_OPTIONS"] =
|
|
127
|
+
`${env["NODE_OPTIONS"] ?? ""} --max-old-space-size=${input.limits.memoryMB}`.trim()
|
|
128
|
+
for (const [key, value] of Object.entries(env)) {
|
|
129
|
+
args.push("--setenv", key, value)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
args.push("--")
|
|
133
|
+
|
|
134
|
+
// prlimit 自身在 /usr/bin;只留文件描述符与进程数上限,超时交给 supervisor 的 wall timeout
|
|
135
|
+
const prlimitArgs = ["--nofile=256", `--nproc=${input.limits.pids}`]
|
|
136
|
+
args.push("/usr/bin/prlimit", ...prlimitArgs, "--", input.command, ...input.argv)
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
command: "bwrap",
|
|
140
|
+
args,
|
|
141
|
+
cwd: resolve(input.workspaceDir),
|
|
142
|
+
env: { ...input.env },
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** 纯函数:插件 profile 的 bwrap launch spec */
|
|
147
|
+
export function buildBwrapPluginLaunch(input: PluginLaunchInput): LaunchSpec {
|
|
148
|
+
const args: string[] = [
|
|
149
|
+
"--unshare-all",
|
|
150
|
+
"--die-with-parent",
|
|
151
|
+
"--new-session",
|
|
152
|
+
"--clearenv",
|
|
153
|
+
"--cap-drop",
|
|
154
|
+
"ALL",
|
|
155
|
+
"--proc",
|
|
156
|
+
"/proc",
|
|
157
|
+
"--dev",
|
|
158
|
+
"/dev",
|
|
159
|
+
"--tmpfs",
|
|
160
|
+
"/tmp",
|
|
161
|
+
]
|
|
162
|
+
for (const dir of existingSystemDirs()) {
|
|
163
|
+
args.push("--ro-bind-try", dir, dir)
|
|
164
|
+
}
|
|
165
|
+
const nodeRuntime = resolveNodeRuntime()
|
|
166
|
+
args.push(...nodeRuntimeMount(nodeRuntime, process.execPath))
|
|
167
|
+
args.push("--ro-bind", resolve(input.pluginDir), "/plugin")
|
|
168
|
+
args.push("--ro-bind", resolve(input.hostSdkDir), "/host")
|
|
169
|
+
args.push("--chdir", "/plugin")
|
|
170
|
+
|
|
171
|
+
const env: Record<string, string> = { ...input.env }
|
|
172
|
+
env["PATH"] = `${nodeRuntime.pathEntry}:${env["PATH"] ?? "/usr/bin:/bin"}`
|
|
173
|
+
env["NODE_OPTIONS"] =
|
|
174
|
+
`${env["NODE_OPTIONS"] ?? ""} --max-old-space-size=${input.limits.memoryMB}`.trim()
|
|
175
|
+
for (const [key, value] of Object.entries(env)) {
|
|
176
|
+
args.push("--setenv", key, value)
|
|
177
|
+
}
|
|
178
|
+
args.push("--")
|
|
179
|
+
args.push(
|
|
180
|
+
"/usr/bin/prlimit",
|
|
181
|
+
"--nofile=256",
|
|
182
|
+
`--nproc=${input.limits.pids}`,
|
|
183
|
+
"--",
|
|
184
|
+
nodeRuntime.nodePath,
|
|
185
|
+
"--permission",
|
|
186
|
+
"--allow-fs-read=/plugin",
|
|
187
|
+
"--allow-fs-read=/host",
|
|
188
|
+
"/host/src/sandbox/plugin-host.ts",
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
command: "bwrap",
|
|
193
|
+
args,
|
|
194
|
+
cwd: resolve(input.pluginDir),
|
|
195
|
+
env: { ...input.env },
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export class BwrapBackend implements SandboxBackend {
|
|
200
|
+
readonly name = "bwrap"
|
|
201
|
+
|
|
202
|
+
async probe(): Promise<SandboxBackendStatus> {
|
|
203
|
+
if (!existsSync("/usr/bin/bwrap")) {
|
|
204
|
+
return {
|
|
205
|
+
available: false,
|
|
206
|
+
backend: this.name,
|
|
207
|
+
reason: "找不到 bwrap",
|
|
208
|
+
version: null,
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const result = spawnSync(
|
|
212
|
+
"bwrap",
|
|
213
|
+
[
|
|
214
|
+
"--unshare-all",
|
|
215
|
+
"--die-with-parent",
|
|
216
|
+
"--ro-bind",
|
|
217
|
+
"/",
|
|
218
|
+
"/",
|
|
219
|
+
"--dev",
|
|
220
|
+
"/dev",
|
|
221
|
+
"--proc",
|
|
222
|
+
"/proc",
|
|
223
|
+
"--",
|
|
224
|
+
"/bin/true",
|
|
225
|
+
],
|
|
226
|
+
{ timeout: 3_000, encoding: "utf8" },
|
|
227
|
+
)
|
|
228
|
+
if (result.status !== 0) {
|
|
229
|
+
return {
|
|
230
|
+
available: false,
|
|
231
|
+
backend: this.name,
|
|
232
|
+
reason: (result.stderr ?? "").trim() || "bwrap 无法创建 namespace",
|
|
233
|
+
version: null,
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
available: true,
|
|
238
|
+
backend: this.name,
|
|
239
|
+
reason: null,
|
|
240
|
+
version: null,
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
buildAgentLaunch(input: AgentLaunchInput): LaunchSpec {
|
|
245
|
+
return buildBwrapLaunch(input)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
buildPluginLaunch(input: PluginLaunchInput): LaunchSpec {
|
|
249
|
+
return buildBwrapPluginLaunch(input)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 无隔离 fallback:仅用于 best-effort / 测试。
|
|
3
|
+
* 不提供 fs、网络、进程隔离;严格模式禁止使用。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
AgentLaunchInput,
|
|
8
|
+
LaunchSpec,
|
|
9
|
+
PluginLaunchInput,
|
|
10
|
+
SandboxBackend,
|
|
11
|
+
SandboxBackendStatus,
|
|
12
|
+
} from "../types.ts"
|
|
13
|
+
|
|
14
|
+
export class DirectBackend implements SandboxBackend {
|
|
15
|
+
readonly name = "direct"
|
|
16
|
+
|
|
17
|
+
async probe(): Promise<SandboxBackendStatus> {
|
|
18
|
+
return {
|
|
19
|
+
available: true,
|
|
20
|
+
backend: this.name,
|
|
21
|
+
reason: null,
|
|
22
|
+
version: null,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
buildAgentLaunch(input: AgentLaunchInput): LaunchSpec {
|
|
27
|
+
return {
|
|
28
|
+
command: input.command,
|
|
29
|
+
args: [...input.argv],
|
|
30
|
+
cwd: input.cwd,
|
|
31
|
+
env: input.env,
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
buildPluginLaunch(input: PluginLaunchInput): LaunchSpec {
|
|
36
|
+
return {
|
|
37
|
+
command: process.execPath,
|
|
38
|
+
args: [input.runnerPath],
|
|
39
|
+
cwd: input.pluginDir,
|
|
40
|
+
env: input.env,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|