@ljwei-stak/dsh-model-route-bot 0.66.3 → 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 +20 -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/README.md +3 -3
- 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,647 @@
|
|
|
1
|
+
// Client channel: the team view the browser renders, and the POST /api/bot endpoints.
|
|
2
|
+
import { randomUUID } from 'node:crypto'
|
|
3
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm'
|
|
4
|
+
import { install as installBtw } from './btw.js'
|
|
5
|
+
import { install as installSide } from './side.js'
|
|
6
|
+
import { handoffReply, questionReply, viewReply } from './parts.js'
|
|
7
|
+
import { validateViewAnswer } from 'deep-view/core'
|
|
8
|
+
import { COLORS, EXTRA_COLORS, ROUTE } from './constants.js'
|
|
9
|
+
|
|
10
|
+
const LANGUAGES = { zh: 'Chinese (简体中文)', en: 'English' }
|
|
11
|
+
const RUN_STATS = ['sessionStats', 'tokenUsage', 'contextPressure']
|
|
12
|
+
|
|
13
|
+
const invalid = message => Object.assign(new Error(message), { code: 'bot/invalid' })
|
|
14
|
+
// Programming and file system errors are DS Bot's own faults, logged with their stack;
|
|
15
|
+
// any other error is a refusal whose message is meant for the user.
|
|
16
|
+
const isInternal = error => error instanceof TypeError || error instanceof ReferenceError || error instanceof RangeError
|
|
17
|
+
|| error instanceof SyntaxError || (typeof error?.code === 'string' && /^E[A-Z_]+$/.test(error.code))
|
|
18
|
+
export function errorCode(error) {
|
|
19
|
+
if (typeof error?.code === 'string' && error.code.startsWith('bot/')) return error.code
|
|
20
|
+
return isInternal(error) ? 'bot/internal' : 'bot/refused'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function install(rt) {
|
|
24
|
+
installBtw(rt)
|
|
25
|
+
installSide(rt)
|
|
26
|
+
const { ctx, state } = rt
|
|
27
|
+
|
|
28
|
+
// The DSH interface language of the last browser that asked for the team.
|
|
29
|
+
let uiLocale
|
|
30
|
+
rt.uiLanguage = () => (uiLocale === undefined ? undefined : LANGUAGES[uiLocale.split('-')[0].toLowerCase()] ?? uiLocale)
|
|
31
|
+
|
|
32
|
+
const ok = value => ({ ok: true, value })
|
|
33
|
+
const fail = (message, code = 'bot/error') => ({ ok: false, error: { code, message, details: {} } })
|
|
34
|
+
// Models come from DSH, not state.json, so a provider added in Harness settings
|
|
35
|
+
// changes the view without a new revision.
|
|
36
|
+
const modelsKey = () => `${rt.cachedCatalog().map(entry => `${entry.ref}${entry.needsKey ? '!' : ''}${entry.reasoning ? `:${JSON.stringify(entry.reasoning)}` : ''}`).join(',')}|${rt.cachedDefault() ?? ''}`
|
|
37
|
+
const view = () => ({
|
|
38
|
+
revision: state.revision,
|
|
39
|
+
workspaceId: state.workspaceId ?? null,
|
|
40
|
+
// `mainBotId` is the first Main Bot, which the sidebar shows as its tile.
|
|
41
|
+
mainBotId: state.mainBotIds[0] ?? null,
|
|
42
|
+
mainBotIds: state.mainBotIds,
|
|
43
|
+
// `sessionId` is the Bot's current own-chat part; `parts` its earlier ones.
|
|
44
|
+
// `waiting`: the Bot holds its greeting until the user picks its model or fixes
|
|
45
|
+
// what failed it. `blocked`: the facts of a failure only the user can fix (turns.js).
|
|
46
|
+
bots: Object.values(state.bots).map(({ segments, kickoff, ...bot }) => ({
|
|
47
|
+
...bot, sessionId: rt.chatOf(bot.id), parts: (segments ?? []).map(segment => segment.sessionId), ...(kickoff ? { waiting: true } : {}),
|
|
48
|
+
})),
|
|
49
|
+
// `sessionId` is the group's own Session's current part, `ownParts` its earlier
|
|
50
|
+
// ones; `parts` the members' hidden group Sessions and their earlier segments,
|
|
51
|
+
// for usage.
|
|
52
|
+
rooms: Object.values(state.rooms).map(({ log, seen, paused, sessions, segments, parts: ownParts, ...room }) => {
|
|
53
|
+
const parts = [...Object.values(sessions ?? {}), ...Object.values(segments ?? {}).flat().map(segment => segment.sessionId), ...(ownParts ?? []).map(segment => segment.sessionId)]
|
|
54
|
+
const last = (log ?? []).filter(line => line.kind === undefined).at(-1)
|
|
55
|
+
return { ...room, sessionId: rt.sessionOfRoom(room), ownParts: (ownParts ?? []).map(segment => segment.sessionId), parts, ...(last ? { preview: last.who === 'User' ? last.text : `${last.who}:${last.text}` } : {}) }
|
|
56
|
+
}),
|
|
57
|
+
// Deleted private Bots whose folder, memory and chats stay closed to the others.
|
|
58
|
+
privateArchives: rt.archivesView(),
|
|
59
|
+
exchangeTurns: state.exchangeTurns,
|
|
60
|
+
questions: state.questions,
|
|
61
|
+
views: state.views ?? {},
|
|
62
|
+
// Aside answers of each Bot's current part, newest first. Not part of the
|
|
63
|
+
// model-visible history; earlier parts send their cards on their own pages.
|
|
64
|
+
btwCards: rt.btwView?.() ?? {},
|
|
65
|
+
// Each Bot's side chats, in creation order. The answers never join the
|
|
66
|
+
// model-visible history; the side panel reads them here.
|
|
67
|
+
sideChats: rt.sideView?.() ?? {},
|
|
68
|
+
// The team's DSH Agent (at most one): plain dsh Sessions, not Bots.
|
|
69
|
+
agents: Object.values(state.agents),
|
|
70
|
+
// Key names and who may use them; values never leave the host.
|
|
71
|
+
secrets: rt.secretsView(),
|
|
72
|
+
capabilities: rt.capabilityLibraryView(),
|
|
73
|
+
secretRequests: state.secretRequests ?? {},
|
|
74
|
+
colors: [...COLORS, ...EXTRA_COLORS],
|
|
75
|
+
models: rt.cachedCatalog(),
|
|
76
|
+
defaultModel: rt.cachedDefault() ?? null,
|
|
77
|
+
permissions: rt.permissionCatalog?.() ?? null,
|
|
78
|
+
modelsKey: modelsKey(),
|
|
79
|
+
prefs: state.prefs,
|
|
80
|
+
// Whether a Desktop window should serve Bot browser reads (see browser.js).
|
|
81
|
+
browser: rt.browserEnabled,
|
|
82
|
+
// False when config.memory turns memory off: no Memory row or panel.
|
|
83
|
+
memory: rt.memoryEnabled !== false,
|
|
84
|
+
// Set while another dsh host holds the team (store.js): why this one cannot change it.
|
|
85
|
+
readOnly: rt.readOnly() ?? null,
|
|
86
|
+
})
|
|
87
|
+
// What changes without a new revision, sent with every state answer. `learning`:
|
|
88
|
+
// each Bot's pending skill proposals. `memoryNews`:
|
|
89
|
+
// per Bot or group chat, its latest memory change; `memoryReviews`: memory reviews waiting or
|
|
90
|
+
// running; `work`: per Bot, the turn it runs and its tool; per group, the members whose
|
|
91
|
+
// turn there runs; the approvals waiting; per Bot, who it waits for to hand back work;
|
|
92
|
+
// and per Session, when its running turn began.
|
|
93
|
+
const live = () => ({
|
|
94
|
+
activity: rt.activityView(),
|
|
95
|
+
work: { bots: rt.workView(), rooms: rt.roomWorkView(), approvals: rt.approvalsView(), waiting: rt.waitingView?.() ?? {}, turns: rt.turnsView?.() ?? {}, steered: rt.steeredView?.() ?? {} },
|
|
96
|
+
memoryNews: rt.memoryNews(),
|
|
97
|
+
memoryReviews: rt.memoryReviews(),
|
|
98
|
+
learning: rt.learningView(),
|
|
99
|
+
connectors: rt.connectorsView(),
|
|
100
|
+
capabilityStatuses: rt.capabilityStatusesView(),
|
|
101
|
+
})
|
|
102
|
+
// The live fields' sources are spread across the host with no single change
|
|
103
|
+
// counter, so the serialized answer itself is the detector: a poll whose `live`
|
|
104
|
+
// still names it gets only the key back, not the fields again. The key carries
|
|
105
|
+
// a per-run prefix: a window can outlive the host, and a counter that restarts
|
|
106
|
+
// at 1 would let its stale key name a different answer.
|
|
107
|
+
const liveRun = randomUUID()
|
|
108
|
+
let liveSeq = 0
|
|
109
|
+
let liveCache = null
|
|
110
|
+
const liveAnswer = () => {
|
|
111
|
+
const value = live()
|
|
112
|
+
const json = JSON.stringify(value)
|
|
113
|
+
if (liveCache?.json === json) return liveCache
|
|
114
|
+
return liveCache = { key: `${liveRun}:${++liveSeq}`, value, json }
|
|
115
|
+
}
|
|
116
|
+
// What a host that only reads the team may still answer.
|
|
117
|
+
// Updates change the dsh profile, not the team, so a reading host may run them too.
|
|
118
|
+
const READS = new Set(['state', 'models', 'efforts', 'providers', 'provider-models', 'earlier', 'chat', 'run-stats', 'room-progress', 'exchange', 'exchange-text', 'file-text', 'usage', 'schedules', 'memory-view', 'browser-wait', 'browser-result', 'diagnostics', 'diagnostics-log', 'diagnostics-clear', 'update-check', 'update-install', 'dsh-update-install', 'repo-stars', 'approval-answer', 'thinking', 'avatar-library'])
|
|
119
|
+
|
|
120
|
+
async function setPrefs(body) {
|
|
121
|
+
if (body.theme !== undefined) {
|
|
122
|
+
if (typeof body.theme !== 'string' || !/^[a-z][\w-]{0,31}$/.test(body.theme)) throw invalid('Unknown theme')
|
|
123
|
+
state.prefs.theme = body.theme
|
|
124
|
+
}
|
|
125
|
+
if (body.accent !== undefined) {
|
|
126
|
+
if (body.accent === null || body.accent === '') delete state.prefs.accent
|
|
127
|
+
else if (/^#[0-9a-f]{6}$/i.test(body.accent)) state.prefs.accent = body.accent
|
|
128
|
+
else throw invalid('Accent must be a #rrggbb color')
|
|
129
|
+
}
|
|
130
|
+
if (body.motion !== undefined) {
|
|
131
|
+
if (body.motion === null || body.motion === 'normal') delete state.prefs.motion
|
|
132
|
+
else if (body.motion === 'quiet' || body.motion === 'lively') state.prefs.motion = body.motion
|
|
133
|
+
else throw invalid('Motion must be quiet, normal or lively')
|
|
134
|
+
}
|
|
135
|
+
if (body.layout !== undefined) {
|
|
136
|
+
if (body.layout === null || body.layout === 'bubble') delete state.prefs.layout
|
|
137
|
+
else if (body.layout === 'flat') state.prefs.layout = body.layout
|
|
138
|
+
else throw invalid('Layout must be bubble or flat')
|
|
139
|
+
}
|
|
140
|
+
if (body.width !== undefined) {
|
|
141
|
+
if (body.width === null || body.width === 80) delete state.prefs.width
|
|
142
|
+
else if (Number.isInteger(body.width) && body.width >= 50 && body.width <= 100) state.prefs.width = body.width
|
|
143
|
+
else throw invalid('Width must be a whole percent from 50 to 100')
|
|
144
|
+
}
|
|
145
|
+
if (body.flatWidth !== undefined) {
|
|
146
|
+
if (body.flatWidth === null || body.flatWidth === 100) delete state.prefs.flatWidth
|
|
147
|
+
else if (Number.isInteger(body.flatWidth) && body.flatWidth >= 50 && body.flatWidth <= 100) state.prefs.flatWidth = body.flatWidth
|
|
148
|
+
else throw invalid('Flat text width must be a whole percent from 50 to 100')
|
|
149
|
+
}
|
|
150
|
+
if (body.defaultBotModel !== undefined) {
|
|
151
|
+
// The model new Bots start on when a create names none. It resolves now, so the
|
|
152
|
+
// saved ref is one the catalog served at save time; a create still re-checks it.
|
|
153
|
+
if (body.defaultBotModel === null || body.defaultBotModel === '') delete state.prefs.defaultBotModel
|
|
154
|
+
else state.prefs.defaultBotModel = await rt.resolveModel(body.defaultBotModel)
|
|
155
|
+
}
|
|
156
|
+
if (body.thinking !== undefined) {
|
|
157
|
+
// A value this version does not know is ignored, not refused: the toggle
|
|
158
|
+
// may grow more states later.
|
|
159
|
+
if (body.thinking === true) state.prefs.thinking = true
|
|
160
|
+
else if (body.thinking === false || body.thinking === null) delete state.prefs.thinking
|
|
161
|
+
}
|
|
162
|
+
if (body.enterToSend !== undefined) {
|
|
163
|
+
if (body.enterToSend === false) state.prefs.enterToSend = false
|
|
164
|
+
else if (body.enterToSend === true || body.enterToSend === null) delete state.prefs.enterToSend
|
|
165
|
+
}
|
|
166
|
+
return rt.save()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const handle = async (endpoint, payload, signal) => {
|
|
170
|
+
try {
|
|
171
|
+
const body = payload ?? {}
|
|
172
|
+
await rt.refresh()
|
|
173
|
+
const readOnly = rt.readOnly()
|
|
174
|
+
if (readOnly !== undefined && !READS.has(endpoint)) return fail(readOnly, 'bot/read-only')
|
|
175
|
+
switch (endpoint) {
|
|
176
|
+
case 'state': {
|
|
177
|
+
if (typeof body.locale === 'string' && /^[a-z]{2,3}(-[\w-]+)?$/i.test(body.locale)) uiLocale = body.locale
|
|
178
|
+
if (readOnly === undefined) await rt.bootstrap()
|
|
179
|
+
await rt.catalog()
|
|
180
|
+
await rt.loadSecrets()
|
|
181
|
+
const answer = liveAnswer()
|
|
182
|
+
if (body.since === state.revision && (body.models === undefined || body.models === modelsKey())) {
|
|
183
|
+
if (body.live === answer.key) return ok({ revision: state.revision, unchanged: true, liveKey: answer.key })
|
|
184
|
+
return ok({ revision: state.revision, unchanged: true, liveKey: answer.key, ...answer.value })
|
|
185
|
+
}
|
|
186
|
+
return ok({ ...view(), liveKey: answer.key, ...answer.value })
|
|
187
|
+
}
|
|
188
|
+
case 'models':
|
|
189
|
+
await rt.catalog({ fresh: true })
|
|
190
|
+
return ok({ modelsKey: modelsKey() })
|
|
191
|
+
case 'efforts':
|
|
192
|
+
return ok(await rt.reasoningOf(String(body.ref ?? '')) ?? { efforts: [] })
|
|
193
|
+
// A Turn's exact thinking time, from the timed stream in its events; `ms`
|
|
194
|
+
// omitted when the stream carries no reasoning records.
|
|
195
|
+
case 'thinking': {
|
|
196
|
+
const sessionId = String(body.sessionId ?? '')
|
|
197
|
+
const turn = Number(body.turn)
|
|
198
|
+
if (!Number.isFinite(turn) || rt.selfOf(sessionId) === undefined) return ok(null)
|
|
199
|
+
await rt.load()
|
|
200
|
+
const ms = await rt.thinkingOf(sessionId, turn)
|
|
201
|
+
return ok(ms === undefined ? {} : { ms })
|
|
202
|
+
}
|
|
203
|
+
// Model providers and their keys, for a window without DSH's own Models page.
|
|
204
|
+
case 'providers':
|
|
205
|
+
return ok(await rt.providersView())
|
|
206
|
+
case 'provider-key':
|
|
207
|
+
if (body.value === null) await rt.clearProviderKey(String(body.provider ?? ''))
|
|
208
|
+
else await rt.setProviderKey(String(body.provider ?? ''), body.value)
|
|
209
|
+
return ok(await rt.providersView())
|
|
210
|
+
case 'provider-add':
|
|
211
|
+
return ok(await rt.addProvider(body))
|
|
212
|
+
case 'provider-models':
|
|
213
|
+
return ok(await rt.discoverModels(body))
|
|
214
|
+
case 'provider-remove':
|
|
215
|
+
return ok(await rt.removeProvider(String(body.provider ?? '')))
|
|
216
|
+
case 'duplicate-bot':
|
|
217
|
+
await rt.load()
|
|
218
|
+
return ok(await rt.duplicateBot(body.id))
|
|
219
|
+
case 'set-prefs':
|
|
220
|
+
await rt.load()
|
|
221
|
+
await setPrefs(body)
|
|
222
|
+
return ok(state.prefs)
|
|
223
|
+
case 'exchange': {
|
|
224
|
+
await rt.load()
|
|
225
|
+
const pair = new Set([body.a, body.b])
|
|
226
|
+
return ok(state.exchanges.filter(entry => pair.has(entry.from) && pair.has(entry.to)))
|
|
227
|
+
}
|
|
228
|
+
case 'exchange-text': {
|
|
229
|
+
await rt.load()
|
|
230
|
+
const entry = state.exchanges.find(item => item.id === body.id)
|
|
231
|
+
if (entry === undefined) return fail('This message is no longer kept', 'bot/stale')
|
|
232
|
+
return ok(await rt.exchangeText(entry))
|
|
233
|
+
}
|
|
234
|
+
// A long Bot post's file (attachLong), for the group chat's "Show all".
|
|
235
|
+
case 'file-text': {
|
|
236
|
+
if (typeof body.file?.attachmentId !== 'string') return fail('No file named', 'bot/invalid')
|
|
237
|
+
const read = await rt.fileText(body.file)
|
|
238
|
+
return read === undefined ? fail('This file cannot be read here', 'bot/unavailable') : ok(read)
|
|
239
|
+
}
|
|
240
|
+
case 'earlier':
|
|
241
|
+
await rt.load()
|
|
242
|
+
return ok(await rt.earlierPage(String(body.sessionId ?? ''), body.cursor ?? undefined))
|
|
243
|
+
case 'chat':
|
|
244
|
+
await rt.load()
|
|
245
|
+
return ok(await rt.chatPage(String(body.botId ?? ''), body.cursor ?? undefined))
|
|
246
|
+
// DSH's own whole-log figures for the current part of a Bot's chat, the ones its
|
|
247
|
+
// status bar shows; a window without that bar shows them in the Bot's details.
|
|
248
|
+
case 'run-stats': {
|
|
249
|
+
await rt.load()
|
|
250
|
+
const bot = rt.botOf(String(body.botId ?? ''))
|
|
251
|
+
if (bot === undefined) return fail('Unknown Bot', 'bot/not-found')
|
|
252
|
+
const projections = ctx.get?.('sessionProjections')
|
|
253
|
+
if (projections === undefined) return ok(null)
|
|
254
|
+
const agent = await rt.resolveAgent(rt.chatOf(bot.id))
|
|
255
|
+
return ok(projections.snapshot(agent.session, RUN_STATS).values)
|
|
256
|
+
}
|
|
257
|
+
case 'usage':
|
|
258
|
+
await rt.load()
|
|
259
|
+
return ok(await rt.usageView({ timeZone: typeof body.timeZone === 'string' ? body.timeZone.slice(0, 64) : undefined }))
|
|
260
|
+
case 'room-progress': {
|
|
261
|
+
// Any part id of the group's own Session means the group itself.
|
|
262
|
+
const room = rt.roomOf(body.roomId) ?? rt.roomOf(rt.pastOwners.get(body.roomId)?.roomId)
|
|
263
|
+
return ok([...rt.roomProgress.entries()].filter(([, entry]) => entry.roomId === (room?.id ?? body.roomId)).map(([callId, entry]) => ({ callId, replies: entry.replies, speaking: entry.slots.filter(slot => slot.status === 'speaking').map(slot => slot.id) })))
|
|
264
|
+
}
|
|
265
|
+
case 'create-bot': {
|
|
266
|
+
await rt.bootstrap()
|
|
267
|
+
const bot = await rt.createBot({ name: body.name, role: body.role, color: body.color, instructions: body.instructions, brief: body.brief, avatar: body.avatar, model: body.model, effort: body.effort, workDir: body.workDir })
|
|
268
|
+
return ok(bot)
|
|
269
|
+
}
|
|
270
|
+
case 'update-bot':
|
|
271
|
+
await rt.load()
|
|
272
|
+
return ok(await rt.updateBot(body.id, body))
|
|
273
|
+
// The repair card's Retry, once the user fixed what failed the Bot's turn.
|
|
274
|
+
case 'resume': {
|
|
275
|
+
await rt.load()
|
|
276
|
+
const bot = rt.botOf(String(body.botId ?? ''))
|
|
277
|
+
if (bot === undefined) return fail('Unknown Bot', 'bot/not-found')
|
|
278
|
+
await rt.resumeBot(bot)
|
|
279
|
+
return ok(true)
|
|
280
|
+
}
|
|
281
|
+
// The avatar library: photos the user imported once, kept beside the team.
|
|
282
|
+
case 'avatar-library':
|
|
283
|
+
await rt.load()
|
|
284
|
+
return ok(await rt.avatarLibraryView({ offset: body.offset, limit: body.limit }))
|
|
285
|
+
case 'avatar-import':
|
|
286
|
+
await rt.load()
|
|
287
|
+
return ok(await rt.avatarLibraryImport(Array.isArray(body.items) ? body.items : []))
|
|
288
|
+
case 'avatar-delete':
|
|
289
|
+
await rt.load()
|
|
290
|
+
return ok(await rt.avatarLibraryDelete(String(body.id ?? '')))
|
|
291
|
+
case 'delete-bot':
|
|
292
|
+
await rt.load()
|
|
293
|
+
await rt.deleteBot(body.id)
|
|
294
|
+
return ok(view())
|
|
295
|
+
case 'private-release':
|
|
296
|
+
await rt.load()
|
|
297
|
+
await rt.releaseArchive(String(body.id ?? ''))
|
|
298
|
+
return ok(view())
|
|
299
|
+
case 'set-main':
|
|
300
|
+
await rt.load()
|
|
301
|
+
await rt.setMain(body.id, body.main !== false, {
|
|
302
|
+
primary: body.primary === true,
|
|
303
|
+
replace: typeof body.replace === 'string' ? body.replace : undefined,
|
|
304
|
+
})
|
|
305
|
+
return ok(view())
|
|
306
|
+
case 'create-room': {
|
|
307
|
+
await rt.load()
|
|
308
|
+
const room = await rt.createRoom({ members: Array.isArray(body.members) ? body.members : [], name: body.name, ...(typeof body.admin === 'string' ? { admin: body.admin } : {}) })
|
|
309
|
+
if (body.greet === true) await rt.greetRoom(room.id).catch(error => rt.warn('ds-bot: greeting in %s failed: %s', room.name, error))
|
|
310
|
+
return ok(room)
|
|
311
|
+
}
|
|
312
|
+
case 'update-room': {
|
|
313
|
+
await rt.load()
|
|
314
|
+
const { room, changes } = await rt.updateRoom(body.id, {
|
|
315
|
+
name: body.name,
|
|
316
|
+
notice: body.notice,
|
|
317
|
+
mode: body.mode,
|
|
318
|
+
admin: body.admin,
|
|
319
|
+
members: Array.isArray(body.members) ? body.members : undefined,
|
|
320
|
+
add: Array.isArray(body.add) ? body.add : undefined,
|
|
321
|
+
remove: Array.isArray(body.remove) ? body.remove : undefined,
|
|
322
|
+
workDir: body.workDir,
|
|
323
|
+
permission: body.permission,
|
|
324
|
+
})
|
|
325
|
+
return ok({ room: view().rooms.find(entry => entry.id === room.id), changes })
|
|
326
|
+
}
|
|
327
|
+
case 'folders':
|
|
328
|
+
case 'folder-pick':
|
|
329
|
+
case 'folder-list':
|
|
330
|
+
await rt.load()
|
|
331
|
+
return ok(await rt.folderEndpoint(endpoint, body, signal))
|
|
332
|
+
case 'team-workspace':
|
|
333
|
+
await rt.load()
|
|
334
|
+
return ok({ team: await rt.setTeamWorkspace(String(body.path ?? '')) })
|
|
335
|
+
case 'add-agent':
|
|
336
|
+
await rt.load()
|
|
337
|
+
return ok(await rt.addAgent())
|
|
338
|
+
case 'agent-session':
|
|
339
|
+
await rt.load()
|
|
340
|
+
return ok({ sessionId: await rt.createAgentSession(body.agentId, body.workspaceId) })
|
|
341
|
+
case 'agent-adopt':
|
|
342
|
+
await rt.load()
|
|
343
|
+
return ok({ sessionId: await rt.adoptAgentSession(body.agentId, body.sessionId) })
|
|
344
|
+
case 'agent-archive':
|
|
345
|
+
await rt.load()
|
|
346
|
+
await rt.archiveAgentSession(body.agentId, body.sessionId)
|
|
347
|
+
return ok(true)
|
|
348
|
+
case 'agent-rename':
|
|
349
|
+
await rt.load()
|
|
350
|
+
await rt.renameAgentSession(body.agentId, body.sessionId, body.title)
|
|
351
|
+
return ok(true)
|
|
352
|
+
case 'remove-agent':
|
|
353
|
+
await rt.load()
|
|
354
|
+
await rt.removeAgent(body.agentId)
|
|
355
|
+
return ok(view())
|
|
356
|
+
case 'set-flags':
|
|
357
|
+
await rt.load()
|
|
358
|
+
return ok(await rt.setFlags(body.id, body))
|
|
359
|
+
case 'delete-room':
|
|
360
|
+
await rt.load()
|
|
361
|
+
await rt.deleteRoom(body.id)
|
|
362
|
+
return ok(view())
|
|
363
|
+
case 'approval-answer':
|
|
364
|
+
rt.answerApproval(body.id, body.choice)
|
|
365
|
+
return ok(true)
|
|
366
|
+
case 'stop-member-work': {
|
|
367
|
+
const stopped = await rt.stopMemberWork(String(body.roomId ?? ''), String(body.botId ?? ''))
|
|
368
|
+
rt.goalUserStopped?.(String(body.roomId ?? ''))
|
|
369
|
+
return ok(stopped)
|
|
370
|
+
}
|
|
371
|
+
case 'stop-room-work': {
|
|
372
|
+
const stopped = await rt.stopRoomWork(String(body.roomId ?? ''))
|
|
373
|
+
rt.goalUserStopped?.(String(body.roomId ?? ''))
|
|
374
|
+
return ok(stopped)
|
|
375
|
+
}
|
|
376
|
+
case 'goal-draft': {
|
|
377
|
+
await rt.load()
|
|
378
|
+
const result = await rt.goalDraft(String(body.roomId ?? ''), body.text)
|
|
379
|
+
return result.error !== undefined ? fail(result.error, result.code ?? 'bot/refused') : ok(view())
|
|
380
|
+
}
|
|
381
|
+
case 'goal-control': {
|
|
382
|
+
await rt.load()
|
|
383
|
+
const result = await rt.goalControl(String(body.roomId ?? ''), body)
|
|
384
|
+
return result.error !== undefined ? fail(result.error, result.code ?? 'bot/refused') : ok(view())
|
|
385
|
+
}
|
|
386
|
+
case 'dismiss-question':
|
|
387
|
+
await rt.load()
|
|
388
|
+
if (rt.own(state.questions, body.sessionId) !== undefined) {
|
|
389
|
+
delete state.questions[body.sessionId]
|
|
390
|
+
await rt.save()
|
|
391
|
+
}
|
|
392
|
+
return ok(true)
|
|
393
|
+
case 'dismiss-view':
|
|
394
|
+
await rt.load()
|
|
395
|
+
if (rt.own(state.views, body.sessionId) !== undefined) {
|
|
396
|
+
delete state.views[body.sessionId]
|
|
397
|
+
await rt.save()
|
|
398
|
+
}
|
|
399
|
+
return ok(true)
|
|
400
|
+
case 'secret-set':
|
|
401
|
+
case 'secret-allow':
|
|
402
|
+
case 'secret-cancel':
|
|
403
|
+
case 'secret-update':
|
|
404
|
+
case 'secret-delete':
|
|
405
|
+
await rt.load()
|
|
406
|
+
return ok(await rt.secretEndpoint(endpoint, body))
|
|
407
|
+
case 'connector-connect':
|
|
408
|
+
case 'connector-disconnect':
|
|
409
|
+
case 'connector-retry':
|
|
410
|
+
await rt.load()
|
|
411
|
+
return ok(await rt.connectorEndpoint(endpoint, body))
|
|
412
|
+
case 'capability-save':
|
|
413
|
+
case 'capability-delete':
|
|
414
|
+
case 'capability-approve':
|
|
415
|
+
case 'bot-capability':
|
|
416
|
+
await rt.load()
|
|
417
|
+
return ok(await rt.capabilityEndpoint(endpoint, body))
|
|
418
|
+
case 'schedules':
|
|
419
|
+
case 'schedule-create':
|
|
420
|
+
case 'schedule-delete':
|
|
421
|
+
await rt.load()
|
|
422
|
+
return ok(await rt.scheduleEndpoint(endpoint, body))
|
|
423
|
+
case 'memory-view':
|
|
424
|
+
case 'memory-ask':
|
|
425
|
+
case 'memory-forget':
|
|
426
|
+
case 'memory-history':
|
|
427
|
+
case 'memory-restore':
|
|
428
|
+
await rt.load()
|
|
429
|
+
return ok(await rt.memoryEndpoint(endpoint, body))
|
|
430
|
+
case 'learning-answer':
|
|
431
|
+
await rt.load()
|
|
432
|
+
return ok(await rt.learningEndpoint(endpoint, body))
|
|
433
|
+
case 'answer-question': {
|
|
434
|
+
if (typeof body.sessionId !== 'string') return fail('Nothing to send', 'bot/invalid')
|
|
435
|
+
await rt.load()
|
|
436
|
+
const past = rt.pastOwners.get(body.sessionId)
|
|
437
|
+
const sessionId = rt.botOf(body.sessionId) ? rt.chatOf(body.sessionId) : past && past.roomId === undefined ? rt.chatOf(past.botId) : body.sessionId
|
|
438
|
+
const question = rt.own(state.questions, sessionId)
|
|
439
|
+
if (question === undefined || question.id !== body.questionId) return fail('This question is no longer open', 'bot/stale')
|
|
440
|
+
const reply = questionReply(question, body.answer)
|
|
441
|
+
if (reply === undefined) return fail('Nothing to send', 'bot/invalid')
|
|
442
|
+
// Claim the question before queueing so a second answer is refused.
|
|
443
|
+
delete state.questions[sessionId]
|
|
444
|
+
try {
|
|
445
|
+
await rt.ensureCapabilities(sessionId)
|
|
446
|
+
const agent = await rt.resolveAgent(sessionId)
|
|
447
|
+
agent.followup(createUserMessage({ content: [{ type: 'text', text: reply.text }], source: reply.source }))
|
|
448
|
+
await ctx.sessions.flush(agent.session)
|
|
449
|
+
} catch (error) {
|
|
450
|
+
state.questions[sessionId] ??= question
|
|
451
|
+
throw error
|
|
452
|
+
}
|
|
453
|
+
await rt.save()
|
|
454
|
+
return ok(true)
|
|
455
|
+
}
|
|
456
|
+
case 'view-reply': {
|
|
457
|
+
if (typeof body.sessionId !== 'string') return fail('Nothing to send', 'bot/invalid')
|
|
458
|
+
await rt.load()
|
|
459
|
+
const past = rt.pastOwners.get(body.sessionId)
|
|
460
|
+
const sessionId = rt.botOf(body.sessionId) ? rt.chatOf(body.sessionId) : past && past.roomId === undefined ? rt.chatOf(past.botId) : body.sessionId
|
|
461
|
+
const pending = rt.own(state.views, sessionId)
|
|
462
|
+
if (pending === undefined || pending.id !== body.viewId) return fail('This view is no longer open', 'bot/stale')
|
|
463
|
+
const checked = validateViewAnswer(pending.view, body.action, body.values)
|
|
464
|
+
if (checked.error !== undefined) return fail(checked.error, 'bot/invalid')
|
|
465
|
+
const reply = viewReply(pending, body.action, body.values)
|
|
466
|
+
if (reply === undefined) return fail('Nothing to send', 'bot/invalid')
|
|
467
|
+
// Claim the view before queueing so a second answer is refused.
|
|
468
|
+
delete state.views[sessionId]
|
|
469
|
+
try {
|
|
470
|
+
await rt.ensureCapabilities(sessionId)
|
|
471
|
+
const agent = await rt.resolveAgent(sessionId)
|
|
472
|
+
agent.followup(createUserMessage({ content: [{ type: 'text', text: reply.text }], source: reply.source }))
|
|
473
|
+
await ctx.sessions.flush(agent.session)
|
|
474
|
+
} catch (error) {
|
|
475
|
+
state.views[sessionId] ??= pending
|
|
476
|
+
throw error
|
|
477
|
+
}
|
|
478
|
+
await rt.save()
|
|
479
|
+
return ok(true)
|
|
480
|
+
}
|
|
481
|
+
case 'send': {
|
|
482
|
+
const text = String(body.text ?? '').trim()
|
|
483
|
+
if (text === '' || typeof body.sessionId !== 'string') return fail('Nothing to send', 'bot/invalid')
|
|
484
|
+
await rt.load()
|
|
485
|
+
// A Bot id, a group chat's id, or an earlier part of either means the
|
|
486
|
+
// current part.
|
|
487
|
+
const past = rt.pastOwners.get(body.sessionId)
|
|
488
|
+
const room = rt.roomOf(body.sessionId)
|
|
489
|
+
const sessionId = rt.botOf(body.sessionId) ? rt.chatOf(body.sessionId)
|
|
490
|
+
: room !== undefined ? rt.sessionOfRoom(room)
|
|
491
|
+
: past?.roomId === undefined && past !== undefined ? rt.chatOf(past.botId)
|
|
492
|
+
: past !== undefined ? rt.sessionOfRoom(rt.roomOf(past.roomId)) : body.sessionId
|
|
493
|
+
await rt.ensureCapabilities(sessionId)
|
|
494
|
+
await ctx.sessionController.prompt({
|
|
495
|
+
requestId: randomUUID(),
|
|
496
|
+
sessionId,
|
|
497
|
+
mode: 'queue',
|
|
498
|
+
content: [{ type: 'text', text }],
|
|
499
|
+
}, AbortSignal.timeout(30_000))
|
|
500
|
+
return ok(true)
|
|
501
|
+
}
|
|
502
|
+
case 'btw-delete': {
|
|
503
|
+
if (typeof body.sessionId !== 'string' || body.sessionId === '' || typeof body.cardId !== 'string' || body.cardId === '') return fail('Nothing to delete', 'bot/invalid')
|
|
504
|
+
await rt.load()
|
|
505
|
+
return ok(await rt.btwDelete({ sessionId: body.sessionId, cardId: body.cardId }))
|
|
506
|
+
}
|
|
507
|
+
// Side chats in the Bot's panel: each question is an aside on a fresh
|
|
508
|
+
// snapshot of this Bot's private chat; the earlier finished pairs of
|
|
509
|
+
// that chat ride along. Nothing is written into the Session.
|
|
510
|
+
case 'side-new': {
|
|
511
|
+
if (typeof body.sessionId !== 'string' || body.sessionId === '') return fail('Nothing to open', 'bot/invalid')
|
|
512
|
+
await rt.load()
|
|
513
|
+
return ok(await rt.sideNew({ sessionId: body.sessionId }))
|
|
514
|
+
}
|
|
515
|
+
case 'side-ask': {
|
|
516
|
+
if (typeof body.sessionId !== 'string' || body.sessionId === '') return fail('Nothing to ask', 'bot/invalid')
|
|
517
|
+
if (body.chatId !== undefined && typeof body.chatId !== 'string') return fail('Nothing to ask', 'bot/invalid')
|
|
518
|
+
if (String(body.text ?? '').trim() === '') return fail('Write a question.', 'bot/invalid')
|
|
519
|
+
await rt.load()
|
|
520
|
+
return ok(await rt.sideAsk({ sessionId: body.sessionId, chatId: body.chatId, recent: body.recent === true, text: String(body.text) }))
|
|
521
|
+
}
|
|
522
|
+
case 'side-cancel': {
|
|
523
|
+
if (typeof body.chatId !== 'string' || body.chatId === '' || typeof body.entryId !== 'string' || body.entryId === '') return fail('Nothing to cancel', 'bot/invalid')
|
|
524
|
+
await rt.load()
|
|
525
|
+
return ok(await rt.sideCancel({ chatId: body.chatId, entryId: body.entryId }))
|
|
526
|
+
}
|
|
527
|
+
case 'side-close': {
|
|
528
|
+
if (typeof body.chatId !== 'string' || body.chatId === '') return fail('Nothing to close', 'bot/invalid')
|
|
529
|
+
await rt.load()
|
|
530
|
+
return ok(await rt.sideClose({ chatId: body.chatId }))
|
|
531
|
+
}
|
|
532
|
+
case 'update-check': {
|
|
533
|
+
const fresh = body.fresh === true
|
|
534
|
+
return ok({ ...(await rt.updateCheck({ fresh })), dsh: (await rt.dshUpdateCheck?.({ fresh })) ?? null })
|
|
535
|
+
}
|
|
536
|
+
case 'update-install':
|
|
537
|
+
return ok(rt.updateStart())
|
|
538
|
+
case 'dsh-update-install':
|
|
539
|
+
return ok(rt.dshUpdateStart?.())
|
|
540
|
+
case 'repo-stars':
|
|
541
|
+
return ok(await rt.repoStars())
|
|
542
|
+
// The Desktop window's long poll for read_browser, and its answers.
|
|
543
|
+
case 'browser-wait': {
|
|
544
|
+
if (rt.browserChannel === undefined) return fail('Bot browsers are off on this host', 'bot/browser-off')
|
|
545
|
+
const clientId = typeof body.clientId === 'string' ? body.clientId.slice(0, 100) : ''
|
|
546
|
+
if (clientId === '') return fail('A browser poll needs a clientId', 'bot/invalid')
|
|
547
|
+
await rt.load()
|
|
548
|
+
const requests = await rt.browserChannel.wait(clientId, rt.browserOpen(body.open))
|
|
549
|
+
return ok({ requests, holds: rt.browserChannel.holdSnapshot() })
|
|
550
|
+
}
|
|
551
|
+
case 'browser-result':
|
|
552
|
+
if (rt.browserChannel === undefined) return fail('Bot browsers are off on this host', 'bot/browser-off')
|
|
553
|
+
return ok(rt.browserChannel.result(body))
|
|
554
|
+
case 'browser-takeover': {
|
|
555
|
+
if (rt.browserChannel === undefined) return fail('Bot browsers are off on this host', 'bot/browser-off')
|
|
556
|
+
await rt.load()
|
|
557
|
+
const botId = String(body.botId ?? '')
|
|
558
|
+
if (!rt.botOf(botId)) return fail('Unknown Bot', 'bot/not-found')
|
|
559
|
+
// Taking over a handoff keeps its reason, note, and call, so its card stays up.
|
|
560
|
+
const asked = rt.browserChannel.holdOf(botId)
|
|
561
|
+
rt.browserChannel.setHold(botId, asked?.callId !== undefined
|
|
562
|
+
? { by: 'user', reason: asked.reason, note: asked.note, callId: asked.callId }
|
|
563
|
+
: { by: 'user', reason: 'takeover', note: 'The user is controlling this browser.' })
|
|
564
|
+
return ok(true)
|
|
565
|
+
}
|
|
566
|
+
// A card's 'done' or 'skip' says how the handoff went. The panel's Give back
|
|
567
|
+
// sends no outcome, so its reply only says that control returned to the Bot.
|
|
568
|
+
case 'browser-give-back': {
|
|
569
|
+
if (rt.browserChannel === undefined) return fail('Bot browsers are off on this host', 'bot/browser-off')
|
|
570
|
+
await rt.load()
|
|
571
|
+
const botId = String(body.botId ?? '')
|
|
572
|
+
if (!rt.botOf(botId)) return fail('Unknown Bot', 'bot/not-found')
|
|
573
|
+
if (body.outcome !== undefined && !['done', 'skip', 'returned'].includes(body.outcome)) return fail('Nothing to send', 'bot/invalid')
|
|
574
|
+
const hold = rt.browserChannel.holdOf(botId)
|
|
575
|
+
if (hold === null) return fail('This browser is not held', 'bot/forbidden')
|
|
576
|
+
if (body.callId !== undefined && body.callId !== hold.callId) return fail('This handoff is no longer open', 'bot/stale')
|
|
577
|
+
// Claim the hold before queueing so a second answer is refused.
|
|
578
|
+
rt.browserChannel.setHold(botId, null)
|
|
579
|
+
if (hold.callId === undefined) return ok(true)
|
|
580
|
+
try {
|
|
581
|
+
const sessionId = rt.chatOf(botId)
|
|
582
|
+
const reply = handoffReply(hold, body.outcome)
|
|
583
|
+
await rt.ensureCapabilities(sessionId)
|
|
584
|
+
const agent = await rt.resolveAgent(sessionId)
|
|
585
|
+
agent.followup(createUserMessage({ content: [{ type: 'text', text: reply.text }], source: reply.source }))
|
|
586
|
+
await ctx.sessions.flush(agent.session)
|
|
587
|
+
} catch (error) {
|
|
588
|
+
if (rt.browserChannel.holdOf(botId) === null) rt.browserChannel.setHold(botId, hold)
|
|
589
|
+
throw error
|
|
590
|
+
}
|
|
591
|
+
return ok(true)
|
|
592
|
+
}
|
|
593
|
+
case 'browser-stop': {
|
|
594
|
+
await rt.load()
|
|
595
|
+
const botId = String(body.botId ?? '')
|
|
596
|
+
if (!rt.botOf(botId)) return fail('Unknown Bot', 'bot/not-found')
|
|
597
|
+
const sessionId = rt.chatOf(botId)
|
|
598
|
+
if (sessionId && rt.live.get(sessionId)?.turn !== undefined) {
|
|
599
|
+
const agent = await rt.resolveAgent(sessionId)
|
|
600
|
+
agent.cancel({ kind: 'user' })
|
|
601
|
+
}
|
|
602
|
+
for (const room of Object.values(rt.state.rooms ?? {})) {
|
|
603
|
+
if (room.members?.includes(botId)) await rt.stopMemberWork?.(room.id, botId)
|
|
604
|
+
}
|
|
605
|
+
rt.browserChannel?.setHold(botId, { by: 'user', reason: 'stopped', note: 'The user is controlling this browser.' })
|
|
606
|
+
return ok(true)
|
|
607
|
+
}
|
|
608
|
+
case 'diagnostics':
|
|
609
|
+
await rt.load()
|
|
610
|
+
return ok(await rt.diagnostics.report(body))
|
|
611
|
+
case 'diagnostics-log':
|
|
612
|
+
return ok(await rt.diagnostics.logText())
|
|
613
|
+
case 'diagnostics-clear':
|
|
614
|
+
await rt.diagnostics.clear()
|
|
615
|
+
return ok(true)
|
|
616
|
+
default:
|
|
617
|
+
return fail(`Unknown endpoint ${endpoint}`, 'bot/not-found')
|
|
618
|
+
}
|
|
619
|
+
} catch (error) {
|
|
620
|
+
const code = errorCode(error)
|
|
621
|
+
const message = error?.message ?? String(error)
|
|
622
|
+
if (code === 'bot/internal') rt.noteFailure({ source: 'api', text: `${endpoint}: ${message}`, code: error?.code ?? error?.name, stack: error?.stack })
|
|
623
|
+
return fail(message, code)
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// An exact Fetch route on the shared /api channel; the browser authenticates with its session cookie.
|
|
628
|
+
ctx.effect(() => ctx.connection.fetch.register({
|
|
629
|
+
path: ROUTE,
|
|
630
|
+
methods: ['POST'],
|
|
631
|
+
requestBody: 'buffered',
|
|
632
|
+
fetch: async (request) => {
|
|
633
|
+
let body
|
|
634
|
+
try { body = await request.json() } catch { body = {} }
|
|
635
|
+
const result = await handle(String(body?.endpoint ?? ''), body?.payload, request.signal)
|
|
636
|
+
return new Response(JSON.stringify(result), { status: 200, headers: { 'content-type': 'application/json' } })
|
|
637
|
+
},
|
|
638
|
+
}), 'ds-bot: api route')
|
|
639
|
+
|
|
640
|
+
ctx.effect(() => {
|
|
641
|
+
void rt.load()
|
|
642
|
+
// Registered last, so this disposer runs before the state lock is released.
|
|
643
|
+
// In-flight side questions abort here; their entries stay answering for the
|
|
644
|
+
// next owner.
|
|
645
|
+
return () => Promise.all([rt.btwDispose?.(), rt.sideDispose?.()])
|
|
646
|
+
}, 'ds-bot: load state')
|
|
647
|
+
}
|