@ljwei-stak/dsh-model-route-bot 0.66.4 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh-plugin/bot-product.mjs +81 -5
- package/.dsh-plugin/client.js +17490 -1791
- package/README.i18n.yaml +4 -4
- package/README.md +409 -364
- package/README.zh.md +409 -364
- package/apps/integration-client/README.md +31 -7
- package/apps/integration-client/api.mjs +14 -3
- package/apps/integration-client/binding-editor.jsx +308 -109
- package/apps/integration-client/binding-model.mjs +262 -0
- package/apps/integration-client/binding.css +144 -0
- package/apps/integration-client/bot-model-status.mjs +18 -0
- package/apps/integration-client/bot-profile-identity.jsx +197 -0
- package/apps/integration-client/bot-profile.css +57 -0
- package/apps/integration-client/bot-profile.mjs +97 -0
- package/apps/integration-client/bot-settings-dialog.css +18 -0
- package/apps/integration-client/bot-settings-dialog.jsx +111 -0
- package/apps/integration-client/bot-thread-header-bridge.css +20 -0
- package/apps/integration-client/bot-thread-header.jsx +278 -0
- package/apps/integration-client/bot-threads-panel.jsx +378 -0
- package/apps/integration-client/bot-threads.css +169 -0
- package/apps/integration-client/chat-design.mjs +250 -0
- package/apps/integration-client/chat-ui.jsx +175 -0
- package/apps/integration-client/chat.css +477 -0
- package/apps/integration-client/composer-draft-binding.mjs +50 -0
- package/apps/integration-client/composer-draft-bound.jsx +14 -0
- package/apps/integration-client/composer-draft-host.jsx +142 -0
- package/apps/integration-client/composer-draft-session.jsx +287 -0
- package/apps/integration-client/composer-draft-witness.mjs +115 -0
- package/apps/integration-client/composer-draft.css +4 -0
- package/apps/integration-client/composer-draft.mjs +322 -0
- package/apps/integration-client/contact-attention.jsx +54 -0
- package/apps/integration-client/data-panel.jsx +439 -0
- package/apps/integration-client/data.css +69 -0
- package/apps/integration-client/desktop-bridge.mjs +1 -0
- package/apps/integration-client/desktop-http-socket.mjs +49 -0
- package/apps/integration-client/desktop-keyboard.mjs +32 -0
- package/apps/integration-client/desktop-pane.jsx +99 -30
- package/apps/integration-client/desktop-stream.mjs +13 -3
- package/apps/integration-client/desktop-tools.jsx +19 -0
- package/apps/integration-client/desktop-touch.mjs +27 -0
- package/apps/integration-client/desktop.css +41 -0
- package/apps/integration-client/independent-chat-controller.mjs +224 -0
- package/apps/integration-client/independent-chat-entry.jsx +61 -0
- package/apps/integration-client/independent-chat-navigation.mjs +21 -0
- package/apps/integration-client/independent-chat-page.jsx +148 -0
- package/apps/integration-client/independent-chat-rich.jsx +64 -0
- package/apps/integration-client/independent-chat-rich.mjs +30 -0
- package/apps/integration-client/independent-chat.css +73 -0
- package/apps/integration-client/index.jsx +24 -16
- package/apps/integration-client/library-panel.jsx +4 -1
- package/apps/integration-client/licenses/jsqr-1.4.0-LICENSE.txt +202 -0
- package/apps/integration-client/main-route-editor.jsx +94 -0
- package/apps/integration-client/main-route-model.mjs +100 -0
- package/apps/integration-client/model-picker.css +37 -0
- package/apps/integration-client/model-picker.jsx +83 -0
- package/apps/integration-client/native-activity.css +141 -0
- package/apps/integration-client/native-activity.jsx +276 -0
- package/apps/integration-client/native-activity.mjs +384 -0
- package/apps/integration-client/notifications-panel.jsx +186 -0
- package/apps/integration-client/notifications.css +126 -0
- package/apps/integration-client/pairing-qr.jsx +79 -0
- package/apps/integration-client/panels.jsx +126 -115
- package/apps/integration-client/product-slots.jsx +46 -0
- package/apps/integration-client/routine-recorder.mjs +23 -2
- package/apps/integration-client/secure-pairing.jsx +84 -0
- package/apps/integration-client/tasks-panel.jsx +32 -10
- package/apps/integration-client/ui-icons.jsx +17 -0
- package/apps/integration-client/usage-explorer.jsx +43 -0
- package/apps/integration-client/usage-report.mjs +71 -0
- package/apps/integration-client/usage.css +34 -0
- package/apps/integration-client/web-panel-model.mjs +140 -0
- package/apps/integration-client/web-panel-page.jsx +221 -0
- package/apps/integration-client/web-panel-pins-live.jsx +28 -0
- package/apps/integration-client/web-panel.css +139 -0
- package/apps/integration-client/workspace-model.mjs +67 -0
- package/apps/integration-client/workspace-navigation.mjs +56 -0
- package/apps/integration-client/workspace-ui.jsx +173 -0
- package/apps/integration-client/workspace.css +332 -0
- package/modules/bot-runtime/SOURCE.json +176 -50
- package/modules/bot-runtime/index.mjs +169 -13
- package/modules/bot-runtime/product-overlay/OVERLAY.json +128 -2
- package/modules/bot-runtime/product-overlay/base/src/client/header.js +244 -0
- package/modules/bot-runtime/product-overlay/base/src/client/learning-card.js +202 -0
- package/modules/bot-runtime/product-overlay/base/src/client/model-menu.js +371 -0
- package/modules/bot-runtime/product-overlay/base/src/client/secret-card.js +363 -0
- package/modules/bot-runtime/product-overlay/base/src/client/sources.js +68 -0
- package/modules/bot-runtime/product-overlay/base/src/client/turn-error.js +260 -0
- package/modules/bot-runtime/product-overlay/base/src/host/channel.js +647 -0
- package/modules/bot-runtime/product-overlay/base/src/host/memory.js +2050 -0
- package/modules/bot-runtime/product-overlay/base/src/host/models.js +314 -0
- package/modules/bot-runtime/product-overlay/base/src/host/parts.js +1473 -0
- package/modules/bot-runtime/product-overlay/base/src/host/privacy.js +368 -0
- package/modules/bot-runtime/product-overlay/base/src/host/roster.js +776 -0
- package/modules/bot-runtime/product-overlay/base/src/host/store.js +512 -0
- package/modules/bot-runtime/product-overlay/base/src/host/usage.js +653 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/details-drawer.js.patch +48 -2
- package/modules/bot-runtime/product-overlay/patches/src/client/header.js.patch +41 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/index.js.patch +33 -1
- package/modules/bot-runtime/product-overlay/patches/src/client/learning-card.js.patch +20 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/model-menu.js.patch +24 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/secret-card.js.patch +25 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/sources.js.patch +52 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/turn-error.js.patch +23 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/channel.js.patch +46 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/memory.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/models.js.patch +16 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/parts.js.patch +95 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/privacy.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/roster.js.patch +145 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/store.js.patch +68 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/usage.js.patch +11 -0
- package/modules/bot-runtime/read-model.mjs +19 -4
- package/modules/bot-runtime/vendor/ds-bot/src/client/details-drawer.js +15 -5
- package/modules/bot-runtime/vendor/ds-bot/src/client/header.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/index.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/learning-card.js +2 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/model-menu.js +5 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/secret-card.js +3 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/sources.js +25 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/turn-error.js +3 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/channel.js +14 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/memory.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/models.js +7 -0
- package/modules/bot-runtime/vendor/ds-bot/src/host/parts.js +26 -9
- package/modules/bot-runtime/vendor/ds-bot/src/host/privacy.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/roster.js +112 -3
- package/modules/bot-runtime/vendor/ds-bot/src/host/store.js +34 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/usage.js +1 -1
- package/modules/independent-runtime/index.mjs +152 -62
- package/modules/independent-runtime/remote-tools.mjs +8 -5
- package/modules/openmaus-protocol/NOTICE +10 -0
- package/modules/openmaus-protocol/README.md +15 -0
- package/modules/openmaus-protocol/SOURCE.json +68 -0
- package/modules/openmaus-protocol/dist/protocol.mjs +1203 -0
- package/modules/openmaus-protocol/index.mjs +1 -0
- package/modules/openmaus-protocol/transport.mjs +77 -0
- package/modules/openmaus-protocol/vendor/LICENSE +202 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/README.md +405 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/credentials.ts +128 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/descriptor.ts +84 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/noise.ts +456 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/phone.ts +104 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/session.ts +417 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.json +2565 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.sha256 +1 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/yamux.ts +335 -0
- package/package.json +18 -6
- package/packages/bot-acp/README.md +156 -0
- package/packages/bot-acp/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-acp/licenses/NOTICE.md +27 -0
- package/packages/bot-acp/src/acp-adapter.mjs +795 -0
- package/packages/bot-acp/src/acp-host.mjs +148 -0
- package/packages/bot-acp/src/native-launch.mjs +121 -0
- package/packages/bot-acp/src/permission-policy.mjs +283 -0
- package/packages/bot-acp/src/rpc-codec.mjs +265 -0
- package/packages/bot-acp/src/session-core.mjs +581 -0
- package/packages/bot-acp/src/stdio-rpc.mjs +329 -0
- package/packages/bot-engines/README.md +281 -0
- package/packages/bot-engines/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-engines/licenses/NOTICE.md +32 -0
- package/packages/bot-engines/src/claude-engine.mjs +772 -0
- package/packages/bot-engines/src/claude-llm-adapter.mjs +797 -0
- package/packages/bot-engines/src/claude-llm-turn.mjs +292 -0
- package/packages/bot-engines/src/claude-permission-broker.mjs +130 -0
- package/packages/bot-engines/src/claude-permission-proxy.mjs +87 -0
- package/packages/bot-engines/src/claude-protocol.mjs +177 -0
- package/packages/bot-engines/src/driver-catalog.mjs +334 -0
- package/packages/bot-engines/src/host-loop-request.mjs +95 -0
- package/packages/bot-engines/src/host-registrations.mjs +105 -0
- package/packages/capability-broker/src/cli-bootstrap.mjs +86 -16
- package/packages/capability-broker/src/cli-bridge-response.mjs +80 -0
- package/packages/capability-broker/src/cli-stdio-bridge.mjs +21 -8
- package/packages/cli-runtime/src/codex-session-core.mjs +4 -0
- package/packages/cli-runtime/src/native-cli-output.mjs +57 -0
- package/packages/cli-runtime/src/native-peer.ps1 +51 -3
- package/packages/cli-runtime/src/native-process.mjs +77 -14
- package/packages/model-router-plugin/package.json +1 -1
- package/packages/orchestration/src/binding-product.mjs +19 -0
- package/packages/orchestration/src/bot-data-actions.mjs +45 -0
- package/packages/orchestration/src/bot-data-chart.mjs +109 -0
- package/packages/orchestration/src/bot-data-context.mjs +44 -0
- package/packages/orchestration/src/bot-data-engine.mjs +809 -0
- package/packages/orchestration/src/bot-data-io.mjs +186 -0
- package/packages/orchestration/src/bot-data-sql.mjs +195 -0
- package/packages/orchestration/src/bot-data-surface.mjs +144 -0
- package/packages/orchestration/src/bot-data-tools.mjs +68 -0
- package/packages/orchestration/src/bot-data-worker.mjs +228 -0
- package/packages/orchestration/src/bot-independent-execution.mjs +195 -0
- package/packages/orchestration/src/bot-independent-stream.mjs +74 -0
- package/packages/orchestration/src/bot-loop.mjs +152 -19
- package/packages/orchestration/src/bot-notifications.mjs +159 -0
- package/packages/orchestration/src/bot-policy.mjs +43 -4
- package/packages/orchestration/src/bot-profile.mjs +171 -0
- package/packages/orchestration/src/bot-routine.mjs +4 -4
- package/packages/orchestration/src/bot-runtime-assembly.mjs +255 -8
- package/packages/orchestration/src/bot-runtime-switch.mjs +13 -2
- package/packages/orchestration/src/bot-serving.mjs +164 -0
- package/packages/orchestration/src/bot-session-store.mjs +105 -9
- package/packages/orchestration/src/bot-settings.mjs +50 -11
- package/packages/orchestration/src/bot-thread-admission.mjs +84 -0
- package/packages/orchestration/src/bot-thread-host.mjs +187 -0
- package/packages/orchestration/src/bot-threads.mjs +790 -0
- package/packages/orchestration/src/data-product-access.mjs +37 -0
- package/packages/orchestration/src/data-tool-contract.mjs +291 -0
- package/packages/orchestration/src/desktop-http-carrier.mjs +120 -0
- package/packages/orchestration/src/desktop-proxy.mjs +311 -0
- package/packages/orchestration/src/desktop-sessions.mjs +43 -9
- package/packages/orchestration/src/ds-lifecycle.mjs +31 -4
- package/packages/orchestration/src/grant-refresh-queue.mjs +17 -0
- package/packages/orchestration/src/independent-chat-page.mjs +39 -0
- package/packages/orchestration/src/independent-chat-service.mjs +308 -0
- package/packages/orchestration/src/independent-context-state.mjs +44 -0
- package/packages/orchestration/src/independent-message-wire.mjs +84 -0
- package/packages/orchestration/src/independent-tool-catalog.mjs +175 -0
- package/packages/orchestration/src/independent-tool-families.mjs +73 -0
- package/packages/orchestration/src/independent-tool-host.mjs +344 -0
- package/packages/orchestration/src/main-bot-grant.mjs +15 -0
- package/packages/orchestration/src/main-model-routes.mjs +208 -0
- package/packages/orchestration/src/model-accounting.mjs +99 -35
- package/packages/orchestration/src/model-auxiliary-dispatch.mjs +187 -0
- package/packages/orchestration/src/model-loop-frame.mjs +179 -0
- package/packages/orchestration/src/model-prepared-generation.mjs +175 -0
- package/packages/orchestration/src/model-request-bounds.mjs +111 -0
- package/packages/orchestration/src/native-activity-contract.mjs +491 -0
- package/packages/orchestration/src/native-activity-service.mjs +102 -0
- package/packages/orchestration/src/native-activity-store.mjs +249 -0
- package/packages/orchestration/src/native-conversation-tools.mjs +78 -0
- package/packages/orchestration/src/native-data-tools.mjs +100 -0
- package/packages/orchestration/src/native-remote-actions.mjs +742 -0
- package/packages/orchestration/src/native-team-tools.mjs +73 -0
- package/packages/orchestration/src/pairing-invite.mjs +37 -0
- package/packages/orchestration/src/product-composition.mjs +1559 -1001
- package/packages/orchestration/src/remote-machines.mjs +82 -14
- package/packages/orchestration/src/routine-execution.mjs +5 -3
- package/packages/orchestration/src/routine-session.mjs +19 -13
- package/packages/orchestration/src/secure-pairing-host.mjs +112 -0
- package/packages/orchestration/src/task-journal.mjs +29 -0
- package/packages/orchestration/src/task-product.mjs +17 -0
- package/packages/orchestration/src/team-assignment-store.mjs +120 -0
- package/packages/orchestration/src/team-bridge.mjs +282 -0
- package/packages/orchestration/src/team-scheduler.mjs +83 -0
- package/packages/orchestration/src/team-sdk-tools.mjs +84 -0
- package/packages/orchestration/src/team-tool-contract.mjs +284 -0
- package/packages/orchestration/src/ui-host-namespace.mjs +23 -0
- package/packages/orchestration/src/web-panel-actions.mjs +103 -0
- package/packages/orchestration/src/web-panel-framing.mjs +227 -0
- package/packages/orchestration/src/web-panel-store.mjs +206 -0
- package/remote-computer-agent/README.md +39 -29
- package/remote-computer-agent/build.mjs +3 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_LICENSE +202 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_NOTICE +10 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_SOURCE.json +68 -0
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs +215 -25
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs.map +4 -4
- package/remote-computer-agent/dist/cli.mjs +1688 -168
- package/remote-computer-agent/dist/cli.mjs.map +4 -4
- package/remote-computer-agent/dist/index.mjs +1579 -122
- package/remote-computer-agent/dist/index.mjs.map +4 -4
- package/remote-computer-agent/dist/native-peer.ps1 +51 -3
- package/remote-computer-agent/package-lock.json +308 -2
- package/remote-computer-agent/package.json +2 -1
- package/remote-computer-agent/pair-device.ps1 +20 -0
- package/remote-computer-agent/setup.ps1 +38 -16
- package/remote-computer-agent/src/cli.ts +24 -3
- package/remote-computer-agent/src/desktop-session.ts +11 -3
- package/remote-computer-agent/src/e2e-server.ts +81 -0
- package/remote-computer-agent/src/pairing-page.ts +35 -0
- package/remote-computer-agent/src/protocol.ts +25 -25
- package/remote-computer-agent/src/secure-pairing.ts +105 -0
- package/remote-computer-agent/src/store.ts +20 -5
- package/remote-computer-agent/src/transport.ts +39 -15
|
@@ -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": "
|
|
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
|
+
`
|