@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.
Files changed (271) hide show
  1. package/.dsh-plugin/bot-product.mjs +81 -5
  2. package/.dsh-plugin/client.js +17490 -1791
  3. package/README.i18n.yaml +4 -4
  4. package/README.md +409 -364
  5. package/README.zh.md +409 -364
  6. package/apps/integration-client/README.md +31 -7
  7. package/apps/integration-client/api.mjs +14 -3
  8. package/apps/integration-client/binding-editor.jsx +308 -109
  9. package/apps/integration-client/binding-model.mjs +262 -0
  10. package/apps/integration-client/binding.css +144 -0
  11. package/apps/integration-client/bot-model-status.mjs +18 -0
  12. package/apps/integration-client/bot-profile-identity.jsx +197 -0
  13. package/apps/integration-client/bot-profile.css +57 -0
  14. package/apps/integration-client/bot-profile.mjs +97 -0
  15. package/apps/integration-client/bot-settings-dialog.css +18 -0
  16. package/apps/integration-client/bot-settings-dialog.jsx +111 -0
  17. package/apps/integration-client/bot-thread-header-bridge.css +20 -0
  18. package/apps/integration-client/bot-thread-header.jsx +278 -0
  19. package/apps/integration-client/bot-threads-panel.jsx +378 -0
  20. package/apps/integration-client/bot-threads.css +169 -0
  21. package/apps/integration-client/chat-design.mjs +250 -0
  22. package/apps/integration-client/chat-ui.jsx +175 -0
  23. package/apps/integration-client/chat.css +477 -0
  24. package/apps/integration-client/composer-draft-binding.mjs +50 -0
  25. package/apps/integration-client/composer-draft-bound.jsx +14 -0
  26. package/apps/integration-client/composer-draft-host.jsx +142 -0
  27. package/apps/integration-client/composer-draft-session.jsx +287 -0
  28. package/apps/integration-client/composer-draft-witness.mjs +115 -0
  29. package/apps/integration-client/composer-draft.css +4 -0
  30. package/apps/integration-client/composer-draft.mjs +322 -0
  31. package/apps/integration-client/contact-attention.jsx +54 -0
  32. package/apps/integration-client/data-panel.jsx +439 -0
  33. package/apps/integration-client/data.css +69 -0
  34. package/apps/integration-client/desktop-bridge.mjs +1 -0
  35. package/apps/integration-client/desktop-http-socket.mjs +49 -0
  36. package/apps/integration-client/desktop-keyboard.mjs +32 -0
  37. package/apps/integration-client/desktop-pane.jsx +99 -30
  38. package/apps/integration-client/desktop-stream.mjs +13 -3
  39. package/apps/integration-client/desktop-tools.jsx +19 -0
  40. package/apps/integration-client/desktop-touch.mjs +27 -0
  41. package/apps/integration-client/desktop.css +41 -0
  42. package/apps/integration-client/independent-chat-controller.mjs +224 -0
  43. package/apps/integration-client/independent-chat-entry.jsx +61 -0
  44. package/apps/integration-client/independent-chat-navigation.mjs +21 -0
  45. package/apps/integration-client/independent-chat-page.jsx +148 -0
  46. package/apps/integration-client/independent-chat-rich.jsx +64 -0
  47. package/apps/integration-client/independent-chat-rich.mjs +30 -0
  48. package/apps/integration-client/independent-chat.css +73 -0
  49. package/apps/integration-client/index.jsx +24 -16
  50. package/apps/integration-client/library-panel.jsx +4 -1
  51. package/apps/integration-client/licenses/jsqr-1.4.0-LICENSE.txt +202 -0
  52. package/apps/integration-client/main-route-editor.jsx +94 -0
  53. package/apps/integration-client/main-route-model.mjs +100 -0
  54. package/apps/integration-client/model-picker.css +37 -0
  55. package/apps/integration-client/model-picker.jsx +83 -0
  56. package/apps/integration-client/native-activity.css +141 -0
  57. package/apps/integration-client/native-activity.jsx +276 -0
  58. package/apps/integration-client/native-activity.mjs +384 -0
  59. package/apps/integration-client/notifications-panel.jsx +186 -0
  60. package/apps/integration-client/notifications.css +126 -0
  61. package/apps/integration-client/pairing-qr.jsx +79 -0
  62. package/apps/integration-client/panels.jsx +126 -115
  63. package/apps/integration-client/product-slots.jsx +46 -0
  64. package/apps/integration-client/routine-recorder.mjs +23 -2
  65. package/apps/integration-client/secure-pairing.jsx +84 -0
  66. package/apps/integration-client/tasks-panel.jsx +32 -10
  67. package/apps/integration-client/ui-icons.jsx +17 -0
  68. package/apps/integration-client/usage-explorer.jsx +43 -0
  69. package/apps/integration-client/usage-report.mjs +71 -0
  70. package/apps/integration-client/usage.css +34 -0
  71. package/apps/integration-client/web-panel-model.mjs +140 -0
  72. package/apps/integration-client/web-panel-page.jsx +221 -0
  73. package/apps/integration-client/web-panel-pins-live.jsx +28 -0
  74. package/apps/integration-client/web-panel.css +139 -0
  75. package/apps/integration-client/workspace-model.mjs +67 -0
  76. package/apps/integration-client/workspace-navigation.mjs +56 -0
  77. package/apps/integration-client/workspace-ui.jsx +173 -0
  78. package/apps/integration-client/workspace.css +332 -0
  79. package/modules/bot-runtime/SOURCE.json +176 -50
  80. package/modules/bot-runtime/index.mjs +169 -13
  81. package/modules/bot-runtime/product-overlay/OVERLAY.json +128 -2
  82. package/modules/bot-runtime/product-overlay/base/src/client/header.js +244 -0
  83. package/modules/bot-runtime/product-overlay/base/src/client/learning-card.js +202 -0
  84. package/modules/bot-runtime/product-overlay/base/src/client/model-menu.js +371 -0
  85. package/modules/bot-runtime/product-overlay/base/src/client/secret-card.js +363 -0
  86. package/modules/bot-runtime/product-overlay/base/src/client/sources.js +68 -0
  87. package/modules/bot-runtime/product-overlay/base/src/client/turn-error.js +260 -0
  88. package/modules/bot-runtime/product-overlay/base/src/host/channel.js +647 -0
  89. package/modules/bot-runtime/product-overlay/base/src/host/memory.js +2050 -0
  90. package/modules/bot-runtime/product-overlay/base/src/host/models.js +314 -0
  91. package/modules/bot-runtime/product-overlay/base/src/host/parts.js +1473 -0
  92. package/modules/bot-runtime/product-overlay/base/src/host/privacy.js +368 -0
  93. package/modules/bot-runtime/product-overlay/base/src/host/roster.js +776 -0
  94. package/modules/bot-runtime/product-overlay/base/src/host/store.js +512 -0
  95. package/modules/bot-runtime/product-overlay/base/src/host/usage.js +653 -0
  96. package/modules/bot-runtime/product-overlay/patches/src/client/details-drawer.js.patch +48 -2
  97. package/modules/bot-runtime/product-overlay/patches/src/client/header.js.patch +41 -0
  98. package/modules/bot-runtime/product-overlay/patches/src/client/index.js.patch +33 -1
  99. package/modules/bot-runtime/product-overlay/patches/src/client/learning-card.js.patch +20 -0
  100. package/modules/bot-runtime/product-overlay/patches/src/client/model-menu.js.patch +24 -0
  101. package/modules/bot-runtime/product-overlay/patches/src/client/secret-card.js.patch +25 -0
  102. package/modules/bot-runtime/product-overlay/patches/src/client/sources.js.patch +52 -0
  103. package/modules/bot-runtime/product-overlay/patches/src/client/turn-error.js.patch +23 -0
  104. package/modules/bot-runtime/product-overlay/patches/src/host/channel.js.patch +46 -0
  105. package/modules/bot-runtime/product-overlay/patches/src/host/memory.js.patch +11 -0
  106. package/modules/bot-runtime/product-overlay/patches/src/host/models.js.patch +16 -0
  107. package/modules/bot-runtime/product-overlay/patches/src/host/parts.js.patch +95 -0
  108. package/modules/bot-runtime/product-overlay/patches/src/host/privacy.js.patch +11 -0
  109. package/modules/bot-runtime/product-overlay/patches/src/host/roster.js.patch +145 -0
  110. package/modules/bot-runtime/product-overlay/patches/src/host/store.js.patch +68 -0
  111. package/modules/bot-runtime/product-overlay/patches/src/host/usage.js.patch +11 -0
  112. package/modules/bot-runtime/read-model.mjs +19 -4
  113. package/modules/bot-runtime/vendor/ds-bot/src/client/details-drawer.js +15 -5
  114. package/modules/bot-runtime/vendor/ds-bot/src/client/header.js +9 -2
  115. package/modules/bot-runtime/vendor/ds-bot/src/client/index.js +9 -2
  116. package/modules/bot-runtime/vendor/ds-bot/src/client/learning-card.js +2 -2
  117. package/modules/bot-runtime/vendor/ds-bot/src/client/model-menu.js +5 -3
  118. package/modules/bot-runtime/vendor/ds-bot/src/client/secret-card.js +3 -3
  119. package/modules/bot-runtime/vendor/ds-bot/src/client/sources.js +25 -2
  120. package/modules/bot-runtime/vendor/ds-bot/src/client/turn-error.js +3 -2
  121. package/modules/bot-runtime/vendor/ds-bot/src/host/channel.js +14 -2
  122. package/modules/bot-runtime/vendor/ds-bot/src/host/memory.js +1 -1
  123. package/modules/bot-runtime/vendor/ds-bot/src/host/models.js +7 -0
  124. package/modules/bot-runtime/vendor/ds-bot/src/host/parts.js +26 -9
  125. package/modules/bot-runtime/vendor/ds-bot/src/host/privacy.js +1 -1
  126. package/modules/bot-runtime/vendor/ds-bot/src/host/roster.js +112 -3
  127. package/modules/bot-runtime/vendor/ds-bot/src/host/store.js +34 -1
  128. package/modules/bot-runtime/vendor/ds-bot/src/host/usage.js +1 -1
  129. package/modules/independent-runtime/index.mjs +152 -62
  130. package/modules/independent-runtime/remote-tools.mjs +8 -5
  131. package/modules/openmaus-protocol/NOTICE +10 -0
  132. package/modules/openmaus-protocol/README.md +15 -0
  133. package/modules/openmaus-protocol/SOURCE.json +68 -0
  134. package/modules/openmaus-protocol/dist/protocol.mjs +1203 -0
  135. package/modules/openmaus-protocol/index.mjs +1 -0
  136. package/modules/openmaus-protocol/transport.mjs +77 -0
  137. package/modules/openmaus-protocol/vendor/LICENSE +202 -0
  138. package/modules/openmaus-protocol/vendor/companion/src/e2e/README.md +405 -0
  139. package/modules/openmaus-protocol/vendor/companion/src/e2e/credentials.ts +128 -0
  140. package/modules/openmaus-protocol/vendor/companion/src/e2e/descriptor.ts +84 -0
  141. package/modules/openmaus-protocol/vendor/companion/src/e2e/noise.ts +456 -0
  142. package/modules/openmaus-protocol/vendor/companion/src/e2e/phone.ts +104 -0
  143. package/modules/openmaus-protocol/vendor/companion/src/e2e/session.ts +417 -0
  144. package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.json +2565 -0
  145. package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.sha256 +1 -0
  146. package/modules/openmaus-protocol/vendor/companion/src/e2e/yamux.ts +335 -0
  147. package/package.json +18 -6
  148. package/packages/bot-acp/README.md +156 -0
  149. package/packages/bot-acp/licenses/LICENSE-OpenMausBot +202 -0
  150. package/packages/bot-acp/licenses/NOTICE.md +27 -0
  151. package/packages/bot-acp/src/acp-adapter.mjs +795 -0
  152. package/packages/bot-acp/src/acp-host.mjs +148 -0
  153. package/packages/bot-acp/src/native-launch.mjs +121 -0
  154. package/packages/bot-acp/src/permission-policy.mjs +283 -0
  155. package/packages/bot-acp/src/rpc-codec.mjs +265 -0
  156. package/packages/bot-acp/src/session-core.mjs +581 -0
  157. package/packages/bot-acp/src/stdio-rpc.mjs +329 -0
  158. package/packages/bot-engines/README.md +281 -0
  159. package/packages/bot-engines/licenses/LICENSE-OpenMausBot +202 -0
  160. package/packages/bot-engines/licenses/NOTICE.md +32 -0
  161. package/packages/bot-engines/src/claude-engine.mjs +772 -0
  162. package/packages/bot-engines/src/claude-llm-adapter.mjs +797 -0
  163. package/packages/bot-engines/src/claude-llm-turn.mjs +292 -0
  164. package/packages/bot-engines/src/claude-permission-broker.mjs +130 -0
  165. package/packages/bot-engines/src/claude-permission-proxy.mjs +87 -0
  166. package/packages/bot-engines/src/claude-protocol.mjs +177 -0
  167. package/packages/bot-engines/src/driver-catalog.mjs +334 -0
  168. package/packages/bot-engines/src/host-loop-request.mjs +95 -0
  169. package/packages/bot-engines/src/host-registrations.mjs +105 -0
  170. package/packages/capability-broker/src/cli-bootstrap.mjs +86 -16
  171. package/packages/capability-broker/src/cli-bridge-response.mjs +80 -0
  172. package/packages/capability-broker/src/cli-stdio-bridge.mjs +21 -8
  173. package/packages/cli-runtime/src/codex-session-core.mjs +4 -0
  174. package/packages/cli-runtime/src/native-cli-output.mjs +57 -0
  175. package/packages/cli-runtime/src/native-peer.ps1 +51 -3
  176. package/packages/cli-runtime/src/native-process.mjs +77 -14
  177. package/packages/model-router-plugin/package.json +1 -1
  178. package/packages/orchestration/src/binding-product.mjs +19 -0
  179. package/packages/orchestration/src/bot-data-actions.mjs +45 -0
  180. package/packages/orchestration/src/bot-data-chart.mjs +109 -0
  181. package/packages/orchestration/src/bot-data-context.mjs +44 -0
  182. package/packages/orchestration/src/bot-data-engine.mjs +809 -0
  183. package/packages/orchestration/src/bot-data-io.mjs +186 -0
  184. package/packages/orchestration/src/bot-data-sql.mjs +195 -0
  185. package/packages/orchestration/src/bot-data-surface.mjs +144 -0
  186. package/packages/orchestration/src/bot-data-tools.mjs +68 -0
  187. package/packages/orchestration/src/bot-data-worker.mjs +228 -0
  188. package/packages/orchestration/src/bot-independent-execution.mjs +195 -0
  189. package/packages/orchestration/src/bot-independent-stream.mjs +74 -0
  190. package/packages/orchestration/src/bot-loop.mjs +152 -19
  191. package/packages/orchestration/src/bot-notifications.mjs +159 -0
  192. package/packages/orchestration/src/bot-policy.mjs +43 -4
  193. package/packages/orchestration/src/bot-profile.mjs +171 -0
  194. package/packages/orchestration/src/bot-routine.mjs +4 -4
  195. package/packages/orchestration/src/bot-runtime-assembly.mjs +255 -8
  196. package/packages/orchestration/src/bot-runtime-switch.mjs +13 -2
  197. package/packages/orchestration/src/bot-serving.mjs +164 -0
  198. package/packages/orchestration/src/bot-session-store.mjs +105 -9
  199. package/packages/orchestration/src/bot-settings.mjs +50 -11
  200. package/packages/orchestration/src/bot-thread-admission.mjs +84 -0
  201. package/packages/orchestration/src/bot-thread-host.mjs +187 -0
  202. package/packages/orchestration/src/bot-threads.mjs +790 -0
  203. package/packages/orchestration/src/data-product-access.mjs +37 -0
  204. package/packages/orchestration/src/data-tool-contract.mjs +291 -0
  205. package/packages/orchestration/src/desktop-http-carrier.mjs +120 -0
  206. package/packages/orchestration/src/desktop-proxy.mjs +311 -0
  207. package/packages/orchestration/src/desktop-sessions.mjs +43 -9
  208. package/packages/orchestration/src/ds-lifecycle.mjs +31 -4
  209. package/packages/orchestration/src/grant-refresh-queue.mjs +17 -0
  210. package/packages/orchestration/src/independent-chat-page.mjs +39 -0
  211. package/packages/orchestration/src/independent-chat-service.mjs +308 -0
  212. package/packages/orchestration/src/independent-context-state.mjs +44 -0
  213. package/packages/orchestration/src/independent-message-wire.mjs +84 -0
  214. package/packages/orchestration/src/independent-tool-catalog.mjs +175 -0
  215. package/packages/orchestration/src/independent-tool-families.mjs +73 -0
  216. package/packages/orchestration/src/independent-tool-host.mjs +344 -0
  217. package/packages/orchestration/src/main-bot-grant.mjs +15 -0
  218. package/packages/orchestration/src/main-model-routes.mjs +208 -0
  219. package/packages/orchestration/src/model-accounting.mjs +99 -35
  220. package/packages/orchestration/src/model-auxiliary-dispatch.mjs +187 -0
  221. package/packages/orchestration/src/model-loop-frame.mjs +179 -0
  222. package/packages/orchestration/src/model-prepared-generation.mjs +175 -0
  223. package/packages/orchestration/src/model-request-bounds.mjs +111 -0
  224. package/packages/orchestration/src/native-activity-contract.mjs +491 -0
  225. package/packages/orchestration/src/native-activity-service.mjs +102 -0
  226. package/packages/orchestration/src/native-activity-store.mjs +249 -0
  227. package/packages/orchestration/src/native-conversation-tools.mjs +78 -0
  228. package/packages/orchestration/src/native-data-tools.mjs +100 -0
  229. package/packages/orchestration/src/native-remote-actions.mjs +742 -0
  230. package/packages/orchestration/src/native-team-tools.mjs +73 -0
  231. package/packages/orchestration/src/pairing-invite.mjs +37 -0
  232. package/packages/orchestration/src/product-composition.mjs +1559 -1001
  233. package/packages/orchestration/src/remote-machines.mjs +82 -14
  234. package/packages/orchestration/src/routine-execution.mjs +5 -3
  235. package/packages/orchestration/src/routine-session.mjs +19 -13
  236. package/packages/orchestration/src/secure-pairing-host.mjs +112 -0
  237. package/packages/orchestration/src/task-journal.mjs +29 -0
  238. package/packages/orchestration/src/task-product.mjs +17 -0
  239. package/packages/orchestration/src/team-assignment-store.mjs +120 -0
  240. package/packages/orchestration/src/team-bridge.mjs +282 -0
  241. package/packages/orchestration/src/team-scheduler.mjs +83 -0
  242. package/packages/orchestration/src/team-sdk-tools.mjs +84 -0
  243. package/packages/orchestration/src/team-tool-contract.mjs +284 -0
  244. package/packages/orchestration/src/ui-host-namespace.mjs +23 -0
  245. package/packages/orchestration/src/web-panel-actions.mjs +103 -0
  246. package/packages/orchestration/src/web-panel-framing.mjs +227 -0
  247. package/packages/orchestration/src/web-panel-store.mjs +206 -0
  248. package/remote-computer-agent/README.md +39 -29
  249. package/remote-computer-agent/build.mjs +3 -0
  250. package/remote-computer-agent/dist/OPENMAUSBOT_LICENSE +202 -0
  251. package/remote-computer-agent/dist/OPENMAUSBOT_NOTICE +10 -0
  252. package/remote-computer-agent/dist/OPENMAUSBOT_SOURCE.json +68 -0
  253. package/remote-computer-agent/dist/cli-stdio-bridge.mjs +215 -25
  254. package/remote-computer-agent/dist/cli-stdio-bridge.mjs.map +4 -4
  255. package/remote-computer-agent/dist/cli.mjs +1688 -168
  256. package/remote-computer-agent/dist/cli.mjs.map +4 -4
  257. package/remote-computer-agent/dist/index.mjs +1579 -122
  258. package/remote-computer-agent/dist/index.mjs.map +4 -4
  259. package/remote-computer-agent/dist/native-peer.ps1 +51 -3
  260. package/remote-computer-agent/package-lock.json +308 -2
  261. package/remote-computer-agent/package.json +2 -1
  262. package/remote-computer-agent/pair-device.ps1 +20 -0
  263. package/remote-computer-agent/setup.ps1 +38 -16
  264. package/remote-computer-agent/src/cli.ts +24 -3
  265. package/remote-computer-agent/src/desktop-session.ts +11 -3
  266. package/remote-computer-agent/src/e2e-server.ts +81 -0
  267. package/remote-computer-agent/src/pairing-page.ts +35 -0
  268. package/remote-computer-agent/src/protocol.ts +25 -25
  269. package/remote-computer-agent/src/secure-pairing.ts +105 -0
  270. package/remote-computer-agent/src/store.ts +20 -5
  271. package/remote-computer-agent/src/transport.ts +39 -15
@@ -1,14 +1,25 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'
1
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'
2
2
  import { PRODUCT_PANELS, TASK_STATES, modelName, money } from './api.mjs'
3
3
  import { TaskWorkspace } from './tasks-panel.jsx'
4
4
  import { normalizeEndpoint, parsePairingPaste, secretNameFor } from './pairing.mjs'
5
5
  import { framePoint } from '../../packages/orchestration/src/desktop-contract.mjs'
6
6
  import { BotBindingEditor } from './binding-editor.jsx'
7
7
  import { LibraryPanel } from './library-panel.jsx'
8
+ import { SecurePairingSetup } from './secure-pairing.jsx'
9
+ import { UsageExplorer } from './usage-explorer.jsx'
8
10
  import { DesktopWorkbench } from './desktop-pane.jsx'
9
11
  import { RoutinesPanel } from './routines-panel.jsx'
10
12
  import { UiIcon } from './ui-icons.jsx'
13
+ import { NotificationsPanel } from './notifications-panel.jsx'
14
+ import { DataPanel } from './data-panel.jsx'
15
+ import { BotThreadsPanel } from './bot-threads-panel.jsx'
16
+ import { WebPanelsPanel } from './web-panel-page.jsx'
17
+ import { BotConversationLink } from './independent-chat-entry.jsx'
18
+ import { openIndependentChat } from './independent-chat-navigation.mjs'
19
+ import { requestWorkbenchView } from '../../.dsh-plugin/client/workbench-state.mjs'
11
20
  import { routingSubmissions, submissionCode } from './routing-submissions.mjs'
21
+ import { WORKSPACE_SECTIONS, workspaceNavigation } from './workspace-navigation.mjs'
22
+ import { TeamDirectory, WorkspaceCommandPalette, WorkspaceHome, WorkspaceInbox, WorkspaceToolbar } from './workspace-ui.jsx'
12
23
 
13
24
  const NAV_EVENT = 'bot-product:navigation'
14
25
  let activePanel = null
@@ -18,7 +29,7 @@ const announcePanel = (id, clear = false) => {
18
29
  document.dispatchEvent(new document.defaultView.CustomEvent(NAV_EVENT, { detail: { id: activePanel } }))
19
30
  }
20
31
 
21
- export function ProductNavigation({ openPanel, panels, compact = false }) {
32
+ export function ProductNavigation({ openPanel, panels, compact = false, onSearch }) {
22
33
  const [active, setActive] = useState(() => activePanel)
23
34
  useEffect(() => {
24
35
  const change = event => setActive(event.detail.id)
@@ -27,15 +38,11 @@ export function ProductNavigation({ openPanel, panels, compact = false }) {
27
38
  return () => document.removeEventListener(NAV_EVENT, change)
28
39
  }, [])
29
40
  return <nav className={`bp-nav${compact ? ' bp-nav-compact' : ''}`} aria-label="Bot 工作系统">
30
- <p className="bp-nav-label">工作空间</p>
31
- {[
32
- [panels.bindings, 'Bot 绑定', 'team', '绑定'], [panels.tasks, '任务', 'tasks', '任务'], [panels.machines, '远程电脑', 'computer', '电脑'],
33
- [panels.library, '资料库', 'folder', '资料'], [panels.routine, '例行任务', 'repeat', '例行'], [panels.usage, '费用与用量', 'chart', '费用'], ['model-router-galgame', '高级工作台', 'settings', '工作台'],
34
- ].map(([id, label, icon, caption]) => <button key={id} type="button" aria-current={active === id ? 'page' : undefined}
35
- onClick={() => { announcePanel(id); openPanel(id) }} title={label} aria-label={label}>
36
- {/* A narrow column still says what each icon opens, so the row is readable without hovering. */}
37
- <UiIcon name={icon} size={17} /><span className="bp-nav-name">{label}</span><span className="bp-nav-caption">{caption}</span>
38
- </button>)}
41
+ <button type="button" className="bp-nav-search" aria-label="搜索工作空间" onClick={() => onSearch ? onSearch() : openPanel(panels.home)} title="搜索工作空间"><UiIcon name="search" size={17} /><span className="bp-nav-name">搜索工作空间</span><span className="bp-nav-caption">查找</span></button>
42
+ {['工作', '资源', '管理'].map(group => <div className="bp-nav-group" key={group}><p className="bp-nav-label">{group}</p>{WORKSPACE_SECTIONS.filter(row => row.group === group).map(({ section, label, icon }) => <button key={section} type="button" aria-current={active === panels[section] ? 'page' : undefined}
43
+ onClick={() => { announcePanel(panels[section]); openPanel(panels[section]) }} title={label} aria-label={label}>
44
+ <UiIcon name={icon} size={17} /><span className="bp-nav-name">{label}</span><span className="bp-nav-caption">{label.slice(0, 2)}</span>
45
+ </button>)}{group === '管理' && <button type="button" aria-current={active === 'model-router-galgame' ? 'page' : undefined} onClick={() => { announcePanel('model-router-galgame'); openPanel('model-router-galgame') }} title="高级工作台" aria-label="高级工作台"><UiIcon name="settings" size={17} /><span className="bp-nav-name">高级工作台</span><span className="bp-nav-caption">路由</span></button>}</div>)}
39
46
  </nav>
40
47
  }
41
48
 
@@ -54,85 +61,43 @@ export
54
61
  * 运行时: which path serves this Bot. Only rendered when the Host exposes the switch, so an older
55
62
  * Host shows nothing instead of a dead control.
56
63
  */
57
- function BotRuntimeRow({ botId, dispatch, busy }) {
58
- const [state, setState] = React.useState(null)
59
- const [error, setError] = React.useState('')
60
- const [working, setWorking] = React.useState(false)
64
+ function BotRuntimeRow({ botId, dispatch, busy, onOpenChat }) {
65
+ const [state, setState] = React.useState(null), [error, setError] = React.useState(''), [working, setWorking] = React.useState(false)
66
+ const generation = React.useRef(0)
61
67
  const load = React.useCallback(async () => {
62
- if (typeof dispatch !== 'function') return
68
+ const mine = ++generation.current
63
69
  try {
64
70
  const rows = await dispatch('botRuntimeStatus', {})
65
- const row = Array.isArray(rows) ? rows.find(item => item.botId === botId) : null
66
- setState({ runtime: row?.mode ?? 'integration', changedAt: row?.changedAt ?? null,
67
- turns: row?.turns ?? null, messages: row?.messages ?? null, snapshot: row?.snapshot ?? null })
68
- setError('')
69
- } catch (failure) {
70
- setState(null)
71
- setError(String(failure?.message ?? failure))
72
- }
73
- }, [botId, dispatch])
74
- const [history, setHistory] = React.useState([])
75
- const [historyError, setHistoryError] = React.useState('')
76
- const loadHistory = React.useCallback(async () => {
77
- if (typeof dispatch !== 'function') return
78
- try {
79
- const rows = await dispatch('botRuntimeHistory', { botId, limit: 20 })
80
- setHistory(Array.isArray(rows) ? rows : [])
81
- setHistoryError('')
82
- } catch (failure) {
83
- setHistory([])
84
- setHistoryError(String(failure?.message ?? failure))
85
- }
86
- }, [botId, dispatch])
87
- const [full, setFull] = React.useState(null)
88
- const openFull = React.useCallback(async index => {
89
- try {
90
- const message = await dispatch('botRuntimeMessage', { botId, index })
91
- setFull({ index, ...message })
92
- setHistoryError('')
93
- } catch (failure) {
94
- setHistoryError(String(failure?.message ?? failure))
95
- setFull(null)
96
- }
71
+ if (mine !== generation.current) return
72
+ if (!Array.isArray(rows)) throw new Error('宿主未确认运行时状态。')
73
+ const row = rows.find(item => item.botId === botId)
74
+ const mode = row ? row.mode : 'integration'
75
+ if (!['integration', 'independent'].includes(mode)) throw new Error('未知运行时,已暂停操作。')
76
+ setState({ runtime: mode, changedAt: row?.changedAt ?? null }); setError('')
77
+ } catch (failure) { if (mine === generation.current) { setState(null); setError(String(failure?.message ?? failure)) } }
97
78
  }, [botId, dispatch])
98
- React.useEffect(() => { void load() }, [load])
99
- if (!state && !error) return null
79
+ React.useEffect(() => { void load(); return () => { generation.current++ } }, [load])
100
80
  const target = state?.runtime === 'independent' ? 'integration' : 'independent'
101
81
  const move = async () => {
102
82
  setWorking(true)
103
- try {
104
- await dispatch('botRuntimeMove', { botId, mode: target })
105
- await load()
106
- } catch (failure) {
107
- setError(String(failure?.message ?? failure))
108
- } finally {
109
- setWorking(false)
110
- }
83
+ try { await dispatch('botRuntimeMove', { botId, mode: target }); await load() }
84
+ catch (failure) { setError(String(failure?.message ?? failure)) }
85
+ finally { setWorking(false) }
111
86
  }
112
87
  return <div className="bp-runtime">
113
- <dl className="bp-facts"><Value label="运行时">{state?.runtime === 'independent' ? '独立循环 + 独立会话库' : 'Harness 会话集成'}</Value>
114
- <Value label="切换时间">{state?.changedAt ? new Date(state.changedAt).toLocaleString('zh-CN') : '从未切换'}</Value></dl>
115
- <div className="bp-actions"><button disabled={busy || working} onClick={() => { void move() }}>{working ? '切换中…' : target === 'independent' ? '迁移到独立运行时' : '迁回 Harness 会话'}</button></div>
116
- <p className="bp-muted">迁移是原子的:Bot 正在对话时会被拒绝。搬过去后该 Bot 的会话写进独立会话库,界面与工具沿用现有授权。</p>
88
+ <dl className="bp-facts"><Value label="运行时">{state ? state.runtime === 'independent' ? '独立循环 + 独立会话库' : 'Harness 会话集成' : '等待宿主确认'}</Value><Value label="切换时间">{state?.changedAt ? new Date(state.changedAt).toLocaleString('zh-CN') : '从未切换'}</Value></dl>
89
+ <div className="bp-actions"><button type="button" disabled={!state || busy || working} onClick={() => { void move() }}>{working ? '切换中…' : target === 'independent' ? '迁移到独立运行时' : '迁回 Harness 会话'}</button>{state?.runtime === 'independent' && <button type="button" onClick={() => onOpenChat?.(botId)}>打开独立聊天</button>}</div>
90
+ <p className="bp-muted">Bot 正在对话时不能迁移。独立运行时请打开独立聊天继续发送,完整消息按稳定编号分页读取。迁回后可从“打开私聊”回到此 Bot 的真实原生会话。</p>
117
91
  {error && <p className="bp-error" role="alert">{error}</p>}
118
- {state ? <details className="bp-log"><summary>最近会话(仅预览)</summary>
119
- <button type="button" disabled={working} onClick={() => { void loadHistory() }}>读取预览</button>
120
- {historyError && <p className="bp-error" role="alert">{historyError}</p>}
121
- {history.length === 0 ? <p className="bp-muted">点“读取预览”加载最近 20 条。</p> : <ul className="bp-checklist">{history.map((row, index) => (
122
- <li key={index}><strong>{row.role}</strong>{row.name ? ` · ${row.name}` : ''} · {row.chars} 字
123
- {row.at ? ` · ${new Date(row.at).toLocaleTimeString('zh-CN')}` : ''}
124
- <small>{row.preview}{String(row.preview ?? '').length < row.chars ? '…' : ''}</small>
125
- <button type="button" onClick={() => { void openFull(index) }}>查看全文</button></li>
126
- ))}</ul>}
127
- {full ? <div className="bp-fulltext"><p className="bp-muted">第 {full.index + 1} 条(从最新往前数)· {full.role} · {full.chars} 字</p>
128
- <pre>{full.content}</pre></div> : null}
129
- </details> : null}
130
92
  </div>
131
93
  }
132
94
 
133
- export function BotBindings({ snapshot, openSession, dispatch, busy }) {
95
+ let bindingScopeKey = 0
96
+ export function BotBindings({ snapshot, openSession, dispatch, busy, focus, scope, onOpenTools, onOpenChatPanel }) {
97
+ const scopeKey = useMemo(() => ++bindingScopeKey, [scope])
134
98
  const bots = snapshot.bots ?? []
135
99
  const [selected, setSelected] = useState(bots[0]?.id ?? '')
100
+ useEffect(() => { if (focus?.botId && bots.some(row => row.id === focus.botId)) setSelected(focus.botId) }, [focus?.version, focus?.botId])
136
101
  const bot = bots.find(row => row.id === selected) ?? bots[0]
137
102
  const section = snapshot.botBindings ?? snapshot.bindings
138
103
  const binding = section?.items?.find(row => row.botId === bot?.id)
@@ -143,12 +108,12 @@ export function BotBindings({ snapshot, openSession, dispatch, busy }) {
143
108
  ['资料库', /^tool\/bot_library_/], ['例行任务', /^tool\/bot_routine_/], ['远程工具', /^remote\//],
144
109
  ].filter(([, pattern]) => capabilities.some(value => pattern.test(value))).map(([label]) => label)
145
110
  if (!bots.length) return <p>还没有 Bot。返回 Main 完成首次设置,或从侧栏新建 Bot。</p>
146
- return <div className="bp-columns">
147
- <div className="bp-bot-list" aria-label="Bot 列表">{bots.map(item => <button key={item.id} aria-current={item.id === bot?.id ? 'true' : undefined} onClick={() => setSelected(item.id)}>
148
- <span className="bp-person-avatar"><UiIcon name="bot" size={23} /></span><span className="bp-person-copy"><strong>{item.name}</strong>{item.main && <Tag>Main</Tag>}<small>{item.role || '尚未填写职责'}</small></span></button>)}</div>
149
- <article className="bp-card"><div className="bp-card-title"><div className="bp-person-heading"><span className="bp-person-avatar bp-person-avatar-large"><UiIcon name="bot" size={27} /></span><div><h2>{bot.name}</h2><p className="bp-muted">{bot.main ? '团队协调 Bot' : '团队成员'}</p></div></div><button className="bp-button-primary" onClick={() => openSession(bot.sessionId)}>打开私聊<UiIcon name="arrow" size={15} /></button></div>
150
- <dl className="bp-facts"><Value label="职责">{bot.role || '未填写'}</Value>
151
- <Value label="准确模型">{modelName(bot.model)}</Value><Value label="模型策略">精确绑定;不可用时暂停并说明原因</Value>
111
+ return <div className="bp-columns bp-team-layout">
112
+ <TeamDirectory snapshot={snapshot} selected={bot?.id} onSelect={setSelected} />
113
+ <article className="bp-card"><div className="bp-card-title"><div className="bp-person-heading"><span className="bp-person-avatar bp-person-avatar-large"><UiIcon name="bot" size={27} /></span><div><h2>{bot.name}</h2><p className="bp-muted">{bot.main ? '团队协调 Bot' : '团队成员'}</p></div></div><BotConversationLink productApi={scope} bot={bot} openSession={openSession} onOpenIndependent={id => openIndependentChat(scope, onOpenChatPanel, id)} available={snapshot.availableActions?.includes('independentChat.view')}/></div>
114
+ {dispatch && (!snapshot.availableActions || snapshot.availableActions.includes('binding.save')) ? <BotBindingEditor key={`${scopeKey}:${bot.id}`} hostScope={scope} bot={bot} record={binding} accounts={snapshot.fundingAccounts} dispatch={dispatch} busy={busy} onOpenTools={onOpenTools} /> : <p className="bp-muted">当前连接可以查看 Bot 配置,暂不支持保存修改。</p>}
115
+ <details className="bp-technical"><summary>当前权限与运行配置</summary><dl className="bp-facts"><Value label="职责">{bot.role || '未填写'}</Value>
116
+ <Value label="宿主已选模型">{modelName(bot.model)}</Value><Value label="最近应用模型">{bot.appliedModel ? modelName(bot.appliedModel) : '宿主尚未报告'}</Value><Value label="模型策略">精确绑定;不可用时暂停并说明原因</Value>
152
117
  <Value label="工作区">{binding?.workspace?.root ?? binding?.workspaceRef?.rootId ?? bot.workDir ?? '未指定,请在 Bot 详情选择文件夹'}</Value>
153
118
  <Value label="执行位置">{binding?.machineId ?? binding?.workspaceRef?.machineId ?? '尚未取得宿主绑定'}</Value>
154
119
  <Value label="最大并发">{binding?.maxConcurrentTasks}</Value></dl>
@@ -158,8 +123,8 @@ export function BotBindings({ snapshot, openSession, dispatch, busy }) {
158
123
  {binding.blockedReason && <Blocked value={{ reason: binding.blockedReason, nextAction: binding.nextAction }} />}
159
124
  </> : <Blocked value={{ reason: '此 Bot 尚未取得有效绑定。', nextAction: '在宿主 Bot 绑定配置中授予明确工具、机器与工作区。' }} />}
160
125
  <p className="bp-muted">角色文本不会授予工具或机器权限。职责、模型、记忆及会话设置沿用私聊右侧的 Bot 详情。</p>
161
- {dispatch && <details className="bp-edit-section"><summary><UiIcon name="settings" size={16} />编辑权限与预算</summary><BotBindingEditor bot={bot} record={binding} accounts={snapshot.fundingAccounts} dispatch={dispatch} busy={busy} /></details>}
162
- <details className="bp-technical"><summary>连接与运行详情</summary><dl className="bp-facts"><Value label="稳定 Bot ID">{bot.id}</Value><Value label="预算范围">{binding?.budgetScope}</Value></dl><h3>完整权限标识</h3><p className="bp-code">{capabilities.join('、') || '无额外能力'}</p>{snapshot.availableActions?.includes('botRuntimeStatus') && <BotRuntimeRow botId={bot.id} dispatch={dispatch} busy={busy} />}</details>
126
+ </details>
127
+ <details className="bp-technical"><summary>连接与运行详情</summary><dl className="bp-facts"><Value label="稳定 Bot ID">{bot.id}</Value><Value label="预算范围">{binding?.budgetScope}</Value></dl><h3>完整权限标识</h3><p className="bp-code">{capabilities.join('、') || '无额外能力'}</p>{snapshot.availableActions?.includes('botRuntimeStatus') && <BotRuntimeRow key={bot.id} botId={bot.id} dispatch={dispatch} busy={busy} onOpenChat={id => openIndependentChat(scope, onOpenChatPanel, id)} />}</details>
163
128
  </article>
164
129
  </div>
165
130
  }
@@ -189,7 +154,7 @@ const submissionFailureMessage = failure => {
189
154
  return '原提交核对尚未完成,请恢复宿主连接后再次核对;请保留核对码,不会重新提交任务。'
190
155
  }
191
156
 
192
- function BotRoutingCard({ snapshot, dispatch, busy, submissionScope = dispatch }) {
157
+ function BotRoutingCard({ snapshot, dispatch, busy, submissionScope = dispatch, navigation, focus }) {
193
158
  const submissions = useMemo(() => routingSubmissions(submissionScope), [submissionScope])
194
159
  const submission = useSyncExternalStore(submissions.subscribe, submissions.getSnapshot, submissions.getSnapshot)
195
160
  const [view, setView] = React.useState(null)
@@ -208,6 +173,8 @@ function BotRoutingCard({ snapshot, dispatch, busy, submissionScope = dispatch }
208
173
  const control = useRef({ live: false, epoch: 0, draft: 0, active: null, prepared: null, ready: false })
209
174
  const form = useRef({ text: '', pin: '', machine: '', root: '', effect: 'read' })
210
175
  const busyRef = useRef(busy)
176
+ const draftFocus = useRef(null)
177
+ const card = useRef(null)
211
178
  busyRef.current = busy
212
179
  const call = useCallback(async (action, payload) => {
213
180
  if (typeof dispatch !== 'function') throw new Error('宿主接口尚未就绪。')
@@ -222,11 +189,26 @@ function BotRoutingCard({ snapshot, dispatch, busy, submissionScope = dispatch }
222
189
  const editDraft = changes => {
223
190
  const next = { ...form.current, ...changes }
224
191
  form.current = next
192
+ if (Object.hasOwn(changes, 'text')) navigation?.saveGoalDraft(next.text)
225
193
  setText(next.text); setPin(next.pin); setMachine(next.machine); setRoot(next.root); setEffect(next.effect)
226
194
  const previous = submissions.getSnapshot()
227
195
  if (previous?.state === 'confirmed') submissions.clear(previous)
228
196
  invalidate()
229
197
  }
198
+ useEffect(() => {
199
+ draftFocus.current = null
200
+ const restored = navigation?.getGoalDraft() ?? ''
201
+ form.current = { text: restored, pin: '', machine: '', root: '', effect: 'read' }
202
+ setText(restored); setPin(''); setMachine(''); setRoot(''); setEffect('read')
203
+ }, [navigation, submissionScope])
204
+ useEffect(() => {
205
+ if (!focus?.compose || !viewReady || draftFocus.current === focus.version) return
206
+ draftFocus.current = focus.version
207
+ const draft = navigation?.claimDraft(focus.version)
208
+ if (draft !== null && draft !== undefined) editDraft({ text: draft })
209
+ card.current?.scrollIntoView?.({ block: 'start', behavior: 'auto' })
210
+ card.current?.querySelector('textarea')?.focus({ preventScroll: true })
211
+ }, [focus?.version, focus?.compose, viewReady, navigation])
230
212
  const begin = useCallback(kind => {
231
213
  const state = control.current
232
214
  if (!state.live || state.active || busyRef.current) return null
@@ -300,6 +282,7 @@ function BotRoutingCard({ snapshot, dispatch, busy, submissionScope = dispatch }
300
282
  if (next?.accepted !== true || typeof planId !== 'string' || !planId.trim()) throw new Error('未取得有效的任务计划编号。')
301
283
  // The receipt belongs to the captured Host even if its card was unmounted meanwhile.
302
284
  submissions.update(reference, { state: 'confirmed', planId, planState: next.planState })
285
+ navigation?.saveGoalDraft('')
303
286
  if (current(token)) state.prepared = null
304
287
  } catch (failure) {
305
288
  if (reference) submissions.update(reference, { state: 'unknown' })
@@ -400,7 +383,7 @@ function BotRoutingCard({ snapshot, dispatch, busy, submissionScope = dispatch }
400
383
  const machines = (snapshot.machines?.items ?? []).filter(item => item.status !== 'revoked')
401
384
  const inputDisabled = busy || !viewReady || working === 'dispatch' || working === 'settings'
402
385
  const nameOf = botId => view.candidates.find(row => row.botId === botId)?.name ?? botId
403
- return <article className="bp-card"><div className="bp-card-title"><h2>Bot 路由</h2><Tag>{view.enabled ? `已开启 · ${routingPresetLabel(view.preset)}` : '未开启'}</Tag>
386
+ return <article className="bp-card bp-task-composer" ref={card}><div className="bp-card-title"><h2>Bot 路由</h2><Tag>{view.enabled ? `已开启 · ${routingPresetLabel(view.preset)}` : '未开启'}</Tag>
404
387
  <button type="button" disabled={busy || Boolean(working)} onClick={() => { void load() }}>刷新候选</button></div>
405
388
  <div className="bp-actions"><label className="bp-check"><input type="checkbox" checked={view.enabled} disabled={busy || Boolean(working) || !viewReady} onChange={event => { void saveSettings({ enabled: event.target.checked, preset: view.preset, autoGrant: view.autoGrant }) }} />开启 Bot 路由</label>
406
389
  <label>策略<select value={view.preset} disabled={busy || Boolean(working) || !viewReady} onChange={event => { void saveSettings({ enabled: view.enabled, preset: event.target.value, autoGrant: view.autoGrant }) }}>
@@ -463,10 +446,10 @@ function BotRoutingCard({ snapshot, dispatch, busy, submissionScope = dispatch }
463
446
  </article>
464
447
  }
465
448
 
466
- export function Tasks({ snapshot, dispatch, busy, submissionScope = dispatch, inspect, openSession }) {
449
+ export function Tasks({ snapshot, dispatch, busy, submissionScope = dispatch, inspect, openSession, focus, navigation }) {
467
450
  if (snapshot.tasks?.status !== 'available') return <Blocked value={snapshot.tasks} />
468
451
  return <TaskWorkspace snapshot={snapshot} dispatch={dispatch} busy={busy} scope={submissionScope} inspect={inspect} openSession={openSession}
469
- routingCard={snapshot.availableActions?.includes('botRoutingInspect') ? <BotRoutingCard snapshot={snapshot} dispatch={dispatch} busy={busy} submissionScope={submissionScope} /> : null} />
452
+ focus={focus} routingCard={snapshot.availableActions?.includes('botRoutingInspect') ? <BotRoutingCard snapshot={snapshot} dispatch={dispatch} busy={busy} submissionScope={submissionScope} navigation={navigation ?? workspaceNavigation(submissionScope)} focus={focus} /> : null} />
470
453
  }
471
454
 
472
455
 
@@ -599,6 +582,7 @@ function MachineSetup({ snapshot, dispatch, busy }) {
599
582
  // The channel reports failures as {ok:false}; silence here is how a pairing looks successful
600
583
  // while nothing was stored, so every step is checked.
601
584
  const unwrap = response => {
585
+ if (response === undefined || response === null) throw new Error('宿主尚未确认这一步,请刷新后核对原始结果。')
602
586
  if (response && response.ok === false) throw new Error(response.error?.message ?? response.error ?? '宿主拒绝了这一步。')
603
587
  return response
604
588
  }
@@ -625,7 +609,7 @@ function MachineSetup({ snapshot, dispatch, busy }) {
625
609
  setWorking(false)
626
610
  }
627
611
  }
628
- return <details className="bp-card bp-machine-setup"><summary>添加电脑(导入配对文件)</summary>
612
+ return <><SecurePairingSetup snapshot={snapshot} dispatch={dispatch} busy={busy} /><details className="bp-card bp-machine-setup"><summary>兼容已有电脑(导入长期配对文件)</summary>
629
613
  <p className="bp-muted">在你自己的 Windows 电脑或云电脑上解压 Agent 包,双击 <code>start.cmd</code>,按向导选共享文件夹和连接地址。回到这里导入向导生成的 <code>pairing.private.json</code>,地址、目录和权限会自动填好。</p>
630
614
  {available.includes('machine.secret') && available.includes('machine.pair') ? <div className="bp-form">
631
615
  <label>配对文件<input type="file" accept=".json,application/json" onChange={event => { void importFile(event) }} /></label>
@@ -640,7 +624,7 @@ function MachineSetup({ snapshot, dispatch, busy }) {
640
624
  {status !== '' && <p className="bp-muted" role="status">{status}</p>}
641
625
  {error !== '' && <p className="bp-error" role="alert">{error}</p>}
642
626
  </div> : <p className="bp-muted">宿主尚未提供配对操作。</p>}
643
- </details>
627
+ </details></>
644
628
  }
645
629
 
646
630
  function MachineControls({ machine, snapshot, dispatch, busy }) {
@@ -704,12 +688,18 @@ function ArtifactDownload({ machine, snapshot, dispatch, busy }) {
704
688
  </details>
705
689
  }
706
690
 
707
- export function Machines({ snapshot, dispatch, busy }) {
691
+ export function Machines({ snapshot, dispatch, busy, focus, transport }) {
692
+ const machineCards = useRef(new Map())
693
+ useEffect(() => {
694
+ if (!focus?.machineId) return
695
+ const card = machineCards.current.get(focus.machineId)
696
+ card?.focus({ preventScroll: true }); card?.scrollIntoView?.({ block: 'start' })
697
+ }, [focus?.version, focus?.machineId])
708
698
  const section = snapshot.machines
709
699
  if (section?.status !== 'available') return <Blocked value={section} />
710
- return <>{dispatch&&<><DesktopWorkbench snapshot={snapshot} dispatch={dispatch} />
700
+ return <>{dispatch&&<><DesktopWorkbench snapshot={snapshot} dispatch={dispatch} transport={transport} />
711
701
  <details><summary>兼容截图操作</summary><DesktopControl snapshot={snapshot} dispatch={dispatch} busy={busy} /></details>
712
- <MachineSetup snapshot={snapshot} dispatch={dispatch} busy={busy} /></>}{section.items.length ? <div className="bp-grid">{section.items.map(machine => <article className="bp-card" key={machine.id ?? machine.machineId}>
702
+ <MachineSetup snapshot={snapshot} dispatch={dispatch} busy={busy} /></>}{focus?.machineId && !section.items.some(machine => (machine.id ?? machine.machineId) === focus.machineId) && <p className="bp-blocked" role="status">所选电脑不在当前宿主记录中,请刷新后核对。</p>}{section.items.length ? <div className="bp-grid">{section.items.map(machine => <article className={`bp-card${focus?.machineId === (machine.id ?? machine.machineId) ? ' bp-selected-resource' : ''}`} key={machine.id ?? machine.machineId} tabIndex={-1} ref={node => { const id = machine.id ?? machine.machineId; if (node) machineCards.current.set(id, node); else machineCards.current.delete(id) }}>
713
703
  <div className="bp-card-title"><h2>{machine.name ?? machine.id ?? machine.machineId}</h2><Tag>{machine.status ?? '未知'}</Tag></div>
714
704
  <dl className="bp-facts"><Value label="平台">{machine.platform}</Value><Value label="Agent 版本">{machine.version}</Value><Value label="最后在线">{machine.lastSeenAt ? new Date(machine.lastSeenAt).toLocaleString('zh-CN') : '从未取得心跳'}</Value>
715
705
  <Value label="能力">{machine.capabilities?.join('、')}</Value><Value label="桌面会话">{machine.desktop?.status ?? '未报告'}</Value></dl>
@@ -743,7 +733,7 @@ function UnknownReview({ item, dispatch, busy }) {
743
733
  </article>
744
734
  }
745
735
 
746
- export function Usage({ snapshot, dispatch, busy, openAdvanced }) {
736
+ export function Usage({ snapshot, dispatch, busy, openAdvanced, scope }) {
747
737
  const section = snapshot.usage
748
738
  if (section?.status !== 'available') return <><Blocked value={section} /><button onClick={openAdvanced}>打开已有 MR 费用与订阅面板</button></>
749
739
  const unresolved = section.unresolved ?? []
@@ -755,11 +745,7 @@ export function Usage({ snapshot, dispatch, busy, openAdvanced }) {
755
745
  <p className="bp-muted">以下执行已经分派,但没有取得可核验的厂商账单;在核对完成前,其 Bot 与资金范围不会放行新的付费调用。核对说明会记录在运行记录中。</p>
756
746
  <div className="bp-grid">{unresolved.map(item => <UnknownReview key={`${item.runId}/${item.packageId}`} item={item} dispatch={dispatch} busy={busy} />)}</div>
757
747
  </section> : null}
758
- {!section.items.length ? <p className="bp-empty">尚无费用收据。</p> : <div className="bp-table-wrap"><table><thead><tr><th>Bot / 任务</th><th>来源</th><th>准确模型</th><th>金额</th><th>状态</th></tr></thead>
759
- <tbody>{section.items.map((row, i) => <tr key={row.id ?? row.receiptId ?? i}><td>{row.botName ?? row.botId ?? '未归属'}<small>{row.taskId ?? row.runId}</small></td>
760
- <td>{({ bot: 'Bot 对话', aux: 'Bot 辅助调用', 'mr-api': 'MR API', cli: '官方 CLI', review: '评审' })[row.source] ?? row.source ?? '未报告'}</td>
761
- <td>{modelName(row.model ?? (row.provider ? { provider: row.provider, model: row.modelId } : null))}</td><td>{row.billing === 'subscription' ? '订阅' : money(row.amountUsd ?? row.costUsd)}</td>
762
- <td>{row.status ?? '未知'}{row.resolution && <Tag>{row.resolution.kind === 'waive' ? '已按零费用放行' : `已确认 ${money(row.resolution.costUsd)}`}</Tag>}</td></tr>)}</tbody></table></div>}
748
+ <UsageExplorer section={section} busy={busy} scope={scope}/>
763
749
  <button onClick={openAdvanced}>打开高级费用、订阅与历史能力</button>
764
750
  </>
765
751
  }
@@ -866,6 +852,29 @@ export function RoleCatalog({ dispatch, busy }) {
866
852
  }
867
853
 
868
854
  export function ProductPage({ section, productApi, openSession, openPanel }) {
855
+ const pageRef = useRef(null)
856
+ useLayoutEffect(() => { if (pageRef.current) { pageRef.current.scrollTop = 0; pageRef.current.scrollLeft = 0 } }, [section, productApi])
857
+ const navigation = useMemo(() => workspaceNavigation(productApi), [productApi])
858
+ const route = useSyncExternalStore(navigation.subscribe, navigation.getSnapshot, navigation.getSnapshot)
859
+ const focus = route?.section === section ? route : null
860
+ const [searchOpen, setSearchOpen] = useState(false)
861
+ const onNavigate = useCallback(destination => {
862
+ navigation.navigate(destination)
863
+ openPanel?.(PRODUCT_PANELS[destination.section])
864
+ }, [navigation, openPanel])
865
+ useEffect(() => { setSearchOpen(false) }, [productApi])
866
+ useEffect(() => { if (focus?.search) setSearchOpen(true) }, [focus?.version, focus?.search])
867
+ useEffect(() => {
868
+ const onKey = event => {
869
+ if (event.defaultPrevented || event.isComposing || event.keyCode === 229 || event.altKey || event.shiftKey || !(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== 'k') return
870
+ if (typeof pageRef.current?.checkVisibility === 'function' && !pageRef.current.checkVisibility()) return
871
+ if (event.target instanceof Element && event.target.closest('.xterm, .bp-stream-canvas[data-control=true]')) return
872
+ if (document.querySelector('[aria-modal="true"]') && !document.querySelector('.bp-palette')) return
873
+ event.preventDefault(); event.stopPropagation(); setSearchOpen(value => !value)
874
+ }
875
+ document.addEventListener('keydown', onKey, true)
876
+ return () => document.removeEventListener('keydown', onKey, true)
877
+ }, [])
869
878
  useEffect(() => {
870
879
  const id = PRODUCT_PANELS[section]
871
880
  announcePanel(id)
@@ -925,31 +934,33 @@ export function ProductPage({ section, productApi, openSession, openPanel }) {
925
934
  }
926
935
  }, [productApi, refresh])
927
936
  const inspectTask = useCallback((payload, signal) => productApi.dispatch('task.inspect', payload, signal), [productApi])
937
+ const desktopTransport = useCallback((action,payload,signal) => productApi.dispatch(action,payload,signal),[productApi])
928
938
  const disabled = busy || snapshotStale || snapshotApi.current !== productApi || Boolean(snapshot?.readOnly)
929
- const title = { bindings: 'Bot 绑定与权限', tasks: '任务', machines: '远程电脑', library: '资料库', routine: '例行任务', usage: '费用与用量' }[section]
930
- const meta = {
931
- bindings: ['team', '让每位伙伴各司其职。管理模型、工具和工作空间。'],
932
- tasks: ['tasks', '把工作交给合适的 Bot,在这里关注进展与结果。'],
933
- machines: ['computer', '连接你自己的电脑,随时查看桌面或接过控制权。'],
934
- library: ['folder', '给 Bot 一份有序的资料,让每次协作都有据可循。'],
935
- routine: ['repeat', '演示一次,保存步骤,把重复的工作交给 Bot。'],
936
- usage: ['chart', '查看真实使用记录,清楚掌握每一笔费用。'],
937
- }[section] ?? ['bot', '']
938
- return <main className="bp-page" data-section={section}><div className="bp-page-inner"><header className="bp-page-header">
939
- <div className="bp-page-heading"><p className="bp-eyebrow"><UiIcon name={meta[0]} size={14} />工作空间<UiIcon name="chevron" size={12} /><span>{title}</span></p><h1>{title}</h1><p className="bp-page-description">{meta[1]}</p></div>
939
+ const meta = WORKSPACE_SECTIONS.find(row => row.section === section) ?? { title: '工作空间', icon: 'home', description: '' }
940
+ const title = meta.title
941
+ return <div className="bp-workspace"><WorkspaceToolbar title={title} onHome={() => onNavigate({ section: 'home' })} onSearch={() => setSearchOpen(true)} /><main ref={pageRef} className="bp-page" data-section={section}><div className="bp-page-inner"><header className={`bp-page-header${section === 'home' ? ' bp-home-header' : ''}`}>
942
+ <div className="bp-page-heading">{section === 'home' ? <p className="bp-eyebrow"><span className="bp-live-dot" />团队工作空间</p> : <><p className="bp-eyebrow"><UiIcon name={meta.icon} size={14} />工作空间<UiIcon name="chevron" size={12} /><span>{title}</span></p><h1>{title}</h1><p className="bp-page-description">{meta.description}</p></>}</div>
940
943
  <div className="bp-page-tools"><span className="bp-sync" data-state={snapshotStale ? 'stale' : snapshot ? 'ready' : 'loading'}>{snapshotStale ? '等待同步' : snapshot ? '已同步' : '正在连接'}</span>
941
944
  <button className="bp-refresh" disabled={busy} onClick={() => refresh()} title="刷新当前页面"><UiIcon name="refresh" size={16} className={busy ? 'bp-spinning' : ''} /><span>刷新</span></button></div>
942
945
  </header><div className="bp-page-body">
943
946
  {error && <div role="alert" className="bp-blocked"><strong>无法取得最新宿主状态</strong><p>{error}</p>{snapshot && <p>{snapshotStale ? '下面保留上次读取结果;状态未更新时,操作暂不可用。' : '下面保留上次读取结果,操作以宿主当前授权为准。'}</p>}</div>}
944
947
  {snapshot?.readOnly && <Blocked value={{ reason: snapshot.readOnly, nextAction: '关闭持有写锁的另一宿主后重新加载。' }} />}
945
948
  {!snapshot && !error && <div className="bp-loading" role="status"><span className="bp-loading-line" aria-hidden="true" /><span className="bp-loading-line" aria-hidden="true" /><p>正在读取宿主状态…</p></div>}
946
- {snapshot && <>{section === 'bindings' && <><BotBindings snapshot={snapshot} openSession={openSession} dispatch={dispatch} busy={disabled} />{snapshot.availableActions?.includes('roles.list') && <RoleCatalog dispatch={dispatch} busy={disabled} />}</>}
947
- {section === 'tasks' && <Tasks snapshot={snapshot} dispatch={dispatch} busy={disabled} submissionScope={productApi} inspect={!snapshotStale && snapshotApi.current === productApi && !snapshot.readOnly ? inspectTask : undefined} openSession={openSession} />}
948
- {section === 'machines' && <Machines snapshot={snapshot} dispatch={dispatch} busy={disabled} />}
949
+ {snapshot && <>{section === 'home' && <WorkspaceHome snapshot={snapshot} onNavigate={onNavigate} openSession={openSession} disabled={disabled} />}
950
+ {section === 'inbox' && <WorkspaceInbox snapshot={snapshot} onNavigate={onNavigate} openSession={openSession} />}
951
+ {section === 'notifications' && <NotificationsPanel productApi={productApi} botIds={snapshot.bots.map(bot => bot.id)} available={!snapshotStale && snapshotApi.current === productApi && !snapshot.readOnly && snapshot.availableActions?.includes('notifications.list')} onNavigate={onNavigate} />}
952
+ {section === 'threads' && <BotThreadsPanel productApi={productApi} botId={focus?.botId ?? snapshot.mainBotId} onOpenSession={!snapshotStale && snapshotApi.current === productApi ? openSession : undefined} available={!snapshotStale && snapshotApi.current === productApi && !snapshot.readOnly && snapshot.availableActions?.includes('threads.list')} />}
953
+ {section === 'webPanel' && <WebPanelsPanel productApi={productApi} dispatch={dispatch} busy={disabled} snapshot={snapshot} focus={focus}
954
+ available={!snapshotStale && snapshotApi.current === productApi && !snapshot.readOnly && snapshot.availableActions?.includes('webPanels.list')}
955
+ selectedId={focus?.panelId} onSelect={panelId => navigation.navigate({ section: 'webPanel', panelId })} onLeave={() => onNavigate({ section: 'home' })} />}
956
+ {section === 'bindings' && <>{!snapshotStale && snapshotApi.current === productApi ? <BotBindings snapshot={snapshot} openSession={openSession} dispatch={dispatch} busy={disabled} focus={focus} scope={productApi} onOpenChatPanel={openPanel} onOpenTools={typeof openPanel === 'function' ? () => { requestWorkbenchView('tools'); openPanel('model-router-galgame') } : undefined} /> : <p role="status">正在读取当前连接的 Bot 配置…</p>}{snapshot.availableActions?.includes('roles.list') && <RoleCatalog dispatch={dispatch} busy={disabled} />}</>}
957
+ {section === 'tasks' && <Tasks snapshot={snapshot} dispatch={dispatch} busy={disabled} submissionScope={productApi} inspect={!snapshotStale && snapshotApi.current === productApi && !snapshot.readOnly ? inspectTask : undefined} openSession={openSession} focus={focus} navigation={navigation} />}
958
+ {section === 'machines' && <Machines snapshot={snapshot} dispatch={dispatch} busy={disabled} focus={focus} transport={desktopTransport} />}
949
959
  {section === 'routine' && <RoutinesPanel snapshot={snapshot} dispatch={dispatch} busy={disabled} onOpenMachines={() => openPanel(PRODUCT_PANELS.machines)} />}
950
- {section === 'library' && <Library snapshot={snapshot} dispatch={dispatch} busy={disabled} />}
951
- {section === 'usage' && <Usage snapshot={snapshot} dispatch={dispatch} busy={disabled} openAdvanced={() => openPanel('model-router-galgame')} />}
960
+ {section === 'library' && <Library snapshot={snapshot} dispatch={dispatch} busy={disabled} focus={focus} />}
961
+ {section === 'data' && <DataPanel snapshot={snapshot} dispatch={dispatch} busy={disabled || !snapshot.availableActions?.includes('data.catalog')} focus={focus} transport={desktopTransport} scope={productApi} />}
962
+ {section === 'usage' && <Usage snapshot={snapshot} dispatch={dispatch} busy={disabled} scope={productApi} openAdvanced={() => openPanel('model-router-galgame')} />}
952
963
  <footer className="bp-page-footer bp-muted">上次同步 {new Date(snapshot.observedAt).toLocaleString('zh-CN')}</footer></>}
953
964
  </div></div>
954
- </main>
965
+ </main><WorkspaceCommandPalette open={searchOpen} onClose={() => setSearchOpen(false)} snapshot={snapshotApi.current === productApi ? snapshot : null} onNavigate={onNavigate} openSession={snapshotApi.current === productApi ? openSession : undefined} /></div>
955
966
  }
@@ -0,0 +1,46 @@
1
+ import React, { useMemo } from 'react'
2
+ import { withComposerDraft } from './composer-draft-host.jsx'
3
+ import { ConversationTools } from './chat-ui.jsx'
4
+ import { ContactAttentionNavigation } from './contact-attention.jsx'
5
+ import { WebPanelPinsLive } from './web-panel-pins-live.jsx'
6
+ import { workspaceNavigation, PRODUCT_PANELS } from './workspace-navigation.mjs'
7
+ import { requestWorkbenchView } from '../../.dsh-plugin/client/workbench-state.mjs'
8
+ import { openIndependentChat } from './independent-chat-navigation.mjs'
9
+
10
+ /** Native slot identities stay intact so their registration watchers cannot select their own replacement. */
11
+ export function createProductSlots(slots,openPanel,productApi,TeamSidebar,client={}){
12
+ const navigate=input=>{workspaceNavigation(productApi).navigate(input);return openPanel(PRODUCT_PANELS[input.section])}
13
+ const conversationAddon=props=><ConversationTools {...props} productApi={productApi} onOpenSession={client.openSession} onOpenIndependentChat={botId=>openIndependentChat(productApi,openPanel,botId)} onNavigate={navigate} onOpenTools={()=>{requestWorkbenchView('tools');return openPanel('model-router-galgame')}}/>
14
+ return new Proxy(slots,{get(target,key){
15
+ if(key==='register')return(options,component)=>{
16
+ if(options.name==='conversation.session.header'&&typeof options.inject==='function'){
17
+ const original=options.inject
18
+ return target.register({...options,inject:(...args)=>{const face=original(...args)??{};return {...face,actions:{...face.actions,conversationAddon}}}},component)
19
+ }
20
+ // The composer draft rides the Host's own composer slot, beside the
21
+ // vendor's "+" extra: one wrapper keeps the registered component and its
22
+ // props untouched, and the draft asks the Host for its namespace through
23
+ // the product API this factory already carries.
24
+ if(options.name==='conversation.input.left'){
25
+ return target.register(options,withComposerDraft(component,{productApi,sessionBindingOf:client.sessionBindingOf}))
26
+ }
27
+ if(component!==TeamSidebar)return target.register(options,component)
28
+ const Sidebar=props=>{
29
+ const sidebarAddon=useMemo(()=>function Footer({wide}){
30
+ const go=id=>{props.actions?.setLeftDrawer?.(false);return openPanel(id)}
31
+ return <>
32
+ <ContactAttentionNavigation productApi={productApi} compact={!wide}
33
+ onNavigate={input=>{workspaceNavigation(productApi).navigate(input);return go(PRODUCT_PANELS[input.section])}}
34
+ onOpenAdvanced={()=>go('model-router-galgame')}/>
35
+ <WebPanelPinsLive productApi={productApi} onSelect={panelId=>navigate({section:'webPanel',panelId})}/>
36
+ </>
37
+ },[props.actions])
38
+ return <TeamSidebar {...props} actions={{...props.actions,sidebarAddon}}/>
39
+ }
40
+ Sidebar.dsBotTeamSidebar=TeamSidebar
41
+ return target.register(options,Sidebar)
42
+ }
43
+ const value=Reflect.get(target,key,target)
44
+ return typeof value==='function'?value.bind(target):value
45
+ }})
46
+ }
@@ -19,6 +19,12 @@ export function createAcknowledgedRoutineRecorder({ now = Date.now, onChange } =
19
19
  while (pending.size) {
20
20
  const [id, item] = pending.entries().next().value
21
21
  if (!item.acknowledged) return
22
+ if (item.release) {
23
+ const at=Math.max(item.sentAt,times.at(-1)??item.sentAt),delayMs=times.length?Math.max(0,Math.round(at-times.at(-1))):0
24
+ if(steps.length>=MAX_ROUTINE_STEPS){invalidate('ROUTINE_TOO_LONG');return}
25
+ if(delayMs>60_000){invalidate('ROUTINE_PAUSE_TOO_LONG');return}
26
+ steps.push({action:'release-input',delayMs});times.push(at);moveGroup=null;inputReleased=true;pending.delete(id);continue
27
+ }
22
28
  const movement = item.event.type === 'pointer' && item.event.action === 'move'
23
29
  const at = Math.max(item.sentAt, times.at(-1) ?? item.sentAt)
24
30
  const continuing = movement && moveGroup?.button === item.event.button
@@ -35,7 +41,7 @@ export function createAcknowledgedRoutineRecorder({ now = Date.now, onChange } =
35
41
  if (!movement) moveGroup = null
36
42
  else if (!continuing) moveGroup = { startedAt: at, bucket: 0, tailIndex: null, button: item.event.button }
37
43
  else { moveGroup.bucket = bucket; moveGroup.tailIndex = index }
38
- observedInputs++; if (replace) coalescedMoves++
44
+ observedInputs++;inputReleased=false; if (replace) coalescedMoves++
39
45
  pending.delete(id)
40
46
  }
41
47
  }
@@ -71,6 +77,21 @@ export function createAcknowledgedRoutineRecorder({ now = Date.now, onChange } =
71
77
  if (status !== 'recording') return
72
78
  invalidate(meta?.failure?.status === 'unknown' || meta?.failure?.possiblyApplied !== false ? 'ROUTINE_ACK_LOST' : 'ROUTINE_DEMONSTRATION_FAILED')
73
79
  },
80
+ releaseSent(meta) {
81
+ if(status!=='recording')return
82
+ if(!sameSession(meta)||!Number.isSafeInteger(meta.id)||meta.id<=lastId||!Number.isFinite(meta.sentAt)){invalidate('ROUTINE_RECORDING_SESSION_CHANGED');return}
83
+ if(pending.size>=MAX_PENDING_INPUTS){invalidate('ROUTINE_INPUT_PENDING_LIMIT');return}
84
+ pending.set(meta.id,{release:true,sentAt:meta.sentAt,acknowledged:false});lastId=meta.id;inputReleased=false;emit()
85
+ },
86
+ releaseAcknowledged(meta) {
87
+ if(status!=='recording')return
88
+ const item=pending.get(meta?.id)
89
+ if(!sameSession(meta)||!item?.release){invalidate('ROUTINE_ACK_MISMATCH');return}
90
+ item.acknowledged=true;flush();emit()
91
+ },
92
+ releaseFailed(meta) {
93
+ if(status==='recording')invalidate(meta?.failure?.possiblyApplied===false?'ROUTINE_DEMONSTRATION_FAILED':'ROUTINE_RELEASE_UNCONFIRMED')
94
+ },
74
95
  metadataChanged(metadata) {
75
96
  if (status !== 'recording' || !metadata?.width) return
76
97
  let next
@@ -88,7 +109,7 @@ export function createAcknowledgedRoutineRecorder({ now = Date.now, onChange } =
88
109
  if (pending.size) fail('ROUTINE_INPUT_PENDING')
89
110
  if (!inputReleased) fail('ROUTINE_RELEASE_REQUIRED')
90
111
  const value = normalizeRoutine({ name, description, steps, recording: { source: 'desktop-session', display,
91
- recordedAt: startedAt, durationMs: Math.max(0, now() - startedAt), acknowledgedInputs: steps.length,
112
+ recordedAt: startedAt, durationMs: Math.max(0, now() - startedAt), acknowledgedInputs: steps.filter(step=>step.action==='input').length,
92
113
  ...(coalescedMoves ? { observedInputs, pointerSampleMs: ROUTINE_POINTER_SAMPLE_MS } : {}) } })
93
114
  status = 'finished'; emit(); return clone(value)
94
115
  },