@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,969 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 宿主 Agent 服务:给 sandbox 插件提供聊天入口。
|
|
3
|
+
* 复用 CLI 的会话存储与 Agent 轮次,不经过 stdout/readline。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawn } from "node:child_process"
|
|
7
|
+
import { randomUUID } from "node:crypto"
|
|
8
|
+
import { accessSync, constants } from "node:fs"
|
|
9
|
+
import { readdir, stat } from "node:fs/promises"
|
|
10
|
+
import { homedir } from "node:os"
|
|
11
|
+
import { basename, delimiter, isAbsolute, join, relative, resolve, sep } from "node:path"
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
HostAgentApi,
|
|
15
|
+
HostAgentApprovalRequest,
|
|
16
|
+
HostAgentAskRequest,
|
|
17
|
+
HostAgentChatInput,
|
|
18
|
+
HostAgentChatResult,
|
|
19
|
+
HostAgentStreamEvent,
|
|
20
|
+
HostAgentToolEvent,
|
|
21
|
+
HostWorkspaceCheck,
|
|
22
|
+
HostWorkspaceFileEntry,
|
|
23
|
+
HostWorkspaceFilesResult,
|
|
24
|
+
} from "../api/host/agent.ts"
|
|
25
|
+
import type { ExecApprovalRequest } from "../commands/agent-turn.ts"
|
|
26
|
+
import type { CommandContext } from "../commands/shared.ts"
|
|
27
|
+
import { executeTurn } from "../commands/conversation.ts"
|
|
28
|
+
import { answerAsk, cancelSessionAsks } from "../agent/pending-asks.ts"
|
|
29
|
+
import { credentialsFile } from "../config/credentials.ts"
|
|
30
|
+
import { readTextIfExists } from "../platform/file.ts"
|
|
31
|
+
import { DEFAULT_SETTINGS } from "../config/defaults.ts"
|
|
32
|
+
import { loadSettings, SETTING_FILE_NAME } from "../config/loader.ts"
|
|
33
|
+
import {
|
|
34
|
+
effectiveModel,
|
|
35
|
+
effectiveReasoning,
|
|
36
|
+
providerApiKey,
|
|
37
|
+
providerOpenAISettings,
|
|
38
|
+
requestProvider,
|
|
39
|
+
type ResolvedModelProvider,
|
|
40
|
+
} from "../config/providers.ts"
|
|
41
|
+
import type { AppSetting, SandboxAccess } from "../config/types.ts"
|
|
42
|
+
import { getElog } from "../logger/elog4js.ts"
|
|
43
|
+
import type { Logger } from "pino"
|
|
44
|
+
import { OpenAIClient } from "../network/openai.ts"
|
|
45
|
+
import { createProxyFetch } from "../network/proxy.ts"
|
|
46
|
+
import type { ResolvedPaths } from "../platform/path.ts"
|
|
47
|
+
import type { AgentToolRegistry } from "./agent-tools.ts"
|
|
48
|
+
import type { PluginRegistry } from "./registry.ts"
|
|
49
|
+
import { getSession, registerSession } from "../session/registry.ts"
|
|
50
|
+
import { Session } from "../session/session.ts"
|
|
51
|
+
import { SessionStore } from "../session/store.ts"
|
|
52
|
+
|
|
53
|
+
export interface HostAgentServiceOptions {
|
|
54
|
+
readonly settings: AppSetting
|
|
55
|
+
readonly paths: ResolvedPaths
|
|
56
|
+
readonly logger: Logger
|
|
57
|
+
readonly registry: PluginRegistry
|
|
58
|
+
readonly agentTools: AgentToolRegistry
|
|
59
|
+
readonly cwd?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const SANDBOX_ACCESS_VALUES: readonly SandboxAccess[] = [
|
|
63
|
+
"read-only",
|
|
64
|
+
"workspace-write",
|
|
65
|
+
"danger-full-access",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
/** 插件的 host.agent.* RPC 可能带上来路不明的 permission,这里按白名单校验 */
|
|
69
|
+
function turnPermission(input: HostAgentChatInput, fallback: SandboxAccess): SandboxAccess {
|
|
70
|
+
const value: unknown = input.permission
|
|
71
|
+
if (value === undefined || value === null) {
|
|
72
|
+
return fallback
|
|
73
|
+
}
|
|
74
|
+
if (!SANDBOX_ACCESS_VALUES.includes(value as SandboxAccess)) {
|
|
75
|
+
throw new Error(`permission 不合法:${String(value)}`)
|
|
76
|
+
}
|
|
77
|
+
return value as SandboxAccess
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface HostAgentService extends HostAgentApi {
|
|
81
|
+
chat(input: HostAgentChatInput): Promise<HostAgentChatResult>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** 打开宿主系统的目录选择对话框;浏览器无法提供原生对话框的绝对路径 */
|
|
85
|
+
interface NativeCommandResult {
|
|
86
|
+
readonly code: number | null
|
|
87
|
+
readonly stdout: string
|
|
88
|
+
readonly stderr: string
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function runNativeCommand(command: string, args: readonly string[]): Promise<NativeCommandResult> {
|
|
92
|
+
return new Promise((resolvePromise) => {
|
|
93
|
+
let settled = false
|
|
94
|
+
let stdout = ""
|
|
95
|
+
let stderr = ""
|
|
96
|
+
const child = spawn(command, [...args], { stdio: ["ignore", "pipe", "pipe"] })
|
|
97
|
+
const timer = setTimeout(() => {
|
|
98
|
+
child.kill("SIGTERM")
|
|
99
|
+
}, 120_000)
|
|
100
|
+
const finish = (code: number | null): void => {
|
|
101
|
+
if (settled) {
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
settled = true
|
|
105
|
+
clearTimeout(timer)
|
|
106
|
+
resolvePromise({ code, stdout, stderr })
|
|
107
|
+
}
|
|
108
|
+
child.stdout?.on("data", (chunk: Buffer) => {
|
|
109
|
+
stdout += chunk.toString("utf8")
|
|
110
|
+
})
|
|
111
|
+
child.stderr?.on("data", (chunk: Buffer) => {
|
|
112
|
+
stderr += chunk.toString("utf8")
|
|
113
|
+
})
|
|
114
|
+
child.once("error", () => {
|
|
115
|
+
finish(null)
|
|
116
|
+
})
|
|
117
|
+
child.once("close", (code) => {
|
|
118
|
+
finish(code)
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function hasExecutable(name: string): boolean {
|
|
124
|
+
for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
|
|
125
|
+
if (dir === "") {
|
|
126
|
+
continue
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
accessSync(join(dir, name), constants.X_OK)
|
|
130
|
+
return true
|
|
131
|
+
} catch {
|
|
132
|
+
/* 继续找 */
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 目录选择框没返回路径时,区分「用户取消」与「对话框根本没起来」
|
|
140
|
+
*
|
|
141
|
+
* 以前所有失败都报「已取消」,界面因此什么都不提示:
|
|
142
|
+
* zenity 打不开显示器时会带着退出码 0 和空的 stdout 退出(只往 stderr 写一行 GTK 警告),
|
|
143
|
+
* 看起来和用户按了取消一模一样。这里按退出码归因,让上层能把真因说出来。
|
|
144
|
+
*/
|
|
145
|
+
function pickerOutcome(command: string, result: NativeCommandResult): HostWorkspaceCheck {
|
|
146
|
+
const cancelled: HostWorkspaceCheck = { ok: false, root: "", name: "", error: "已取消" }
|
|
147
|
+
// 用户关掉窗口:zenity / kdialog 退出码 1;osascript 是 AppleScript 的 -128
|
|
148
|
+
if (result.code === 1 || result.stderr.includes("-128")) {
|
|
149
|
+
return cancelled
|
|
150
|
+
}
|
|
151
|
+
if (result.code === null) {
|
|
152
|
+
return { ok: false, root: "", name: "", error: `无法启动 ${command}(未找到或不可执行)` }
|
|
153
|
+
}
|
|
154
|
+
const detail =
|
|
155
|
+
result.stderr
|
|
156
|
+
.split("\n")
|
|
157
|
+
.map((line) => line.trim())
|
|
158
|
+
.filter((line) => line !== "")
|
|
159
|
+
.pop() ?? ""
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
root: "",
|
|
163
|
+
name: "",
|
|
164
|
+
error:
|
|
165
|
+
detail === ""
|
|
166
|
+
? `${command} 没有返回目录(退出码 ${result.code})`
|
|
167
|
+
: `${command} 无法显示选择框:${detail}`,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const IGNORED_DIRECTORIES = new Set([
|
|
172
|
+
".git",
|
|
173
|
+
".hg",
|
|
174
|
+
".svn",
|
|
175
|
+
"node_modules",
|
|
176
|
+
".pnpm-store",
|
|
177
|
+
".yarn",
|
|
178
|
+
".npm",
|
|
179
|
+
".next",
|
|
180
|
+
".nuxt",
|
|
181
|
+
".svelte-kit",
|
|
182
|
+
".output",
|
|
183
|
+
".vercel",
|
|
184
|
+
".expo",
|
|
185
|
+
".dart_tool",
|
|
186
|
+
"dist",
|
|
187
|
+
"build",
|
|
188
|
+
"out",
|
|
189
|
+
"target",
|
|
190
|
+
"coverage",
|
|
191
|
+
".cache",
|
|
192
|
+
".turbo",
|
|
193
|
+
".parcel-cache",
|
|
194
|
+
".gradle",
|
|
195
|
+
".mvn",
|
|
196
|
+
".venv",
|
|
197
|
+
"venv",
|
|
198
|
+
"Pods",
|
|
199
|
+
".terraform",
|
|
200
|
+
"__pycache__",
|
|
201
|
+
".tox",
|
|
202
|
+
".mypy_cache",
|
|
203
|
+
".pytest_cache",
|
|
204
|
+
".ruff_cache",
|
|
205
|
+
".idea",
|
|
206
|
+
".vscode",
|
|
207
|
+
])
|
|
208
|
+
|
|
209
|
+
const MAX_FILE_SCAN_ENTRIES = 20_000
|
|
210
|
+
const MAX_FILE_RESULTS = 30
|
|
211
|
+
const MAX_FILE_CANDIDATES = 2_000
|
|
212
|
+
const MAX_SEARCH_DEPTH = 12
|
|
213
|
+
const MAX_SEARCH_QUERY_CHARS = 200
|
|
214
|
+
const MAX_INTERJECT_CHARS = 32_000
|
|
215
|
+
/** 单次搜索的时间软上限:慢盘或超大仓库里不拖住宿主事件循环 */
|
|
216
|
+
const MAX_SEARCH_MILLISECONDS = 500
|
|
217
|
+
/** 浏览单个目录时最多列出的条目数 */
|
|
218
|
+
const MAX_BROWSE_ENTRIES = 200
|
|
219
|
+
|
|
220
|
+
async function validateWorkspacePath(path: string): Promise<HostWorkspaceCheck> {
|
|
221
|
+
const value = typeof path === "string" ? path.trim() : ""
|
|
222
|
+
if (value === "") {
|
|
223
|
+
return { ok: false, root: "", name: "", error: "路径不能为空" }
|
|
224
|
+
}
|
|
225
|
+
const resolved = resolve(value)
|
|
226
|
+
const name = basename(resolved)
|
|
227
|
+
try {
|
|
228
|
+
const info = await stat(resolved)
|
|
229
|
+
if (!info.isDirectory()) {
|
|
230
|
+
return { ok: false, root: resolved, name, error: "不是目录" }
|
|
231
|
+
}
|
|
232
|
+
} catch {
|
|
233
|
+
return { ok: false, root: resolved, name, error: "目录不存在" }
|
|
234
|
+
}
|
|
235
|
+
return { ok: true, root: resolved, name: name === "" ? resolved : name }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** query 的字符是否按顺序出现在 text 里(大小写已归一) */
|
|
239
|
+
function isSubsequence(query: string, text: string): boolean {
|
|
240
|
+
let cursor = 0
|
|
241
|
+
for (const char of text) {
|
|
242
|
+
if (char === query[cursor]) {
|
|
243
|
+
cursor += 1
|
|
244
|
+
if (cursor === query.length) {
|
|
245
|
+
return true
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return false
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** 路径段匹配等级:0 全等、1 前缀、2 包含;allowSubsequence 时再加 3 子序列 */
|
|
253
|
+
function segmentScore(segment: string, needle: string, allowSubsequence: boolean): number | null {
|
|
254
|
+
if (segment === needle) {
|
|
255
|
+
return 0
|
|
256
|
+
}
|
|
257
|
+
if (segment.startsWith(needle)) {
|
|
258
|
+
return 1
|
|
259
|
+
}
|
|
260
|
+
if (segment.includes(needle)) {
|
|
261
|
+
return 2
|
|
262
|
+
}
|
|
263
|
+
if (allowSubsequence && isSubsequence(needle, segment)) {
|
|
264
|
+
return 3
|
|
265
|
+
}
|
|
266
|
+
return null
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* 单一关键字的打分:越小越靠前。
|
|
271
|
+
* 0 文件名全等、1 文件名前缀、2 文件名某段前缀 / 扩展名、3 文件名包含、
|
|
272
|
+
* 4 路径段命中、5 路径包含、6 文件名子序列;不匹配返回 null。
|
|
273
|
+
* 路径子序列(任何文件都能拼出短查询)噪声太大,不参与匹配。
|
|
274
|
+
*/
|
|
275
|
+
function singleKeyScore(name: string, path: string, query: string): number | null {
|
|
276
|
+
const needle = query.toLowerCase()
|
|
277
|
+
if (needle === "") {
|
|
278
|
+
return 0
|
|
279
|
+
}
|
|
280
|
+
const nameLower = name.toLowerCase()
|
|
281
|
+
if (nameLower === needle) {
|
|
282
|
+
return 0
|
|
283
|
+
}
|
|
284
|
+
if (nameLower.startsWith(needle)) {
|
|
285
|
+
return 1
|
|
286
|
+
}
|
|
287
|
+
const segments = nameLower.split(/[.\-_\s]+/).filter((segment) => segment !== "")
|
|
288
|
+
if (segments.some((segment) => segment.startsWith(needle))) {
|
|
289
|
+
return 2
|
|
290
|
+
}
|
|
291
|
+
// ".ts" 这类只写扩展名的查询
|
|
292
|
+
if (needle.startsWith(".") && nameLower.endsWith(needle)) {
|
|
293
|
+
return 2
|
|
294
|
+
}
|
|
295
|
+
if (nameLower.includes(needle)) {
|
|
296
|
+
return 3
|
|
297
|
+
}
|
|
298
|
+
const pathLower = path.toLowerCase()
|
|
299
|
+
if (
|
|
300
|
+
pathLower.startsWith(needle) ||
|
|
301
|
+
pathLower.split("/").some((segment) => segment.startsWith(needle))
|
|
302
|
+
) {
|
|
303
|
+
return 4
|
|
304
|
+
}
|
|
305
|
+
if (pathLower.includes(needle)) {
|
|
306
|
+
return 5
|
|
307
|
+
}
|
|
308
|
+
if (isSubsequence(needle, nameLower)) {
|
|
309
|
+
return 6
|
|
310
|
+
}
|
|
311
|
+
return null
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 带斜杠的查询按路径段链式匹配(a/b/c):前几段必须依次命中目录段,
|
|
316
|
+
* 最后一段命中文件名或目录名。这样 `@assets/app` 也能找到 `src/assets/app.ts`,
|
|
317
|
+
* 不必先输入工作区根目录下的完整路径。
|
|
318
|
+
*/
|
|
319
|
+
function chainScore(
|
|
320
|
+
pathSegments: readonly string[],
|
|
321
|
+
querySegments: readonly string[],
|
|
322
|
+
): number | null {
|
|
323
|
+
const nameSegment = pathSegments[pathSegments.length - 1]
|
|
324
|
+
const lastQuery = querySegments[querySegments.length - 1]
|
|
325
|
+
if (nameSegment === undefined || lastQuery === undefined) {
|
|
326
|
+
return null
|
|
327
|
+
}
|
|
328
|
+
const lastNameScore = segmentScore(nameSegment, lastQuery, true)
|
|
329
|
+
if (lastNameScore === null) {
|
|
330
|
+
return null
|
|
331
|
+
}
|
|
332
|
+
let score = lastNameScore
|
|
333
|
+
let cursor = 0
|
|
334
|
+
for (let index = 0; index < querySegments.length - 1; index += 1) {
|
|
335
|
+
const needle = querySegments[index]
|
|
336
|
+
if (needle === undefined) {
|
|
337
|
+
return null
|
|
338
|
+
}
|
|
339
|
+
let matched = -1
|
|
340
|
+
for (let position = cursor; position < pathSegments.length - 1; position += 1) {
|
|
341
|
+
const segment = pathSegments[position]
|
|
342
|
+
if (segment === undefined) {
|
|
343
|
+
continue
|
|
344
|
+
}
|
|
345
|
+
const matchedScore = segmentScore(segment, needle, false)
|
|
346
|
+
if (matchedScore === null) {
|
|
347
|
+
continue
|
|
348
|
+
}
|
|
349
|
+
matched = position
|
|
350
|
+
score += matchedScore
|
|
351
|
+
break
|
|
352
|
+
}
|
|
353
|
+
if (matched === -1) {
|
|
354
|
+
return null
|
|
355
|
+
}
|
|
356
|
+
cursor = matched + 1
|
|
357
|
+
}
|
|
358
|
+
return score
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
interface FileCandidate extends HostWorkspaceFileEntry {
|
|
362
|
+
readonly depth: number
|
|
363
|
+
readonly score: number
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** 目录浏览:只列一层,目录在前,按名称排序 */
|
|
367
|
+
async function browseDirectory(root: string, directory: string): Promise<HostWorkspaceFilesResult> {
|
|
368
|
+
let dirents
|
|
369
|
+
try {
|
|
370
|
+
dirents = await readdir(directory, { withFileTypes: true })
|
|
371
|
+
} catch {
|
|
372
|
+
return { ok: true, root, entries: [], truncated: false }
|
|
373
|
+
}
|
|
374
|
+
dirents.sort((left, right) => {
|
|
375
|
+
const leftRank = left.isDirectory() ? 0 : 1
|
|
376
|
+
const rightRank = right.isDirectory() ? 0 : 1
|
|
377
|
+
if (leftRank !== rightRank) {
|
|
378
|
+
return leftRank - rightRank
|
|
379
|
+
}
|
|
380
|
+
return left.name.localeCompare(right.name)
|
|
381
|
+
})
|
|
382
|
+
const entries: HostWorkspaceFileEntry[] = []
|
|
383
|
+
let truncated = false
|
|
384
|
+
for (const dirent of dirents) {
|
|
385
|
+
if (dirent.isSymbolicLink()) {
|
|
386
|
+
continue
|
|
387
|
+
}
|
|
388
|
+
if (dirent.name === "." || dirent.name === "..") {
|
|
389
|
+
continue
|
|
390
|
+
}
|
|
391
|
+
const isDirectory = dirent.isDirectory()
|
|
392
|
+
// 根目录一层里藏起来的依赖/构建目录不展示;显式进入时仍可浏览
|
|
393
|
+
if (isDirectory && directory === root && IGNORED_DIRECTORIES.has(dirent.name)) {
|
|
394
|
+
continue
|
|
395
|
+
}
|
|
396
|
+
if (entries.length >= MAX_BROWSE_ENTRIES) {
|
|
397
|
+
truncated = true
|
|
398
|
+
break
|
|
399
|
+
}
|
|
400
|
+
entries.push({
|
|
401
|
+
path: relative(root, join(directory, dirent.name)).split(sep).join("/"),
|
|
402
|
+
name: dirent.name,
|
|
403
|
+
type: isDirectory ? "directory" : "file",
|
|
404
|
+
})
|
|
405
|
+
}
|
|
406
|
+
return { ok: true, root, entries, truncated }
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function compareCandidates(left: FileCandidate, right: FileCandidate): number {
|
|
410
|
+
if (left.score !== right.score) {
|
|
411
|
+
return left.score - right.score
|
|
412
|
+
}
|
|
413
|
+
if (left.type !== right.type) {
|
|
414
|
+
return left.type === "directory" ? -1 : 1
|
|
415
|
+
}
|
|
416
|
+
if (left.depth !== right.depth) {
|
|
417
|
+
return left.depth - right.depth
|
|
418
|
+
}
|
|
419
|
+
return left.path.localeCompare(right.path)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* 在工作区里按文件名 / 路径搜索(广度优先,浅层条目优先进入扫描预算)。
|
|
424
|
+
* - query 为空或以 `/` 结尾且路径存在:只列该目录一层
|
|
425
|
+
* - 单关键字(`app`):文件名优先,其次路径段 / 路径包含 / 子序列
|
|
426
|
+
* - 带斜杠(`assets/app`):按路径段链式匹配,不要求前缀目录真实存在
|
|
427
|
+
*/
|
|
428
|
+
export async function searchWorkspaceFiles(
|
|
429
|
+
rootPath: string,
|
|
430
|
+
query: string,
|
|
431
|
+
): Promise<HostWorkspaceFilesResult> {
|
|
432
|
+
const check = await validateWorkspacePath(rootPath)
|
|
433
|
+
if (!check.ok) {
|
|
434
|
+
return {
|
|
435
|
+
ok: false,
|
|
436
|
+
root: "",
|
|
437
|
+
entries: [],
|
|
438
|
+
truncated: false,
|
|
439
|
+
error: check.error ?? "目录不可用",
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const root = check.root
|
|
443
|
+
const raw = (typeof query === "string" ? query : "").trim().replaceAll("\\", "/")
|
|
444
|
+
if (raw.length > MAX_SEARCH_QUERY_CHARS) {
|
|
445
|
+
return {
|
|
446
|
+
ok: false,
|
|
447
|
+
root,
|
|
448
|
+
entries: [],
|
|
449
|
+
truncated: false,
|
|
450
|
+
error: "搜索词过长",
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const withoutLeading = raw.replace(/^\/+/, "")
|
|
454
|
+
const segments = withoutLeading
|
|
455
|
+
.split("/")
|
|
456
|
+
.filter((segment) => segment !== "" && segment !== ".")
|
|
457
|
+
if (segments.length === 0) {
|
|
458
|
+
return await browseDirectory(root, root)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// 输入了完整目录路径(以 / 结尾)时直接列出它的内容
|
|
462
|
+
if (withoutLeading.endsWith("/")) {
|
|
463
|
+
const target = resolve(root, segments.join("/"))
|
|
464
|
+
const rel = relative(root, target)
|
|
465
|
+
if (!rel.startsWith("..") && !isAbsolute(rel)) {
|
|
466
|
+
try {
|
|
467
|
+
const info = await stat(target)
|
|
468
|
+
if (info.isDirectory()) {
|
|
469
|
+
return await browseDirectory(root, target)
|
|
470
|
+
}
|
|
471
|
+
} catch {
|
|
472
|
+
/* 目录不存在,退回模糊搜索 */
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const querySegments = segments.map((segment) => segment.toLowerCase())
|
|
478
|
+
const singleNeedle = querySegments.length === 1 ? (querySegments[0] ?? null) : null
|
|
479
|
+
const candidates: FileCandidate[] = []
|
|
480
|
+
const deadline = performance.now() + MAX_SEARCH_MILLISECONDS
|
|
481
|
+
let visited = 0
|
|
482
|
+
let truncated = false
|
|
483
|
+
const queue: Array<{ readonly dir: string; readonly depth: number }> = [{ dir: root, depth: 0 }]
|
|
484
|
+
while (queue.length > 0) {
|
|
485
|
+
if (visited >= MAX_FILE_SCAN_ENTRIES || performance.now() > deadline) {
|
|
486
|
+
truncated = true
|
|
487
|
+
break
|
|
488
|
+
}
|
|
489
|
+
const current = queue.shift()
|
|
490
|
+
if (current === undefined) {
|
|
491
|
+
break
|
|
492
|
+
}
|
|
493
|
+
let dirents
|
|
494
|
+
try {
|
|
495
|
+
dirents = await readdir(current.dir, { withFileTypes: true })
|
|
496
|
+
} catch {
|
|
497
|
+
continue
|
|
498
|
+
}
|
|
499
|
+
dirents.sort((left, right) => left.name.localeCompare(right.name))
|
|
500
|
+
for (const dirent of dirents) {
|
|
501
|
+
if (visited >= MAX_FILE_SCAN_ENTRIES || candidates.length >= MAX_FILE_CANDIDATES) {
|
|
502
|
+
truncated = true
|
|
503
|
+
break
|
|
504
|
+
}
|
|
505
|
+
visited += 1
|
|
506
|
+
if (dirent.isSymbolicLink()) {
|
|
507
|
+
continue
|
|
508
|
+
}
|
|
509
|
+
const isDirectory = dirent.isDirectory()
|
|
510
|
+
if (isDirectory && IGNORED_DIRECTORIES.has(dirent.name)) {
|
|
511
|
+
continue
|
|
512
|
+
}
|
|
513
|
+
const rel = relative(root, join(current.dir, dirent.name)).split(sep).join("/")
|
|
514
|
+
const score =
|
|
515
|
+
singleNeedle === null
|
|
516
|
+
? chainScore(rel.split("/"), querySegments)
|
|
517
|
+
: singleKeyScore(dirent.name, rel, singleNeedle)
|
|
518
|
+
if (score !== null) {
|
|
519
|
+
candidates.push({
|
|
520
|
+
path: rel,
|
|
521
|
+
name: dirent.name,
|
|
522
|
+
type: isDirectory ? "directory" : "file",
|
|
523
|
+
depth: current.depth,
|
|
524
|
+
score,
|
|
525
|
+
})
|
|
526
|
+
}
|
|
527
|
+
if (isDirectory && current.depth < MAX_SEARCH_DEPTH) {
|
|
528
|
+
queue.push({ dir: join(current.dir, dirent.name), depth: current.depth + 1 })
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
candidates.sort(compareCandidates)
|
|
533
|
+
const entries = candidates.slice(0, MAX_FILE_RESULTS).map((entry) => ({
|
|
534
|
+
path: entry.path,
|
|
535
|
+
name: entry.name,
|
|
536
|
+
type: entry.type,
|
|
537
|
+
}))
|
|
538
|
+
const limited = candidates.length > MAX_FILE_RESULTS
|
|
539
|
+
return { ok: true, root, entries, truncated: truncated || limited }
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
export function createHostAgentService(options: HostAgentServiceOptions): HostAgentService {
|
|
543
|
+
const cwd = options.cwd ?? process.cwd()
|
|
544
|
+
const store = new SessionStore()
|
|
545
|
+
const queues = new Map<string, Promise<unknown>>()
|
|
546
|
+
/**
|
|
547
|
+
* 每个会话当前这一轮的 AbortController
|
|
548
|
+
*
|
|
549
|
+
* stop() 除了标记停止,还要把正在进行的模型请求打断——
|
|
550
|
+
* 否则「停止」只是等这一轮自己跑完,慢模型要等很久。
|
|
551
|
+
*/
|
|
552
|
+
const turnAborts = new Map<string, AbortController>()
|
|
553
|
+
const pendingApprovals = new Map<
|
|
554
|
+
string,
|
|
555
|
+
{
|
|
556
|
+
readonly sessionId: string
|
|
557
|
+
readonly resolve: (approved: boolean) => void
|
|
558
|
+
readonly timer: ReturnType<typeof setTimeout>
|
|
559
|
+
}
|
|
560
|
+
>()
|
|
561
|
+
const APPROVAL_TIMEOUT_MS = 120_000
|
|
562
|
+
|
|
563
|
+
const settleApproval = (approvalId: string, approved: boolean): boolean => {
|
|
564
|
+
const pending = pendingApprovals.get(approvalId)
|
|
565
|
+
if (pending === undefined) {
|
|
566
|
+
return false
|
|
567
|
+
}
|
|
568
|
+
pendingApprovals.delete(approvalId)
|
|
569
|
+
clearTimeout(pending.timer)
|
|
570
|
+
pending.resolve(approved)
|
|
571
|
+
return true
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** 停止一轮时把它挂着的提权请求按拒绝了结,避免循环卡在等待里 */
|
|
575
|
+
const settleSessionApprovals = (sessionId: string): void => {
|
|
576
|
+
for (const [approvalId, pending] of [...pendingApprovals]) {
|
|
577
|
+
if (pending.sessionId !== sessionId) {
|
|
578
|
+
continue
|
|
579
|
+
}
|
|
580
|
+
pendingApprovals.delete(approvalId)
|
|
581
|
+
clearTimeout(pending.timer)
|
|
582
|
+
pending.resolve(false)
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const settingsPath = join(options.paths.configDir, SETTING_FILE_NAME)
|
|
587
|
+
|
|
588
|
+
// 每轮都读一次配置与凭据:网页/CLI 改供应商、模型、Key 后不需要重启服务。
|
|
589
|
+
// 配置文件不存在时用启动时传入的配置(测试与 --config 覆盖场景依赖这一点)。
|
|
590
|
+
const currentSettings = async (): Promise<AppSetting> => {
|
|
591
|
+
try {
|
|
592
|
+
if ((await readTextIfExists(settingsPath)) === null) {
|
|
593
|
+
return options.settings ?? DEFAULT_SETTINGS
|
|
594
|
+
}
|
|
595
|
+
const loaded = await loadSettings(settingsPath)
|
|
596
|
+
return loaded.getData() ?? options.settings ?? DEFAULT_SETTINGS
|
|
597
|
+
} catch {
|
|
598
|
+
return options.settings ?? DEFAULT_SETTINGS
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const createProviderClient = (
|
|
603
|
+
settings: AppSetting,
|
|
604
|
+
provider: ResolvedModelProvider,
|
|
605
|
+
): OpenAIClient => {
|
|
606
|
+
let proxyFetch: ReturnType<typeof createProxyFetch> | undefined
|
|
607
|
+
try {
|
|
608
|
+
proxyFetch = createProxyFetch(settings.network.proxy)
|
|
609
|
+
} catch {
|
|
610
|
+
proxyFetch = undefined
|
|
611
|
+
}
|
|
612
|
+
// 自定义供应商没有 key 时不能悄悄回落到内置 OpenAI key:显式传空串让创建失败
|
|
613
|
+
const apiKey = providerApiKey(provider, options.paths.configDir) ?? ""
|
|
614
|
+
const result = OpenAIClient.create({
|
|
615
|
+
settings: providerOpenAISettings(settings, provider),
|
|
616
|
+
apiKey,
|
|
617
|
+
credentialsFile: credentialsFile(options.paths.configDir),
|
|
618
|
+
logger: options.logger.child({ module: "openai" }),
|
|
619
|
+
fetch: proxyFetch,
|
|
620
|
+
})
|
|
621
|
+
const created = result.getData()
|
|
622
|
+
if (!result.isSuccess() || created === null) {
|
|
623
|
+
throw new Error(result.getCode() ?? "openai.missing-api-key")
|
|
624
|
+
}
|
|
625
|
+
return created
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
const context = (
|
|
629
|
+
settings: AppSetting,
|
|
630
|
+
client: OpenAIClient,
|
|
631
|
+
model: string,
|
|
632
|
+
provider: ResolvedModelProvider,
|
|
633
|
+
): CommandContext => ({
|
|
634
|
+
global: { json: true, quiet: true, help: false, version: false },
|
|
635
|
+
settings,
|
|
636
|
+
settingsFile: settingsPath,
|
|
637
|
+
configCode: null,
|
|
638
|
+
paths: options.paths,
|
|
639
|
+
logger: options.logger,
|
|
640
|
+
elog: getElog(),
|
|
641
|
+
registry: options.registry,
|
|
642
|
+
agentTools: options.agentTools,
|
|
643
|
+
openai: client,
|
|
644
|
+
activeProvider: provider.id,
|
|
645
|
+
activeModel: model,
|
|
646
|
+
loadPlugins: async () => [],
|
|
647
|
+
})
|
|
648
|
+
|
|
649
|
+
/** 校验工作目录:存在且是目录 */
|
|
650
|
+
const validateWorkspace = async (path: string): Promise<HostWorkspaceCheck> => {
|
|
651
|
+
return await validateWorkspacePath(path)
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/** 打开原生目录选择对话框:Linux 用 zenity/kdialog,macOS 用 osascript,Windows 用 FolderBrowserDialog */
|
|
655
|
+
const pickDirectory = async (): Promise<HostWorkspaceCheck> => {
|
|
656
|
+
const title = "选择工作区文件夹"
|
|
657
|
+
if (process.platform === "darwin") {
|
|
658
|
+
const result = await runNativeCommand("osascript", [
|
|
659
|
+
"-e",
|
|
660
|
+
`POSIX path of (choose folder with prompt "${title}")`,
|
|
661
|
+
])
|
|
662
|
+
const selected = result.stdout.trim()
|
|
663
|
+
if (result.code === 0 && selected !== "") {
|
|
664
|
+
return await validateWorkspace(selected)
|
|
665
|
+
}
|
|
666
|
+
return pickerOutcome("osascript", result)
|
|
667
|
+
}
|
|
668
|
+
if (process.platform === "win32") {
|
|
669
|
+
const script = [
|
|
670
|
+
"Add-Type -AssemblyName System.Windows.Forms",
|
|
671
|
+
"$dialog = New-Object System.Windows.Forms.FolderBrowserDialog",
|
|
672
|
+
`$dialog.Description = '${title}'`,
|
|
673
|
+
"if ($dialog.ShowDialog() -eq 'OK') { Write-Output $dialog.SelectedPath }",
|
|
674
|
+
].join("; ")
|
|
675
|
+
const result = await runNativeCommand("powershell.exe", [
|
|
676
|
+
"-NoProfile",
|
|
677
|
+
"-NonInteractive",
|
|
678
|
+
"-Command",
|
|
679
|
+
script,
|
|
680
|
+
])
|
|
681
|
+
const selected = result.stdout.trim()
|
|
682
|
+
if (result.code === 0 && selected !== "") {
|
|
683
|
+
return await validateWorkspace(selected)
|
|
684
|
+
}
|
|
685
|
+
return pickerOutcome("powershell", result)
|
|
686
|
+
}
|
|
687
|
+
if (hasExecutable("zenity")) {
|
|
688
|
+
const result = await runNativeCommand("zenity", [
|
|
689
|
+
"--file-selection",
|
|
690
|
+
"--directory",
|
|
691
|
+
"--title",
|
|
692
|
+
title,
|
|
693
|
+
])
|
|
694
|
+
const selected = result.stdout.trim()
|
|
695
|
+
return result.code === 0 && selected !== ""
|
|
696
|
+
? await validateWorkspace(selected)
|
|
697
|
+
: pickerOutcome("zenity", result)
|
|
698
|
+
}
|
|
699
|
+
if (hasExecutable("kdialog")) {
|
|
700
|
+
const result = await runNativeCommand("kdialog", [
|
|
701
|
+
"--getexistingdirectory",
|
|
702
|
+
homedir(),
|
|
703
|
+
"--title",
|
|
704
|
+
title,
|
|
705
|
+
])
|
|
706
|
+
const selected = result.stdout.trim()
|
|
707
|
+
return result.code === 0 && selected !== ""
|
|
708
|
+
? await validateWorkspace(selected)
|
|
709
|
+
: pickerOutcome("kdialog", result)
|
|
710
|
+
}
|
|
711
|
+
return {
|
|
712
|
+
ok: false,
|
|
713
|
+
root: "",
|
|
714
|
+
name: "",
|
|
715
|
+
error: "没有可用的系统文件夹对话框(Linux 需要 zenity 或 kdialog)",
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const chatOnce = async (
|
|
720
|
+
input: HostAgentChatInput,
|
|
721
|
+
onDelta?: (text: string) => void,
|
|
722
|
+
onStep?: (event: { readonly turn: number; readonly step: number }) => void,
|
|
723
|
+
onApproval?: (request: HostAgentApprovalRequest) => void,
|
|
724
|
+
onReasoning?: (text: string) => void,
|
|
725
|
+
onTool?: (event: HostAgentToolEvent) => void,
|
|
726
|
+
onAsk?: (request: HostAgentAskRequest) => void,
|
|
727
|
+
onSession?: (sessionId: string) => void,
|
|
728
|
+
): Promise<HostAgentChatResult> => {
|
|
729
|
+
const message = input.message.trim()
|
|
730
|
+
if (message === "") {
|
|
731
|
+
throw new Error("消息不能为空")
|
|
732
|
+
}
|
|
733
|
+
const settings = await currentSettings()
|
|
734
|
+
const provider = requestProvider(settings, input.provider)
|
|
735
|
+
const model = effectiveModel(settings, provider, input.model)
|
|
736
|
+
const reasoning = effectiveReasoning(settings, provider, input.reasoning)
|
|
737
|
+
const client = createProviderClient(settings, provider)
|
|
738
|
+
const permission = turnPermission(input, settings.sandbox.access)
|
|
739
|
+
if (permission !== settings.sandbox.access) {
|
|
740
|
+
// 插件可以在单轮里提升权限(例如网页的权限切换器);记录下来便于审计
|
|
741
|
+
options.logger.warn(
|
|
742
|
+
{
|
|
743
|
+
requested: permission,
|
|
744
|
+
configured: settings.sandbox.access,
|
|
745
|
+
sessionId: input.sessionId ?? null,
|
|
746
|
+
},
|
|
747
|
+
permission === "danger-full-access"
|
|
748
|
+
? "插件本轮请求 danger-full-access 权限"
|
|
749
|
+
: "插件本轮覆盖 sandbox 权限",
|
|
750
|
+
)
|
|
751
|
+
}
|
|
752
|
+
let turnCwd = cwd
|
|
753
|
+
if (input.cwd !== undefined) {
|
|
754
|
+
const workspace = await validateWorkspace(input.cwd)
|
|
755
|
+
if (!workspace.ok) {
|
|
756
|
+
throw new Error(`工作区不可用:${workspace.error ?? "目录不存在"}`)
|
|
757
|
+
}
|
|
758
|
+
turnCwd = workspace.root
|
|
759
|
+
}
|
|
760
|
+
const references = Array.isArray(input.references)
|
|
761
|
+
? input.references
|
|
762
|
+
.filter(
|
|
763
|
+
(item): item is string =>
|
|
764
|
+
typeof item === "string" && item !== "" && item.length <= 512,
|
|
765
|
+
)
|
|
766
|
+
.slice(0, 20)
|
|
767
|
+
: []
|
|
768
|
+
const messageForAgent =
|
|
769
|
+
references.length === 0
|
|
770
|
+
? message
|
|
771
|
+
: `${message}\n\n引用的工作区路径:\n${references.map((item) => `- ${item}`).join("\n")}\n需要时请按路径读取。`
|
|
772
|
+
const data =
|
|
773
|
+
input.sessionId === undefined
|
|
774
|
+
? await store.create({ cwd: turnCwd, model })
|
|
775
|
+
: await store.open(input.sessionId, turnCwd)
|
|
776
|
+
registerSession(new Session(data, store))
|
|
777
|
+
onSession?.(data.meta.id)
|
|
778
|
+
const requestApproval =
|
|
779
|
+
onApproval === undefined
|
|
780
|
+
? undefined
|
|
781
|
+
: async (request: ExecApprovalRequest): Promise<boolean> => {
|
|
782
|
+
const approvalId = `apv_${randomUUID()}`
|
|
783
|
+
return await new Promise<boolean>((resolvePromise) => {
|
|
784
|
+
const timer = setTimeout(() => {
|
|
785
|
+
pendingApprovals.delete(approvalId)
|
|
786
|
+
resolvePromise(false)
|
|
787
|
+
}, APPROVAL_TIMEOUT_MS)
|
|
788
|
+
pendingApprovals.set(approvalId, {
|
|
789
|
+
sessionId: data.meta.id,
|
|
790
|
+
resolve: (approved) => resolvePromise(approved),
|
|
791
|
+
timer,
|
|
792
|
+
})
|
|
793
|
+
try {
|
|
794
|
+
onApproval({
|
|
795
|
+
approvalId,
|
|
796
|
+
command: request.command,
|
|
797
|
+
argv: request.argv,
|
|
798
|
+
cwd: request.cwd,
|
|
799
|
+
currentPermission: request.currentPermission,
|
|
800
|
+
requestedPermission: request.requestedPermission,
|
|
801
|
+
justification: request.justification,
|
|
802
|
+
...(request.network === true ? { network: true } : {}),
|
|
803
|
+
...(request.background === true ? { background: true } : {}),
|
|
804
|
+
...(typeof request.stdin === "string" && request.stdin !== ""
|
|
805
|
+
? { stdin: request.stdin }
|
|
806
|
+
: {}),
|
|
807
|
+
...(typeof request.timeoutMs === "number"
|
|
808
|
+
? { timeoutMs: request.timeoutMs }
|
|
809
|
+
: {}),
|
|
810
|
+
})
|
|
811
|
+
} catch {
|
|
812
|
+
pendingApprovals.delete(approvalId)
|
|
813
|
+
clearTimeout(timer)
|
|
814
|
+
resolvePromise(false)
|
|
815
|
+
}
|
|
816
|
+
})
|
|
817
|
+
}
|
|
818
|
+
const turnAbort = new AbortController()
|
|
819
|
+
turnAborts.set(data.meta.id, turnAbort)
|
|
820
|
+
let result
|
|
821
|
+
try {
|
|
822
|
+
result = await executeTurn({
|
|
823
|
+
ctx: context(settings, client, model, provider),
|
|
824
|
+
signal: turnAbort.signal,
|
|
825
|
+
client,
|
|
826
|
+
store,
|
|
827
|
+
session: data,
|
|
828
|
+
mode: "run",
|
|
829
|
+
input: messageForAgent,
|
|
830
|
+
model,
|
|
831
|
+
reasoning,
|
|
832
|
+
system: input.system,
|
|
833
|
+
stream: false,
|
|
834
|
+
display: false,
|
|
835
|
+
allowExec: false,
|
|
836
|
+
permission,
|
|
837
|
+
onDelta,
|
|
838
|
+
onReasoning,
|
|
839
|
+
onTool,
|
|
840
|
+
onStep,
|
|
841
|
+
approveExec: requestApproval,
|
|
842
|
+
...(onAsk === undefined
|
|
843
|
+
? // 没有界面通道(agent.chat)时立即跳过提问,避免一轮对话挂到超时
|
|
844
|
+
{ askUser: async () => null }
|
|
845
|
+
: { onAsk }),
|
|
846
|
+
})
|
|
847
|
+
} finally {
|
|
848
|
+
turnAborts.delete(data.meta.id)
|
|
849
|
+
}
|
|
850
|
+
if (!result.isSuccess()) {
|
|
851
|
+
throw new Error(result.getCode() ?? "agent.failed")
|
|
852
|
+
}
|
|
853
|
+
const turn = result.getData()
|
|
854
|
+
if (turn === null) {
|
|
855
|
+
throw new Error("agent.empty-result")
|
|
856
|
+
}
|
|
857
|
+
return {
|
|
858
|
+
sessionId: turn.sessionId,
|
|
859
|
+
turn: turn.turn,
|
|
860
|
+
steps: turn.steps,
|
|
861
|
+
reply: turn.reply,
|
|
862
|
+
model: turn.model,
|
|
863
|
+
usage: turn.usage,
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const enqueue = async (
|
|
868
|
+
input: HostAgentChatInput,
|
|
869
|
+
onDelta?: (text: string) => void,
|
|
870
|
+
onStep?: (event: { readonly turn: number; readonly step: number }) => void,
|
|
871
|
+
onApproval?: (request: HostAgentApprovalRequest) => void,
|
|
872
|
+
onReasoning?: (text: string) => void,
|
|
873
|
+
onTool?: (event: HostAgentToolEvent) => void,
|
|
874
|
+
onAsk?: (request: HostAgentAskRequest) => void,
|
|
875
|
+
onSession?: (sessionId: string) => void,
|
|
876
|
+
): Promise<HostAgentChatResult> => {
|
|
877
|
+
const key = `${input.cwd ?? ""}|${input.sessionId ?? "new"}`
|
|
878
|
+
const previous = queues.get(key) ?? Promise.resolve()
|
|
879
|
+
const next = previous.then(
|
|
880
|
+
async () =>
|
|
881
|
+
await chatOnce(
|
|
882
|
+
input,
|
|
883
|
+
onDelta,
|
|
884
|
+
onStep,
|
|
885
|
+
onApproval,
|
|
886
|
+
onReasoning,
|
|
887
|
+
onTool,
|
|
888
|
+
onAsk,
|
|
889
|
+
onSession,
|
|
890
|
+
),
|
|
891
|
+
async () =>
|
|
892
|
+
await chatOnce(
|
|
893
|
+
input,
|
|
894
|
+
onDelta,
|
|
895
|
+
onStep,
|
|
896
|
+
onApproval,
|
|
897
|
+
onReasoning,
|
|
898
|
+
onTool,
|
|
899
|
+
onAsk,
|
|
900
|
+
onSession,
|
|
901
|
+
),
|
|
902
|
+
)
|
|
903
|
+
queues.set(
|
|
904
|
+
key,
|
|
905
|
+
next.catch(() => undefined),
|
|
906
|
+
)
|
|
907
|
+
const result = await next
|
|
908
|
+
if (input.sessionId === undefined) {
|
|
909
|
+
queues.delete(key)
|
|
910
|
+
queues.set(result.sessionId, Promise.resolve(result))
|
|
911
|
+
}
|
|
912
|
+
return result
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
return {
|
|
916
|
+
async chat(input) {
|
|
917
|
+
return await enqueue(input)
|
|
918
|
+
},
|
|
919
|
+
validateWorkspace,
|
|
920
|
+
pickDirectory,
|
|
921
|
+
async searchWorkspaceFiles(root, query) {
|
|
922
|
+
return await searchWorkspaceFiles(root, query)
|
|
923
|
+
},
|
|
924
|
+
async stream(input, onEvent: (event: HostAgentStreamEvent) => void) {
|
|
925
|
+
return await enqueue(
|
|
926
|
+
input,
|
|
927
|
+
(text) => onEvent({ type: "delta", text }),
|
|
928
|
+
(event) => onEvent({ type: "step", turn: event.turn, step: event.step }),
|
|
929
|
+
(request) => onEvent({ type: "approval", request }),
|
|
930
|
+
(text) => onEvent({ type: "reasoning", text }),
|
|
931
|
+
(event) => onEvent({ type: "tool", ...event }),
|
|
932
|
+
(request) => onEvent({ type: "ask", request }),
|
|
933
|
+
(sessionId) => onEvent({ type: "session", sessionId }),
|
|
934
|
+
)
|
|
935
|
+
},
|
|
936
|
+
async interject(sessionId, message) {
|
|
937
|
+
const content = typeof message === "string" ? message.trim() : ""
|
|
938
|
+
if (content === "") {
|
|
939
|
+
return false
|
|
940
|
+
}
|
|
941
|
+
const session = getSession(sessionId)
|
|
942
|
+
// 只有正在跑的一轮才能插话;其余情况交给调用方按新一轮处理
|
|
943
|
+
if (session === null || !session.active) {
|
|
944
|
+
return false
|
|
945
|
+
}
|
|
946
|
+
await session.message(content.slice(0, MAX_INTERJECT_CHARS), "interject")
|
|
947
|
+
return true
|
|
948
|
+
},
|
|
949
|
+
async stop(sessionId) {
|
|
950
|
+
const session = getSession(sessionId)
|
|
951
|
+
if (session === null || !session.active) {
|
|
952
|
+
return false
|
|
953
|
+
}
|
|
954
|
+
session.requestStop()
|
|
955
|
+
// 挂着等人的请求就地了结,循环才能读到停止标记
|
|
956
|
+
cancelSessionAsks(sessionId)
|
|
957
|
+
settleSessionApprovals(sessionId)
|
|
958
|
+
// 打断正在进行的模型请求:已收到的正文照常保留,本轮按「已停止」收尾
|
|
959
|
+
turnAborts.get(sessionId)?.abort()
|
|
960
|
+
return true
|
|
961
|
+
},
|
|
962
|
+
async respondApproval(approvalId, approved) {
|
|
963
|
+
return settleApproval(approvalId, approved)
|
|
964
|
+
},
|
|
965
|
+
async respondAsk(askId, answer) {
|
|
966
|
+
return answerAsk(askId, answer)
|
|
967
|
+
},
|
|
968
|
+
}
|
|
969
|
+
}
|