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