@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
@@ -0,0 +1,363 @@
1
+ // Secret cards: a Bot's request_secret shows a card where the user types a key the
2
+ // Bot never sees, or lets the Bot use a key that is already saved. The settings page
3
+ // lists the saved keys and who may use them. Values only ever travel from these
4
+ // inputs to the host; nothing here reads one back.
5
+ import { createElement as h, useEffect, useId, useRef, useState } from 'react'
6
+ import { CheckIcon, CloseIcon, TrashIcon } from './icons.js'
7
+ import { BotAvatar, BotMark } from './mark.js'
8
+ import { contentText, currentPart } from './text.js'
9
+ import { dateLocale, t } from './i18n.js'
10
+ import { SPRING_POP, SPRING_SHAPE, spring } from './motion.js'
11
+ import { useDockHold } from './card-hold.js'
12
+ import { heldCardKey } from './sources.js'
13
+ import { CellEmpty } from './cell-empty.js'
14
+
15
+ const MIN_VALUE = 4
16
+ const NONE = {}
17
+
18
+ export const KeyIcon = () => h('svg', { width: 15, height: 15, viewBox: '0 0 20 20', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true },
19
+ h('path', { d: 'M7 9.5a3.5 3.5 0 1 1 0 .01z' }), h('path', { d: 'M10.4 10.6L17 10.6M14.5 10.6v2.4M16.6 10.6v1.8' }))
20
+ const LockIcon = () => h('svg', { width: 14, height: 14, viewBox: '0 0 20 20', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true },
21
+ h('path', { d: 'M6.5 9V7a3.5 3.5 0 0 1 7 0v2' }), h('rect', { x: 4.5, y: 9, width: 11, height: 8, rx: 2 }))
22
+ const WarnIcon = () => h('svg', { width: 14, height: 14, viewBox: '0 0 20 20', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true },
23
+ h('path', { d: 'M10 3.5l7 12.5H3z' }), h('path', { d: 'M10 8.5v3.5M10 14.2h.01' }))
24
+
25
+ // Password managers ignore a text field masked by CSS, so it never offers to save the
26
+ // key; browsers without that CSS fall back to a new-password field.
27
+ const MASK_BY_CSS = typeof CSS !== 'undefined' && typeof CSS.supports === 'function' && CSS.supports('-webkit-text-security', 'disc')
28
+
29
+ const insecure = () => typeof location !== 'undefined' && location.protocol === 'http:'
30
+ && !/^(localhost|127(?:\.\d{1,3}){3}|\[::1\])$/.test(location.hostname)
31
+
32
+ const scopeLabel = (scope, roster) => {
33
+ if (scope === 'all') return t('All Bots')
34
+ const names = (scope ?? []).map(id => roster.byId[id]?.name).filter(Boolean)
35
+ return names.length === 0 ? t('No Bot') : names.join(t(' and '))
36
+ }
37
+ const when = time => (Number.isFinite(time) ? new Date(time).toLocaleString(dateLocale(), { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '')
38
+
39
+ export function MaskedInput({ value, onChange, onEnter, label, autoFocus = false }) {
40
+ return h('input', {
41
+ className: 'bt-secret-input',
42
+ type: MASK_BY_CSS ? 'text' : 'password',
43
+ 'data-masked': MASK_BY_CSS ? '' : undefined,
44
+ value,
45
+ autoFocus,
46
+ autoComplete: MASK_BY_CSS ? 'off' : 'new-password',
47
+ autoCorrect: 'off',
48
+ autoCapitalize: 'off',
49
+ spellCheck: false,
50
+ 'data-1p-ignore': '',
51
+ 'data-lpignore': 'true',
52
+ 'data-bwignore': '',
53
+ 'data-form-type': 'other',
54
+ 'aria-label': label,
55
+ placeholder: t('Paste or type the key'),
56
+ onChange: event => onChange(event.target.value),
57
+ onKeyDown: (event) => {
58
+ if (event.key !== 'Enter' || event.nativeEvent.isComposing) return
59
+ event.preventDefault()
60
+ onEnter?.()
61
+ },
62
+ })
63
+ }
64
+
65
+ function ScopeSwitch({ value, onChange, bot }) {
66
+ const options = [['all', t('All Bots')], ['bot', t('Only {name}', { name: bot.name })]]
67
+ return h('div', { className: 'bt-secret-scope', role: 'radiogroup', 'aria-label': t('Who may use it') },
68
+ h('span', { className: 'bt-secret-glide', 'data-at': value === 'all' ? 0 : 1, 'aria-hidden': true }),
69
+ options.map(([id, text]) => h('button', {
70
+ key: id, type: 'button', role: 'radio', 'aria-checked': value === id, className: 'bt-secret-seg', onClick: () => onChange(id),
71
+ }, text)))
72
+ }
73
+
74
+ function SecretHead({ bot, title, detail, name, onClose }) {
75
+ return h('div', { className: 'bt-q-head bt-secret-head' },
76
+ h('span', { className: 'bt-secret-who' }, h(BotMark, { bot, size: 28 }), h('span', { className: 'bt-secret-lock' }, h(LockIcon))),
77
+ h('div', { className: 'bt-q-text' },
78
+ h('p', { className: 'bt-q-title' }, title),
79
+ h('div', { className: 'bt-q-detail' }, h('code', { className: 'bt-secret-name' }, name), detail ? ` · ${detail}` : '')),
80
+ onClose ? h('button', { type: 'button', className: 'bt-q-x', 'aria-label': t('Cancel the request'), title: t('Cancel'), onClick: onClose }, h(CloseIcon)) : null)
81
+ }
82
+
83
+ export function SecretCard({ sessionId, bot, request, roster, actions, inline = false }) {
84
+ const [value, setValue] = useState('')
85
+ const [scope, setScope] = useState('all')
86
+ const [replacing, setReplacing] = useState(false)
87
+ const [busy, setBusy] = useState(false)
88
+ const [error, setError] = useState('')
89
+ const busyRef = useRef(false)
90
+ const hold = useDockHold(sessionId, 'secret', request.id, actions, inline)
91
+ const noteId = useId()
92
+ const secret = (roster.secrets ?? []).find(entry => entry.name === request.name)
93
+ const run = (make) => {
94
+ if (busyRef.current) return undefined
95
+ hold()
96
+ busyRef.current = true
97
+ setBusy(true)
98
+ setError('')
99
+ return Promise.resolve().then(make).then(() => setValue(''), failure => setError(failure?.message ?? String(failure))).finally(() => {
100
+ busyRef.current = false
101
+ setBusy(false)
102
+ })
103
+ }
104
+ const cancel = () => run(() => actions.secretCancel(request.id))
105
+ const long = value.trim().length >= MIN_VALUE
106
+ const save = () => { if (long && !busy) void run(() => actions.secretSet({ requestId: request.id, value, scope })) }
107
+ const allow = () => { if (!busy) void run(() => actions.secretAllow(request.id)) }
108
+ const filling = request.kind === 'fill' || replacing
109
+ const warn = insecure() ? h('div', { className: 'bt-secret-warn', role: 'note' }, h(WarnIcon), t('This page is not on HTTPS: the key crosses the network unencrypted.')) : null
110
+ return h('div', { className: 'bt-q bt-secret', role: 'group', 'aria-describedby': noteId, 'data-kind': request.kind, onFocusCapture: hold, onPointerDownCapture: hold },
111
+ h(SecretHead, {
112
+ bot,
113
+ title: request.kind === 'allow' ? t('Let {bot} use {name}?', { bot: bot.name, name: request.name }) : t('{name} needs a key', { name: bot.name }),
114
+ detail: request.purpose,
115
+ name: request.name,
116
+ onClose: busy ? undefined : cancel,
117
+ }),
118
+ request.kind === 'allow' ? h('div', { className: 'bt-secret-saved' }, h(KeyIcon), t('Saved for {scope}', { scope: scopeLabel(secret?.scope, roster) })) : null,
119
+ filling ? h('div', { className: 'bt-secret-body' },
120
+ h('label', { className: 'bt-q-field bt-secret-field' }, h(MaskedInput, { value, onChange: next => { hold(); setValue(next) }, onEnter: save, label: t('Value of {name}', { name: request.name }), autoFocus: replacing })),
121
+ request.kind === 'fill' ? h(ScopeSwitch, { value: scope, onChange: next => { hold(); setScope(next) }, bot }) : null) : null,
122
+ h('div', { className: 'bt-secret-note', id: noteId },
123
+ t("{bot} never sees the value. It reaches {bot}'s shell as ", { bot: bot.name }), h('code', null, `$DSH_SECRET_${request.name}`), '.',
124
+ filling && value !== '' && !long ? t(' At least {count} characters.', { count: MIN_VALUE }) : ''),
125
+ warn,
126
+ error ? h('div', { className: 'bt-secret-error', role: 'alert' }, error) : null,
127
+ h('div', { className: 'bt-q-foot' },
128
+ request.kind === 'allow' && !replacing ? h('button', { type: 'button', className: 'bt-q-btn', disabled: busy, onClick: () => { hold(); setReplacing(true) } }, t('Replace value')) : null,
129
+ h('button', { type: 'button', className: 'bt-q-btn', disabled: busy, onClick: cancel }, t('Cancel')),
130
+ filling
131
+ ? h('button', { type: 'button', className: 'bt-q-submit', disabled: !long || busy, onClick: save }, busy ? t('Saving…') : t('Save'))
132
+ : h('button', { type: 'button', className: 'bt-q-submit', disabled: busy, onClick: allow }, busy ? t('Allowing…') : t('Allow'))))
133
+ }
134
+
135
+ function SettledSecret({ bot, request, roster }) {
136
+ const canceled = request.status === 'canceled'
137
+ const text = canceled ? t('{bot} asked for {name}', { bot: bot.name, name: request.name })
138
+ : request.status === 'allowed' ? t('Allowed {bot} to use {name}', { bot: bot.name, name: request.name })
139
+ : t('Saved {name}', { name: request.name })
140
+ return h('div', { className: 'bt-q bt-q-settled bt-secret bt-secret-done', role: 'group', 'aria-label': text, 'data-status': request.status },
141
+ h('div', { className: 'bt-q-head' },
142
+ h('span', { className: 'bt-secret-who' }, h(BotMark, { bot, size: 22 })),
143
+ h('div', { className: 'bt-q-text' }, h('p', { className: 'bt-q-title' }, text,
144
+ canceled ? null : h('span', { className: 'bt-secret-scope-tag' }, ` · ${scopeLabel(request.scope, roster)}`))),
145
+ canceled ? h('span', { className: 'bt-q-badge' }, t('Canceled')) : h('span', { className: 'bt-q-check bt-secret-check', title: t('Done') }, h(CheckIcon))))
146
+ }
147
+
148
+ function RequestView({ sessionId, bot, request, roster, actions }) {
149
+ return request.status === 'pending'
150
+ ? h(SecretCard, { key: JSON.stringify([sessionId, request.id, request.kind]), sessionId, bot, request, roster, actions, inline: true })
151
+ : h(SettledSecret, { bot, request, roster })
152
+ }
153
+
154
+ // The card at its request_secret call; a key that was already there gets one line.
155
+ export function SecretToolCell({ sessionId, callId, name, done, content, roster, actions, heldCards = NONE }) {
156
+ const bot = roster.byId[sessionId]
157
+ if (!bot) return h(CellEmpty)
158
+ // Call ids repeat across a Bot's chat parts.
159
+ const request = (roster.secretRequests?.[bot.id] ?? []).find(entry => entry.callId === callId && (entry.sessionId ?? sessionId) === sessionId)
160
+ if (request?.status === 'pending' && heldCards[heldCardKey('secret', request.id)] > 0) return h(CellEmpty)
161
+ if (request) return h('div', { className: 'bt-q-inline' }, h(RequestView, { sessionId, bot, request, roster, actions }))
162
+ if (done && /already saved/.test(contentText(content))) {
163
+ return h('div', { className: 'bt-event' }, h('span', { className: 'bt-secret-event' }, h(KeyIcon), t('{bot} is using {name}', { bot: bot.name, name: name ?? t('a saved key') })))
164
+ }
165
+ // A read-only window shows no card, so the refusal is said in one line, or the Bot
166
+ // just trails off with nothing on screen.
167
+ if (done && contentText(content).startsWith('No card can be shown now')) {
168
+ return h('div', { className: 'bt-event' }, h('span', { className: 'bt-secret-event' }, h(KeyIcon), t('{bot} could not ask for a key: this window can only view the team', { bot: bot.name })))
169
+ }
170
+ return h(CellEmpty)
171
+ }
172
+
173
+ // A card whose call the transcript does not draw (an earlier part of the chat, a
174
+ // hidden turn, a call outside the loaded history) waits above the composer instead.
175
+ // Once editing starts it keeps that component until completion or cancellation.
176
+ export function SecretDock({ sessionId, useRoster, useCards, useHeldCards, actions }) {
177
+ const roster = useRoster(value => value)
178
+ const shown = useCards?.(state => state[sessionId]) ?? NONE
179
+ const held = useHeldCards?.(state => state[sessionId]) ?? NONE
180
+ const bot = roster.byId[sessionId]
181
+ if (!bot || currentPart(bot) !== sessionId) return null
182
+ const drawn = entry => entry.callId && entry.sessionId === sessionId && shown[entry.callId] > 0 && !(held[heldCardKey('secret', entry.id)] > 0)
183
+ const waiting = (roster.secretRequests?.[bot.id] ?? []).filter(entry => entry.status === 'pending' && !drawn(entry))
184
+ if (waiting.length === 0) return null
185
+ return h('div', { className: 'bt-q-dock bt-secret-dock' }, waiting.map(request => h(SecretCard, { key: JSON.stringify([sessionId, request.id, request.kind]), sessionId, bot, request, roster, actions })))
186
+ }
187
+
188
+ // -------------------------------------------------------------------------
189
+ // Settings page
190
+
191
+ function ScopeEditor({ secret, roster, onSave }) {
192
+ const [mode, setMode] = useState(secret.scope === 'all' ? 'all' : 'some')
193
+ const [picked, setPicked] = useState(secret.scope === 'all' ? [] : secret.scope)
194
+ const toggle = id => setPicked(list => (list.includes(id) ? list.filter(item => item !== id) : [...list, id]))
195
+ const same = mode === 'all' ? secret.scope === 'all' : secret.scope !== 'all' && [...picked].sort().join() === [...secret.scope].sort().join()
196
+ return h('div', { className: 'bt-secret-edit-block' },
197
+ h('div', { className: 'bt-secret-edit-label' }, t('Who may use it')),
198
+ h('div', { className: 'bt-secret-radios', role: 'radiogroup', 'aria-label': t('Who may use {name}', { name: secret.name }) },
199
+ [['all', t('All Bots')], ['some', t('Chosen Bots')]].map(([id, text]) => h('label', { key: id, className: 'bt-secret-radio' },
200
+ h('input', { type: 'radio', name: `scope-${secret.name}`, checked: mode === id, onChange: () => setMode(id) }), text))),
201
+ mode === 'some' ? h('div', { className: 'bt-secret-bots' }, roster.bots.map(bot => h('button', {
202
+ key: bot.id, type: 'button', className: 'bt-secret-bot', 'aria-pressed': picked.includes(bot.id), onClick: () => toggle(bot.id),
203
+ }, h(BotAvatar, { bot, size: 20, badge: false, live: false }), bot.name, picked.includes(bot.id) ? h(CheckIcon) : null))) : null,
204
+ h('div', { className: 'bt-secret-row-actions' },
205
+ h('button', { type: 'button', className: 'bt-soft bt-soft-sm', disabled: same, onClick: () => onSave(mode === 'all' ? 'all' : picked) }, t('Save access'))))
206
+ }
207
+
208
+ function PurposeEditor({ secret, onSave }) {
209
+ const [purpose, setPurpose] = useState(secret.purpose ?? '')
210
+ const same = purpose.trim() === (secret.purpose ?? '')
211
+ return h('div', { className: 'bt-secret-edit-block' },
212
+ h('div', { className: 'bt-secret-edit-label' }, t('What it is for')),
213
+ h('div', { className: 'bt-secret-inline' },
214
+ h('input', { className: 'bt-input', 'aria-label': t('What it is for'), placeholder: t('What it is for'), value: purpose, spellCheck: false, autoComplete: 'off', onChange: event => setPurpose(event.target.value) }),
215
+ h('button', { type: 'button', className: 'bt-soft bt-soft-sm', disabled: same, onClick: () => onSave(purpose.trim()) }, t('Save'))))
216
+ }
217
+
218
+ function ValueEditor({ secret, onSave }) {
219
+ const [value, setValue] = useState('')
220
+ const [replaced, setReplaced] = useState(false)
221
+ const long = value.trim().length >= MIN_VALUE
222
+ const save = () => {
223
+ if (!long) return
224
+ void Promise.resolve(onSave(value)).then(ok => { if (ok) { setValue(''); setReplaced(true) } })
225
+ }
226
+ const edit = next => { setValue(next); setReplaced(false) }
227
+ return h('div', { className: 'bt-secret-edit-block' },
228
+ h('div', { className: 'bt-secret-edit-label' }, t('Replace the value')),
229
+ h('div', { className: 'bt-secret-inline' },
230
+ h('label', { className: 'bt-q-field bt-secret-field' }, h(MaskedInput, { value, onChange: edit, onEnter: save, label: t('New value of {name}', { name: secret.name }) })),
231
+ h('button', { type: 'button', className: 'bt-soft bt-soft-sm', disabled: !long, onClick: save }, t('Replace'))),
232
+ value !== '' && !long ? h('div', { className: 'bt-set-hint' }, t('At least {count} characters.', { count: MIN_VALUE }))
233
+ : replaced ? h('div', { className: 'bt-set-hint bt-secret-ok', role: 'status' }, h(CheckIcon), t('Replaced. Bots get the new value from their next command.')) : null)
234
+ }
235
+
236
+ function SecretRow({ secret, roster, actions, run, open, onToggle }) {
237
+ const [confirm, setConfirm] = useState(false)
238
+ useEffect(() => { if (!open) setConfirm(false) }, [open])
239
+ const asker = secret.requestedBy ? roster.byId[secret.requestedBy]?.name ?? t('a deleted Bot') : t('you')
240
+ const allowed = secret.scope === 'all' ? [] : secret.scope.map(id => roster.byId[id]).filter(Boolean)
241
+ const meta = [t('Asked by {name}', { name: asker }), t('added {time}', { time: when(secret.createdAt) }), secret.lastUsedAt ? t('last used {time}', { time: when(secret.lastUsedAt) }) : t('not used yet')].join(' · ')
242
+ return h('div', { className: 'bt-secret-item', 'data-open': open ? '' : undefined },
243
+ h('button', { type: 'button', className: 'bt-set-row bt-secret-summary', 'aria-expanded': open, onClick: onToggle },
244
+ h('span', { className: 'bt-set-copy' },
245
+ h('span', { className: 'bt-secret-title' }, h(KeyIcon), h('code', { className: 'bt-secret-name' }, secret.name), secret.purpose ? h('span', { className: 'bt-secret-purpose' }, secret.purpose) : null),
246
+ h('span', { className: 'bt-set-hint' }, meta)),
247
+ h('span', { className: 'bt-set-control' },
248
+ secret.scope === 'all' ? h('span', { className: 'bt-tag' }, t('All Bots'))
249
+ : allowed.length === 0 ? h('span', { className: 'bt-tag' }, t('No Bot'))
250
+ : h('span', { className: 'bt-secret-faces', title: allowed.map(bot => bot.name).join(', ') }, allowed.slice(0, 5).map(bot => h(BotAvatar, { key: bot.id, bot, size: 20, badge: false, live: false }))))),
251
+ open ? h('div', { className: 'bt-secret-edit' },
252
+ h(ScopeEditor, { key: JSON.stringify(secret.scope), secret, roster, onSave: scope => run(actions.secretUpdate(secret.name, { scope })) }),
253
+ h(PurposeEditor, { key: secret.purpose ?? '', secret, onSave: purpose => run(actions.secretUpdate(secret.name, { purpose })) }),
254
+ h(ValueEditor, { secret, onSave: value => run(actions.secretSet({ name: secret.name, value })) }),
255
+ h('div', { className: 'bt-secret-row-actions' },
256
+ confirm
257
+ ? [
258
+ h('span', { key: 'q', className: 'bt-set-hint' }, t('Delete {name}? Bots lose it at once.', { name: secret.name })),
259
+ h('button', { key: 'no', type: 'button', className: 'bt-soft bt-soft-sm', onClick: () => setConfirm(false) }, t('Keep')),
260
+ h('button', { key: 'yes', type: 'button', className: 'bt-soft bt-soft-sm bt-secret-danger', onClick: () => run(actions.secretDelete(secret.name)) }, t('Delete')),
261
+ ]
262
+ : h('button', { type: 'button', className: 'bt-soft bt-soft-sm bt-secret-danger', onClick: () => setConfirm(true) }, h(TrashIcon), t('Delete')))) : null)
263
+ }
264
+
265
+ function AddSecret({ actions, run, onDone }) {
266
+ const [name, setName] = useState('')
267
+ const [purpose, setPurpose] = useState('')
268
+ const [value, setValue] = useState('')
269
+ const ready = /^[A-Za-z][\w-]{0,63}$/.test(name.trim()) && value.trim().length >= MIN_VALUE
270
+ const save = () => {
271
+ if (!ready) return
272
+ void run(actions.secretSet({ name, purpose, value, scope: 'all' })).then(ok => { if (ok) { setValue(''); onDone() } })
273
+ }
274
+ return h('div', { className: 'bt-set-row bt-set-paste bt-secret-add' },
275
+ h('input', { className: 'bt-input', 'aria-label': t('Key name'), placeholder: t('NAME, e.g. GITHUB_TOKEN'), value: name, spellCheck: false, autoComplete: 'off', autoFocus: true, onChange: event => setName(event.target.value.toUpperCase()) }),
276
+ h('input', { className: 'bt-input', 'aria-label': t('What it is for'), placeholder: t('What it is for'), value: purpose, autoComplete: 'off', onChange: event => setPurpose(event.target.value) }),
277
+ h('label', { className: 'bt-q-field bt-secret-field' }, h(MaskedInput, { value, onChange: setValue, onEnter: save, label: t('Value') })),
278
+ h('div', { className: 'bt-secret-row-actions' },
279
+ h('span', { className: 'bt-set-hint' }, t('New keys are for all Bots; narrow it down afterwards.')),
280
+ h('button', { type: 'button', className: 'bt-soft bt-soft-sm', onClick: onDone }, t('Cancel')),
281
+ h('button', { type: 'button', className: 'bt-soft bt-soft-sm', disabled: !ready, onClick: save }, t('Save key'))))
282
+ }
283
+
284
+ export function SecretsPage({ roster, actions, run, Section }) {
285
+ const [open, setOpen] = useState(null)
286
+ const [adding, setAdding] = useState(false)
287
+ const secrets = roster.secrets ?? []
288
+ return [
289
+ h('p', { key: 'about', className: 'bt-secret-about' },
290
+ t('Keys your Bots asked for. Bots never see the values: they use them as '), h('code', null, '$DSH_SECRET_NAME'),
291
+ t(' in their shell, and every value is replaced by '), h('code', null, '[secret:NAME]'), t(' before anything goes to a model.')),
292
+ h(Section, { key: 'list', title: t('Secrets · {count}', { count: secrets.length }) },
293
+ secrets.length === 0 && !adding ? h('div', { className: 'bt-set-row' }, h('span', { className: 'bt-set-hint' }, t('No keys yet. When a Bot needs one, it shows a card in its chat.'))) : null,
294
+ secrets.map(secret => h(SecretRow, { key: secret.name, secret, roster, actions, run, open: open === secret.name, onToggle: () => setOpen(open === secret.name ? null : secret.name) })),
295
+ adding ? h(AddSecret, { actions, run, onDone: () => setAdding(false) }) : null),
296
+ adding ? null : h('div', { key: 'add', className: 'bt-actions' }, h('button', { type: 'button', className: 'bt-soft bt-soft-sm', onClick: () => setAdding(true) }, t('Add a key'))),
297
+ ]
298
+ }
299
+
300
+ export const SECRET_CSS = `
301
+ .bt-secret-head{align-items:center;gap:10px}
302
+ .bt-secret-who{position:relative;flex:none;display:inline-flex}
303
+ .bt-secret-lock{position:absolute;right:-5px;bottom:-4px;width:16px;height:16px;border-radius:50%;background:var(--bt-main);color:var(--bt-ink-2);display:inline-flex;align-items:center;justify-content:center;box-shadow:0 0 0 1.5px var(--bt-bubble)}
304
+ .bt-secret-lock svg{width:11px;height:11px}
305
+ .bt-secret-name{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px;line-height:16px;padding:1px 6px;border-radius:5px;background:var(--bt-hover);color:var(--bt-ink)}
306
+ .bt-secret-body{display:flex;flex-direction:column;gap:8px}
307
+ .bt-secret-field{align-items:center;min-height:36px}
308
+ .bt-secret-input{flex:1;width:100%;min-width:0;margin:0;padding:0;border:0;outline:none;background:transparent;color:var(--bt-ink);font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:14px;line-height:20px;letter-spacing:.04em}
309
+ .bt-secret-input[data-masked]{-webkit-text-security:disc}
310
+ .bt-secret-input::placeholder{color:var(--bt-ink-3);font-family:var(--bt-font);letter-spacing:0}
311
+ .bt-secret-scope{position:relative;display:inline-grid;grid-template-columns:1fr 1fr;align-self:flex-start;padding:2px;border-radius:999px;background:var(--bt-hover)}
312
+ .bt-secret-glide{position:absolute;top:2px;bottom:2px;left:2px;width:calc(50% - 2px);border-radius:999px;background:var(--bt-main);box-shadow:0 1px 3px rgba(20,20,20,.08),inset 0 0 0 .5px color-mix(in srgb,var(--bt-ink) 6%,transparent);transition:${spring(SPRING_SHAPE, 'transform')}}
313
+ .bt-secret-glide[data-at="1"]{transform:translateX(100%)}
314
+ .bt-secret-seg{position:relative;z-index:1;height:28px;padding:0 14px;border:0;border-radius:999px;background:none;color:var(--bt-ink-2);font:inherit;font-size:13px;white-space:nowrap;cursor:pointer;transition:color .16s ease}
315
+ .bt-secret-seg[aria-checked=true]{color:var(--bt-ink);font-weight:500}
316
+ .bt-secret-seg:focus-visible{outline:2px solid var(--bt-accent);outline-offset:-2px}
317
+ /* A key card asks the user to act, so the process group carrying it stays open in every
318
+ work-details mode (same override as .bt-group-replies in styles.js). */
319
+ [data-step-process]:has(.bt-secret)>div:first-child{display:none!important}
320
+ [data-step-process]:has(.bt-secret)>[data-step-process-body]{display:block!important;content-visibility:visible!important;max-height:none!important;overflow:visible!important;mask-image:none!important;scrollbar-gutter:auto!important}
321
+ .bt-secret-ok{display:flex;align-items:center;gap:4px;margin-top:6px;animation:bt-secret-in .22s cubic-bezier(.22,1,.36,1)}
322
+ .bt-secret-ok svg{width:13px;height:13px;color:#16a34a}
323
+ @keyframes bt-secret-in{from{opacity:0;transform:translateY(-2px)}}
324
+ .bt-secret-saved{display:flex;align-items:center;gap:6px;font-size:13px;line-height:18px;color:var(--bt-ink-2)}
325
+ .bt-secret-note{font-size:12px;line-height:17px;color:var(--bt-ink-3)}
326
+ .bt-secret-note code,.bt-secret-about code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px}
327
+ .bt-secret-warn{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;font-size:12px;line-height:16px;color:#b25e09;background:color-mix(in srgb,#f59e0b 12%,transparent)}
328
+ .bt-secret-error{font-size:12px;line-height:16px;color:var(--bt-danger)}
329
+ .bt-secret .bt-q-btn:disabled{opacity:.5;cursor:default}
330
+ .bt-secret-done .bt-q-head{align-items:center}
331
+ .bt-secret-done .bt-q-title{font-size:13px;line-height:18px;color:var(--bt-ink-2);font-weight:500}
332
+ .bt-secret-scope-tag{font-weight:400;color:var(--bt-ink-3)}
333
+ .bt-secret-check{color:var(--bt-green,#22a06b);animation:bt-secret-pop ${SPRING_POP.duration}ms ${SPRING_POP.easing} backwards}
334
+ .bt-secret-done{animation:bt-secret-settle .32s cubic-bezier(.22,1,.36,1) backwards}
335
+ @keyframes bt-secret-pop{from{opacity:0;transform:scale(.4)}}
336
+ @keyframes bt-secret-settle{from{opacity:.4;transform:scale(.985)}}
337
+ .bt-secret-event{display:inline-flex;align-items:center;gap:5px}
338
+ .bt-secret-dock{flex-direction:column;align-items:center}
339
+ .bt-secret-dock .bt-q{max-width:min(550px,calc(100% - 32px))}
340
+ .bt-secret-about{margin:0 0 12px;padding:0 4px;font-size:12px;line-height:17px;color:var(--bt-ink-2)}
341
+ .bt-secret-title{display:flex;align-items:center;gap:6px;min-width:0;color:var(--bt-ink)}
342
+ .bt-secret-title svg{flex:none;color:var(--bt-ink-2)}
343
+ .bt-secret-purpose{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--bt-ink-2)}
344
+ .bt-secret-faces{display:inline-flex}
345
+ .bt-secret-faces>*+*{margin-left:-6px}
346
+ .bt-secret-edit{display:flex;flex-direction:column;gap:14px;padding:4px 12px 14px;animation:bt-q-in ${SPRING_SHAPE.duration}ms ${SPRING_SHAPE.easing} backwards,bt-veil-in .2s ease-out backwards}
347
+ .bt-secret-edit-block{display:flex;flex-direction:column;gap:8px}
348
+ .bt-secret-edit-label{font-size:12px;line-height:16px;font-weight:600;color:var(--bt-ink-2)}
349
+ .bt-secret-radios{display:flex;gap:16px;font-size:13px}
350
+ .bt-secret-radio{display:inline-flex;align-items:center;gap:6px;cursor:pointer}
351
+ .bt-secret-bots{display:flex;flex-wrap:wrap;gap:6px}
352
+ .bt-secret-bot{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 10px 0 5px;border-radius:999px;border:.5px solid var(--bt-line);background:var(--bt-card);color:var(--bt-ink-2);font:inherit;font-size:12px;cursor:pointer;transition:background-color .12s ease,color .12s ease,border-color .12s ease}
353
+ .bt-secret-bot[aria-pressed=true]{background:var(--bt-active);color:var(--bt-ink);border-color:var(--bt-line-2)}
354
+ .bt-secret-bot svg{width:13px;height:13px}
355
+ .bt-secret-inline{display:flex;gap:8px;align-items:center}
356
+ .bt-secret-inline .bt-input{flex:1;height:34px}
357
+ .bt-secret-row-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px;flex-wrap:wrap}
358
+ .bt-secret-row-actions .bt-set-hint{flex:1}
359
+ .bt-secret-danger{color:var(--bt-danger);display:inline-flex;align-items:center;gap:4px}
360
+ .bt-secret-danger svg{width:13px;height:13px}
361
+ .bt-secret-add .bt-input{height:34px}
362
+ @media (prefers-reduced-motion:reduce){.bt-secret-glide{transition:none}.bt-secret-check,.bt-secret-done,.bt-secret-ok,.bt-secret-edit{animation:none}}
363
+ `
@@ -0,0 +1,68 @@
1
+ // -------------------------------------------------------------------------
2
+ // Small observable sources for the inject `hooks` compartment.
3
+
4
+ export function createSource(initial) {
5
+ let value = initial
6
+ const listeners = new Set()
7
+ return {
8
+ getSnapshot: () => value,
9
+ subscribe: (listener) => { listeners.add(listener); return () => { listeners.delete(listener) } },
10
+ set(next) {
11
+ const resolved = typeof next === 'function' ? next(value) : next
12
+ if (resolved === value) return
13
+ value = resolved
14
+ for (const listener of [...listeners]) listener()
15
+ },
16
+ }
17
+ }
18
+
19
+ export const EMPTY_ROSTER = { revision: -1, mainBotId: null, mainBotIds: [], bots: [], rooms: [], agents: [], exchangeTurns: {}, questions: {}, btwCards: {}, sideChats: {}, learning: {}, secrets: [], capabilities: [], capabilityStatuses: {}, secretRequests: {}, models: [], defaultModel: null, modelsKey: null, prefs: {}, byId: {}, roomsById: {}, agentsById: {}, agentOf: {}, ready: false }
20
+ // Any Main Bot holds the Main Bot powers; `mainBotId`, the first one, is the sidebar tile.
21
+ export const isMainOf = (roster, id) => id !== undefined && (roster.mainBotIds ?? []).includes(id)
22
+
23
+ // A Bot's chat is a chain of Sessions ("parts"): the Bot id is its first part's id
24
+ // and `sessionId` its current one. Every part id finds the Bot; Session state
25
+ // (running, unread, turns) is read from the current part, team state from the Bot id.
26
+ export function indexRoster(value) {
27
+ const byId = {}
28
+ for (const bot of value.bots) {
29
+ for (const id of [...(bot.parts ?? []), bot.sessionId ?? bot.id, bot.id]) byId[id] = bot
30
+ }
31
+ const roomsById = {}
32
+ // A group's own Session is a chain of parts like a Bot's chat: `room.id` is its
33
+ // first part's id and `sessionId` its current one; every part id finds the room.
34
+ for (const room of value.rooms) {
35
+ for (const id of [room.id, room.sessionId, ...(room.ownParts ?? [])]) roomsById[id] = room
36
+ }
37
+ // The DSH Agent is not a Bot: it indexes by its own id and by each Session it owns.
38
+ const agentsById = {}
39
+ const agentOf = {}
40
+ for (const agent of value.agents ?? []) {
41
+ agentsById[agent.id] = agent
42
+ for (const sessionId of agent.sessions ?? []) agentOf[sessionId] = agent.id
43
+ }
44
+ return { ...value, agents: value.agents ?? [], byId, roomsById, agentsById, agentOf, ready: true }
45
+ }
46
+
47
+ // Whether a main-view Session id belongs to the team's DSH Agent: the conversation
48
+ // group then stays withdrawn while the shell keeps the sidebar. Before the roster
49
+ // arrives, the ids cached from the last pull answer.
50
+ export function isAgentSession(roster, cache, sessionId) {
51
+ if (sessionId === undefined || sessionId === null || sessionId === '') return false
52
+ if (roster.ready) return roster.agentOf[sessionId] !== undefined
53
+ return cache.has(sessionId)
54
+ }
55
+
56
+ // A request can reuse a call id; question and secret ids belong to separate kinds.
57
+ export const heldCardKey = (kind, requestId) => JSON.stringify([kind, requestId])
58
+
59
+ // Per Session, how many mounted cells draw or hold each card; 0 drops the entry.
60
+ export function countCards(state, sessionId, callIds, delta) {
61
+ const own = { ...state[sessionId] }
62
+ for (const callId of callIds) {
63
+ const count = (own[callId] ?? 0) + delta
64
+ if (count > 0) own[callId] = count
65
+ else delete own[callId]
66
+ }
67
+ return { ...state, [sessionId]: own }
68
+ }