@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,68 @@
|
|
|
1
|
+
--- a/src/host/store.js
|
|
2
|
+
+++ b/src/host/store.js
|
|
3
|
+
@@ -430,6 +430,8 @@
|
|
4
|
+
const roomOwners = new Map()
|
|
5
|
+
/** @type {Map<string, {botId?:string, roomId?:string}>} */
|
|
6
|
+
const pastOwners = new Map()
|
|
7
|
+
+ /** @type {Map<string, string>} */
|
|
8
|
+
+ const threadOwners = new Map()
|
|
9
|
+
const chatOf = botId => botOf(botId)?.sessionId ?? botId
|
|
10
|
+
const sessionOfRoom = room => (room === undefined ? undefined : room.sessionId ?? room.id)
|
|
11
|
+
const indexSessions = () => {
|
|
12
|
+
@@ -437,9 +439,23 @@
|
|
13
|
+
groupOwners.clear()
|
|
14
|
+
roomOwners.clear()
|
|
15
|
+
pastOwners.clear()
|
|
16
|
+
+ threadOwners.clear()
|
|
17
|
+
for (const bot of Object.values(state.bots)) {
|
|
18
|
+
for (const segment of bot.segments ?? []) pastOwners.set(segment.sessionId, { botId: bot.id })
|
|
19
|
+
chatOwners.set(chatOf(bot.id), bot.id)
|
|
20
|
+
+ // A live thread Session is another own chat of the same Bot: it speaks
|
|
21
|
+
+ // with the Bot's identity, prompt, tools and money, so it sits in
|
|
22
|
+
+ // chatOwners beside the Bot's default chat. chatOf() stays the default
|
|
23
|
+
+ // chat, which is a different thing from any one thread. An archived
|
|
24
|
+
+ // thread keeps its history readable and gains no authority.
|
|
25
|
+
+ for (const thread of bot.threads ?? []) {
|
|
26
|
+
+ if (thread?.sessionId === undefined) continue
|
|
27
|
+
+ if (thread.archivedAt === undefined || thread.archivedAt === null) {
|
|
28
|
+
+ if (chatOwners.has(thread.sessionId)) { warn('ds-bot: thread Session %s is owned already; it keeps its first owner', thread.sessionId); continue }
|
|
29
|
+
+ chatOwners.set(thread.sessionId, bot.id)
|
|
30
|
+
+ threadOwners.set(thread.sessionId, bot.id)
|
|
31
|
+
+ } else pastOwners.set(thread.sessionId, { botId: bot.id, threadId: thread.threadId })
|
|
32
|
+
+ }
|
|
33
|
+
}
|
|
34
|
+
for (const room of Object.values(state.rooms)) {
|
|
35
|
+
const sessionId = sessionOfRoom(room)
|
|
36
|
+
@@ -461,6 +477,22 @@
|
|
37
|
+
const selfOf = id => chatOwners.get(id) ?? groupOwners.get(id)?.botId ?? pastOwners.get(id)?.botId
|
|
38
|
+
// A Session where the Bot speaks now, as opposed to an archived segment.
|
|
39
|
+
const isBotSession = id => chatOwners.has(id) || groupOwners.has(id)
|
|
40
|
+
+ // Threads: several independent own chats under one Bot. threadsOf() is the
|
|
41
|
+
+ // read-only record list, threadOf() the live owner of one Session, and
|
|
42
|
+
+ // sessionsOfBot() every Session that speaks for a Bot.
|
|
43
|
+
+ const threadsOf = botId => (botOf(botId)?.threads ?? []).map(thread => ({ ...thread }))
|
|
44
|
+
+ const threadOf = sessionId => {
|
|
45
|
+
+ if (typeof sessionId !== 'string') return undefined
|
|
46
|
+
+ for (const bot of Object.values(state.bots)) {
|
|
47
|
+
+ for (const thread of bot.threads ?? []) {
|
|
48
|
+
+ if (thread.sessionId !== sessionId) continue
|
|
49
|
+
+ return thread.archivedAt === undefined || thread.archivedAt === null ? { botId: bot.id, threadId: thread.threadId } : undefined
|
|
50
|
+
+ }
|
|
51
|
+
+ }
|
|
52
|
+
+ return undefined
|
|
53
|
+
+ }
|
|
54
|
+
+ const threadSessionOf = (botId, threadId) => threadsOf(botId).find(thread => thread.threadId === threadId)?.sessionId
|
|
55
|
+
+ const sessionsOfBot = botId => [...new Set([botId, chatOf(botId), ...(botOf(botId)?.segments ?? []).map(segment => segment.sessionId), ...threadsOf(botId).map(thread => thread.sessionId), ...groupSessionsOf(botId)])]
|
|
56
|
+
const groupSessionsOf = botId => Object.values(state.rooms).map(room => room.sessions?.[botId]).filter(Boolean)
|
|
57
|
+
const dropGroupSession = (room, botId) => {
|
|
58
|
+
if (room.memberDirs !== undefined) delete room.memberDirs[botId]
|
|
59
|
+
@@ -506,7 +538,8 @@
|
|
60
|
+
Object.assign(rt, {
|
|
61
|
+
state, load, save, refresh, readOnly, own,
|
|
62
|
+
botOf, roomOf, isMain, chatOf, sessionOfRoom, selfOf, isBotSession,
|
|
63
|
+
- chatOwners, groupOwners, roomOwners, pastOwners,
|
|
64
|
+
+ chatOwners, groupOwners, roomOwners, pastOwners, threadOwners,
|
|
65
|
+
+ threadsOf, threadOf, threadSessionOf, sessionsOfBot,
|
|
66
|
+
groupSessionsOf, dropGroupSession, retireGroupSession, findBot, findRooms, isAdminOf,
|
|
67
|
+
})
|
|
68
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
--- a/src/host/usage.js
|
|
2
|
+
+++ b/src/host/usage.js
|
|
3
|
+
@@ -304,7 +304,7 @@
|
|
4
|
+
const owners = new Map()
|
|
5
|
+
const botOwner = bot => ({ owner: bot.id, name: bot.name })
|
|
6
|
+
for (const bot of Object.values(state.bots)) {
|
|
7
|
+
- for (const sessionId of [bot.id, rt.chatOf(bot.id), ...(bot.segments ?? []).map(segment => segment.sessionId)]) owners.set(sessionId, botOwner(bot))
|
|
8
|
+
+ for (const sessionId of rt.sessionsOfBot(bot.id)) owners.set(sessionId, botOwner(bot))
|
|
9
|
+
}
|
|
10
|
+
for (const room of Object.values(state.rooms)) {
|
|
11
|
+
for (const sessionId of [rt.sessionOfRoom(room), ...(room.parts ?? []).map(segment => segment.sessionId)].filter(Boolean)) owners.set(sessionId, { owner: `room:${room.id}`, name: room.name })
|
|
@@ -7,19 +7,32 @@ const missing = {
|
|
|
7
7
|
botBindings: blocked('宿主尚未提供 Bot 执行绑定投影。', '接入 Bot 设置服务后,可在这里查看和配置每个 Bot 的模型、工作区与资金范围。'),
|
|
8
8
|
fundingAccounts: blocked('宿主尚未提供资金账户目录投影。', '接入资金账户服务后,可在这里按 provider 配置本地限额策略。'),
|
|
9
9
|
tasks: blocked('宿主尚未提供统一任务服务的只读投影。', '接入 Task Service 后,可在这里跟踪、取消或核对任务。'),
|
|
10
|
+
threads: blocked('宿主尚未提供 Bot 线程目录投影。', '接入 Bot 线程服务后,可在这里为同一个 Bot 建立多条独立会话。'),
|
|
10
11
|
machines: blocked('尚未接入远程电脑目录。', '安装并配对同版本 remote-computer-agent,然后在机器目录中登记。'),
|
|
12
|
+
webPanels: blocked('宿主尚未提供固定网页面板目录。', '接入固定网页面板存储后,可在这里把常用网页固定在侧栏。'),
|
|
11
13
|
usage: blocked('宿主尚未提供统一费用与预占投影。', '接入唯一 MR 资金库的只读投影;已有 Token 用量仍可在 DS 用量页查看。'),
|
|
12
14
|
}
|
|
13
15
|
const ACTIONS = new Set([
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
+
'ui.namespace',
|
|
17
|
+
'nativeActivity.list',
|
|
18
|
+
'botProfile.inspect', 'botProfile.save',
|
|
19
|
+
'independentChat.send', 'independentChat.inspect', 'independentChat.cancel', 'independentChat.view', 'independentChat.messages',
|
|
20
|
+
'independentChat.newConversation',
|
|
21
|
+
'task.inspect', 'task.attention', 'task.cancel', 'task.retry', 'task.reconcile', 'task.plan.submit', 'task.plan.cancel', 'usage.resolve',
|
|
22
|
+
'threads.readModel', 'threads.list', 'threads.create', 'threads.rename', 'threads.switch', 'threads.open', 'threads.archive', 'threads.unarchive', 'threads.ensure', 'threads.recover', 'threads.reconcile',
|
|
23
|
+
'webPanels.list', 'webPanels.probe', 'webPanels.save', 'webPanels.remove', 'webPanels.order',
|
|
24
|
+
'notifications.readModel', 'notifications.list', 'notifications.read', 'notifications.markAll',
|
|
25
|
+
'data.catalog', 'data.load', 'data.describe', 'data.sql', 'data.show', 'data.page', 'data.export', 'data.remove', 'data.cancel',
|
|
26
|
+
'binding.inspect', 'binding.save', 'funding.account.save', 'machine.secret', 'machine.pair', 'machine.pair.invite', 'machine.bind', 'machine.probe',
|
|
16
27
|
'machine.revoke', 'machine.reconcile', 'machine.artifact', 'machine.desktop.view', 'machine.desktop.act',
|
|
17
28
|
'machine.desktop.open', 'machine.desktop.renew', 'machine.desktop.close',
|
|
29
|
+
'machine.desktop.poll', 'machine.desktop.send',
|
|
18
30
|
'library.list', 'library.read', 'library.write', 'library.upload', 'library.download', 'library.remove', 'library.mkdir', 'library.search',
|
|
19
31
|
'routine.list', 'routine.run', 'routine.save', 'routine.remove', 'routine.history', 'routine.cancel', 'routine.resolve', 'roles.list',
|
|
20
32
|
'botRoutingInspect', 'botRoutingDecide', 'botRouting.dispatch', 'botRouting.save', 'botRouting.status',
|
|
21
33
|
'botRuntimeStatus', 'botRuntimeMove', 'botRuntimeHistory', 'botRuntimeMessage',
|
|
22
34
|
])
|
|
35
|
+
const READ_ONLY_ACTIONS = new Set(['independentChat.inspect', 'independentChat.view', 'independentChat.messages', 'nativeActivity.list'])
|
|
23
36
|
const plain = value => value && typeof value === 'object' && !Array.isArray(value)
|
|
24
37
|
const error = (code, message) => Object.assign(new Error(message), { code })
|
|
25
38
|
const desktopClientOrigin = request => {
|
|
@@ -63,6 +76,7 @@ export function createProductReadModel({ runtime, lifecycle, providers = {}, act
|
|
|
63
76
|
lifecycle: lifecycle.status(),
|
|
64
77
|
readOnly: runtime.readOnly() ?? null,
|
|
65
78
|
bots: Object.values(state.bots).map(bot => ({ id: bot.id, name: bot.name, role: bot.role ?? '', model: bot.model ?? null,
|
|
79
|
+
appliedModel: typeof bot.appliedModel === 'string' ? bot.appliedModel : null,
|
|
66
80
|
sessionId: runtime.chatOf(bot.id), workDir: bot.workDir ?? null, main: state.mainBotIds?.includes(bot.id) ?? false })),
|
|
67
81
|
mainBotId: state.mainBotIds?.[0] ?? null,
|
|
68
82
|
...Object.fromEntries(values),
|
|
@@ -76,9 +90,10 @@ export function createProductReadModel({ runtime, lifecycle, providers = {}, act
|
|
|
76
90
|
throw error('INVALID_PRODUCT_ACTION', '未知或格式无效的操作。')
|
|
77
91
|
}
|
|
78
92
|
if (typeof actions[request.action] !== 'function') throw error('PRODUCT_ACTION_UNAVAILABLE', '此宿主尚未接入该操作,请查看页面中的接入说明。')
|
|
79
|
-
|
|
93
|
+
const readOnlyReason = runtime.readOnly()
|
|
94
|
+
if (readOnlyReason !== undefined && !READ_ONLY_ACTIONS.has(request.action)) throw error('PRODUCT_READ_ONLY', readOnlyReason)
|
|
80
95
|
// context belongs to authorizeRequest(), never to the JSON request.
|
|
81
|
-
return actions[request.action](request.payload, context)
|
|
96
|
+
return actions[request.action](request.payload, readOnlyReason !== undefined ? { ...context, readOnly: true } : context)
|
|
82
97
|
},
|
|
83
98
|
})
|
|
84
99
|
}
|
|
@@ -195,13 +195,20 @@ function GroupOverview({ room, roster, actions, usage, useWork, openSettings })
|
|
|
195
195
|
|
|
196
196
|
export function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, roster, actions, useSessions, useSessionStatus, useActivity, useWork, useBrowserTabs, useBrowserPages }) {
|
|
197
197
|
const owner = roster.byId[sessionId]
|
|
198
|
-
|
|
198
|
+
// A thread Session is its own part: the drawer shows that thread's
|
|
199
|
+
// transcript, stats, questions and usage, while everything Bot-scoped
|
|
200
|
+
// (settings, model, folder, memory, computer) stays the Bot's — a thread has
|
|
201
|
+
// no authority of its own.
|
|
202
|
+
const thread = roster.threadsById?.[sessionId]
|
|
203
|
+
const threadName = typeof thread?.name === 'string' ? thread.name.trim() : ''
|
|
204
|
+
const part = thread ? (thread.sessionId ?? sessionId) : (owner ? currentPart(owner) : sessionId)
|
|
199
205
|
const isMainBot = isMainOf(roster, owner?.id)
|
|
200
206
|
const [tab, setTab] = useState('details')
|
|
201
207
|
const room = roster.roomsById[sessionId]
|
|
202
208
|
// The info pane swaps its overview for the Bot or group settings view in place.
|
|
203
209
|
const [view, setView] = useState(startTab === 'settings' && (owner || room) ? 'settings' : 'overview')
|
|
204
|
-
|
|
210
|
+
// A thread is named in the thread directory, never by renaming its Bot here.
|
|
211
|
+
const [editing, setEditing] = useState(Boolean(startRenaming && thread === undefined && (roster.byId[sessionId] || roster.roomsById[sessionId])))
|
|
205
212
|
const [draftName, setDraftName] = useState(roster.byId[sessionId]?.name ?? (roster.roomsById[sessionId]?.named ? roster.roomsById[sessionId].name : ''))
|
|
206
213
|
const [copied, setCopied] = useState(false)
|
|
207
214
|
const [error, setError] = useState('')
|
|
@@ -219,7 +226,7 @@ export function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, ro
|
|
|
219
226
|
const running = useSessionStatus(map => map.get(part)?.running === true)
|
|
220
227
|
const doing = useActivity(map => map[bot?.id ?? sessionId])
|
|
221
228
|
const transcript = useSessions(list => transcriptOf(list, part, bot?.name ?? room?.name ?? t('Bot'), roster.exchangeTurns[part]))
|
|
222
|
-
const usage = useSessions(list => usageOf(list, [part, ...(bot?.parts ?? room?.parts ?? [])]))
|
|
229
|
+
const usage = useSessions(list => usageOf(list, [part, ...(thread === undefined ? (bot?.parts ?? room?.parts ?? []) : [])]))
|
|
223
230
|
const run = promise => Promise.resolve(promise)
|
|
224
231
|
.then(() => { setError(''); return true })
|
|
225
232
|
.catch(failure => { setError(failure?.message ?? String(failure)); return false })
|
|
@@ -281,8 +288,11 @@ export function DetailsDrawer({ sessionId, startRenaming, startTab, hasStrip, ro
|
|
|
281
288
|
onChange: event => setDraftName(event.target.value), onBlur: saveName,
|
|
282
289
|
onKeyDown: (event) => { if (event.key === 'Enter') void saveName(); if (event.key === 'Escape') { event.stopPropagation(); setEditing(false) } },
|
|
283
290
|
})
|
|
284
|
-
:
|
|
285
|
-
|
|
291
|
+
: thread !== undefined
|
|
292
|
+
? h('span', { className: 'bt-drawer-name', 'data-thread': '' }, current)
|
|
293
|
+
: h('button', { type: 'button', className: 'bt-drawer-name', 'aria-label': t('Rename {name}', { name: current }), title: t('Rename'), onClick: () => { setDraftName(room && !room.named ? '' : current); setEditing(true) } }, current),
|
|
294
|
+
subtitle ? h('span', { className: 'bt-drawer-role' }, subtitle) : null,
|
|
295
|
+
threadName ? h('span', { className: 'bt-drawer-role', 'data-thread': threadName }, threadName) : null),
|
|
286
296
|
tabs.length > 1 ? h('div', { key: 'tabs', ref: glide.box, className: 'bt-tabs', role: 'tablist', 'data-move': side },
|
|
287
297
|
h('span', { ref: glide.thumb, className: 'bt-thumb', 'aria-hidden': true }),
|
|
288
298
|
tabs.map(key => h('button', { key, type: 'button', role: 'tab', className: 'bt-tab', 'aria-selected': tab === key, onClick: () => setTab(key) }, t(TAB_LABELS[key])))) : null,
|
|
@@ -72,6 +72,11 @@ export function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus
|
|
|
72
72
|
const doing = useActivity(map => map[owner])
|
|
73
73
|
const bot = roster.byId[sessionId]
|
|
74
74
|
const room = roster.roomsById[sessionId]
|
|
75
|
+
// A thread is another own chat of this Bot: the header keeps naming the Bot,
|
|
76
|
+
// because that is who is speaking, and adds the thread so the owner can tell
|
|
77
|
+
// two contexts of the same Bot apart at a glance.
|
|
78
|
+
const thread = roster.threadsById?.[sessionId]
|
|
79
|
+
const threadName = typeof thread?.name === 'string' ? thread.name.trim() : ''
|
|
75
80
|
const goal = room?.goal
|
|
76
81
|
const goalCompose = useUi(state => state.goalCompose === sessionId)
|
|
77
82
|
const goalOpen = useUi(state => state.goalOpen === sessionId)
|
|
@@ -116,7 +121,7 @@ export function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus
|
|
|
116
121
|
}, [showGoal])
|
|
117
122
|
const pill = h('button', {
|
|
118
123
|
type: 'button', className: 'bt-pill', 'aria-expanded': away ? undefined : detailsOpen, 'data-memory': memoryNotice ? 'updated' : undefined,
|
|
119
|
-
'aria-label': away ? t('Back to chat') : memoryNotice ? t('Memory updated. Open {name}\'s memory', { name: roster.byId[changed]?.name ?? name }) : t('View conversation details'),
|
|
124
|
+
'aria-label': away ? t('Back to chat') : memoryNotice ? t('Memory updated. Open {name}\'s memory', { name: roster.byId[changed]?.name ?? name }) : threadName ? `${t('View conversation details')} · ${threadName}` : t('View conversation details'),
|
|
120
125
|
title: away ? name : undefined,
|
|
121
126
|
onClick: () => (away ? selectView('chat') : memoryNotice ? actions.openMemory(changed) : actions.toggleDetails(sessionId)),
|
|
122
127
|
},
|
|
@@ -127,6 +132,7 @@ export function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus
|
|
|
127
132
|
h('span', { className: 'bt-pill-goal' }, h('span', { className: 'bt-pill-ico', 'aria-hidden': true }, h(GoalIcon)), h('span', { className: 'bt-pill-title' }, view.title)),
|
|
128
133
|
h('span', { className: 'bt-pill-name' }, name))
|
|
129
134
|
: h('span', { className: 'bt-pill-name' }, away ? t('Back to chat') : bot?.name ?? room?.name ?? (title || t('New chat'))),
|
|
135
|
+
threadName ? h('span', { className: 'bt-swap' }, threadName) : null,
|
|
130
136
|
goalShown && view?.number !== undefined ? h('span', { className: 'bt-pill-count' }, `${view.number}/${view.count}`) : null,
|
|
131
137
|
h(MemoryNotice, { shown: memoryNotice }),
|
|
132
138
|
h('span', { className: 'bt-pill-go', 'aria-hidden': true }, h(GoArrow)))
|
|
@@ -134,7 +140,8 @@ export function HeaderPill({ sessionId, useRoster, useSessions, useSessionStatus
|
|
|
134
140
|
showGoal ? h(GoalIsland, { room, goal, view, open: goalOpen, composing: goalCompose, roomWork, roster, actions, pill }) : pill,
|
|
135
141
|
h('span', { className: 'bt-sr', role: 'status' }, memoryNotice ? t('Memory updated') : ''),
|
|
136
142
|
h(NewMessagePills, { key: sessionId, sessionId, useSessions, useUi, useTurnClock }),
|
|
137
|
-
h(ActivityIndicator, { key: `activity:${sessionId}`, sessionId, useRoster, useSessionStatus, useSession, useLive, useWork, actions })
|
|
143
|
+
h(ActivityIndicator, { key: `activity:${sessionId}`, sessionId, useRoster, useSessionStatus, useSession, useLive, useWork, actions }),
|
|
144
|
+
typeof actions.conversationAddon === 'function' ? h(actions.conversationAddon, { sessionId, bot, room, roster, actions, running }) : null)
|
|
138
145
|
}
|
|
139
146
|
|
|
140
147
|
// New-message pills. "N new messages ↓" takes Back to bottom's place when
|
|
@@ -199,7 +199,7 @@ export function apply(ctx) {
|
|
|
199
199
|
for (const id of pendingAdopt.keys()) {
|
|
200
200
|
if (roster.getSnapshot().agentOf[id] !== undefined || listed[id] === undefined) pendingAdopt.delete(id)
|
|
201
201
|
}
|
|
202
|
-
for (const id of [...value.bots.map(currentPart), ...value.rooms.map(room => room.sessionId ?? room.id)]) {
|
|
202
|
+
for (const id of [...value.bots.map(currentPart), ...value.rooms.map(room => room.sessionId ?? room.id), ...(value.bots ?? []).flatMap(bot => (bot.threads ?? []).map(thread => thread.sessionId))]) {
|
|
203
203
|
if (refreshed.has(id)) continue
|
|
204
204
|
refreshed.add(id)
|
|
205
205
|
void Promise.resolve(ctx.sessions.refreshProjections?.(id)).catch(quietly('refresh projections'))
|
|
@@ -407,6 +407,13 @@ export function apply(ctx) {
|
|
|
407
407
|
// A Bot id or a group chat id, or any part of either's chat, opens the current part.
|
|
408
408
|
const partFor = (id) => {
|
|
409
409
|
const snapshot = roster.getSnapshot()
|
|
410
|
+
// A thread Session, or a thread id, opens its own Session: it is not the
|
|
411
|
+
// Bot's default chat, and opening it is never redirected there. The thread
|
|
412
|
+
// is resolved before the Bot, because a thread Session also resolves to its
|
|
413
|
+
// Bot in `byId` — every other Bot surface needs that, and this one line
|
|
414
|
+
// keeps the two from being confused.
|
|
415
|
+
const thread = snapshot.threadsById?.[id]
|
|
416
|
+
if (thread !== undefined) return thread.sessionId ?? id
|
|
410
417
|
const bot = snapshot.byId[id]
|
|
411
418
|
if (bot !== undefined) return currentPart(bot)
|
|
412
419
|
const room = snapshot.roomsById[id]
|
|
@@ -437,7 +444,7 @@ export function apply(ctx) {
|
|
|
437
444
|
const snapshot = roster.getSnapshot()
|
|
438
445
|
if (!snapshot.ready || !snapshot.mainBotId) return
|
|
439
446
|
const main = mainView()
|
|
440
|
-
if (main !== undefined && snapshot.byId[main] === undefined && snapshot.roomsById[main] === undefined) {
|
|
447
|
+
if (main !== undefined && snapshot.byId[main] === undefined && snapshot.roomsById[main] === undefined && snapshot.threadsById?.[main] === undefined) {
|
|
441
448
|
actions.openSession(snapshot.mainBotId)
|
|
442
449
|
}
|
|
443
450
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createElement as h, useId, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import { useTurnFacts } from './btw.js'
|
|
3
3
|
import { editLines, lineDiff, proposalPlacement } from './learning-card-model.js'
|
|
4
|
-
import {
|
|
4
|
+
import { isOwnChatOf } from './sources.js'
|
|
5
5
|
import { t } from './i18n.js'
|
|
6
6
|
import { CheckIcon, MemoryIcon, PencilIcon } from './icons.js'
|
|
7
7
|
import { spring, SPRING_TAP } from './motion.js'
|
|
@@ -106,7 +106,7 @@ function LearningProposals({ turn, dock, sessionId, useRoster, actions }) {
|
|
|
106
106
|
const facts = useTurnFacts(sessionId, actions)
|
|
107
107
|
const [failed, setFailed] = useState({})
|
|
108
108
|
const bot = roster.byId?.[sessionId]
|
|
109
|
-
if (!bot ||
|
|
109
|
+
if (!bot || !isOwnChatOf(bot, sessionId)) return null
|
|
110
110
|
const proposals = roster.learning?.[bot.id] ?? []
|
|
111
111
|
const visible = [...proposals]
|
|
112
112
|
for (const entry of Object.values(failed)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createElement as h, Fragment, useEffect, useLayoutEffect, useRef, useState, useSyncExternalStore } from 'react'
|
|
2
2
|
import { createPortal } from 'react-dom'
|
|
3
|
-
import {
|
|
3
|
+
import { isOwnChatOf } from './sources.js'
|
|
4
4
|
import { CheckIcon, SlidersIcon } from './icons.js'
|
|
5
5
|
import { BotMark } from './mark.js'
|
|
6
6
|
import { KeyIcon, MaskedInput } from './secret-card.js'
|
|
@@ -202,9 +202,11 @@ export function ModelChoiceDock({ sessionId, useRoster, actions }) {
|
|
|
202
202
|
const bot = roster.byId[sessionId]
|
|
203
203
|
const [busy, setBusy] = useState('')
|
|
204
204
|
const [error, setError] = useState('')
|
|
205
|
-
|
|
205
|
+
// A thread is another own chat of the same Bot, so the Bot's model choice
|
|
206
|
+
// belongs to it exactly as it belongs to the default chat.
|
|
207
|
+
const shown = Boolean(bot) && isOwnChatOf(bot, sessionId) && roster.ready
|
|
206
208
|
useEffect(() => { if (shown) void actions.refreshModels?.() }, [shown, sessionId])
|
|
207
|
-
if (!bot ||
|
|
209
|
+
if (!bot || !isOwnChatOf(bot, sessionId) || !roster.ready) return null
|
|
208
210
|
const models = roster.models ?? []
|
|
209
211
|
// A model whose provider has no API key fails every turn, so it is no choice.
|
|
210
212
|
const usable = models.filter(model => !model.needsKey)
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
import { createElement as h, useEffect, useId, useRef, useState } from 'react'
|
|
6
6
|
import { CheckIcon, CloseIcon, TrashIcon } from './icons.js'
|
|
7
7
|
import { BotAvatar, BotMark } from './mark.js'
|
|
8
|
-
import { contentText
|
|
8
|
+
import { contentText } from './text.js'
|
|
9
9
|
import { dateLocale, t } from './i18n.js'
|
|
10
10
|
import { SPRING_POP, SPRING_SHAPE, spring } from './motion.js'
|
|
11
11
|
import { useDockHold } from './card-hold.js'
|
|
12
|
-
import { heldCardKey } from './sources.js'
|
|
12
|
+
import { heldCardKey, isOwnChatOf } from './sources.js'
|
|
13
13
|
import { CellEmpty } from './cell-empty.js'
|
|
14
14
|
|
|
15
15
|
const MIN_VALUE = 4
|
|
@@ -178,7 +178,7 @@ export function SecretDock({ sessionId, useRoster, useCards, useHeldCards, actio
|
|
|
178
178
|
const shown = useCards?.(state => state[sessionId]) ?? NONE
|
|
179
179
|
const held = useHeldCards?.(state => state[sessionId]) ?? NONE
|
|
180
180
|
const bot = roster.byId[sessionId]
|
|
181
|
-
if (!bot ||
|
|
181
|
+
if (!bot || !isOwnChatOf(bot, sessionId)) return null
|
|
182
182
|
const drawn = entry => entry.callId && entry.sessionId === sessionId && shown[entry.callId] > 0 && !(held[heldCardKey('secret', entry.id)] > 0)
|
|
183
183
|
const waiting = (roster.secretRequests?.[bot.id] ?? []).filter(entry => entry.status === 'pending' && !drawn(entry))
|
|
184
184
|
if (waiting.length === 0) return null
|
|
@@ -16,7 +16,7 @@ export function createSource(initial) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
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 }
|
|
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: {}, threadsById: {}, ready: false }
|
|
20
20
|
// Any Main Bot holds the Main Bot powers; `mainBotId`, the first one, is the sidebar tile.
|
|
21
21
|
export const isMainOf = (roster, id) => id !== undefined && (roster.mainBotIds ?? []).includes(id)
|
|
22
22
|
|
|
@@ -28,6 +28,21 @@ export function indexRoster(value) {
|
|
|
28
28
|
for (const bot of value.bots) {
|
|
29
29
|
for (const id of [...(bot.parts ?? []), bot.sessionId ?? bot.id, bot.id]) byId[id] = bot
|
|
30
30
|
}
|
|
31
|
+
// A thread is another own chat of its Bot, and it is indexed twice on
|
|
32
|
+
// purpose. `threadsById` holds the thread itself — its own Session, its name,
|
|
33
|
+
// its archive — so opening one opens the thread and never the Bot's default
|
|
34
|
+
// chat. Its Session also resolves to its Bot in `byId`, so every surface that
|
|
35
|
+
// asks "whose chat is this?" (the header, the composer's extra menu, the
|
|
36
|
+
// details drawer, and with it the model, memory, computer, voice and
|
|
37
|
+
// attachment controls) keeps the Bot instead of going blank inside a thread.
|
|
38
|
+
// A thread never takes a Bot's own chat identity: a Session the Bot already
|
|
39
|
+
// owns stays the Bot's.
|
|
40
|
+
const threadsById = {}
|
|
41
|
+
for (const bot of value.bots) for (const thread of bot.threads ?? []) {
|
|
42
|
+
if (thread.sessionId !== undefined) threadsById[thread.sessionId] = { botId: bot.id, ...thread }
|
|
43
|
+
if (thread.threadId !== undefined) threadsById[thread.threadId] = { botId: bot.id, ...thread }
|
|
44
|
+
if (thread.sessionId !== undefined && byId[thread.sessionId] === undefined) byId[thread.sessionId] = bot
|
|
45
|
+
}
|
|
31
46
|
const roomsById = {}
|
|
32
47
|
// A group's own Session is a chain of parts like a Bot's chat: `room.id` is its
|
|
33
48
|
// first part's id and `sessionId` its current one; every part id finds the room.
|
|
@@ -41,9 +56,17 @@ export function indexRoster(value) {
|
|
|
41
56
|
agentsById[agent.id] = agent
|
|
42
57
|
for (const sessionId of agent.sessions ?? []) agentOf[sessionId] = agent.id
|
|
43
58
|
}
|
|
44
|
-
return { ...value, agents: value.agents ?? [], byId, roomsById, agentsById, agentOf, ready: true }
|
|
59
|
+
return { ...value, agents: value.agents ?? [], byId, roomsById, agentsById, agentOf, threadsById, ready: true }
|
|
45
60
|
}
|
|
46
61
|
|
|
62
|
+
// Whether a Session is one of this Bot's own chats: its current part, or one of
|
|
63
|
+
// its threads' Sessions. A thread is another own chat of the same Bot, so the
|
|
64
|
+
// Bot-scoped surfaces (model choice, memory learning, secret requests, turn
|
|
65
|
+
// errors) belong to it exactly as they belong to the default chat. Earlier
|
|
66
|
+
// parts are deliberately not included: they keep the behaviour they had.
|
|
67
|
+
export const isOwnChatOf = (bot, sessionId) => bot !== undefined && bot !== null && typeof sessionId === 'string'
|
|
68
|
+
&& ((bot.sessionId ?? bot.id) === sessionId || (bot.threads ?? []).some(thread => thread.sessionId === sessionId))
|
|
69
|
+
|
|
47
70
|
// Whether a main-view Session id belongs to the team's DSH Agent: the conversation
|
|
48
71
|
// group then stays withdrawn while the shell keeps the sidebar. Before the roster
|
|
49
72
|
// arrives, the ids cached from the last pull answer.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { createElement as h, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import { isHiddenTurn } from './text.js'
|
|
3
3
|
import { CheckIcon, CopyIcon } from './icons.js'
|
|
4
4
|
import { ModelKeyCard, manageModels } from './model-menu.js'
|
|
5
5
|
import { knownFailure } from './diagnostics.js'
|
|
6
6
|
import { hostText, t } from './i18n.js'
|
|
7
|
+
import { isOwnChatOf } from './sources.js'
|
|
7
8
|
|
|
8
9
|
// -------------------------------------------------------------------------
|
|
9
10
|
// A failed turn in a Bot's chat: what went wrong and what the user can do about it,
|
|
@@ -157,7 +158,7 @@ export function FailureCell({ node, sessionId, useRoster, useTurnClock, actions
|
|
|
157
158
|
const provider = blocked?.provider ?? providerOf(bot, roster)
|
|
158
159
|
const text = failureText(data.code, { name: bot?.name ?? t('This Bot'), provider: providerName(roster, provider), status: blocked?.status })
|
|
159
160
|
const facts = failureFacts(data, blocked)
|
|
160
|
-
const current = bot !== undefined &&
|
|
161
|
+
const current = bot !== undefined && isOwnChatOf(bot, sessionId) && !later
|
|
161
162
|
// The key route never takes the account's credential, so a sign-in alone leaves the Bot
|
|
162
163
|
// failing; it moves onto the account route. With no account model listed yet, the
|
|
163
164
|
// user picks one.
|
|
@@ -43,8 +43,16 @@ export function install(rt) {
|
|
|
43
43
|
// `sessionId` is the Bot's current own-chat part; `parts` its earlier ones.
|
|
44
44
|
// `waiting`: the Bot holds its greeting until the user picks its model or fixes
|
|
45
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),
|
|
46
|
+
bots: Object.values(state.bots).map(({ segments, threads, kickoff, ...bot }) => ({
|
|
47
|
+
...bot, sessionId: rt.chatOf(bot.id), parts: (segments ?? []).map(segment => segment.sessionId),
|
|
48
|
+
// `sessionId` stays the default chat; `threads` are the Bot's other own
|
|
49
|
+
// chats, each with its own Session. An archived thread keeps its id and
|
|
50
|
+
// its history and opens read-only.
|
|
51
|
+
threads: (threads ?? []).map(thread => ({ threadId: thread.threadId, sessionId: thread.sessionId,
|
|
52
|
+
...(thread.name === undefined ? {} : { name: thread.name }),
|
|
53
|
+
...(thread.createdAt === undefined ? {} : { createdAt: thread.createdAt }),
|
|
54
|
+
...(thread.archivedAt === undefined || thread.archivedAt === null ? {} : { archivedAt: thread.archivedAt }) })),
|
|
55
|
+
...(kickoff ? { waiting: true } : {}),
|
|
48
56
|
})),
|
|
49
57
|
// `sessionId` is the group's own Session's current part, `ownParts` its earlier
|
|
50
58
|
// ones; `parts` the members' hidden group Sessions and their earlier segments,
|
|
@@ -435,6 +443,7 @@ export function install(rt) {
|
|
|
435
443
|
await rt.load()
|
|
436
444
|
const past = rt.pastOwners.get(body.sessionId)
|
|
437
445
|
const sessionId = rt.botOf(body.sessionId) ? rt.chatOf(body.sessionId) : past && past.roomId === undefined ? rt.chatOf(past.botId) : body.sessionId
|
|
446
|
+
if (past?.threadId !== undefined) return fail('This thread is archived; open it again to write in it', 'bot/archived')
|
|
438
447
|
const question = rt.own(state.questions, sessionId)
|
|
439
448
|
if (question === undefined || question.id !== body.questionId) return fail('This question is no longer open', 'bot/stale')
|
|
440
449
|
const reply = questionReply(question, body.answer)
|
|
@@ -458,6 +467,7 @@ export function install(rt) {
|
|
|
458
467
|
await rt.load()
|
|
459
468
|
const past = rt.pastOwners.get(body.sessionId)
|
|
460
469
|
const sessionId = rt.botOf(body.sessionId) ? rt.chatOf(body.sessionId) : past && past.roomId === undefined ? rt.chatOf(past.botId) : body.sessionId
|
|
470
|
+
if (past?.threadId !== undefined) return fail('This thread is archived; open it again to write in it', 'bot/archived')
|
|
461
471
|
const pending = rt.own(state.views, sessionId)
|
|
462
472
|
if (pending === undefined || pending.id !== body.viewId) return fail('This view is no longer open', 'bot/stale')
|
|
463
473
|
const checked = validateViewAnswer(pending.view, body.action, body.values)
|
|
@@ -485,6 +495,8 @@ export function install(rt) {
|
|
|
485
495
|
// A Bot id, a group chat's id, or an earlier part of either means the
|
|
486
496
|
// current part.
|
|
487
497
|
const past = rt.pastOwners.get(body.sessionId)
|
|
498
|
+
// An archived thread keeps its history but takes no new input.
|
|
499
|
+
if (past?.threadId !== undefined) return fail('This thread is archived; open it again to write in it', 'bot/archived')
|
|
488
500
|
const room = rt.roomOf(body.sessionId)
|
|
489
501
|
const sessionId = rt.botOf(body.sessionId) ? rt.chatOf(body.sessionId)
|
|
490
502
|
: room !== undefined ? rt.sessionOfRoom(room)
|
|
@@ -866,7 +866,7 @@ export function install(rt) {
|
|
|
866
866
|
// A deleted Bot's memory moves to the archive, whole.
|
|
867
867
|
function archiveBotMemory(botId) {
|
|
868
868
|
const bot = rt.botOf(botId)
|
|
869
|
-
for (const sessionId of
|
|
869
|
+
for (const sessionId of rt.sessionsOfBot(botId)) renewMemory(sessionId)
|
|
870
870
|
summaries.delete(botId)
|
|
871
871
|
news.delete(botId)
|
|
872
872
|
return queued(async () => {
|
|
@@ -244,6 +244,13 @@ export function install(rt) {
|
|
|
244
244
|
await selectSessionModel(sessionId, route)
|
|
245
245
|
.catch(error => rt.warn('ds-bot: model for %s in a group not applied: %s', bot.name, error))
|
|
246
246
|
}
|
|
247
|
+
// A thread follows its Bot's model, as a group Session does.
|
|
248
|
+
for (const sessionId of rt.threadsOf(bot.id).map(thread => thread.sessionId)) {
|
|
249
|
+
const applied = sessionSelections.get(sessionId)
|
|
250
|
+
if (!force && applied !== undefined && sameSelection(applied, route)) continue
|
|
251
|
+
await selectSessionModel(sessionId, route)
|
|
252
|
+
.catch(error => rt.warn('ds-bot: model for a thread of %s not applied: %s', bot.name, error))
|
|
253
|
+
}
|
|
247
254
|
if (!ownChanged) return ref
|
|
248
255
|
bot.appliedModel = ref
|
|
249
256
|
if (effort === undefined) delete bot.appliedEffort
|
|
@@ -446,6 +446,12 @@ export function install(rt) {
|
|
|
446
446
|
const header = session.requestHeader()
|
|
447
447
|
const route = header?.config
|
|
448
448
|
if (!route?.provider || !route?.model) throw new Error('the Session has not called a model yet')
|
|
449
|
+
// The Host's prepared auxiliary stream binds one adapter generation for this note and
|
|
450
|
+
// admits the money against that same generation. A Host that has not wired it refuses
|
|
451
|
+
// the note here, rather than re-resolving the route at dispatch the way a bare stream
|
|
452
|
+
// does.
|
|
453
|
+
const auxiliary = ctx.get('botAuxiliaryModelStream')
|
|
454
|
+
if (typeof auxiliary !== 'function') throw new Error('the Host has no prepared auxiliary model stream')
|
|
449
455
|
const messages = session.deriveMessages()
|
|
450
456
|
const base = folder ? FOLDER_PROMPT : HANDOFF_PROMPT
|
|
451
457
|
let retry
|
|
@@ -453,7 +459,7 @@ export function install(rt) {
|
|
|
453
459
|
for (let attempt = 1; attempt <= NOTE_ATTEMPTS; attempt += 1) {
|
|
454
460
|
const prompt = retry === undefined ? base : `${base}\n\n${retry}`
|
|
455
461
|
const assembler = new BlockAssembler()
|
|
456
|
-
for await (const chunk of
|
|
462
|
+
for await (const chunk of auxiliary.stream({
|
|
457
463
|
provider: route.provider,
|
|
458
464
|
model: route.model,
|
|
459
465
|
...(route.reasoningEffort ? { reasoningEffort: route.reasoningEffort } : {}),
|
|
@@ -515,6 +521,12 @@ export function install(rt) {
|
|
|
515
521
|
const session = agent.session
|
|
516
522
|
const route = session.requestHeader()?.config
|
|
517
523
|
if (!route?.provider || !route?.model) throw new Error('the Session has not called a model yet')
|
|
524
|
+
// The Host's prepared auxiliary stream binds one adapter generation for this checkpoint,
|
|
525
|
+
// so the summary is written by the generation the accounting admitted. A Host that has
|
|
526
|
+
// not wired it refuses the checkpoint instead of pricing one generation and streaming
|
|
527
|
+
// another.
|
|
528
|
+
const auxiliary = ctx.get('botAuxiliaryModelStream')
|
|
529
|
+
if (typeof auxiliary !== 'function') throw new Error('the Host has no prepared auxiliary model stream')
|
|
518
530
|
// The results list grows with the work, so the checkpoint may write as much as one
|
|
519
531
|
// of the Bot's own replies.
|
|
520
532
|
const maxTokens = route.maxTokens
|
|
@@ -522,7 +534,7 @@ export function install(rt) {
|
|
|
522
534
|
const tail = checkpointTail(head, session.deriveMessages())
|
|
523
535
|
const prompt = tail.length === 0 ? CHECKPOINT_PROMPT : `${CHECKPOINT_PROMPT}\n\n${checkpointTailNote(tail)}`
|
|
524
536
|
const assembler = new BlockAssembler()
|
|
525
|
-
for await (const chunk of
|
|
537
|
+
for await (const chunk of auxiliary.stream({
|
|
526
538
|
provider: route.provider,
|
|
527
539
|
model: route.model,
|
|
528
540
|
...(route.reasoningEffort ? { reasoningEffort: route.reasoningEffort } : {}),
|
|
@@ -571,21 +583,22 @@ export function install(rt) {
|
|
|
571
583
|
lines.push({ who: line.who, text: line.who === 'you' || line.who === 'user' ? line.text.replace(/^(?:You|User): /, '') : line.text })
|
|
572
584
|
if (lines.length >= STEWARD_LINES) break
|
|
573
585
|
}
|
|
574
|
-
|
|
575
|
-
//
|
|
576
|
-
|
|
586
|
+
// The decision needs no long thinking. Whether this model can be asked to think less
|
|
587
|
+
// is decided from the generation this very call prepares, not from a read of whatever
|
|
588
|
+
// registration happens to be current now.
|
|
589
|
+
const auxiliary = ctx.get('botAuxiliaryModelStream')
|
|
590
|
+
if (typeof auxiliary !== 'function') throw new Error('the Host has no prepared auxiliary model stream')
|
|
577
591
|
const assembler = new BlockAssembler()
|
|
578
|
-
for await (const chunk of
|
|
592
|
+
for await (const chunk of auxiliary.stream({
|
|
579
593
|
provider: route.provider,
|
|
580
594
|
model: route.model,
|
|
581
|
-
...(off ? { reasoningEffort: 'off' } : {}),
|
|
582
595
|
system: STEWARD_SYSTEM,
|
|
583
596
|
messages: rt.tagUsage([{ role: 'user', content: [{ type: 'text', text: stewardRequest({ lines, midTurn, checkpoints: checkpointCount(session.deriveMessages()), group }) }] }], { sessionId: agent.id, kind: 'compaction' }),
|
|
584
597
|
maxTokens: STEWARD_MAX_TOKENS,
|
|
585
598
|
sessionId: session.id,
|
|
586
599
|
purpose: 'compaction',
|
|
587
600
|
signal: AbortSignal.timeout(STEWARD_TIMEOUT_MS),
|
|
588
|
-
})) assembler.push(chunk)
|
|
601
|
+
}, { controls: auxiliary.reasoningOff })) assembler.push(chunk)
|
|
589
602
|
const finish = assembler.finish
|
|
590
603
|
if (finish.kind === 'error' || finish.kind === 'aborted') throw new Error(finish.failure?.message ?? finish.kind)
|
|
591
604
|
return parseDecision(assembler.blocks().filter(block => block.type === 'text').map(block => block.text).join('\n'))
|
|
@@ -732,7 +745,10 @@ export function install(rt) {
|
|
|
732
745
|
|
|
733
746
|
const placeOf = (sessionId) => {
|
|
734
747
|
const botId = rt.chatOwners.get(sessionId)
|
|
735
|
-
|
|
748
|
+
// A thread is not a part chain: it keeps its own Session for as long as it
|
|
749
|
+
// lives, so the steward neither condenses it nor hands its input on to the
|
|
750
|
+
// Bot's default chat.
|
|
751
|
+
if (botId !== undefined) return rt.threadOf(sessionId) === undefined ? { botId } : undefined
|
|
736
752
|
const owner = rt.groupOwners.get(sessionId)
|
|
737
753
|
if (owner !== undefined) return { botId: owner.botId, roomId: owner.roomId }
|
|
738
754
|
const roomId = rt.roomOwners.get(sessionId)
|
|
@@ -1234,6 +1250,7 @@ export function install(rt) {
|
|
|
1234
1250
|
// past it.
|
|
1235
1251
|
async function checkContext(agent) {
|
|
1236
1252
|
if (switches.has(agent.id) || freshParts.has(agent.id) || checking.has(agent.id) || readOnly()) return
|
|
1253
|
+
if (placeOf(agent.id) === undefined) return
|
|
1237
1254
|
if (lastTurnEnd(agent)?.data?.reason?.kind === 'error') return
|
|
1238
1255
|
const lines = await contextLines(agent.session)
|
|
1239
1256
|
const tokens = tokensOf(agent.session)
|
|
@@ -30,7 +30,7 @@ export function install(rt) {
|
|
|
30
30
|
const privateBots = () => Object.values(state.bots).filter(bot => bot.private === true)
|
|
31
31
|
// Deleting a private Bot does not make the files and memory it leaves behind public.
|
|
32
32
|
const protectedBots = () => [...new Map([...(state.privateArchives ?? []), ...privateBots()].map(bot => [bot.id, bot])).values()]
|
|
33
|
-
const sessionsOf = bot => [...new Set([bot.id, ...(bot.sessionIds ?? [rt.
|
|
33
|
+
const sessionsOf = bot => [...new Set([bot.id, ...(bot.sessionIds ?? [rt.sessionsOfBot(bot.id)])])]
|
|
34
34
|
const dataRoots = () => [rt.dshHome, rt.home, process.env.DS_BOT_USER_DSH_HOME?.trim()].filter(Boolean)
|
|
35
35
|
|
|
36
36
|
// A path as it is and as the file system resolves it, folded for comparing. realBeyond
|