@ljwei-stak/dsh-model-route-bot 0.66.4 → 0.67.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/.dsh-plugin/bot-product.mjs +81 -5
- package/.dsh-plugin/client.js +17490 -1791
- package/README.i18n.yaml +4 -4
- package/README.md +409 -364
- package/README.zh.md +409 -364
- package/apps/integration-client/README.md +31 -7
- package/apps/integration-client/api.mjs +14 -3
- package/apps/integration-client/binding-editor.jsx +308 -109
- package/apps/integration-client/binding-model.mjs +262 -0
- package/apps/integration-client/binding.css +144 -0
- package/apps/integration-client/bot-model-status.mjs +18 -0
- package/apps/integration-client/bot-profile-identity.jsx +197 -0
- package/apps/integration-client/bot-profile.css +57 -0
- package/apps/integration-client/bot-profile.mjs +97 -0
- package/apps/integration-client/bot-settings-dialog.css +18 -0
- package/apps/integration-client/bot-settings-dialog.jsx +111 -0
- package/apps/integration-client/bot-thread-header-bridge.css +20 -0
- package/apps/integration-client/bot-thread-header.jsx +278 -0
- package/apps/integration-client/bot-threads-panel.jsx +378 -0
- package/apps/integration-client/bot-threads.css +169 -0
- package/apps/integration-client/chat-design.mjs +250 -0
- package/apps/integration-client/chat-ui.jsx +175 -0
- package/apps/integration-client/chat.css +477 -0
- package/apps/integration-client/composer-draft-binding.mjs +50 -0
- package/apps/integration-client/composer-draft-bound.jsx +14 -0
- package/apps/integration-client/composer-draft-host.jsx +142 -0
- package/apps/integration-client/composer-draft-session.jsx +287 -0
- package/apps/integration-client/composer-draft-witness.mjs +115 -0
- package/apps/integration-client/composer-draft.css +4 -0
- package/apps/integration-client/composer-draft.mjs +322 -0
- package/apps/integration-client/contact-attention.jsx +54 -0
- package/apps/integration-client/data-panel.jsx +439 -0
- package/apps/integration-client/data.css +69 -0
- package/apps/integration-client/desktop-bridge.mjs +1 -0
- package/apps/integration-client/desktop-http-socket.mjs +49 -0
- package/apps/integration-client/desktop-keyboard.mjs +32 -0
- package/apps/integration-client/desktop-pane.jsx +99 -30
- package/apps/integration-client/desktop-stream.mjs +13 -3
- package/apps/integration-client/desktop-tools.jsx +19 -0
- package/apps/integration-client/desktop-touch.mjs +27 -0
- package/apps/integration-client/desktop.css +41 -0
- package/apps/integration-client/independent-chat-controller.mjs +224 -0
- package/apps/integration-client/independent-chat-entry.jsx +61 -0
- package/apps/integration-client/independent-chat-navigation.mjs +21 -0
- package/apps/integration-client/independent-chat-page.jsx +148 -0
- package/apps/integration-client/independent-chat-rich.jsx +64 -0
- package/apps/integration-client/independent-chat-rich.mjs +30 -0
- package/apps/integration-client/independent-chat.css +73 -0
- package/apps/integration-client/index.jsx +24 -16
- package/apps/integration-client/library-panel.jsx +4 -1
- package/apps/integration-client/licenses/jsqr-1.4.0-LICENSE.txt +202 -0
- package/apps/integration-client/main-route-editor.jsx +94 -0
- package/apps/integration-client/main-route-model.mjs +100 -0
- package/apps/integration-client/model-picker.css +37 -0
- package/apps/integration-client/model-picker.jsx +83 -0
- package/apps/integration-client/native-activity.css +141 -0
- package/apps/integration-client/native-activity.jsx +276 -0
- package/apps/integration-client/native-activity.mjs +384 -0
- package/apps/integration-client/notifications-panel.jsx +186 -0
- package/apps/integration-client/notifications.css +126 -0
- package/apps/integration-client/pairing-qr.jsx +79 -0
- package/apps/integration-client/panels.jsx +126 -115
- package/apps/integration-client/product-slots.jsx +46 -0
- package/apps/integration-client/routine-recorder.mjs +23 -2
- package/apps/integration-client/secure-pairing.jsx +84 -0
- package/apps/integration-client/tasks-panel.jsx +32 -10
- package/apps/integration-client/ui-icons.jsx +17 -0
- package/apps/integration-client/usage-explorer.jsx +43 -0
- package/apps/integration-client/usage-report.mjs +71 -0
- package/apps/integration-client/usage.css +34 -0
- package/apps/integration-client/web-panel-model.mjs +140 -0
- package/apps/integration-client/web-panel-page.jsx +221 -0
- package/apps/integration-client/web-panel-pins-live.jsx +28 -0
- package/apps/integration-client/web-panel.css +139 -0
- package/apps/integration-client/workspace-model.mjs +67 -0
- package/apps/integration-client/workspace-navigation.mjs +56 -0
- package/apps/integration-client/workspace-ui.jsx +173 -0
- package/apps/integration-client/workspace.css +332 -0
- package/modules/bot-runtime/SOURCE.json +176 -50
- package/modules/bot-runtime/index.mjs +169 -13
- package/modules/bot-runtime/product-overlay/OVERLAY.json +128 -2
- package/modules/bot-runtime/product-overlay/base/src/client/header.js +244 -0
- package/modules/bot-runtime/product-overlay/base/src/client/learning-card.js +202 -0
- package/modules/bot-runtime/product-overlay/base/src/client/model-menu.js +371 -0
- package/modules/bot-runtime/product-overlay/base/src/client/secret-card.js +363 -0
- package/modules/bot-runtime/product-overlay/base/src/client/sources.js +68 -0
- package/modules/bot-runtime/product-overlay/base/src/client/turn-error.js +260 -0
- package/modules/bot-runtime/product-overlay/base/src/host/channel.js +647 -0
- package/modules/bot-runtime/product-overlay/base/src/host/memory.js +2050 -0
- package/modules/bot-runtime/product-overlay/base/src/host/models.js +314 -0
- package/modules/bot-runtime/product-overlay/base/src/host/parts.js +1473 -0
- package/modules/bot-runtime/product-overlay/base/src/host/privacy.js +368 -0
- package/modules/bot-runtime/product-overlay/base/src/host/roster.js +776 -0
- package/modules/bot-runtime/product-overlay/base/src/host/store.js +512 -0
- package/modules/bot-runtime/product-overlay/base/src/host/usage.js +653 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/details-drawer.js.patch +48 -2
- package/modules/bot-runtime/product-overlay/patches/src/client/header.js.patch +41 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/index.js.patch +33 -1
- package/modules/bot-runtime/product-overlay/patches/src/client/learning-card.js.patch +20 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/model-menu.js.patch +24 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/secret-card.js.patch +25 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/sources.js.patch +52 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/turn-error.js.patch +23 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/channel.js.patch +46 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/memory.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/models.js.patch +16 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/parts.js.patch +95 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/privacy.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/roster.js.patch +145 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/store.js.patch +68 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/usage.js.patch +11 -0
- package/modules/bot-runtime/read-model.mjs +19 -4
- package/modules/bot-runtime/vendor/ds-bot/src/client/details-drawer.js +15 -5
- package/modules/bot-runtime/vendor/ds-bot/src/client/header.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/index.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/learning-card.js +2 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/model-menu.js +5 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/secret-card.js +3 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/sources.js +25 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/turn-error.js +3 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/channel.js +14 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/memory.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/models.js +7 -0
- package/modules/bot-runtime/vendor/ds-bot/src/host/parts.js +26 -9
- package/modules/bot-runtime/vendor/ds-bot/src/host/privacy.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/roster.js +112 -3
- package/modules/bot-runtime/vendor/ds-bot/src/host/store.js +34 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/usage.js +1 -1
- package/modules/independent-runtime/index.mjs +152 -62
- package/modules/independent-runtime/remote-tools.mjs +8 -5
- package/modules/openmaus-protocol/NOTICE +10 -0
- package/modules/openmaus-protocol/README.md +15 -0
- package/modules/openmaus-protocol/SOURCE.json +68 -0
- package/modules/openmaus-protocol/dist/protocol.mjs +1203 -0
- package/modules/openmaus-protocol/index.mjs +1 -0
- package/modules/openmaus-protocol/transport.mjs +77 -0
- package/modules/openmaus-protocol/vendor/LICENSE +202 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/README.md +405 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/credentials.ts +128 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/descriptor.ts +84 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/noise.ts +456 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/phone.ts +104 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/session.ts +417 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.json +2565 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.sha256 +1 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/yamux.ts +335 -0
- package/package.json +18 -6
- package/packages/bot-acp/README.md +156 -0
- package/packages/bot-acp/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-acp/licenses/NOTICE.md +27 -0
- package/packages/bot-acp/src/acp-adapter.mjs +795 -0
- package/packages/bot-acp/src/acp-host.mjs +148 -0
- package/packages/bot-acp/src/native-launch.mjs +121 -0
- package/packages/bot-acp/src/permission-policy.mjs +283 -0
- package/packages/bot-acp/src/rpc-codec.mjs +265 -0
- package/packages/bot-acp/src/session-core.mjs +581 -0
- package/packages/bot-acp/src/stdio-rpc.mjs +329 -0
- package/packages/bot-engines/README.md +281 -0
- package/packages/bot-engines/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-engines/licenses/NOTICE.md +32 -0
- package/packages/bot-engines/src/claude-engine.mjs +772 -0
- package/packages/bot-engines/src/claude-llm-adapter.mjs +797 -0
- package/packages/bot-engines/src/claude-llm-turn.mjs +292 -0
- package/packages/bot-engines/src/claude-permission-broker.mjs +130 -0
- package/packages/bot-engines/src/claude-permission-proxy.mjs +87 -0
- package/packages/bot-engines/src/claude-protocol.mjs +177 -0
- package/packages/bot-engines/src/driver-catalog.mjs +334 -0
- package/packages/bot-engines/src/host-loop-request.mjs +95 -0
- package/packages/bot-engines/src/host-registrations.mjs +105 -0
- package/packages/capability-broker/src/cli-bootstrap.mjs +86 -16
- package/packages/capability-broker/src/cli-bridge-response.mjs +80 -0
- package/packages/capability-broker/src/cli-stdio-bridge.mjs +21 -8
- package/packages/cli-runtime/src/codex-session-core.mjs +4 -0
- package/packages/cli-runtime/src/native-cli-output.mjs +57 -0
- package/packages/cli-runtime/src/native-peer.ps1 +51 -3
- package/packages/cli-runtime/src/native-process.mjs +77 -14
- package/packages/model-router-plugin/package.json +1 -1
- package/packages/orchestration/src/binding-product.mjs +19 -0
- package/packages/orchestration/src/bot-data-actions.mjs +45 -0
- package/packages/orchestration/src/bot-data-chart.mjs +109 -0
- package/packages/orchestration/src/bot-data-context.mjs +44 -0
- package/packages/orchestration/src/bot-data-engine.mjs +809 -0
- package/packages/orchestration/src/bot-data-io.mjs +186 -0
- package/packages/orchestration/src/bot-data-sql.mjs +195 -0
- package/packages/orchestration/src/bot-data-surface.mjs +144 -0
- package/packages/orchestration/src/bot-data-tools.mjs +68 -0
- package/packages/orchestration/src/bot-data-worker.mjs +228 -0
- package/packages/orchestration/src/bot-independent-execution.mjs +195 -0
- package/packages/orchestration/src/bot-independent-stream.mjs +74 -0
- package/packages/orchestration/src/bot-loop.mjs +152 -19
- package/packages/orchestration/src/bot-notifications.mjs +159 -0
- package/packages/orchestration/src/bot-policy.mjs +43 -4
- package/packages/orchestration/src/bot-profile.mjs +171 -0
- package/packages/orchestration/src/bot-routine.mjs +4 -4
- package/packages/orchestration/src/bot-runtime-assembly.mjs +255 -8
- package/packages/orchestration/src/bot-runtime-switch.mjs +13 -2
- package/packages/orchestration/src/bot-serving.mjs +164 -0
- package/packages/orchestration/src/bot-session-store.mjs +105 -9
- package/packages/orchestration/src/bot-settings.mjs +50 -11
- package/packages/orchestration/src/bot-thread-admission.mjs +84 -0
- package/packages/orchestration/src/bot-thread-host.mjs +187 -0
- package/packages/orchestration/src/bot-threads.mjs +790 -0
- package/packages/orchestration/src/data-product-access.mjs +37 -0
- package/packages/orchestration/src/data-tool-contract.mjs +291 -0
- package/packages/orchestration/src/desktop-http-carrier.mjs +120 -0
- package/packages/orchestration/src/desktop-proxy.mjs +311 -0
- package/packages/orchestration/src/desktop-sessions.mjs +43 -9
- package/packages/orchestration/src/ds-lifecycle.mjs +31 -4
- package/packages/orchestration/src/grant-refresh-queue.mjs +17 -0
- package/packages/orchestration/src/independent-chat-page.mjs +39 -0
- package/packages/orchestration/src/independent-chat-service.mjs +308 -0
- package/packages/orchestration/src/independent-context-state.mjs +44 -0
- package/packages/orchestration/src/independent-message-wire.mjs +84 -0
- package/packages/orchestration/src/independent-tool-catalog.mjs +175 -0
- package/packages/orchestration/src/independent-tool-families.mjs +73 -0
- package/packages/orchestration/src/independent-tool-host.mjs +344 -0
- package/packages/orchestration/src/main-bot-grant.mjs +15 -0
- package/packages/orchestration/src/main-model-routes.mjs +208 -0
- package/packages/orchestration/src/model-accounting.mjs +99 -35
- package/packages/orchestration/src/model-auxiliary-dispatch.mjs +187 -0
- package/packages/orchestration/src/model-loop-frame.mjs +179 -0
- package/packages/orchestration/src/model-prepared-generation.mjs +175 -0
- package/packages/orchestration/src/model-request-bounds.mjs +111 -0
- package/packages/orchestration/src/native-activity-contract.mjs +491 -0
- package/packages/orchestration/src/native-activity-service.mjs +102 -0
- package/packages/orchestration/src/native-activity-store.mjs +249 -0
- package/packages/orchestration/src/native-conversation-tools.mjs +78 -0
- package/packages/orchestration/src/native-data-tools.mjs +100 -0
- package/packages/orchestration/src/native-remote-actions.mjs +742 -0
- package/packages/orchestration/src/native-team-tools.mjs +73 -0
- package/packages/orchestration/src/pairing-invite.mjs +37 -0
- package/packages/orchestration/src/product-composition.mjs +1559 -1001
- package/packages/orchestration/src/remote-machines.mjs +82 -14
- package/packages/orchestration/src/routine-execution.mjs +5 -3
- package/packages/orchestration/src/routine-session.mjs +19 -13
- package/packages/orchestration/src/secure-pairing-host.mjs +112 -0
- package/packages/orchestration/src/task-journal.mjs +29 -0
- package/packages/orchestration/src/task-product.mjs +17 -0
- package/packages/orchestration/src/team-assignment-store.mjs +120 -0
- package/packages/orchestration/src/team-bridge.mjs +282 -0
- package/packages/orchestration/src/team-scheduler.mjs +83 -0
- package/packages/orchestration/src/team-sdk-tools.mjs +84 -0
- package/packages/orchestration/src/team-tool-contract.mjs +284 -0
- package/packages/orchestration/src/ui-host-namespace.mjs +23 -0
- package/packages/orchestration/src/web-panel-actions.mjs +103 -0
- package/packages/orchestration/src/web-panel-framing.mjs +227 -0
- package/packages/orchestration/src/web-panel-store.mjs +206 -0
- package/remote-computer-agent/README.md +39 -29
- package/remote-computer-agent/build.mjs +3 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_LICENSE +202 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_NOTICE +10 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_SOURCE.json +68 -0
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs +215 -25
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs.map +4 -4
- package/remote-computer-agent/dist/cli.mjs +1688 -168
- package/remote-computer-agent/dist/cli.mjs.map +4 -4
- package/remote-computer-agent/dist/index.mjs +1579 -122
- package/remote-computer-agent/dist/index.mjs.map +4 -4
- package/remote-computer-agent/dist/native-peer.ps1 +51 -3
- package/remote-computer-agent/package-lock.json +308 -2
- package/remote-computer-agent/package.json +2 -1
- package/remote-computer-agent/pair-device.ps1 +20 -0
- package/remote-computer-agent/setup.ps1 +38 -16
- package/remote-computer-agent/src/cli.ts +24 -3
- package/remote-computer-agent/src/desktop-session.ts +11 -3
- package/remote-computer-agent/src/e2e-server.ts +81 -0
- package/remote-computer-agent/src/pairing-page.ts +35 -0
- package/remote-computer-agent/src/protocol.ts +25 -25
- package/remote-computer-agent/src/secure-pairing.ts +105 -0
- package/remote-computer-agent/src/store.ts +20 -5
- package/remote-computer-agent/src/transport.ts +39 -15
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
# 统一 Bot 客户端
|
|
2
2
|
|
|
3
|
+
## 工作空间重构(本地开发预览)
|
|
4
|
+
|
|
5
|
+
`workspace-navigation.mjs` 定义工作空间页面和按产品 API 隔离的 UI 深链、一次性目标转交与内存草稿;不存权限或执行凭据。`workspace-model.mjs` 从已提交投影生成总览、搜索与看板;未加载的任务不计入统计,不推算节省时间。
|
|
6
|
+
|
|
7
|
+
`workspace-ui.jsx` 提供首页、任务待办、成员目录、键盘搜索和外观控制。`workspace.css` 重排产品导航与信息层级,并在明暗主题、紧凑密度和窄屏下使用同一组语义颜色。产品页内 Ctrl/Cmd K 打开工作空间搜索;聊天中的原搜索继续由 Bot 界面处理。外观偏好保存在本机浏览器,目标文本仅在当前宿主 API 的内存里保留。
|
|
8
|
+
|
|
9
|
+
新目标仍先检查候选与预览,再通过原始提交确认流程派发;看板不提供拖动改状态。任务链接由 Bot ID 与 Task ID 共同定位,电脑搜索只聚焦原电脑卡片,不自动连接或取得控制。
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
node --test tests/workspace-model.test.mjs tests/workspace-ui.integration.test.mjs
|
|
13
|
+
node scripts/verify-workspace-browser.mjs
|
|
14
|
+
node scripts/verify-workspace-browser.mjs --serve
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
浏览器脚本运行真实产品组件与离线 fixture,截图明确标注为本地验证数据;它不证明实际 Harness Desktop 的完整安装、Electron 或用户 profile 已验收。
|
|
18
|
+
|
|
3
19
|
`index.jsx` 在同一 ModuleLoader factory 中安装完整冻结 DS 界面和现有 MR 高级工作台。
|
|
4
20
|
默认沿用 DS 的 Main、Bot/工作群、记忆、侧聊、语音、设置、能力与计划任务。
|
|
5
|
-
通过公共 sidebar.workspaces slot 包装原 TeamSidebar
|
|
6
|
-
|
|
21
|
+
通过公共 sidebar.workspaces slot 包装原 TeamSidebar,联系人保留主区域,底部使用
|
|
22
|
+
ContactNavigation 的待办、工作空间和资源菜单。HeaderPill 通过自有 product-overlay
|
|
23
|
+
增加会话工具,原注册组件身份、工作目标、审批、输入和执行授权继续由 native 路径负责。
|
|
7
24
|
|
|
8
|
-
|
|
25
|
+
产品页读取宿主 `schemaVersion:1` 投影。缺后端、宿主读取错误、只读锁、未知费用、
|
|
9
26
|
未在线/锁屏机器都有明确状态和下一步;列表不放示例 Bot、机器、任务或收据。
|
|
10
27
|
Task 的核对/取消/重试只有宿主声明动作可用时显示,最终授权仍由宿主执行。
|
|
11
28
|
|
|
@@ -15,16 +32,23 @@ node scripts/integration-product-client.mjs --check
|
|
|
15
32
|
node --test tests/bot-runtime-product.test.mjs tests/bot-runtime-ui.test.mjs
|
|
16
33
|
```
|
|
17
34
|
|
|
18
|
-
|
|
19
|
-
|
|
35
|
+
默认生成主包统一的 `.dsh-plugin/client.js`,`--output <path>` 可显式指定其他位置。
|
|
36
|
+
构建 API `bundleProductClient()` 返回 `{code,inputs,bytes}`,
|
|
20
37
|
`generateProductClient({output,check})` 可供主构建脚本调用。生成物只有一个 loader id
|
|
21
38
|
`@ljwei-stak/dsh-model-route-bot`,没有单独 `ds-bot` 的 loader。纯路由包 `@ljwei-stak/dsh-model-router` 使用独立工作台入口,不导入此集成客户端。
|
|
22
39
|
React、ReactDOM 与 Harness UI primitives 继续由宿主提供。
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
|
|
41
|
+
实际 bundle 大小以构建结果为准。旧 MR 的单独客户端保持原构建方式。
|
|
42
|
+
发布还需在主包的客户端依赖声明中包含 DS 公开
|
|
26
43
|
`inject` 所需的 sessions、uiWorkspace、uiConversation、slots、theme、layout 等服务。
|
|
27
44
|
|
|
28
45
|
组件 DOM 检查覆盖未知 Operation 原依据、核对请求使用原 taskId、同模型不同 Bot、
|
|
29
46
|
未接入权限、未知金额及 Windows 锁屏原因。实际浏览器/Electron 的整体布局、键盘/窄屏
|
|
30
47
|
和默认 profile 升级仍需单独验收;本目录的 DOM 检查不宣称这些已通过。
|
|
48
|
+
|
|
49
|
+
安全配对使用 `secure-pairing.jsx` 与 `pairing-qr.jsx`,支持短期邀请、独立确认码、摄像头
|
|
50
|
+
和本地二维码图片解码。jsQR 1.4.0 的 Apache-2.0 许可证随
|
|
51
|
+
`licenses/jsqr-1.4.0-LICENSE.txt` 分发;源码和作者见 https://github.com/cozmo/jsQR。
|
|
52
|
+
二维码图像不会发送给模型或外部解码服务。摄像头仅在显式点击后开启。
|
|
53
|
+
协议、Host 与 Agent 的实现及验证边界见
|
|
54
|
+
`docs/implementation/20261107-secure-pairing.md`;尚不代表全功能迁移或真实用户桌面验收完成。
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
export
|
|
1
|
+
export { PRODUCT_PANELS } from './workspace-navigation.mjs'
|
|
2
2
|
|
|
3
3
|
export function createProductApi(fetcher = globalThis.fetch, clientLocation = globalThis.location) {
|
|
4
|
+
const listeners = new Set()
|
|
4
5
|
const call = async (action, payload, signal) => {
|
|
5
6
|
// Desktop's protocol bridge removes Origin before forwarding to the HTTP
|
|
6
7
|
// Host. Preserve only the known native viewer origin as a transport hint;
|
|
7
8
|
// owner admission and Bot/machine authority still belong to the Host.
|
|
8
|
-
|
|
9
|
+
// The desktop bridge strips Origin, so the native viewer supplies its own. A web panel probe
|
|
10
|
+
// needs the same hint: it evaluates frame-ancestors against the origin the page is really
|
|
11
|
+
// framed from, and an HTTP Host origin must never stand in for dsh-app://app.
|
|
12
|
+
const nativeDesktop = ['machine.desktop.open', 'machine.desktop.renew', 'machine.desktop.poll', 'machine.desktop.send', 'webPanels.probe'].includes(action)
|
|
9
13
|
&& clientLocation?.protocol === 'dsh-app:' && clientLocation?.host === 'app'
|
|
10
14
|
const response = await fetcher('api/bot-product', { method: 'POST', credentials: 'same-origin',
|
|
11
15
|
headers: { 'content-type': 'application/json', ...(nativeDesktop ? { 'x-dsh-desktop-origin': 'dsh-app://app' } : {}) },
|
|
@@ -15,7 +19,14 @@ export function createProductApi(fetcher = globalThis.fetch, clientLocation = gl
|
|
|
15
19
|
if (!response.ok || body.ok !== true) throw Object.assign(new Error(body.error?.message ?? `宿主请求失败(HTTP ${response.status})`), { code: body.error?.code })
|
|
16
20
|
return body.value
|
|
17
21
|
}
|
|
18
|
-
return Object.freeze({ snapshot: signal => call('snapshot', undefined, signal),
|
|
22
|
+
return Object.freeze({ snapshot: signal => call('snapshot', undefined, signal), attention: signal => call('task.attention', {}, signal),
|
|
23
|
+
async dispatch(action, payload, signal) {
|
|
24
|
+
const value = await call(action, payload, signal)
|
|
25
|
+
for (const listener of [...listeners]) { try { listener(action) } catch {} }
|
|
26
|
+
return value
|
|
27
|
+
},
|
|
28
|
+
subscribeActions(listener) { listeners.add(listener); return () => listeners.delete(listener) },
|
|
29
|
+
})
|
|
19
30
|
}
|
|
20
31
|
|
|
21
32
|
export const TASK_STATES = Object.freeze({ queued: '排队中', ready:'等待执行', running: '执行中', cancelling: '停止中', cancelled: '已停止', succeeded: '已完成', failed: '失败',
|
|
@@ -1,118 +1,317 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react'
|
|
1
|
+
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import { Blocked } from './panels.jsx'
|
|
3
|
+
import { UiIcon } from './ui-icons.jsx'
|
|
4
|
+
import bindingCss from './binding.css'
|
|
5
|
+
import { BotModelPicker } from './model-picker.jsx'
|
|
6
|
+
import { botModelStatus } from './bot-model-status.mjs'
|
|
7
|
+
import { MainRouteEditor } from './main-route-editor.jsx'
|
|
8
|
+
import { mainDefaultFields, mainRoutePayload, mainRouteProblems, startMainRoutes } from './main-route-model.mjs'
|
|
9
|
+
import {
|
|
10
|
+
accountFor, bindingProblems, buildBinding, defaultMode, EXECUTION_MODES, failureText, findModel, FUNDING_MODES,
|
|
11
|
+
MODE_HELP, MODE_LABEL, numberOrNull, routeOf, ROUTING_PRESETS, scopeAllows, seedForm,
|
|
12
|
+
toolCards, workspaceKey, workspaceLabel, workspaceOptions,
|
|
13
|
+
} from './binding-model.mjs'
|
|
3
14
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Bot binding editor.
|
|
17
|
+
*
|
|
18
|
+
* The props contract is unchanged: `{ bot, record, accounts, dispatch, busy }`.
|
|
19
|
+
* `record` is the Host's committed read model (settings + trusted catalog); every
|
|
20
|
+
* choice offered here comes from that catalog, and a save always carries the
|
|
21
|
+
* revision it was read at, so the Host keeps authority over what is granted.
|
|
22
|
+
*
|
|
23
|
+
* Layout follows the Bot first: pick a provider, then an exact model, then the
|
|
24
|
+
* funding scope; workspace, capabilities and budgets stay available but folded.
|
|
25
|
+
* The official-tools module is a separate region because granting a CLI execution
|
|
26
|
+
* path is a different decision from choosing the model this Bot talks with.
|
|
27
|
+
*/
|
|
28
|
+
export function BotBindingEditor({ bot, record, accounts, dispatch, busy, hostScope, onOpenTools }) {
|
|
29
|
+
const connection = hostScope ?? dispatch
|
|
30
|
+
const draftConnection = useRef(connection)
|
|
31
|
+
const catalog = record?.catalog
|
|
8
32
|
const [form, setForm] = useState(null)
|
|
33
|
+
const [dirty, setDirty] = useState(false)
|
|
34
|
+
const [feedback, setFeedback] = useState(null)
|
|
35
|
+
const [working, setWorking] = useState('')
|
|
36
|
+
const seedKey = useRef('')
|
|
37
|
+
const seedRevision = useRef(null)
|
|
38
|
+
const saveToken = useRef(0)
|
|
39
|
+
const workingRef = useRef('')
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
// The stylesheet ships with this component so the editor needs no parent wiring;
|
|
43
|
+
// index.jsx may concatenate it instead, but not both at once.
|
|
44
|
+
const style = document.createElement('style')
|
|
45
|
+
style.dataset.botBindingEditor = '1'
|
|
46
|
+
style.textContent = bindingCss
|
|
47
|
+
document.head.append(style)
|
|
48
|
+
return () => { saveToken.current++; workingRef.current = ''; style.remove() }
|
|
49
|
+
}, [])
|
|
50
|
+
|
|
9
51
|
useEffect(() => {
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
setForm(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}, [bot
|
|
31
|
-
|
|
32
|
-
const
|
|
52
|
+
if (draftConnection.current !== connection) {
|
|
53
|
+
draftConnection.current = connection; seedKey.current = ''; saveToken.current++; workingRef.current = ''; setWorking(''); setFeedback(null)
|
|
54
|
+
}
|
|
55
|
+
if (!record) { saveToken.current++; seedKey.current = ''; seedRevision.current = null; setForm(null); return }
|
|
56
|
+
const botId = bot?.id ?? ''
|
|
57
|
+
const key = `${botId}#${record.revision ?? 0}`
|
|
58
|
+
if (seedKey.current === key) return
|
|
59
|
+
const sameBot = seedKey.current.split('#')[0] === botId
|
|
60
|
+
seedKey.current = key
|
|
61
|
+
// A late Host or Bot switch invalidates the in-flight save of the previous Bot.
|
|
62
|
+
if (!sameBot) { saveToken.current += 1; workingRef.current = ''; setWorking('') }
|
|
63
|
+
// A revision bump from another window must not silently drop unsaved work.
|
|
64
|
+
if (sameBot && dirty) {
|
|
65
|
+
setFeedback({ kind: 'notice', text: `宿主中的绑定已更新到修订号 ${record.revision ?? 0};你在此处未保存的修改仍然保留,保存时会按旧修订号提交,宿主可能拒绝。` })
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
seedRevision.current = record.revision
|
|
69
|
+
setForm(seedForm(record, accounts))
|
|
70
|
+
setDirty(false)
|
|
71
|
+
setFeedback(current => (current?.kind === 'saved' ? current : null))
|
|
72
|
+
}, [bot?.id, record, dirty, accounts, connection])
|
|
73
|
+
|
|
74
|
+
const edit = patch => {
|
|
75
|
+
setForm(current => (current ? { ...current, ...patch } : current))
|
|
76
|
+
setFeedback(null)
|
|
77
|
+
setDirty(true)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const model = form ? findModel(catalog, form.model) : null
|
|
81
|
+
const savedRoute = form?.model ?? ''
|
|
82
|
+
const savedMissing = Boolean(savedRoute) && !model
|
|
83
|
+
const machineOptions = useMemo(() => workspaceOptions(catalog), [catalog])
|
|
84
|
+
const [machineId, rootId] = (form?.workspace ?? '').split('/')
|
|
85
|
+
const machine = machineOptions.find(item => item.machineId === machineId) ?? null
|
|
86
|
+
const root = machine?.roots.find(item => item.rootId === rootId) ?? null
|
|
87
|
+
const account = accountFor(accounts, model?.provider)
|
|
88
|
+
const mainRoutes = bot?.main ? form?.mainRoutes : undefined
|
|
89
|
+
const scope = (catalog?.fundingScopes ?? []).find(item => item.id === account?.id) ?? null
|
|
90
|
+
const tools = useMemo(() => toolCards({ catalog, model, grants: form?.officialTools }), [catalog, model, form?.officialTools])
|
|
91
|
+
const problems = form ? bindingProblems({ form, model, account, machine, root, catalog, scope, mainRoutes: Boolean(mainRoutes) }) : []
|
|
92
|
+
if (mainRoutes) problems.push(...mainRouteProblems(mainRoutes, { defaultRoute: form.model, enabled: form.enabled, catalog, accounts }))
|
|
93
|
+
const staleTools = tools.filter(tool => tool.stale)
|
|
94
|
+
const hostModel = botModelStatus(bot, catalog?.models)
|
|
95
|
+
|
|
33
96
|
if (!record || !form) return <Blocked value={{ reason: '此 Bot 尚未取得绑定目录。' }} />
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
97
|
+
|
|
98
|
+
const accountLimitsReady = numberOrNull(form.accountDaily) !== null && numberOrNull(form.accountMonthly) !== null
|
|
99
|
+
|
|
100
|
+
const setToolGrant = (tool, cliModel, checked) => {
|
|
101
|
+
const others = form.officialTools.filter(item => item.toolId !== tool.toolId)
|
|
102
|
+
edit({ officialTools: checked
|
|
103
|
+
? [...others, { toolId: tool.toolId, cliModel, mode: tool.grant?.mode ?? defaultMode(tool.modes) }]
|
|
104
|
+
: others })
|
|
105
|
+
}
|
|
106
|
+
const setToolMode = (toolId, mode) => edit({ officialTools: form.officialTools.map(item => (item.toolId === toolId ? { ...item, mode } : item)) })
|
|
107
|
+
const dropStaleTools = () => edit({ officialTools: form.officialTools.filter(item => !staleTools.some(tool => tool.toolId === item.toolId)) })
|
|
108
|
+
|
|
109
|
+
const enableAccount = async (target, dailyLimitUsd, monthlyLimitUsd) => {
|
|
110
|
+
if (!target || workingRef.current) return
|
|
111
|
+
const token = saveToken.current
|
|
112
|
+
workingRef.current = 'funding'
|
|
113
|
+
setWorking('funding')
|
|
114
|
+
setFeedback(null)
|
|
115
|
+
try {
|
|
116
|
+
await dispatch('funding.account.save', { scopeId: target.id, expectedRevision: target.revision, enabled: true,
|
|
117
|
+
dailyLimitUsd, monthlyLimitUsd })
|
|
118
|
+
if (saveToken.current !== token) return
|
|
119
|
+
setFeedback({ kind: 'saved', text: `已启用 ${target.provider} 资金范围。保存绑定后生效。` })
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (saveToken.current !== token) return
|
|
122
|
+
setFeedback({ kind: 'error', text: failureText(error) })
|
|
123
|
+
} finally { if (saveToken.current === token) { workingRef.current = ''; setWorking('') } }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const save = async event => {
|
|
42
127
|
event.preventDefault()
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
dailyBudgetUsd: routing.dailyBudgetUsd === '' ? null : Number(routing.dailyBudgetUsd),
|
|
65
|
-
monthlyBudgetUsd: routing.monthlyBudgetUsd === '' ? null : Number(routing.monthlyBudgetUsd),
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
return dispatch('binding.save', { botId: bot.id, expectedRevision: record.revision, settings })
|
|
128
|
+
// A second click in the same batch must not submit twice against the same revision.
|
|
129
|
+
if (workingRef.current || problems.length) return
|
|
130
|
+
const token = saveToken.current
|
|
131
|
+
const defaultForm = mainRoutes ? { ...form, ...mainDefaultFields(mainRoutes, form.model) } : form
|
|
132
|
+
const settings = buildBinding({ form: defaultForm, model, account, machine, root })
|
|
133
|
+
if (mainRoutes !== undefined) settings.mainRoutes = mainRoutePayload(mainRoutes)
|
|
134
|
+
workingRef.current = 'binding'
|
|
135
|
+
setWorking('binding')
|
|
136
|
+
setFeedback({ kind: 'saving', text: '正在提交到宿主校验…' })
|
|
137
|
+
try {
|
|
138
|
+
const expectedRevision = seedRevision.current
|
|
139
|
+
const result = await dispatch('binding.save', { botId: bot.id, expectedRevision, settings })
|
|
140
|
+
if (saveToken.current !== token) return
|
|
141
|
+
if (!Number.isSafeInteger(result?.revision) || result.revision <= expectedRevision || (result.botId && result.botId !== bot.id)) throw new Error('尚未收到当前 Bot 的保存确认,请刷新配置后核对。')
|
|
142
|
+
seedRevision.current = result?.revision ?? seedRevision.current
|
|
143
|
+
setDirty(false)
|
|
144
|
+
setFeedback({ kind: 'saved', text: `设置已保存。` })
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (saveToken.current !== token) return
|
|
147
|
+
setFeedback({ kind: 'error', text: failureText(error) })
|
|
148
|
+
} finally { if (saveToken.current === token) { workingRef.current = ''; setWorking('') } }
|
|
69
149
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
<
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
{form.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
150
|
+
|
|
151
|
+
const fundingMode = FUNDING_MODES.find(item => item.value === form.fundingMode) ?? FUNDING_MODES[0]
|
|
152
|
+
const summary = [
|
|
153
|
+
['准确模型', savedRoute || '未选择'],
|
|
154
|
+
['资金范围', mainRoutes ? `${mainRoutes.routes.filter(route => route.enabled).length} 条路线 · 共享限额` : account ? `${account.provider} · ${form.fundingMode === 'api' ? 'API' : '订阅'}${account.enabled ? '' : '(未启用)'}` : '未匹配'],
|
|
155
|
+
['工作区', machine && root ? workspaceLabel(machine, root) : '未选择'],
|
|
156
|
+
['官方工具', form.officialTools.length ? form.officialTools.map(tool => `${tool.toolId}/${tool.cliModel}·${MODE_LABEL[tool.mode] ?? tool.mode}`).join(',') : bot?.main ? 'Main 全权限 · 待配置默认执行方式' : '未授予'],
|
|
157
|
+
['绑定状态', record.status === 'available' ? '已启用' : '未启用'],
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
return <div className="bb-root">
|
|
161
|
+
<header className="bb-head">
|
|
162
|
+
<p className="bb-eyebrow">Bot 绑定 · {bot?.name ?? ''}</p>
|
|
163
|
+
<h3 className="bb-title">模型与工具</h3>
|
|
164
|
+
<p className="bb-sub">{bot?.main ? '配置 Main 的模型路线、默认模型与官方工具。' : '选择默认模型,为这个 Bot 添加官方工具。'}</p>
|
|
165
|
+
{bot?.main && <p className="bb-note" role="note"><strong>Main · 全权限主控</strong> 可使用宿主全部已接入模型路线与工具。下方配置默认模型、工作区和资金。</p>}
|
|
166
|
+
<details className="bb-overview"><summary>配置概览<span>{form.enabled ? '已启用' : '未启用'}</span></summary><dl className="bb-summary">{summary.map(([label, value]) => <div key={label}><dt>{label}</dt><dd>{value}</dd></div>)}</dl></details>
|
|
167
|
+
</header>
|
|
168
|
+
{record.status !== 'available' && record.settings?.enabled && <Blocked value={{ reason: record.blockedReason ?? '此 Bot 的执行绑定尚未就绪。', nextAction: record.nextAction }} />}
|
|
169
|
+
<form className="bp-form bb-form" onSubmit={save}>
|
|
170
|
+
<section className="bb-model-card" aria-label="Bot 模型设置">
|
|
171
|
+
<BotModelPicker models={mainRoutes ? (catalog?.models ?? []).filter(item => mainRoutes.routes.some(route => route.enabled && routeOf(route) === routeOf(item))) : catalog?.models ?? []} value={form.model} disabled={Boolean(busy || working)}
|
|
172
|
+
note={form.enabled ? '保存后用于当前 Bot。' : '此 Bot 的绑定已停用。保存会保留选择,启用后用于对话。'}
|
|
173
|
+
onSelect={choice => {
|
|
174
|
+
const selected = findModel(catalog, routeOf(choice)), modes = selected?.fundingModes
|
|
175
|
+
edit({ provider: choice.provider, model: routeOf(choice), ...(modes && !modes.includes(form.fundingMode) ? { fundingMode: modes[0] } : {}) })
|
|
176
|
+
}}/>
|
|
177
|
+
<p className="bb-model-applied" data-model-different={hostModel.different ? '' : undefined} title={hostModel.title} role="status">{hostModel.appliedText}</p>
|
|
178
|
+
{bot?.main && !mainRoutes && <button type="button" className="bb-link" disabled={Boolean(busy || working)}
|
|
179
|
+
onClick={() => edit({ mainRoutes: startMainRoutes(form, model, account, record.settings?.funding) })}>配置多条模型路线</button>}
|
|
180
|
+
</section>
|
|
181
|
+
{mainRoutes ? <MainRouteEditor draft={mainRoutes} defaultRoute={form.model} catalog={catalog} accounts={accounts} busy={Boolean(busy || working)}
|
|
182
|
+
onChange={draft => edit({ mainRoutes: draft })}
|
|
183
|
+
onDefault={route => edit({ provider: route.provider, model: routeOf(route) })}
|
|
184
|
+
onEnableAccount={enableAccount}
|
|
185
|
+
onSingleRoute={() => edit({ ...mainDefaultFields(mainRoutes, form.model), mainRoutes: null })} /> :
|
|
186
|
+
<details className="bb-budget-config"><summary><UiIcon name="chart" size={16}/>费用与预算<span className="bb-pill">{account?.provider ?? '待配置'}</span></summary>
|
|
187
|
+
<ol className="bb-steps">
|
|
188
|
+
<li className="bb-step" data-state={model && account?.enabled ? 'done' : 'blocked'}>
|
|
189
|
+
<div className="bb-step-head"><UiIcon name="chart" size={17}/>
|
|
190
|
+
<div><strong>资金范围</strong><p>{fundingMode.help}</p></div>
|
|
191
|
+
<span className="bb-step-state"><span className="bb-pill" data-tone={account?.enabled ? 'current' : 'warn'}>{account ? `${account.provider}${account.enabled ? '' : ' · 未启用'}` : '无匹配范围'}</span></span></div>
|
|
192
|
+
<div className="bb-step-body">
|
|
193
|
+
<div className="bb-row">{FUNDING_MODES.map(item => <button key={item.value} type="button" className="bb-option bb-option-inline"
|
|
194
|
+
aria-pressed={form.fundingMode === item.value} disabled={Boolean(model?.fundingModes && !model.fundingModes.includes(item.value))} onClick={() => edit({ fundingMode: item.value })}>{item.label}</button>)}</div>
|
|
195
|
+
{!model && <p className="bb-note">先选择模型,宿主才能给出这个提供商的资金范围。</p>}
|
|
196
|
+
{model && !account && <p className="bb-warn">宿主目录里没有 {model.provider} 的资金范围。请刷新宿主或改选模型;未匹配范围时无法启用绑定。</p>}
|
|
197
|
+
{model && account && !account.enabled && <aside className="bp-blocked">
|
|
198
|
+
<strong>资金范围尚未启用</strong><p>{account.provider} 需要本地预算上限;它不是厂商余额。</p>
|
|
199
|
+
<div className="bb-fields">
|
|
200
|
+
<label className="bb-field"><span>账户每日上限(USD)</span><input type="number" min="0" step="0.01" value={form.accountDaily} onChange={event => edit({ accountDaily: event.target.value })} /></label>
|
|
201
|
+
<label className="bb-field"><span>账户每月上限(USD)</span><input type="number" min="0" step="0.01" value={form.accountMonthly} onChange={event => edit({ accountMonthly: event.target.value })} /></label>
|
|
202
|
+
</div>
|
|
203
|
+
<div className="bb-row"><button type="button" disabled={busy || Boolean(working) || !accountLimitsReady} onClick={() => enableAccount(account, numberOrNull(form.accountDaily), numberOrNull(form.accountMonthly))}>
|
|
204
|
+
{working === 'funding' ? '启用中…' : `启用 ${account.provider} 资金范围`}</button>
|
|
205
|
+
{!accountLimitsReady && <small className="bb-note">留空表示未配置,宿主会拒绝启用;0 表示禁止付费。</small>}</div>
|
|
206
|
+
</aside>}
|
|
207
|
+
{model && account?.enabled && scope && !scopeAllows(scope, form.fundingMode, routeOf(model)) && <p className="bb-warn">宿主发布该资金范围时不包含当前渠道或准确模型,保存会被拒绝。</p>}
|
|
208
|
+
{form.fundingMode === 'api' ? <>
|
|
209
|
+
<div className="bb-fields">
|
|
210
|
+
<label className="bb-field"><span>输入价格(USD / 百万 Token)</span><input type="number" min="0" step="0.000001" value={form.input} onChange={event => edit({ input: event.target.value })} /></label>
|
|
211
|
+
<label className="bb-field"><span>输出价格(USD / 百万 Token)</span><input type="number" min="0" step="0.000001" value={form.output} onChange={event => edit({ output: event.target.value })} /></label>
|
|
212
|
+
<label className="bb-field"><span>最大输入 Token</span><input type="number" min="1" step="1" value={form.maxInputTokens} onChange={event => edit({ maxInputTokens: event.target.value })} /></label>
|
|
213
|
+
<label className="bb-field"><span>最大输出 Token</span><input type="number" min="1" step="1" value={form.maxOutputTokens} onChange={event => edit({ maxOutputTokens: event.target.value })} /></label>
|
|
214
|
+
<label className="bb-field"><span>Bot 每日预算(USD)</span><input type="number" min="0" step="0.01" value={form.daily} onChange={event => edit({ daily: event.target.value })} /></label>
|
|
215
|
+
<label className="bb-field"><span>Bot 每月预算(USD)</span><input type="number" min="0" step="0.01" value={form.monthly} onChange={event => edit({ monthly: event.target.value })} /></label>
|
|
216
|
+
</div>
|
|
217
|
+
<p className="bb-note">价格与请求上界由 Bot 绑定自己承担,即使别的 Bot 用同一个模型。留空的字段会按“未配置”提交,不会变成 0。</p>
|
|
218
|
+
</> : <>
|
|
219
|
+
<div className="bb-fields">
|
|
220
|
+
<label className="bb-field"><span>每日订阅调用次数</span><input type="number" min="0" step="1" value={form.dailyCalls} onChange={event => edit({ dailyCalls: event.target.value })} /></label>
|
|
221
|
+
<label className="bb-field"><span>每月订阅调用次数</span><input type="number" min="0" step="1" value={form.monthlyCalls} onChange={event => edit({ monthlyCalls: event.target.value })} /></label>
|
|
222
|
+
</div>
|
|
223
|
+
<p className="bb-note">订阅按次数计数;不会自动回退到 API 计价。</p>
|
|
224
|
+
</>}
|
|
225
|
+
</div>
|
|
226
|
+
</li>
|
|
227
|
+
</ol>
|
|
228
|
+
</details>}
|
|
229
|
+
|
|
230
|
+
<details className="bb-fold">
|
|
231
|
+
<summary><UiIcon name="folder" size={16} />工作区与能力<small>{bot?.main ? 'Main 全权限' : machine && root ? `${workspaceLabel(machine, root)} · ${form.workspaceMode === 'read-only' ? '只读' : '可写'} · ${form.capabilities.length} 项能力` : '未选择'}</small></summary>
|
|
232
|
+
<div className="bb-fold-body">
|
|
233
|
+
<label className="bb-field bb-field-wide"><span>工作区(机器 / 根目录)</span>
|
|
234
|
+
<select aria-label={`${bot?.name ?? ''} 的绑定工作区`} value={form.workspace} onChange={event => edit({ workspace: event.target.value })}>
|
|
235
|
+
<option value="">请选择</option>
|
|
236
|
+
{machineOptions.flatMap(entry => entry.roots.map(entry2 => <option key={workspaceKey(entry.machineId, entry2.rootId)} value={workspaceKey(entry.machineId, entry2.rootId)}>{entry.label} / {entry2.label}</option>))}
|
|
237
|
+
</select></label>
|
|
238
|
+
{machine && root && <label className="bb-field bb-field-wide"><span>工作区权限</span>
|
|
239
|
+
<select aria-label={`${bot?.name ?? ''} 的工作区权限`} value={form.workspaceMode} onChange={event => edit({ workspaceMode: event.target.value })}>
|
|
240
|
+
{['read-only', 'workspace-write'].filter(mode => root.modes.includes(mode)).map(mode => <option key={mode} value={mode}>{mode === 'read-only' ? '只读' : '允许写入'}</option>)}
|
|
241
|
+
</select></label>}
|
|
242
|
+
{machine && root && !root.modes.includes(form.workspaceMode) && <p className="bb-warn">所选根目录已不支持当前权限模式,请重新选择。</p>}
|
|
243
|
+
{!bot?.main && <><div className="bb-row"><strong className="bb-note">路由与任务能力</strong>
|
|
244
|
+
<button type="button" className="bb-link" onClick={() => edit({ capabilities: (catalog?.capabilities ?? []).map(item => item.id) })}>全选</button>
|
|
245
|
+
<button type="button" className="bb-link" onClick={() => edit({ capabilities: [] })}>清空</button></div>
|
|
246
|
+
<p className="bb-note">留空表示不授予额外能力;此界面不会在打开时替你勾选。</p>
|
|
247
|
+
<div className="bb-list">{(catalog?.capabilities ?? []).map(item => <label key={item.id} className="bb-check">
|
|
248
|
+
<input type="checkbox" checked={form.capabilities.includes(item.id)} onChange={event => edit({ capabilities: event.target.checked ? [...form.capabilities, item.id] : form.capabilities.filter(id => id !== item.id) })} />
|
|
249
|
+
<span>{item.label}<small>{item.tools.join('、') || '无工具'}</small></span></label>)}</div></>}
|
|
250
|
+
<label className="bb-field bb-field-wide"><span>最大并发任务</span><input type="number" min="1" max="32" step="1" value={form.maxConcurrentTasks} onChange={event => edit({ maxConcurrentTasks: event.target.value })} /></label>
|
|
251
|
+
<fieldset className="bb-fieldset">
|
|
252
|
+
<legend className="bb-note">Bot 路由策略(这个 Bot 派活给别人时用哪套取舍)</legend>
|
|
253
|
+
<label className="bb-field bb-field-wide"><span>预设</span>
|
|
254
|
+
<select aria-label={`${bot?.name ?? ''} 的路由预设`} value={form.routing.preset} onChange={event => edit({ routing: { ...form.routing, preset: event.target.value } })}>
|
|
255
|
+
{ROUTING_PRESETS.map(item => <option key={item.value || 'global'} value={item.value}>{item.label}</option>)}</select></label>
|
|
256
|
+
<div className="bb-fields">
|
|
257
|
+
<label className="bb-field"><span>每日预算(USD)</span><input type="number" min="0" step="0.01" value={form.routing.dailyBudgetUsd} onChange={event => edit({ routing: { ...form.routing, dailyBudgetUsd: event.target.value } })} placeholder="留空表示不单独限制" /></label>
|
|
258
|
+
<label className="bb-field"><span>每月预算(USD)</span><input type="number" min="0" step="0.01" value={form.routing.monthlyBudgetUsd} onChange={event => edit({ routing: { ...form.routing, monthlyBudgetUsd: event.target.value } })} placeholder="留空表示不单独限制" /></label>
|
|
259
|
+
</div>
|
|
260
|
+
</fieldset>
|
|
261
|
+
</div>
|
|
262
|
+
</details>
|
|
263
|
+
|
|
264
|
+
<section className="bb-tools" aria-label={bot?.main ? '官方工具(默认执行配置)' : '官方工具(额外执行授权)'}>
|
|
265
|
+
<div className="bb-tools-head">
|
|
266
|
+
<strong><UiIcon name="settings" size={15} />官方工具</strong>
|
|
267
|
+
{typeof onOpenTools === 'function' && <button type="button" className="bb-link" onClick={() => {
|
|
268
|
+
const token = saveToken.current
|
|
269
|
+
Promise.resolve().then(onOpenTools).catch(() => { if (saveToken.current === token) setFeedback({ kind: 'error', text: '暂时无法打开官方工具设置,请重试。' }) })
|
|
270
|
+
}}>安装与登录<UiIcon name="arrow" size={13}/></button>}
|
|
271
|
+
<p>{bot?.main ? 'Main 已有全部工具权限;下方配置已核验的 CLI 模型与执行方式。' : '为这个 Bot 添加官方工具,选择允许使用的模型与执行方式。'}</p>
|
|
272
|
+
</div>
|
|
273
|
+
{tools.length === 0 ? <p className="bb-tool-empty">宿主目录里没有可授予的官方 CLI 工具。安装与登录状态在官方工具面板查看;未核验的身份不会在这里显示为可用。</p> : (
|
|
274
|
+
<ul className="bb-tool-list">{tools.map(tool => <li key={tool.toolId} className="bb-tool" data-granted={Boolean(tool.grant)} data-stale={tool.stale}>
|
|
275
|
+
<div className="bb-tool-head"><div className="bb-tool-title">
|
|
276
|
+
<strong>{tool.label}</strong>
|
|
277
|
+
<p>{tool.vendor ? `${tool.vendor} · ` : ''}{tool.source || '厂商官方 CLI'}</p></div>
|
|
278
|
+
<span className="bb-pill" data-tone={tool.grant ? (tool.stale ? 'warn' : 'ok') : 'muted'}>{bot?.main ? (tool.stale ? '配置待核验' : tool.grant ? '执行已配置' : '全权限 · 待配置') : tool.grant ? (tool.stale ? '授权已失效' : '已授予') : '未授予'}</span></div>
|
|
279
|
+
<div className="bb-tool-body">
|
|
280
|
+
{tool.grantTools.length > 0 && <details className="bb-tool-permissions"><summary>工具权限明细</summary><p className="bb-note">{tool.grantTools.join('、')}</p></details>}
|
|
281
|
+
{tool.identities.length === 0 ? <p className="bb-note">当前模型没有已核验的 {tool.toolId} CLI 身份,{bot?.main ? '需要先完成执行配置' : '宿主不会接受授予'}。请改选模型,或先在官方工具面板安装并核验该 CLI。</p> : (
|
|
282
|
+
<div className="bb-list">{tool.identities.map(identity => {
|
|
283
|
+
const checked = tool.grant?.cliModel === identity.model
|
|
284
|
+
return <label key={`${identity.toolId}/${identity.model}`} className="bb-check">
|
|
285
|
+
<input type="checkbox" checked={checked} onChange={event => setToolGrant(tool, identity.model, event.target.checked)} />
|
|
286
|
+
<span>{identity.toolId} / {identity.model}<small>宿主已核验的 CLI 模型名</small></span></label>
|
|
287
|
+
})}</div>)}
|
|
288
|
+
{tool.grant && <label className="bb-field bb-field-wide"><span>执行模式</span>
|
|
289
|
+
<select aria-label={`${tool.label} 的执行模式`} value={tool.grant.mode} onChange={event => setToolMode(tool.toolId, event.target.value)}>
|
|
290
|
+
{EXECUTION_MODES.filter(mode => tool.modes.includes(mode)).map(mode => <option key={mode} value={mode}>{MODE_LABEL[mode]}</option>)}</select>
|
|
291
|
+
<small>{MODE_HELP[tool.grant.mode] ?? ''}</small></label>}
|
|
292
|
+
{tool.grant && tool.stale && <p className="bb-warn">这个授权指向的 CLI 模型与当前模型尚未核验,宿主会拒绝保存。请移除,或改选已核验的身份。</p>}
|
|
293
|
+
</div></li>)}</ul>)}
|
|
294
|
+
<p className="bb-tools-foot">{bot?.main ? 'Main 的全权限不受普通 Bot 能力勾选限制。工具仍需安装、登录并核验 CLI 身份;这里保存默认执行配置。' : '每个工具只能保留一个已核验 CLI 身份;换模型不会自动移除或改指向旧授权。写入与 CLI 自身权限会扩大这个 Bot 的执行范围,请按需要选择。'}</p>
|
|
295
|
+
{staleTools.length > 0 && <div className="bb-row"><button type="button" className="bb-link" onClick={dropStaleTools}>移除 {staleTools.length} 项未核验授权</button></div>}
|
|
296
|
+
</section>
|
|
297
|
+
|
|
298
|
+
<div className="bb-save">
|
|
299
|
+
<label className="bb-check"><input type="checkbox" checked={form.enabled} onChange={event => edit({ enabled: event.target.checked })} />
|
|
300
|
+
<span>启用此绑定<small>模型、工作区、资金范围与必要预算必须完整;未启用时保持停用,不授予执行权。</small></span></label>
|
|
301
|
+
{problems.length > 0 && <div className="bb-danger"><strong>还不能保存</strong>
|
|
302
|
+
<ul>{problems.map(item => <li key={item}>{item}</li>)}</ul></div>}
|
|
303
|
+
<div className="bb-save-row">
|
|
304
|
+
<span className="bb-pill" data-tone={dirty ? 'warn' : 'muted'}>{dirty ? '有未保存修改' : '与宿主一致'}</span>
|
|
305
|
+
<span className="bb-pill">修订号 {record.revision ?? 0}</span>
|
|
306
|
+
<button type="submit" className="bp-button-primary" disabled={busy || Boolean(working) || problems.length > 0}>
|
|
307
|
+
{working === 'binding' ? '保存中…' : '保存绑定'}</button>
|
|
308
|
+
</div>
|
|
309
|
+
{feedback && <p className="bb-feedback" data-kind={feedback.kind} role="status">{feedback.text}</p>}
|
|
310
|
+
{dirty && seedRevision.current !== record.revision && <button type="button" className="bb-link" onClick={() => {
|
|
311
|
+
saveToken.current++; workingRef.current = ''; setWorking(''); seedRevision.current = record.revision
|
|
312
|
+
setForm(seedForm(record, accounts)); setDirty(false); setFeedback(null)
|
|
313
|
+
}}>放弃草稿并载入最新设置</button>}
|
|
314
|
+
</div>
|
|
116
315
|
</form>
|
|
117
|
-
</
|
|
316
|
+
</div>
|
|
118
317
|
}
|