@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
@@ -12,7 +12,7 @@
12
12
  {
13
13
  "path": "src/client/details-drawer.js",
14
14
  "kind": "patched",
15
- "summary": "Adds the 电脑操作 (desktop) tab to a 1:1 Bot's drawer and renders DesktopPane in it.",
15
+ "summary": "Adds the 电脑操作 (desktop) tab to a 1:1 Bot's drawer and renders DesktopPane in it; a thread Session keeps its own transcript, stats, questions and usage, and its name is shown instead of offering to rename the Bot.",
16
16
  "base": {
17
17
  "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
18
18
  "sha256": "f4917de604dc7c3daf3b4a5267a04c326c9b20aea94bb5e27bfcb6e106396172"
@@ -30,7 +30,7 @@
30
30
  {
31
31
  "path": "src/client/index.js",
32
32
  "kind": "patched",
33
- "summary": "Accepts a wrapped TeamSidebar in the sidebar.workspaces slot probe, so the product's own wrapper still counts as the DS Bot team surface.",
33
+ "summary": "Opens a thread Session as itself in partFor — resolved before the Bot, because a thread Session also resolves to its Bot — keeps homeToMainBot from bouncing an open thread to Main, and refreshes thread projections.",
34
34
  "base": {
35
35
  "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
36
36
  "sha256": "4fd22b26d817ca3ae5454e5a8042c1c4b806ff4ae78c9e38f90ee5ee3c996b9a"
@@ -53,6 +53,132 @@
53
53
  "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
54
54
  "sha256": "3818fea953dc7a766d7dcb738661a448a922f84ac42b8d434ac7fb83e09b9c65"
55
55
  }
56
+ },
57
+ {
58
+ "path": "src/client/header.js",
59
+ "kind": "patched",
60
+ "summary": "Renders the product conversationAddon after the native header identity, preserving goals, activity and slot component identity, and names the open thread beside its Bot so two contexts of one Bot are never confused.",
61
+ "base": {
62
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
63
+ "sha256": "2e16b329fcc5e0994066db13fe54558ac70fdd7b4131155c89b7aa04d0c59bab"
64
+ }
65
+ },
66
+ {
67
+ "path": "src/host/roster.js",
68
+ "kind": "patched",
69
+ "summary": "Adds the trusted thread records addThreadSession/renameThread/setThreadArchived (one save each, the Bot model and permission preset applied, no notice or turn), keeps updateBotProfile untouched, and archives thread Sessions on Bot delete.",
70
+ "base": {
71
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
72
+ "sha256": "0a06730a0ce2ecf6b67d82319ef9b7fc127b3b2c2170b6e50d3d77ce4c866e32"
73
+ }
74
+ },
75
+ {
76
+ "path": "src/host/store.js",
77
+ "kind": "patched",
78
+ "summary": "Indexes a Bot thread Session as another own chat of the same Bot: live threads sit in chatOwners/threadOwners, archived ones in pastOwners with no authority, and threadsOf/threadOf/threadSessionOf/sessionsOfBot are exposed for the product.",
79
+ "base": {
80
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
81
+ "sha256": "aae02595f97b2337e485f16a84091c9d91d40f6dbdc211350fe3fe0cddf52b19"
82
+ }
83
+ },
84
+ {
85
+ "path": "src/host/parts.js",
86
+ "kind": "patched",
87
+ "summary": "Keeps the part steward off thread Sessions: a thread is not a part chain, so nothing condenses it or hands its input on to the Bot default chat.",
88
+ "base": {
89
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
90
+ "sha256": "f7a2e272f0296414075d2318d45cf58871fff3a22c1d3035707a75bec77ef619"
91
+ }
92
+ },
93
+ {
94
+ "path": "src/host/models.js",
95
+ "kind": "patched",
96
+ "summary": "Applies a Bot model change to its live thread Sessions as it already does for group Sessions.",
97
+ "base": {
98
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
99
+ "sha256": "db1424879e28d6252818eccbb4bc763bc87ff355be503abe3f95399aaed869a9"
100
+ }
101
+ },
102
+ {
103
+ "path": "src/host/usage.js",
104
+ "kind": "patched",
105
+ "summary": "Attributes a thread Session usage to its Bot through the shared sessionsOfBot list.",
106
+ "base": {
107
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
108
+ "sha256": "3d4ff2168d7dfb726c2d5454f36eef58dec1c768b79c2c0c17f4192a34b2bbd7"
109
+ }
110
+ },
111
+ {
112
+ "path": "src/host/privacy.js",
113
+ "kind": "patched",
114
+ "summary": "Covers thread Sessions when a private Bot files are purged.",
115
+ "base": {
116
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
117
+ "sha256": "370b5adf762714a8b794f2079a8bbfd8f9c63811bf599ecadad80a63a9f12c5a"
118
+ }
119
+ },
120
+ {
121
+ "path": "src/host/memory.js",
122
+ "kind": "patched",
123
+ "summary": "Renews memory for thread Sessions when a Bot memory is archived.",
124
+ "base": {
125
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
126
+ "sha256": "5e145d02c6c9d8d217b1725ae8bd88483c6a847300d50f107da70575696b7503"
127
+ }
128
+ },
129
+ {
130
+ "path": "src/host/channel.js",
131
+ "kind": "patched",
132
+ "summary": "Projects a Bot threads to the client and refuses input aimed at an archived thread instead of redirecting it to the default chat.",
133
+ "base": {
134
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
135
+ "sha256": "0e05d9a424b188fe338f7102404a9fb295069d0ece241d63cc4f87be18947d03"
136
+ }
137
+ },
138
+ {
139
+ "path": "src/client/sources.js",
140
+ "kind": "patched",
141
+ "summary": "Indexes thread Sessions in a read-only threadsById map by Session id and thread id, resolves a thread Session to its Bot in byId (never taking a Bot's own chat identity), and exports isOwnChatOf so Bot-scoped surfaces recognise a thread as another own chat of the same Bot.",
142
+ "base": {
143
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
144
+ "sha256": "1cc04070d1869dcc382efc31fafcff8fda9c3271342896c98355c37299458e1c"
145
+ }
146
+ },
147
+ {
148
+ "path": "src/client/model-menu.js",
149
+ "kind": "patched",
150
+ "summary": "The Bot's model-choice dock belongs to a thread too: it recognises a thread Session as another own chat of the same Bot instead of only the default chat.",
151
+ "base": {
152
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
153
+ "sha256": "78c28d9459e78ce4ffa9b3edc64d7d2218b02112eaf3b0ae204cf2700321b7a5"
154
+ }
155
+ },
156
+ {
157
+ "path": "src/client/learning-card.js",
158
+ "kind": "patched",
159
+ "summary": "Memory learning proposals are shown for a thread Session, which is another own chat of the same Bot, not only for the default chat.",
160
+ "base": {
161
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
162
+ "sha256": "29e0ddffc08814b3e9676fb40474ba5ae0702edb1b53267d52e51b4b31be733f"
163
+ }
164
+ },
165
+ {
166
+ "path": "src/client/secret-card.js",
167
+ "kind": "patched",
168
+ "summary": "The secret-request dock waits for a thread Session's own cards, so a tool asking for a secret inside a thread is answered there.",
169
+ "base": {
170
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
171
+ "sha256": "2ac7d7546918b7104d7af8b71d08a6fc07bdc2aeb4168d3ad26bddf2f9fd4ff5"
172
+ }
173
+ },
174
+ {
175
+ "path": "src/client/turn-error.js",
176
+ "kind": "patched",
177
+ "summary": "A turn failure inside a thread keeps its sign-in and model-move affordances, exactly as the Bot's default chat does.",
178
+ "base": {
179
+ "commit": "71d344bf193f3f18185f25de3d8e5c483046910a",
180
+ "sha256": "4ed1e99fb3fe66be3a55176389e1ab08e282070e23e9e06089a20f6dbec3b59b"
181
+ }
56
182
  }
57
183
  ]
58
184
  }
@@ -0,0 +1,244 @@
1
+ import { createElement as h, Fragment, useEffect, useLayoutEffect, useRef, useState } from 'react'
2
+ import { createPortal } from 'react-dom'
3
+ import { currentPart, latestMemoryChange } from './text.js'
4
+ import { ArrowUpIcon, CloseIcon, ArrowDownIcon, GoalIcon, MemoryIcon } from './icons.js'
5
+ import { botState, BotAvatar, RoomAvatar } from './mark.js'
6
+ import { ActivityIndicator } from './activity-row.js'
7
+ import { clockLabel, openingTurn, unseenSince } from './cells.js'
8
+ import { GoalIsland, goalView } from './goal.js'
9
+ import { viewBottom } from './motion.js'
10
+ import { t } from './i18n.js'
11
+
12
+ // -------------------------------------------------------------------------
13
+ // Conversation header
14
+
15
+ // The pill's tail: a → that springs in on hover, flipping to ← while details are open.
16
+ const GoArrow = () => h('svg', { width: 14, height: 14, viewBox: '-12 -12 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2.2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true }, h('path', { d: 'M-7 0H7M2 -5L7 0L2 5' }))
17
+
18
+ // How long the pill says "Memory updated" after the change reaches this window.
19
+ const MEMORY_NOTICE_MS = 3_200
20
+ const noNews = select => select({})
21
+
22
+ // True while the latest memory change of the Bots in this conversation is news.
23
+ function useMemoryNotice(ids, useMemoryNews) {
24
+ const seen = useMemoryNews(map => Math.max(0, ...ids.map(id => map?.[id]?.seen ?? 0)))
25
+ const [shown, setShown] = useState(false)
26
+ useEffect(() => {
27
+ const left = seen + MEMORY_NOTICE_MS - Date.now()
28
+ setShown(seen > 0 && left > 0)
29
+ if (seen === 0 || left <= 0) return undefined
30
+ const timer = setTimeout(() => setShown(false), left)
31
+ return () => clearTimeout(timer)
32
+ }, [seen])
33
+ return shown
34
+ }
35
+
36
+ // The notice the pill stretches to show. Its width is measured, so the pill's spring
37
+ // runs between two real widths.
38
+ function MemoryNotice({ shown }) {
39
+ const inner = useRef(null)
40
+ const [width, setWidth] = useState(0)
41
+ const label = t('Memory updated')
42
+ useLayoutEffect(() => { if (shown) setWidth(inner.current?.scrollWidth ?? 0) }, [label, shown])
43
+ return h('span', { className: 'bt-pill-mem', 'aria-hidden': true, style: { '--bt-mem-w': `${width}px` } },
44
+ h('span', { className: 'bt-pill-mem-in', ref: inner }, h('span', { className: 'bt-pill-mem-ico' }, h(MemoryIcon)), label))
45
+ }
46
+
47
+ export function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus, useSession, useActivity, useMemoryNews, useTurnClock, useUi, useLive, useWork, useStore, useConversationViews, selectView, actions }) {
48
+ const roster = useRoster(value => value)
49
+ const selectedView = useStore?.(state => state.view)
50
+ const away = useConversationViews?.(views => views.some(view => view.id === selectedView && view.id !== 'chat')) === true
51
+ && typeof selectView === 'function'
52
+ const title = useSessions(list => list.byId[sessionId]?.displayTitle ?? list.byId[sessionId]?.title ?? '')
53
+ const firstTurns = useSessions((list) => {
54
+ const outline = list.projectionsBySession?.[sessionId]?.values?.turnOutline ?? list.byId[sessionId]?.projectionValues?.turnOutline
55
+ return Array.isArray(outline) ? outline.slice(0, 8).map(entry => entry.turn).join(',') : ''
56
+ })
57
+ const hiddenTurns = roster.exchangeTurns?.[sessionId]
58
+ const visit = useUi(state => state.visits?.[sessionId])
59
+ // The opening separator has no turn before it, so the transcript column draws it,
60
+ // unless the opening is unread: then the first trigger draws it under the divider.
61
+ const opening = useTurnClock((state) => {
62
+ const first = openingTurn(state[sessionId], firstTurns, hiddenTurns)
63
+ return first && !unseenSince(visit, first.start) ? clockLabel(first.start) : ''
64
+ })
65
+ useEffect(() => {
66
+ if (opening) document.body.style.setProperty('--bt-first-sep', JSON.stringify(opening))
67
+ else document.body.style.removeProperty('--bt-first-sep')
68
+ return () => document.body.style.removeProperty('--bt-first-sep')
69
+ }, [opening])
70
+ const running = useSessionStatus(map => map.get(sessionId)?.running === true)
71
+ const owner = roster.byId[sessionId]?.id ?? sessionId
72
+ const doing = useActivity(map => map[owner])
73
+ const bot = roster.byId[sessionId]
74
+ const room = roster.roomsById[sessionId]
75
+ const goal = room?.goal
76
+ const goalCompose = useUi(state => state.goalCompose === sessionId)
77
+ const goalOpen = useUi(state => state.goalOpen === sessionId)
78
+ // The island's live faces: who is at work in this group right now. Non-group
79
+ // chats subscribe to a constant so their header does not redraw on work.
80
+ const roomWorkKey = useWork?.(work => (room ? JSON.stringify(work.rooms?.[sessionId] ?? []) : '')) ?? '[]'
81
+ const roomWork = room ? JSON.parse(roomWorkKey || '[]') : []
82
+ const view = goal ? goalView(goal, { roster, roomWork }) : null
83
+ const goalShown = room !== undefined && !away && goal !== undefined
84
+ const detailsOpen = useUi(state => state.details === sessionId)
85
+ const name = bot?.name ?? room?.name ?? ''
86
+ const useNews = useMemoryNews ?? noNews
87
+ const memoryIds = bot ? [bot.id] : room ? [room.id, ...room.members] : []
88
+ const notice = useMemoryNotice(memoryIds, useNews)
89
+ // While the notice shows, the pill opens the memory that changed: a Bot's, or the group's.
90
+ const changed = useNews(map => latestMemoryChange(map, memoryIds))
91
+ // The composer placeholder is not localizable per Session, so CSS reads it from here.
92
+ useEffect(() => {
93
+ document.body.style.setProperty('--bt-placeholder', JSON.stringify(name ? t('Message {name}', { name }) : t('Message')))
94
+ }, [name, t('Message')])
95
+ // The unread label under a pending steering bubble reads its text from here.
96
+ useEffect(() => {
97
+ document.body.style.setProperty('--bt-steer-unread', JSON.stringify(t('Not read yet')))
98
+ }, [t('Not read yet')])
99
+ useEffect(() => {
100
+ actions.beginVisit(sessionId)
101
+ return () => actions.endVisit(sessionId)
102
+ }, [sessionId])
103
+ // Group chats have no voice mode, so the composer CSS needs to know the Session kind.
104
+ useEffect(() => {
105
+ if (room) document.body.dataset.btRoom = ''
106
+ else delete document.body.dataset.btRoom
107
+ return () => { delete document.body.dataset.btRoom }
108
+ }, [Boolean(room)])
109
+ const memoryNotice = !away && notice && changed !== null && roster.memory !== false
110
+ const showGoal = room !== undefined && !away && (goal !== undefined || goalCompose)
111
+ // The goal island pads the transcript so its pod does not cover the first message.
112
+ useEffect(() => {
113
+ if (showGoal) document.body.dataset.btGoal = ''
114
+ else delete document.body.dataset.btGoal
115
+ return () => { delete document.body.dataset.btGoal }
116
+ }, [showGoal])
117
+ const pill = h('button', {
118
+ type: 'button', className: 'bt-pill', 'aria-expanded': away ? undefined : detailsOpen, 'data-memory': memoryNotice ? 'updated' : undefined,
119
+ 'aria-label': away ? t('Back to chat') : memoryNotice ? t('Memory updated. Open {name}\'s memory', { name: roster.byId[changed]?.name ?? name }) : t('View conversation details'),
120
+ title: away ? name : undefined,
121
+ onClick: () => (away ? selectView('chat') : memoryNotice ? actions.openMemory(changed) : actions.toggleDetails(sessionId)),
122
+ },
123
+ bot ? h(BotAvatar, { bot, size: 24, state: botState(running, doing, roster.questions?.[currentPart(bot)] !== undefined), badge: false })
124
+ : room ? h(RoomAvatar, { room, roster, size: 24 }) : null,
125
+ goalShown
126
+ ? h('span', { className: 'bt-pill-swap' },
127
+ h('span', { className: 'bt-pill-goal' }, h('span', { className: 'bt-pill-ico', 'aria-hidden': true }, h(GoalIcon)), h('span', { className: 'bt-pill-title' }, view.title)),
128
+ h('span', { className: 'bt-pill-name' }, name))
129
+ : h('span', { className: 'bt-pill-name' }, away ? t('Back to chat') : bot?.name ?? room?.name ?? (title || t('New chat'))),
130
+ goalShown && view?.number !== undefined ? h('span', { className: 'bt-pill-count' }, `${view.number}/${view.count}`) : null,
131
+ h(MemoryNotice, { shown: memoryNotice }),
132
+ h('span', { className: 'bt-pill-go', 'aria-hidden': true }, h(GoArrow)))
133
+ return h('div', { className: 'bt-head' },
134
+ showGoal ? h(GoalIsland, { room, goal, view, open: goalOpen, composing: goalCompose, roomWork, roster, actions, pill }) : pill,
135
+ h('span', { className: 'bt-sr', role: 'status' }, memoryNotice ? t('Memory updated') : ''),
136
+ h(NewMessagePills, { key: sessionId, sessionId, useSessions, useUi, useTurnClock }),
137
+ h(ActivityIndicator, { key: `activity:${sessionId}`, sessionId, useRoster, useSessionStatus, useSession, useLive, useWork, actions }))
138
+ }
139
+
140
+ // New-message pills. "N new messages ↓" takes Back to bottom's place when
141
+ // replies land while the user is scrolled up; "N new messages ↑" points at an unread
142
+ // divider above the viewport until it has been seen. The shell shows Back to bottom
143
+ // only while unpinned, so its presence is the pinned signal.
144
+ function useTranscriptView() {
145
+ const [view, setView] = useState(null)
146
+ useEffect(() => {
147
+ let frame = 0
148
+ let watched = null
149
+ const observer = new MutationObserver(() => schedule())
150
+ const measure = () => {
151
+ frame = 0
152
+ const scroller = document.querySelector('[data-conversation-scroll]')
153
+ if (scroller !== watched) {
154
+ observer.disconnect()
155
+ watched = scroller
156
+ if (scroller) observer.observe(scroller, { childList: true, subtree: true })
157
+ }
158
+ if (!scroller) { setView(null); return }
159
+ const box = scroller.getBoundingClientRect()
160
+ const headerBottom = document.querySelector('header[class*="_header"]')?.getBoundingClientRect().bottom ?? box.top + 50
161
+ const sep = scroller.querySelector('.bt-new-sep')
162
+ let divider = null
163
+ if (sep) {
164
+ const at = sep.getBoundingClientRect()
165
+ divider = at.bottom <= headerBottom ? 'above' : at.top >= box.bottom ? 'below' : 'in-view'
166
+ }
167
+ const composer = parseFloat(getComputedStyle(scroller).getPropertyValue('--dsh-composer-height')) || 152
168
+ const next = {
169
+ unpinned: scroller.querySelector('[class*="_toBottomSlot"]>button') !== null,
170
+ divider,
171
+ center: Math.round(box.left + box.width / 2),
172
+ top: Math.round(headerBottom + 8),
173
+ bottom: Math.round(viewBottom() - box.bottom + composer + 8),
174
+ }
175
+ setView(prev => (prev && Object.keys(next).every(key => prev[key] === next[key]) ? prev : next))
176
+ }
177
+ const schedule = () => { if (!frame) frame = requestAnimationFrame(measure) }
178
+ document.addEventListener('scroll', schedule, true)
179
+ window.addEventListener('resize', schedule)
180
+ window.visualViewport?.addEventListener('resize', schedule)
181
+ window.visualViewport?.addEventListener('scroll', schedule)
182
+ schedule()
183
+ return () => {
184
+ cancelAnimationFrame(frame)
185
+ observer.disconnect()
186
+ document.removeEventListener('scroll', schedule, true)
187
+ window.removeEventListener('resize', schedule)
188
+ window.visualViewport?.removeEventListener('resize', schedule)
189
+ window.visualViewport?.removeEventListener('scroll', schedule)
190
+ }
191
+ }, [])
192
+ return view
193
+ }
194
+
195
+ function NewMessagePills({ sessionId, useSessions, useUi, useTurnClock }) {
196
+ const replies = useSessions((list) => {
197
+ const outline = list.projectionsBySession?.[sessionId]?.values?.turnOutline ?? list.byId[sessionId]?.projectionValues?.turnOutline
198
+ return Array.isArray(outline) ? outline.reduce((count, entry) => count + (entry.response ? 1 : 0), 0) : 0
199
+ })
200
+ const visit = useUi(state => state.visits?.[sessionId])
201
+ const unread = useTurnClock((state) => {
202
+ if (typeof visit?.seen !== 'number') return 0
203
+ let count = 0
204
+ for (const entry of Object.values(state[sessionId] ?? {})) {
205
+ if (entry.first !== undefined && entry.first > visit.seen && entry.first <= visit.entered) count += 1
206
+ }
207
+ return count
208
+ })
209
+ const view = useTranscriptView()
210
+ const unpinned = view?.unpinned === true
211
+ const [release, setRelease] = useState({ base: null, dismissed: false })
212
+ const [above, setAbove] = useState({ seen: false, dismissed: false })
213
+ useEffect(() => { setRelease({ base: unpinned ? replies : null, dismissed: false }) }, [unpinned])
214
+ useEffect(() => { if (view?.divider === 'in-view') setAbove(state => (state.seen ? state : { ...state, seen: true })) }, [view?.divider])
215
+ const fresh = release.base === null ? 0 : replies - release.base
216
+ const showDown = unpinned && fresh > 0 && !release.dismissed
217
+ const showUp = view?.divider === 'above' && !above.seen && !above.dismissed
218
+ useEffect(() => {
219
+ const scroller = document.querySelector('[data-conversation-scroll]')
220
+ if (!scroller) return undefined
221
+ scroller.toggleAttribute('data-bt-news', showDown)
222
+ return () => scroller.removeAttribute('data-bt-news')
223
+ }, [showDown])
224
+ if (!view || (!showDown && !showUp)) return null
225
+ const label = count => (count === 1 ? t('1 new message') : count > 0 ? t('{count} new messages', { count }) : t('New messages'))
226
+ const pill = (direction, count, style, onJump, onDismiss) => h('div', { className: 'bt-news', 'data-direction': direction, style },
227
+ h('button', { type: 'button', className: 'bt-news-jump', onClick: onJump },
228
+ h('span', { className: 'bt-news-ico' }, h(direction === 'up' ? ArrowUpIcon : ArrowDownIcon)), label(count)),
229
+ h('button', {
230
+ type: 'button', className: 'bt-news-x', 'aria-label': t('Dismiss new messages'),
231
+ onClick: (event) => { event.stopPropagation(); onDismiss() },
232
+ }, h(CloseIcon)))
233
+ const toLatest = () => {
234
+ const scroller = document.querySelector('[data-conversation-scroll]')
235
+ const back = scroller?.querySelector('[class*="_toBottomSlot"]>button')
236
+ if (back) back.click()
237
+ else scroller?.scrollTo({ top: scroller.scrollHeight, behavior: 'smooth' })
238
+ }
239
+ const toDivider = () => document.querySelector('[data-conversation-scroll] .bt-new-sep')?.scrollIntoView({ block: 'center', behavior: 'smooth' })
240
+ return createPortal(h(Fragment, null,
241
+ showUp ? pill('up', unread, { left: view.center, top: view.top }, toDivider, () => setAbove(state => ({ ...state, dismissed: true }))) : null,
242
+ showDown ? pill('down', fresh, { left: view.center, bottom: view.bottom }, toLatest, () => setRelease(state => ({ ...state, dismissed: true }))) : null),
243
+ document.body)
244
+ }
@@ -0,0 +1,202 @@
1
+ import { createElement as h, useId, useMemo, useRef, useState } from 'react'
2
+ import { useTurnFacts } from './btw.js'
3
+ import { editLines, lineDiff, proposalPlacement } from './learning-card-model.js'
4
+ import { currentPart } from './text.js'
5
+ import { t } from './i18n.js'
6
+ import { CheckIcon, MemoryIcon, PencilIcon } from './icons.js'
7
+ import { spring, SPRING_TAP } from './motion.js'
8
+
9
+ const failedAnswers = new Map()
10
+
11
+ function ProposalCard({ proposal, initialError = '', onFailure, onResolved, actions }) {
12
+ const [busy, setBusy] = useState(false)
13
+ const [error, setError] = useState(initialError)
14
+ const busyRef = useRef(false)
15
+ const titleId = useId()
16
+ const answer = approve => {
17
+ if (busyRef.current) return
18
+ busyRef.current = true
19
+ setBusy(true)
20
+ setError('')
21
+ void Promise.resolve().then(() => actions.learningAnswer(proposal.id, approve))
22
+ .then(() => onResolved(proposal.id), failure => {
23
+ const message = failure?.message ?? String(failure)
24
+ setError(message)
25
+ onFailure(proposal, message)
26
+ })
27
+ .finally(() => {
28
+ busyRef.current = false
29
+ setBusy(false)
30
+ })
31
+ }
32
+ const update = proposal.action === 'update'
33
+ const rows = useMemo(() => lineDiff(update ? proposal.previous?.content : '', proposal.content), [update, proposal.previous?.content, proposal.content])
34
+ if (proposal.kind === 'agents') {
35
+ const blocks = proposal.edits.map(edit => [
36
+ ...(edit.op === 'add' ? [] : editLines(edit.old)).map(line => ({ kind: 'removed', line })),
37
+ ...(edit.op === 'remove' ? [] : editLines(edit.text)).map(line => ({ kind: 'added', line })),
38
+ ])
39
+ return h('section', { className: 'bt-q bt-learning-card', role: 'group', 'aria-labelledby': titleId, 'aria-busy': busy },
40
+ h(ProposalHead, { titleId, icon: PencilIcon, title: t('AGENTS.md update') }),
41
+ h(FileBox, { name: 'AGENTS.md', path: proposal.folder, state: proposal.exists === false ? 'Added' : 'Modified', blocks }),
42
+ proposal.edits.map((edit, index) => edit.why ? h('p', { className: 'bt-learning-why', key: index }, edit.why) : null),
43
+ error ? h('div', { className: 'bt-learning-error', role: 'alert' }, error) : null,
44
+ h(ProposalFoot, { busy, answer }))
45
+ }
46
+ const oldDetail = update && typeof proposal.previous?.description === 'string' && proposal.previous.description !== proposal.description ? proposal.previous.description : undefined
47
+ return h('section', { className: 'bt-q bt-learning-card', role: 'group', 'aria-labelledby': titleId, 'aria-busy': busy },
48
+ h(ProposalHead, { titleId, title: proposal.name, kind: t(update ? 'Skill update' : 'New skill'), detail: proposal.description, oldDetail }),
49
+ h(FileBox, { name: 'SKILL.md', path: `skills/${proposal.name}`, state: update ? 'Updated' : 'Added', blocks: [rows] }),
50
+ proposal.why ? h('p', { className: 'bt-learning-why' }, proposal.why) : null,
51
+ error ? h('div', { className: 'bt-learning-error', role: 'alert' }, error) : null,
52
+ h(ProposalFoot, { busy, answer }))
53
+ }
54
+
55
+ function ProposalHead({ titleId, icon = MemoryIcon, title, kind, detail, oldDetail }) {
56
+ return h('div', { className: 'bt-q-head' },
57
+ h('span', { className: 'bt-learning-icon', 'aria-hidden': true }, h(icon)),
58
+ h('div', { className: 'bt-q-text' },
59
+ h('p', { className: 'bt-q-title', id: titleId }, title),
60
+ kind ? h('div', { className: 'bt-q-detail' }, kind) : null,
61
+ oldDetail ? h('p', { className: 'bt-learning-description bt-learning-old' }, h('del', null, oldDetail)) : null,
62
+ detail ? h('p', { className: 'bt-learning-description' }, detail) : null),
63
+ h('span', { className: 'bt-q-badge bt-learning-status' }, h('span', { className: 'bt-learning-dot', 'aria-hidden': true }), t('Awaiting approval')))
64
+ }
65
+
66
+ function ProposalFoot({ busy, answer }) {
67
+ return h('div', { className: 'bt-q-foot bt-learning-foot' },
68
+ h('button', { type: 'button', className: 'bt-learning-btn bt-learning-decline', disabled: busy, onClick: () => answer(false) }, t('Decline')),
69
+ h('button', { type: 'button', className: 'bt-learning-btn bt-learning-approve', disabled: busy, onClick: () => answer(true) },
70
+ h(CheckIcon), t('Approve')))
71
+ }
72
+
73
+ const COLLAPSE_ROWS = 8
74
+ const SIGNS = { added: '+', removed: '\u2212', same: '' }
75
+
76
+ function FileBox({ name, path, state, blocks }) {
77
+ const [open, setOpen] = useState(false)
78
+ const all = blocks.flat()
79
+ const added = all.filter(row => row.kind === 'added').length
80
+ const removed = all.filter(row => row.kind === 'removed').length
81
+ const folds = all.length > COLLAPSE_ROWS
82
+ const shut = folds && !open
83
+ return h('div', { className: 'bt-learning-file' },
84
+ h('div', { className: 'bt-learning-filehead' },
85
+ h('span', { className: 'bt-learning-name' }, name),
86
+ path ? h('span', { className: 'bt-learning-path' }, path) : null,
87
+ h('span', { className: 'bt-learning-spacer' }),
88
+ added > 0 ? h('span', { className: 'bt-learning-plus' }, `+${added}`) : null,
89
+ removed > 0 ? h('span', { className: 'bt-learning-minus' }, `\u2212${removed}`) : null,
90
+ h('span', { className: `bt-learning-state bt-learning-state-${state.toLowerCase()}` }, t(state))),
91
+ h('div', { className: shut ? 'bt-learning-code bt-learning-shut' : 'bt-learning-code' },
92
+ blocks.map((rows, blockIndex) => [
93
+ blockIndex > 0 ? h('div', { className: 'bt-learning-gap', key: `gap-${blockIndex}` }) : null,
94
+ ...rows.map((row, rowIndex) => h('div', { className: `bt-learning-row bt-learning-${row.kind}`, key: `${blockIndex}-${rowIndex}` },
95
+ h('span', { className: 'bt-learning-num', 'aria-hidden': true }, row.number ?? ''),
96
+ h('span', { className: 'bt-learning-sign', 'aria-hidden': true }, SIGNS[row.kind]),
97
+ h('span', { className: 'bt-learning-text' }, row.line || '\u00a0'))),
98
+ ])),
99
+ folds ? h('div', { className: shut ? 'bt-learning-morebar bt-learning-morebar-shut' : 'bt-learning-morebar' },
100
+ h('button', { type: 'button', className: 'bt-learning-more', 'aria-expanded': open, onClick: () => setOpen(!open) },
101
+ t(open ? 'Show less' : 'Show all {count} lines', { count: all.length }))) : null)
102
+ }
103
+
104
+ function LearningProposals({ turn, dock, sessionId, useRoster, actions }) {
105
+ const roster = useRoster(value => value)
106
+ const facts = useTurnFacts(sessionId, actions)
107
+ const [failed, setFailed] = useState({})
108
+ const bot = roster.byId?.[sessionId]
109
+ if (!bot || currentPart(bot) !== sessionId) return null
110
+ const proposals = roster.learning?.[bot.id] ?? []
111
+ const visible = [...proposals]
112
+ for (const entry of Object.values(failed)) {
113
+ if (entry.proposal.botId === bot.id && !proposals.some(proposal => proposal.id === entry.proposal.id)) visible.push(entry.proposal)
114
+ }
115
+ for (const entry of failedAnswers.values()) {
116
+ if (entry.proposal.botId === bot.id && !proposals.some(proposal => proposal.id === entry.proposal.id)
117
+ && !visible.some(proposal => proposal.id === entry.proposal.id)) visible.push(entry.proposal)
118
+ }
119
+ const cards = visible.filter(proposal => {
120
+ const placement = proposalPlacement(proposal, facts?.turns)
121
+ return dock ? placement.kind === 'dock' : placement.kind === 'turn' && placement.turn === turn?.turn
122
+ })
123
+ if (cards.length === 0) return null
124
+ const onFailure = (proposal, error) => {
125
+ failedAnswers.set(proposal.id, { proposal, error })
126
+ setFailed(current => ({ ...current, [proposal.id]: { proposal, error } }))
127
+ }
128
+ const onResolved = id => setFailed(current => {
129
+ failedAnswers.delete(id)
130
+ if (!(id in current)) return current
131
+ const next = { ...current }
132
+ delete next[id]
133
+ return next
134
+ })
135
+ return h('div', { className: dock ? 'bt-learning-stack bt-learning-dock' : 'bt-learning-stack' }, cards.map(proposal => h('div', { className: 'bt-q-inline', key: proposal.id },
136
+ h(ProposalCard, {
137
+ proposal,
138
+ initialError: failed[proposal.id]?.error ?? failedAnswers.get(proposal.id)?.error,
139
+ onFailure,
140
+ onResolved,
141
+ actions,
142
+ }))))
143
+ }
144
+
145
+ export function LearningTurnTail(props) {
146
+ return h(LearningProposals, { ...props, dock: false })
147
+ }
148
+
149
+ export function LearningDock(props) {
150
+ return h(LearningProposals, { ...props, dock: true })
151
+ }
152
+
153
+ export const LEARNING_CSS = `
154
+ .bt-learning-stack{display:flex;flex-direction:column;gap:4px}
155
+ .bt-learning-dock{width:100%;max-width:var(--dsh-chat-content-width,748px);margin:0 auto;box-sizing:border-box}
156
+ .bt-learning-card .bt-q-head{gap:12px}
157
+ .bt-learning-icon{flex:none;display:inline-flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:10px;background:var(--bt-hover);color:var(--bt-ink-2)}
158
+ .bt-learning-icon svg{width:18px;height:18px}
159
+ .bt-learning-description{margin:6px 0 0;font-size:13px;line-height:19px;color:var(--bt-ink)}
160
+ .bt-learning-old{color:var(--bt-ink-3)}
161
+ .bt-learning-status{display:inline-flex;align-items:center;gap:6px;border-style:solid;border-color:color-mix(in srgb,var(--bt-star) 35%,transparent);color:color-mix(in srgb,var(--bt-star) 80%,var(--bt-ink))}
162
+ .bt-learning-dot{width:6px;height:6px;border-radius:50%;background:var(--bt-star)}
163
+ .bt-learning-file{border-radius:12px;overflow:hidden;background:color-mix(in srgb,var(--bt-ink) 2.5%,var(--bt-card));box-shadow:inset 0 0 0 1px var(--bt-q-rule)}
164
+ .bt-learning-filehead{display:flex;align-items:center;gap:8px;min-width:0;padding:8px 12px;box-shadow:inset 0 -1px 0 var(--bt-q-rule-2);font-size:12px;line-height:18px}
165
+ .bt-learning-name{flex:none;font-weight:600;color:var(--bt-ink)}
166
+ .bt-learning-path{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--bt-ink-3)}
167
+ .bt-learning-spacer{flex:1}
168
+ .bt-learning-plus,.bt-learning-minus{flex:none;font-weight:600;font-variant-numeric:tabular-nums}
169
+ .bt-learning-plus{color:var(--bt-green)}
170
+ .bt-learning-minus{color:var(--bt-danger)}
171
+ .bt-learning-state{flex:none;height:20px;padding:0 8px;border-radius:999px;font-size:11px;line-height:20px;font-weight:500;background:var(--bt-hover);color:var(--bt-ink-2)}
172
+ .bt-learning-state-added{background:color-mix(in srgb,var(--bt-green) 13%,transparent);color:color-mix(in srgb,var(--bt-green) 70%,var(--bt-ink))}
173
+ .bt-learning-code{padding:6px 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:20px;color:var(--bt-ink)}
174
+ .bt-learning-shut{max-height:172px;overflow:hidden}
175
+ .bt-learning-row{display:grid;grid-template-columns:36px 16px 1fr}
176
+ .bt-learning-num{padding-right:6px;text-align:right;color:var(--bt-ink-3);font-variant-numeric:tabular-nums;user-select:none}
177
+ .bt-learning-sign{text-align:center;user-select:none}
178
+ .bt-learning-text{min-width:0;padding-right:12px;white-space:pre-wrap;overflow-wrap:anywhere}
179
+ .bt-learning-added{background:color-mix(in srgb,var(--bt-green) 9%,transparent)}
180
+ .bt-learning-added .bt-learning-sign{color:var(--bt-green)}
181
+ .bt-learning-removed{background:color-mix(in srgb,var(--bt-danger) 7%,transparent)}
182
+ .bt-learning-removed .bt-learning-sign{color:var(--bt-danger)}
183
+ .bt-learning-removed .bt-learning-text{color:var(--bt-ink-2)}
184
+ .bt-learning-gap{height:1px;margin:6px 12px;background:var(--bt-q-rule)}
185
+ .bt-learning-morebar{display:flex;justify-content:center;padding:4px 0 8px}
186
+ .bt-learning-morebar-shut{position:relative;margin-top:-36px;padding-top:28px;background:linear-gradient(transparent,color-mix(in srgb,var(--bt-ink) 2.5%,var(--bt-card)) 60%)}
187
+ .bt-learning-more{height:24px;padding:0 10px;border:0;border-radius:999px;background:var(--bt-card);box-shadow:inset 0 0 0 1px var(--bt-q-edge);font:inherit;font-size:12px;color:var(--bt-ink-2);cursor:pointer;transition:background-color .12s ease,color .12s ease}
188
+ .bt-learning-more:hover{background:var(--bt-hover);color:var(--bt-ink)}
189
+ .bt-learning-more:focus-visible{outline:2px solid var(--bt-accent);outline-offset:2px}
190
+ .bt-learning-why{margin:0;font-size:12px;line-height:17px;color:var(--bt-ink-2)}
191
+ .bt-learning-btn{flex:none;display:inline-flex;align-items:center;gap:6px;height:34px;padding:0 16px;border:0;border-radius:999px;font:inherit;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .12s ease,opacity .12s ease,${spring(SPRING_TAP, 'scale')}}
192
+ .bt-learning-btn svg{width:15px;height:15px}
193
+ .bt-learning-decline{background:transparent;box-shadow:inset 0 0 0 1px var(--bt-line-2);color:var(--bt-ink)}
194
+ .bt-learning-decline:hover:not(:disabled){background:var(--bt-hover)}
195
+ .bt-learning-approve{padding-left:13px;background:var(--bt-accent);color:var(--bt-accent-ink)}
196
+ .bt-learning-approve:hover:not(:disabled){opacity:.86}
197
+ .bt-learning-btn:disabled{opacity:.45;cursor:default}
198
+ .bt-learning-btn:active:not(:disabled){scale:.96}
199
+ .bt-learning-btn:focus-visible{outline:2px solid var(--bt-accent);outline-offset:2px}
200
+ @media (prefers-reduced-motion:reduce){.bt-learning-btn{transition:background-color .12s ease,opacity .12s ease}.bt-learning-btn:active:not(:disabled){scale:none}}
201
+ .bt-learning-error{font-size:12px;line-height:16px;color:var(--bt-danger)}
202
+ `