@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,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 插件目录与命名规则。
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join } from "node:path"
|
|
6
|
+
|
|
7
|
+
import { getPaths, type ResolvedPaths } from "../platform/path.ts"
|
|
8
|
+
|
|
9
|
+
/** 插件目录名/包名:字母数字开头,允许 . _ -,最长 64 */
|
|
10
|
+
export const PLUGIN_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/
|
|
11
|
+
|
|
12
|
+
/** 插件根目录:<dataDir>/plugins */
|
|
13
|
+
export function pluginsRoot(paths: ResolvedPaths = getPaths()): string {
|
|
14
|
+
return join(paths.dataDir, "plugins")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isValidPluginName(name: string): boolean {
|
|
18
|
+
return PLUGIN_NAME.test(name)
|
|
19
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 插件命令注册表。
|
|
3
|
+
* 内置命令保留,插件命令挂在顶级;重名拒绝,别名同样参与冲突检查。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PluginCommand } from "../api/host/plugin.ts"
|
|
7
|
+
|
|
8
|
+
/** 内置命令名,插件不可占用 */
|
|
9
|
+
export const RESERVED_COMMANDS: ReadonlySet<string> = new Set([
|
|
10
|
+
"chat",
|
|
11
|
+
"run",
|
|
12
|
+
"session",
|
|
13
|
+
"config",
|
|
14
|
+
"plugin",
|
|
15
|
+
"doctor",
|
|
16
|
+
"version",
|
|
17
|
+
"help",
|
|
18
|
+
])
|
|
19
|
+
|
|
20
|
+
const COMMAND_NAME = /^[a-z][a-z0-9-]*$/
|
|
21
|
+
|
|
22
|
+
export interface RegisteredPluginCommand {
|
|
23
|
+
readonly pluginName: string
|
|
24
|
+
readonly command: PluginCommand
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class PluginRegistry {
|
|
28
|
+
readonly #reserved: ReadonlySet<string>
|
|
29
|
+
readonly #commands = new Map<string, RegisteredPluginCommand>()
|
|
30
|
+
readonly #aliases = new Map<string, RegisteredPluginCommand>()
|
|
31
|
+
readonly #pluginNames = new Set<string>()
|
|
32
|
+
|
|
33
|
+
constructor(reserved: ReadonlySet<string> = RESERVED_COMMANDS) {
|
|
34
|
+
this.#reserved = reserved
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 注册插件命令;非法、保留名或重名时抛错 */
|
|
38
|
+
register(pluginName: string, command: PluginCommand): void {
|
|
39
|
+
if (
|
|
40
|
+
!COMMAND_NAME.test(command.name) ||
|
|
41
|
+
command.name.length < 2 ||
|
|
42
|
+
command.name.length > 32
|
|
43
|
+
) {
|
|
44
|
+
throw new Error(`插件命令名不合法:${command.name}`)
|
|
45
|
+
}
|
|
46
|
+
if (this.#reserved.has(command.name)) {
|
|
47
|
+
throw new Error(`插件不能覆盖内置命令:${command.name}`)
|
|
48
|
+
}
|
|
49
|
+
if (this.#commands.has(command.name) || this.#aliases.has(command.name)) {
|
|
50
|
+
throw new Error(`插件命令重名:${command.name}`)
|
|
51
|
+
}
|
|
52
|
+
if (typeof command.summary !== "string" || command.summary.trim() === "") {
|
|
53
|
+
throw new Error(`插件命令缺少 summary:${command.name}`)
|
|
54
|
+
}
|
|
55
|
+
if (typeof command.run !== "function") {
|
|
56
|
+
throw new Error(`插件命令缺少 run:${command.name}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const record: RegisteredPluginCommand = { pluginName, command }
|
|
60
|
+
this.#commands.set(command.name, record)
|
|
61
|
+
this.#pluginNames.add(pluginName)
|
|
62
|
+
|
|
63
|
+
for (const alias of command.aliases ?? []) {
|
|
64
|
+
if (!COMMAND_NAME.test(alias) || alias.length < 2 || alias.length > 32) {
|
|
65
|
+
throw new Error(`插件命令别名不合法:${alias}`)
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
this.#reserved.has(alias) ||
|
|
69
|
+
this.#commands.has(alias) ||
|
|
70
|
+
this.#aliases.has(alias)
|
|
71
|
+
) {
|
|
72
|
+
throw new Error(`插件命令别名冲突:${alias}`)
|
|
73
|
+
}
|
|
74
|
+
this.#aliases.set(alias, record)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
resolve(name: string): RegisteredPluginCommand | undefined {
|
|
79
|
+
return this.#commands.get(name) ?? this.#aliases.get(name)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
list(): readonly RegisteredPluginCommand[] {
|
|
83
|
+
return [...this.#commands.values()]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
removePlugin(pluginName: string): void {
|
|
87
|
+
for (const [name, record] of this.#commands) {
|
|
88
|
+
if (record.pluginName === pluginName) {
|
|
89
|
+
this.#commands.delete(name)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const [alias, record] of this.#aliases) {
|
|
93
|
+
if (record.pluginName === pluginName) {
|
|
94
|
+
this.#aliases.delete(alias)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
this.#pluginNames.delete(pluginName)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
pluginNames(): readonly string[] {
|
|
101
|
+
return [...this.#pluginNames]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 插件运行时目录:
|
|
3
|
+
* - 数据:<dataDir>/plugin-data/<id>/
|
|
4
|
+
* - 配置:<configDir>/plugins/<id>.json(插件自己的 JSON 配置)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { join } from "node:path"
|
|
8
|
+
|
|
9
|
+
import type { PluginConfig } from "../api/host/plugin.ts"
|
|
10
|
+
import { ensureDir, readJsonIfExists } from "../platform/file.ts"
|
|
11
|
+
import { getPaths, type ResolvedPaths } from "../platform/path.ts"
|
|
12
|
+
import { PLUGIN_NAME } from "./paths.ts"
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line no-control-regex
|
|
15
|
+
const CONTROL_CHARS = /[\u0000-\u001f\u007f]/
|
|
16
|
+
|
|
17
|
+
function assertPluginId(id: string): void {
|
|
18
|
+
if (!PLUGIN_NAME.test(id) || CONTROL_CHARS.test(id)) {
|
|
19
|
+
throw new Error(`插件 id 不合法:${id}`)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 插件专属数据目录,平台会确保存在 */
|
|
24
|
+
export function pluginDataDir(id: string, paths: ResolvedPaths = getPaths()): string {
|
|
25
|
+
assertPluginId(id)
|
|
26
|
+
return join(paths.dataDir, "plugin-data", id)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 插件专属配置文件 */
|
|
30
|
+
export function pluginConfigFile(id: string, paths: ResolvedPaths = getPaths()): string {
|
|
31
|
+
assertPluginId(id)
|
|
32
|
+
return join(paths.configDir, "plugins", `${id}.json`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function ensurePluginDataDir(
|
|
36
|
+
id: string,
|
|
37
|
+
paths: ResolvedPaths = getPaths(),
|
|
38
|
+
): Promise<string> {
|
|
39
|
+
const dir = pluginDataDir(id, paths)
|
|
40
|
+
await ensureDir(dir)
|
|
41
|
+
return dir
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 读取插件配置;文件不存在时返回空对象 */
|
|
45
|
+
export async function loadPluginConfig(
|
|
46
|
+
id: string,
|
|
47
|
+
paths: ResolvedPaths = getPaths(),
|
|
48
|
+
): Promise<PluginConfig> {
|
|
49
|
+
const value = await readJsonIfExists<unknown>(pluginConfigFile(id, paths))
|
|
50
|
+
if (value === null) {
|
|
51
|
+
return {}
|
|
52
|
+
}
|
|
53
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
54
|
+
throw new Error(`插件配置必须是 JSON 对象:${pluginConfigFile(id, paths)}`)
|
|
55
|
+
}
|
|
56
|
+
return value as Record<string, unknown>
|
|
57
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
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
|
+
"`package.json` 的 `packageManager` 声明插件使用哪个包管理器(`npm@11.20.0`、`pnpm@10` 等)。",
|
|
102
|
+
"安装 zip 或目录时,有 `dependencies` 会按这个声明自动安装;没有声明时按 lockfile 推断。",
|
|
103
|
+
"",
|
|
104
|
+
"## 加载",
|
|
105
|
+
"",
|
|
106
|
+
"```bash",
|
|
107
|
+
`wellspring plugin link \"$PWD\"`,
|
|
108
|
+
"wellspring plugin list",
|
|
109
|
+
`wellspring ${commandName}`,
|
|
110
|
+
"```",
|
|
111
|
+
"",
|
|
112
|
+
].join("\n")
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function scaffoldPlugin(
|
|
116
|
+
options: ScaffoldPluginOptions,
|
|
117
|
+
): Promise<ScaffoldPluginResult> {
|
|
118
|
+
const name = options.name.trim()
|
|
119
|
+
if (!PLUGIN_NAME.test(name)) {
|
|
120
|
+
throw new Error(`插件名不合法:${name}`)
|
|
121
|
+
}
|
|
122
|
+
const parentDir = resolve(options.parentDir ?? process.cwd())
|
|
123
|
+
const dir = join(parentDir, name)
|
|
124
|
+
const existing = await statIfExists(dir)
|
|
125
|
+
if (existing !== null) {
|
|
126
|
+
if (options.force !== true) {
|
|
127
|
+
throw new Error(`目录已存在:${dir}`)
|
|
128
|
+
}
|
|
129
|
+
await removePath(dir)
|
|
130
|
+
}
|
|
131
|
+
await ensureDir(dir)
|
|
132
|
+
|
|
133
|
+
const commandName = commandNameOf(name)
|
|
134
|
+
await writeJsonAtomic(join(dir, "package.json"), {
|
|
135
|
+
name,
|
|
136
|
+
version: "0.1.0",
|
|
137
|
+
packageManager: "npm@11.20.0",
|
|
138
|
+
type: "module",
|
|
139
|
+
main: "index.ts",
|
|
140
|
+
wellspring: { apiVersion: 1 },
|
|
141
|
+
})
|
|
142
|
+
await writeTextAtomic(join(dir, "index.ts"), template(name, commandName))
|
|
143
|
+
await writeJsonAtomic(join(dir, "tsconfig.json"), {
|
|
144
|
+
compilerOptions: {
|
|
145
|
+
target: "ES2024",
|
|
146
|
+
module: "NodeNext",
|
|
147
|
+
moduleResolution: "NodeNext",
|
|
148
|
+
strict: true,
|
|
149
|
+
noEmit: true,
|
|
150
|
+
allowImportingTsExtensions: true,
|
|
151
|
+
skipLibCheck: true,
|
|
152
|
+
types: ["node"],
|
|
153
|
+
},
|
|
154
|
+
include: ["index.ts"],
|
|
155
|
+
})
|
|
156
|
+
await writeTextAtomic(join(dir, "README.md"), readmeTemplate(name, commandName))
|
|
157
|
+
return { dir, commandName }
|
|
158
|
+
}
|
|
@@ -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
|
+
}
|