@ljwei-stak/dsh-model-route-bot 0.66.4 → 0.67.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh-plugin/bot-product.mjs +81 -5
- package/.dsh-plugin/client.js +17490 -1791
- package/README.i18n.yaml +4 -4
- package/README.md +409 -364
- package/README.zh.md +409 -364
- package/apps/integration-client/README.md +31 -7
- package/apps/integration-client/api.mjs +14 -3
- package/apps/integration-client/binding-editor.jsx +308 -109
- package/apps/integration-client/binding-model.mjs +262 -0
- package/apps/integration-client/binding.css +144 -0
- package/apps/integration-client/bot-model-status.mjs +18 -0
- package/apps/integration-client/bot-profile-identity.jsx +197 -0
- package/apps/integration-client/bot-profile.css +57 -0
- package/apps/integration-client/bot-profile.mjs +97 -0
- package/apps/integration-client/bot-settings-dialog.css +18 -0
- package/apps/integration-client/bot-settings-dialog.jsx +111 -0
- package/apps/integration-client/bot-thread-header-bridge.css +20 -0
- package/apps/integration-client/bot-thread-header.jsx +278 -0
- package/apps/integration-client/bot-threads-panel.jsx +378 -0
- package/apps/integration-client/bot-threads.css +169 -0
- package/apps/integration-client/chat-design.mjs +250 -0
- package/apps/integration-client/chat-ui.jsx +175 -0
- package/apps/integration-client/chat.css +477 -0
- package/apps/integration-client/composer-draft-binding.mjs +50 -0
- package/apps/integration-client/composer-draft-bound.jsx +14 -0
- package/apps/integration-client/composer-draft-host.jsx +142 -0
- package/apps/integration-client/composer-draft-session.jsx +287 -0
- package/apps/integration-client/composer-draft-witness.mjs +115 -0
- package/apps/integration-client/composer-draft.css +4 -0
- package/apps/integration-client/composer-draft.mjs +322 -0
- package/apps/integration-client/contact-attention.jsx +54 -0
- package/apps/integration-client/data-panel.jsx +439 -0
- package/apps/integration-client/data.css +69 -0
- package/apps/integration-client/desktop-bridge.mjs +1 -0
- package/apps/integration-client/desktop-http-socket.mjs +49 -0
- package/apps/integration-client/desktop-keyboard.mjs +32 -0
- package/apps/integration-client/desktop-pane.jsx +99 -30
- package/apps/integration-client/desktop-stream.mjs +13 -3
- package/apps/integration-client/desktop-tools.jsx +19 -0
- package/apps/integration-client/desktop-touch.mjs +27 -0
- package/apps/integration-client/desktop.css +41 -0
- package/apps/integration-client/independent-chat-controller.mjs +224 -0
- package/apps/integration-client/independent-chat-entry.jsx +61 -0
- package/apps/integration-client/independent-chat-navigation.mjs +21 -0
- package/apps/integration-client/independent-chat-page.jsx +148 -0
- package/apps/integration-client/independent-chat-rich.jsx +64 -0
- package/apps/integration-client/independent-chat-rich.mjs +30 -0
- package/apps/integration-client/independent-chat.css +73 -0
- package/apps/integration-client/index.jsx +24 -16
- package/apps/integration-client/library-panel.jsx +4 -1
- package/apps/integration-client/licenses/jsqr-1.4.0-LICENSE.txt +202 -0
- package/apps/integration-client/main-route-editor.jsx +94 -0
- package/apps/integration-client/main-route-model.mjs +100 -0
- package/apps/integration-client/model-picker.css +37 -0
- package/apps/integration-client/model-picker.jsx +83 -0
- package/apps/integration-client/native-activity.css +141 -0
- package/apps/integration-client/native-activity.jsx +276 -0
- package/apps/integration-client/native-activity.mjs +384 -0
- package/apps/integration-client/notifications-panel.jsx +186 -0
- package/apps/integration-client/notifications.css +126 -0
- package/apps/integration-client/pairing-qr.jsx +79 -0
- package/apps/integration-client/panels.jsx +126 -115
- package/apps/integration-client/product-slots.jsx +46 -0
- package/apps/integration-client/routine-recorder.mjs +23 -2
- package/apps/integration-client/secure-pairing.jsx +84 -0
- package/apps/integration-client/tasks-panel.jsx +32 -10
- package/apps/integration-client/ui-icons.jsx +17 -0
- package/apps/integration-client/usage-explorer.jsx +43 -0
- package/apps/integration-client/usage-report.mjs +71 -0
- package/apps/integration-client/usage.css +34 -0
- package/apps/integration-client/web-panel-model.mjs +140 -0
- package/apps/integration-client/web-panel-page.jsx +221 -0
- package/apps/integration-client/web-panel-pins-live.jsx +28 -0
- package/apps/integration-client/web-panel.css +139 -0
- package/apps/integration-client/workspace-model.mjs +67 -0
- package/apps/integration-client/workspace-navigation.mjs +56 -0
- package/apps/integration-client/workspace-ui.jsx +173 -0
- package/apps/integration-client/workspace.css +332 -0
- package/modules/bot-runtime/SOURCE.json +176 -50
- package/modules/bot-runtime/index.mjs +169 -13
- package/modules/bot-runtime/product-overlay/OVERLAY.json +128 -2
- package/modules/bot-runtime/product-overlay/base/src/client/header.js +244 -0
- package/modules/bot-runtime/product-overlay/base/src/client/learning-card.js +202 -0
- package/modules/bot-runtime/product-overlay/base/src/client/model-menu.js +371 -0
- package/modules/bot-runtime/product-overlay/base/src/client/secret-card.js +363 -0
- package/modules/bot-runtime/product-overlay/base/src/client/sources.js +68 -0
- package/modules/bot-runtime/product-overlay/base/src/client/turn-error.js +260 -0
- package/modules/bot-runtime/product-overlay/base/src/host/channel.js +647 -0
- package/modules/bot-runtime/product-overlay/base/src/host/memory.js +2050 -0
- package/modules/bot-runtime/product-overlay/base/src/host/models.js +314 -0
- package/modules/bot-runtime/product-overlay/base/src/host/parts.js +1473 -0
- package/modules/bot-runtime/product-overlay/base/src/host/privacy.js +368 -0
- package/modules/bot-runtime/product-overlay/base/src/host/roster.js +776 -0
- package/modules/bot-runtime/product-overlay/base/src/host/store.js +512 -0
- package/modules/bot-runtime/product-overlay/base/src/host/usage.js +653 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/details-drawer.js.patch +48 -2
- package/modules/bot-runtime/product-overlay/patches/src/client/header.js.patch +41 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/index.js.patch +33 -1
- package/modules/bot-runtime/product-overlay/patches/src/client/learning-card.js.patch +20 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/model-menu.js.patch +24 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/secret-card.js.patch +25 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/sources.js.patch +52 -0
- package/modules/bot-runtime/product-overlay/patches/src/client/turn-error.js.patch +23 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/channel.js.patch +46 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/memory.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/models.js.patch +16 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/parts.js.patch +95 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/privacy.js.patch +11 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/roster.js.patch +145 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/store.js.patch +68 -0
- package/modules/bot-runtime/product-overlay/patches/src/host/usage.js.patch +11 -0
- package/modules/bot-runtime/read-model.mjs +19 -4
- package/modules/bot-runtime/vendor/ds-bot/src/client/details-drawer.js +15 -5
- package/modules/bot-runtime/vendor/ds-bot/src/client/header.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/index.js +9 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/learning-card.js +2 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/model-menu.js +5 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/secret-card.js +3 -3
- package/modules/bot-runtime/vendor/ds-bot/src/client/sources.js +25 -2
- package/modules/bot-runtime/vendor/ds-bot/src/client/turn-error.js +3 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/channel.js +14 -2
- package/modules/bot-runtime/vendor/ds-bot/src/host/memory.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/models.js +7 -0
- package/modules/bot-runtime/vendor/ds-bot/src/host/parts.js +26 -9
- package/modules/bot-runtime/vendor/ds-bot/src/host/privacy.js +1 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/roster.js +112 -3
- package/modules/bot-runtime/vendor/ds-bot/src/host/store.js +34 -1
- package/modules/bot-runtime/vendor/ds-bot/src/host/usage.js +1 -1
- package/modules/independent-runtime/index.mjs +152 -62
- package/modules/independent-runtime/remote-tools.mjs +8 -5
- package/modules/openmaus-protocol/NOTICE +10 -0
- package/modules/openmaus-protocol/README.md +15 -0
- package/modules/openmaus-protocol/SOURCE.json +68 -0
- package/modules/openmaus-protocol/dist/protocol.mjs +1203 -0
- package/modules/openmaus-protocol/index.mjs +1 -0
- package/modules/openmaus-protocol/transport.mjs +77 -0
- package/modules/openmaus-protocol/vendor/LICENSE +202 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/README.md +405 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/credentials.ts +128 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/descriptor.ts +84 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/noise.ts +456 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/phone.ts +104 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/session.ts +417 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.json +2565 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.sha256 +1 -0
- package/modules/openmaus-protocol/vendor/companion/src/e2e/yamux.ts +335 -0
- package/package.json +18 -6
- package/packages/bot-acp/README.md +156 -0
- package/packages/bot-acp/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-acp/licenses/NOTICE.md +27 -0
- package/packages/bot-acp/src/acp-adapter.mjs +795 -0
- package/packages/bot-acp/src/acp-host.mjs +148 -0
- package/packages/bot-acp/src/native-launch.mjs +121 -0
- package/packages/bot-acp/src/permission-policy.mjs +283 -0
- package/packages/bot-acp/src/rpc-codec.mjs +265 -0
- package/packages/bot-acp/src/session-core.mjs +581 -0
- package/packages/bot-acp/src/stdio-rpc.mjs +329 -0
- package/packages/bot-engines/README.md +281 -0
- package/packages/bot-engines/licenses/LICENSE-OpenMausBot +202 -0
- package/packages/bot-engines/licenses/NOTICE.md +32 -0
- package/packages/bot-engines/src/claude-engine.mjs +772 -0
- package/packages/bot-engines/src/claude-llm-adapter.mjs +797 -0
- package/packages/bot-engines/src/claude-llm-turn.mjs +292 -0
- package/packages/bot-engines/src/claude-permission-broker.mjs +130 -0
- package/packages/bot-engines/src/claude-permission-proxy.mjs +87 -0
- package/packages/bot-engines/src/claude-protocol.mjs +177 -0
- package/packages/bot-engines/src/driver-catalog.mjs +334 -0
- package/packages/bot-engines/src/host-loop-request.mjs +95 -0
- package/packages/bot-engines/src/host-registrations.mjs +105 -0
- package/packages/capability-broker/src/cli-bootstrap.mjs +86 -16
- package/packages/capability-broker/src/cli-bridge-response.mjs +80 -0
- package/packages/capability-broker/src/cli-stdio-bridge.mjs +21 -8
- package/packages/cli-runtime/src/codex-session-core.mjs +4 -0
- package/packages/cli-runtime/src/native-cli-output.mjs +57 -0
- package/packages/cli-runtime/src/native-peer.ps1 +51 -3
- package/packages/cli-runtime/src/native-process.mjs +77 -14
- package/packages/model-router-plugin/package.json +1 -1
- package/packages/orchestration/src/binding-product.mjs +19 -0
- package/packages/orchestration/src/bot-data-actions.mjs +45 -0
- package/packages/orchestration/src/bot-data-chart.mjs +109 -0
- package/packages/orchestration/src/bot-data-context.mjs +44 -0
- package/packages/orchestration/src/bot-data-engine.mjs +809 -0
- package/packages/orchestration/src/bot-data-io.mjs +186 -0
- package/packages/orchestration/src/bot-data-sql.mjs +195 -0
- package/packages/orchestration/src/bot-data-surface.mjs +144 -0
- package/packages/orchestration/src/bot-data-tools.mjs +68 -0
- package/packages/orchestration/src/bot-data-worker.mjs +228 -0
- package/packages/orchestration/src/bot-independent-execution.mjs +195 -0
- package/packages/orchestration/src/bot-independent-stream.mjs +74 -0
- package/packages/orchestration/src/bot-loop.mjs +152 -19
- package/packages/orchestration/src/bot-notifications.mjs +159 -0
- package/packages/orchestration/src/bot-policy.mjs +43 -4
- package/packages/orchestration/src/bot-profile.mjs +171 -0
- package/packages/orchestration/src/bot-routine.mjs +4 -4
- package/packages/orchestration/src/bot-runtime-assembly.mjs +255 -8
- package/packages/orchestration/src/bot-runtime-switch.mjs +13 -2
- package/packages/orchestration/src/bot-serving.mjs +164 -0
- package/packages/orchestration/src/bot-session-store.mjs +105 -9
- package/packages/orchestration/src/bot-settings.mjs +50 -11
- package/packages/orchestration/src/bot-thread-admission.mjs +84 -0
- package/packages/orchestration/src/bot-thread-host.mjs +187 -0
- package/packages/orchestration/src/bot-threads.mjs +790 -0
- package/packages/orchestration/src/data-product-access.mjs +37 -0
- package/packages/orchestration/src/data-tool-contract.mjs +291 -0
- package/packages/orchestration/src/desktop-http-carrier.mjs +120 -0
- package/packages/orchestration/src/desktop-proxy.mjs +311 -0
- package/packages/orchestration/src/desktop-sessions.mjs +43 -9
- package/packages/orchestration/src/ds-lifecycle.mjs +31 -4
- package/packages/orchestration/src/grant-refresh-queue.mjs +17 -0
- package/packages/orchestration/src/independent-chat-page.mjs +39 -0
- package/packages/orchestration/src/independent-chat-service.mjs +308 -0
- package/packages/orchestration/src/independent-context-state.mjs +44 -0
- package/packages/orchestration/src/independent-message-wire.mjs +84 -0
- package/packages/orchestration/src/independent-tool-catalog.mjs +175 -0
- package/packages/orchestration/src/independent-tool-families.mjs +73 -0
- package/packages/orchestration/src/independent-tool-host.mjs +344 -0
- package/packages/orchestration/src/main-bot-grant.mjs +15 -0
- package/packages/orchestration/src/main-model-routes.mjs +208 -0
- package/packages/orchestration/src/model-accounting.mjs +99 -35
- package/packages/orchestration/src/model-auxiliary-dispatch.mjs +187 -0
- package/packages/orchestration/src/model-loop-frame.mjs +179 -0
- package/packages/orchestration/src/model-prepared-generation.mjs +175 -0
- package/packages/orchestration/src/model-request-bounds.mjs +111 -0
- package/packages/orchestration/src/native-activity-contract.mjs +491 -0
- package/packages/orchestration/src/native-activity-service.mjs +102 -0
- package/packages/orchestration/src/native-activity-store.mjs +249 -0
- package/packages/orchestration/src/native-conversation-tools.mjs +78 -0
- package/packages/orchestration/src/native-data-tools.mjs +100 -0
- package/packages/orchestration/src/native-remote-actions.mjs +742 -0
- package/packages/orchestration/src/native-team-tools.mjs +73 -0
- package/packages/orchestration/src/pairing-invite.mjs +37 -0
- package/packages/orchestration/src/product-composition.mjs +1559 -1001
- package/packages/orchestration/src/remote-machines.mjs +82 -14
- package/packages/orchestration/src/routine-execution.mjs +5 -3
- package/packages/orchestration/src/routine-session.mjs +19 -13
- package/packages/orchestration/src/secure-pairing-host.mjs +112 -0
- package/packages/orchestration/src/task-journal.mjs +29 -0
- package/packages/orchestration/src/task-product.mjs +17 -0
- package/packages/orchestration/src/team-assignment-store.mjs +120 -0
- package/packages/orchestration/src/team-bridge.mjs +282 -0
- package/packages/orchestration/src/team-scheduler.mjs +83 -0
- package/packages/orchestration/src/team-sdk-tools.mjs +84 -0
- package/packages/orchestration/src/team-tool-contract.mjs +284 -0
- package/packages/orchestration/src/ui-host-namespace.mjs +23 -0
- package/packages/orchestration/src/web-panel-actions.mjs +103 -0
- package/packages/orchestration/src/web-panel-framing.mjs +227 -0
- package/packages/orchestration/src/web-panel-store.mjs +206 -0
- package/remote-computer-agent/README.md +39 -29
- package/remote-computer-agent/build.mjs +3 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_LICENSE +202 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_NOTICE +10 -0
- package/remote-computer-agent/dist/OPENMAUSBOT_SOURCE.json +68 -0
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs +215 -25
- package/remote-computer-agent/dist/cli-stdio-bridge.mjs.map +4 -4
- package/remote-computer-agent/dist/cli.mjs +1688 -168
- package/remote-computer-agent/dist/cli.mjs.map +4 -4
- package/remote-computer-agent/dist/index.mjs +1579 -122
- package/remote-computer-agent/dist/index.mjs.map +4 -4
- package/remote-computer-agent/dist/native-peer.ps1 +51 -3
- package/remote-computer-agent/package-lock.json +308 -2
- package/remote-computer-agent/package.json +2 -1
- package/remote-computer-agent/pair-device.ps1 +20 -0
- package/remote-computer-agent/setup.ps1 +38 -16
- package/remote-computer-agent/src/cli.ts +24 -3
- package/remote-computer-agent/src/desktop-session.ts +11 -3
- package/remote-computer-agent/src/e2e-server.ts +81 -0
- package/remote-computer-agent/src/pairing-page.ts +35 -0
- package/remote-computer-agent/src/protocol.ts +25 -25
- package/remote-computer-agent/src/secure-pairing.ts +105 -0
- package/remote-computer-agent/src/store.ts +20 -5
- package/remote-computer-agent/src/transport.ts +39 -15
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
// Store: the saved team (state.json), its migrations, the lock that keeps a second dsh
|
|
2
|
+
// host on the same data from writing it, and the indexes that map a Session to the Bot
|
|
3
|
+
// or group chat it belongs to.
|
|
4
|
+
import { copyFileSync, mkdirSync, readFileSync, statSync } from 'node:fs'
|
|
5
|
+
import { mkdir, unlink, writeFile } from 'node:fs/promises'
|
|
6
|
+
import { join } from 'node:path'
|
|
7
|
+
import { LEGACY_THEMES, MAX_EXCHANGES, ROOM_MODES, TEXT_KEPT } from './constants.js'
|
|
8
|
+
import { replaceFile } from './files.js'
|
|
9
|
+
import { homeLock } from './home-lock.js'
|
|
10
|
+
import { currentColor, headOf, normalize, unquote } from './text.js'
|
|
11
|
+
|
|
12
|
+
// A table read that sees only the keys the table holds itself. Ids that come from
|
|
13
|
+
// outside (a request body, a tool argument, a name a model gives) go through this, so
|
|
14
|
+
// "__proto__" and other inherited names read as "not there" instead of returning
|
|
15
|
+
// Object.prototype.
|
|
16
|
+
export const own = (table, key) => (table != null && Object.hasOwn(table, key) ? table[key] : undefined)
|
|
17
|
+
|
|
18
|
+
export function install(rt) {
|
|
19
|
+
const { ctx, home, statePath } = rt
|
|
20
|
+
|
|
21
|
+
/** @type {{version:1, workspaceId?:string, mainBotIds:string[], bots:Record<string,any>, privateArchives:any[], rooms:Record<string,any>, agents:Record<string,any>, capabilities:{items:Record<string,any>}, exchanges:any[], exchangeTurns:Record<string,number[]>, revision:number}} */
|
|
22
|
+
const state = { version: 1, mainBotIds: [], bots: {}, privateArchives: [], rooms: {}, agents: {}, capabilities: { items: {} }, exchanges: [], exchangeTurns: {}, works: {}, requests: {}, questions: {}, views: {}, prefs: {}, switching: {}, revision: 0 }
|
|
23
|
+
let loaded = null
|
|
24
|
+
let writing = Promise.resolve()
|
|
25
|
+
const { warn } = rt
|
|
26
|
+
|
|
27
|
+
const EMPTY = JSON.stringify(state)
|
|
28
|
+
// The other dsh host that holds state.lock while this one only reads (see home-lock.js);
|
|
29
|
+
// undefined while this host may write.
|
|
30
|
+
let holder
|
|
31
|
+
let lockBroken = false
|
|
32
|
+
let stuck
|
|
33
|
+
let warnedReadOnly = false
|
|
34
|
+
let stopped = false
|
|
35
|
+
// mtime and size of the state.json this host last read or wrote.
|
|
36
|
+
let seen = ''
|
|
37
|
+
// Bumped each time the in-memory state is replaced from the file, so a queued
|
|
38
|
+
// save cannot write a snapshot taken of the state it replaced.
|
|
39
|
+
let lockEpoch = 0
|
|
40
|
+
const stamp = () => {
|
|
41
|
+
try {
|
|
42
|
+
const { mtimeMs, size } = statSync(statePath)
|
|
43
|
+
return `${mtimeMs}:${size}`
|
|
44
|
+
} catch {
|
|
45
|
+
return ''
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const lock = homeLock(join(home, 'state.lock'))
|
|
49
|
+
const claimLock = () => {
|
|
50
|
+
if (lockBroken) return
|
|
51
|
+
try {
|
|
52
|
+
mkdirSync(home, { recursive: true })
|
|
53
|
+
holder = lock.claim()
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// An unusable lock (a read-only or odd file system) must not stop the team; a
|
|
56
|
+
// save that then cannot write still rejects.
|
|
57
|
+
lockBroken = true
|
|
58
|
+
holder = undefined
|
|
59
|
+
warn('ds-bot: cannot lock %s, so another dsh on the same data could undo changes: %s', home, error)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const readOnly = () => {
|
|
63
|
+
if (stuck !== undefined) return `The Bot team file ${statePath} could not be read (${stuck}). DS Bot does not change it; fix or move the file and reload.`
|
|
64
|
+
if (holder === undefined) return undefined
|
|
65
|
+
const where = holder.pid ? `pid ${holder.pid}${holder.host ? ` on ${holder.host}` : ''}` : 'unknown process'
|
|
66
|
+
return `The Bot team in ${home} is open in another dsh (Web or Desktop, ${where}). This window shows the team but cannot change it; close the other one and reload.`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Synchronous on purpose: the saved team and the session indexes built from it
|
|
70
|
+
// exist before the first tool or shell env call can resolve a Session.
|
|
71
|
+
const readFileState = () => {
|
|
72
|
+
let contents
|
|
73
|
+
try {
|
|
74
|
+
contents = readFileSync(statePath, 'utf8')
|
|
75
|
+
} catch (error) {
|
|
76
|
+
seen = stamp()
|
|
77
|
+
if (error?.code === 'ENOENT') {
|
|
78
|
+
stuck = undefined
|
|
79
|
+
return undefined
|
|
80
|
+
}
|
|
81
|
+
stuck = error?.code ?? error?.message ?? String(error)
|
|
82
|
+
warn('ds-bot: unreadable state %s: %s', statePath, error)
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
seen = stamp()
|
|
86
|
+
let parsed
|
|
87
|
+
try {
|
|
88
|
+
parsed = JSON.parse(contents)
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const backup = `${statePath}.broken-${Date.now()}`
|
|
91
|
+
try {
|
|
92
|
+
copyFileSync(statePath, backup)
|
|
93
|
+
stuck = undefined
|
|
94
|
+
warn('ds-bot: unreadable state %s: %s; backed up as %s', statePath, error, backup)
|
|
95
|
+
} catch (copyError) {
|
|
96
|
+
stuck = copyError?.code ?? copyError?.message ?? error?.message ?? String(error)
|
|
97
|
+
warn('ds-bot: unreadable state %s: %s; backup failed: %s', statePath, error, copyError)
|
|
98
|
+
}
|
|
99
|
+
return undefined
|
|
100
|
+
}
|
|
101
|
+
if (parsed?.version === 1) {
|
|
102
|
+
stuck = undefined
|
|
103
|
+
return parsed
|
|
104
|
+
}
|
|
105
|
+
stuck = 'saved by a newer DS Bot; update DS Bot'
|
|
106
|
+
return undefined
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Fill `state` from the saved file and bring an older team up to date. Returns the
|
|
110
|
+
// groups whose Session titles still need the new name.
|
|
111
|
+
const adopt = (parsed) => {
|
|
112
|
+
lockEpoch += 1
|
|
113
|
+
const hadCapabilities = parsed !== null && parsed !== undefined && Object.hasOwn(parsed, 'capabilities')
|
|
114
|
+
for (const key of Object.keys(state)) delete state[key]
|
|
115
|
+
Object.assign(state, JSON.parse(EMPTY), parsed ?? {})
|
|
116
|
+
state.prefs ??= {}
|
|
117
|
+
// Teams saved before the DSH Agent existed carry no `agents`; corrupt values reset.
|
|
118
|
+
if (state.agents === null || typeof state.agents !== 'object' || Array.isArray(state.agents)) state.agents = {}
|
|
119
|
+
let migrated = !hadCapabilities
|
|
120
|
+
if (state.capabilities === null || typeof state.capabilities !== 'object' || Array.isArray(state.capabilities)) {
|
|
121
|
+
state.capabilities = { items: {} }
|
|
122
|
+
migrated = true
|
|
123
|
+
} else if (state.capabilities.items === null || typeof state.capabilities.items !== 'object' || Array.isArray(state.capabilities.items)) {
|
|
124
|
+
state.capabilities.items = {}
|
|
125
|
+
migrated = true
|
|
126
|
+
}
|
|
127
|
+
for (const bot of Object.values(state.bots)) {
|
|
128
|
+
if (!Array.isArray(bot.capabilities)) {
|
|
129
|
+
bot.capabilities = []
|
|
130
|
+
migrated = true
|
|
131
|
+
}
|
|
132
|
+
if (bot.color !== currentColor(bot.color)) {
|
|
133
|
+
bot.color = currentColor(bot.color)
|
|
134
|
+
migrated = true
|
|
135
|
+
}
|
|
136
|
+
// LEGACY: Bots saved with a lab ran on that lab's default model; the model they
|
|
137
|
+
// last ran on becomes their own, so dropping the lab changes nothing for them.
|
|
138
|
+
if ('lab' in bot) {
|
|
139
|
+
if (!bot.model && bot.appliedModel) bot.model = bot.appliedModel
|
|
140
|
+
delete bot.lab
|
|
141
|
+
migrated = true
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// A team from before several Main Bots were allowed names a single one.
|
|
145
|
+
if (!Array.isArray(state.mainBotIds)) state.mainBotIds = []
|
|
146
|
+
if ('mainBotId' in state) {
|
|
147
|
+
if (state.mainBotId && !state.mainBotIds.includes(state.mainBotId)) state.mainBotIds.unshift(state.mainBotId)
|
|
148
|
+
delete state.mainBotId
|
|
149
|
+
migrated = true
|
|
150
|
+
}
|
|
151
|
+
state.mainBotIds = state.mainBotIds.filter(id => state.bots[id])
|
|
152
|
+
if (!Array.isArray(state.privateArchives)) state.privateArchives = []
|
|
153
|
+
// Teams from before the whale was stored at setup: their first Bot, while still a
|
|
154
|
+
// Main Bot, gets it once wherever it has no look of its own. After that the whale is
|
|
155
|
+
// an ordinary look the user can change, and it never passes to another Bot.
|
|
156
|
+
if (state.whaleStored !== true) {
|
|
157
|
+
const first = Object.values(state.bots).reduce((best, bot) => (best === undefined || (bot.createdAt ?? 0) < (best.createdAt ?? 0) ? bot : best), undefined)
|
|
158
|
+
if (first !== undefined && state.mainBotIds.includes(first.id)) {
|
|
159
|
+
if (!first.avatar?.shape && !first.avatar?.image) first.avatar = { ...first.avatar, shape: 'whale' }
|
|
160
|
+
if (!first.color) first.color = 'deepseek'
|
|
161
|
+
}
|
|
162
|
+
state.whaleStored = true
|
|
163
|
+
if (first !== undefined) migrated = true
|
|
164
|
+
}
|
|
165
|
+
if (Object.hasOwn(LEGACY_THEMES, state.prefs.theme ?? '')) {
|
|
166
|
+
state.prefs.theme = LEGACY_THEMES[state.prefs.theme]
|
|
167
|
+
migrated = true
|
|
168
|
+
}
|
|
169
|
+
if (state.works === null || typeof state.works !== 'object' || Array.isArray(state.works)) state.works = {}
|
|
170
|
+
if (state.requests === null || typeof state.requests !== 'object' || Array.isArray(state.requests)) state.requests = {}
|
|
171
|
+
if (state.views === null || typeof state.views !== 'object' || Array.isArray(state.views)) state.views = {}
|
|
172
|
+
// LEGACY: aside cards lived in this file only before the feature shipped;
|
|
173
|
+
// they are kept under btw/ per part now. A saved copy is dropped, not moved.
|
|
174
|
+
if ('btwCards' in state) {
|
|
175
|
+
delete state.btwCards
|
|
176
|
+
migrated = true
|
|
177
|
+
}
|
|
178
|
+
const writer = holder === undefined && stuck === undefined
|
|
179
|
+
// LEGACY: Bots no longer count the rounds they start in a group.
|
|
180
|
+
for (const room of Object.values(state.rooms)) {
|
|
181
|
+
if (!('botRounds' in room)) continue
|
|
182
|
+
delete room.botRounds
|
|
183
|
+
migrated = true
|
|
184
|
+
}
|
|
185
|
+
// LEGACY: the late-round limit went with the round caps, and the relay limit that
|
|
186
|
+
// followed it went too. Calls it parked stay until the group's next round runs them.
|
|
187
|
+
for (const room of Object.values(state.rooms)) {
|
|
188
|
+
if (['lateRounds', 'quietNoticed', 'quietShown', 'relays', 'relayLimit'].every(key => room[key] === undefined)) continue
|
|
189
|
+
delete room.lateRounds
|
|
190
|
+
delete room.quietNoticed
|
|
191
|
+
delete room.quietShown
|
|
192
|
+
delete room.relays
|
|
193
|
+
delete room.relayLimit
|
|
194
|
+
migrated = true
|
|
195
|
+
}
|
|
196
|
+
// Groups from before group settings existed reply in turn, led by a Main Bot member.
|
|
197
|
+
for (const room of Object.values(state.rooms)) {
|
|
198
|
+
if (room.admin !== undefined && ROOM_MODES.includes(room.mode)) continue
|
|
199
|
+
if (room.admin === undefined) room.admin = room.members.find(id => state.mainBotIds.includes(id)) ?? null
|
|
200
|
+
if (!ROOM_MODES.includes(room.mode)) room.mode = 'everyone'
|
|
201
|
+
room.notice ??= ''
|
|
202
|
+
migrated = true
|
|
203
|
+
}
|
|
204
|
+
// A Bot deleted while a group was made or changed could stay listed in it, even as its
|
|
205
|
+
// admin; it leaves the group now.
|
|
206
|
+
for (const room of Object.values(state.rooms)) {
|
|
207
|
+
const members = room.members.filter(id => state.bots[id])
|
|
208
|
+
const admin = members.includes(room.admin) ? room.admin : null
|
|
209
|
+
if (members.length === room.members.length && admin === room.admin) continue
|
|
210
|
+
room.members = members
|
|
211
|
+
room.admin = admin
|
|
212
|
+
migrated = true
|
|
213
|
+
}
|
|
214
|
+
// Teams from before #206 kept every long message whole in this file, written whole on
|
|
215
|
+
// every save. A message between Bots is whole in its recipient's Session; a group
|
|
216
|
+
// line's whole text moves to a file of its own, which only the writing host makes.
|
|
217
|
+
for (const entry of state.exchanges) {
|
|
218
|
+
if (typeof entry.text !== 'string' || entry.text.length <= TEXT_KEPT || entry.length !== undefined) continue
|
|
219
|
+
entry.length = entry.text.length
|
|
220
|
+
entry.text = headOf(entry.text, TEXT_KEPT)
|
|
221
|
+
migrated = true
|
|
222
|
+
}
|
|
223
|
+
for (const room of writer ? Object.values(state.rooms) : []) {
|
|
224
|
+
if (!Array.isArray(room.log)) continue
|
|
225
|
+
const log = room.log.map(line => (typeof line.text === 'string' && line.length === undefined && typeof line.seq === 'number' ? rt.keepGroupLine(room.id, line, line.text) : line))
|
|
226
|
+
if (log.some((line, index) => line !== room.log[index])) {
|
|
227
|
+
room.log = log
|
|
228
|
+
migrated = true
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Unnamed groups from before names followed their members may still list old names.
|
|
232
|
+
const stale = Object.values(state.rooms).filter(room => !room.named && room.name !== rt.autoRoomName(room.members))
|
|
233
|
+
for (const room of stale) room.name = rt.autoRoomName(room.members)
|
|
234
|
+
indexSessions()
|
|
235
|
+
return { migrated: migrated || stale.length > 0, stale }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const load = () => loaded ??= (async () => {
|
|
239
|
+
claimLock()
|
|
240
|
+
if (holder !== undefined) warn('ds-bot: %s', readOnly())
|
|
241
|
+
const { migrated, stale } = adopt(readFileState())
|
|
242
|
+
if (holder !== undefined) return
|
|
243
|
+
if (migrated && stuck === undefined) {
|
|
244
|
+
// A failed write must not poison `loaded`: the migrated state stays in
|
|
245
|
+
// memory and the next save() writes it.
|
|
246
|
+
try {
|
|
247
|
+
await save()
|
|
248
|
+
} catch (error) {
|
|
249
|
+
warn('ds-bot: saving the migrated Bot team failed: %s', error)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Not awaited: renaming a session must not hold up the first roster read.
|
|
253
|
+
for (const room of stale) void rt.retitleRoom(room)
|
|
254
|
+
})()
|
|
255
|
+
|
|
256
|
+
// An outside edit of state.json while this host held the lock, found at a
|
|
257
|
+
// reclaim or by a queued write that saw the file change: this host's in-memory
|
|
258
|
+
// state goes aside first, then the file on disk is adopted. Queued behind
|
|
259
|
+
// `writing` — and behind any adopt already queued — so no save or second
|
|
260
|
+
// refresh slips between finding the change and the adopt.
|
|
261
|
+
let adopting
|
|
262
|
+
const adoptAfterReclaim = () => {
|
|
263
|
+
const run = (adopting ?? writing).then(async () => {
|
|
264
|
+
if (stamp() !== seen) {
|
|
265
|
+
const aside = `${statePath}.replaced-${Date.now()}`
|
|
266
|
+
try {
|
|
267
|
+
await writeFile(aside, JSON.stringify(state))
|
|
268
|
+
warn('ds-bot: %s changed while no host held the team; it is adopted and this host\'s state is kept as %s', statePath, aside)
|
|
269
|
+
} catch (error) {
|
|
270
|
+
warn('ds-bot: %s changed while no host held the team; it is adopted, and this host\'s state is lost because it could not be kept aside: %s', statePath, error)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
adopt(readFileState())
|
|
274
|
+
})
|
|
275
|
+
writing = run.catch(() => {})
|
|
276
|
+
const tracked = run.finally(() => { if (adopting === tracked) adopting = undefined })
|
|
277
|
+
adopting = tracked
|
|
278
|
+
return tracked
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// The lock was this host's and is its own again. An adopt still queued is
|
|
282
|
+
// awaited; otherwise the file is compared with what this host last saw:
|
|
283
|
+
// unchanged means nothing external happened and the in-memory state stays,
|
|
284
|
+
// changed means an outside edit is adopted the usual way.
|
|
285
|
+
const reclaimHeld = () => {
|
|
286
|
+
warnedReadOnly = false
|
|
287
|
+
if (adopting !== undefined) return adopting
|
|
288
|
+
return stamp() === seen ? undefined : adoptAfterReclaim()
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// A reading host follows the file and takes over once the other host has gone.
|
|
292
|
+
const refresh = async () => {
|
|
293
|
+
await load()
|
|
294
|
+
const hadHolder = holder !== undefined
|
|
295
|
+
if (hadHolder) {
|
|
296
|
+
claimLock()
|
|
297
|
+
if (holder === undefined) {
|
|
298
|
+
warn('ds-bot: the other dsh has closed; this one now saves the Bot team')
|
|
299
|
+
warnedReadOnly = false
|
|
300
|
+
adopt(readFileState())
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
} else if (!lockBroken && !lock.owns()) {
|
|
304
|
+
// A cached "writer" is stale once the lock file no longer names this host:
|
|
305
|
+
// another dsh may have taken it over, or the file may just be gone. Re-claim
|
|
306
|
+
// and re-check instead of trusting the flag.
|
|
307
|
+
claimLock()
|
|
308
|
+
if (lockBroken) return
|
|
309
|
+
if (holder === undefined) {
|
|
310
|
+
await reclaimHeld()
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
warn('ds-bot: %s', readOnly())
|
|
314
|
+
}
|
|
315
|
+
if (adopting !== undefined) return adopting
|
|
316
|
+
if ((stuck !== undefined || holder !== undefined) && stamp() !== seen) adopt(readFileState())
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const warnReadOnly = () => {
|
|
320
|
+
if (!warnedReadOnly) {
|
|
321
|
+
warnedReadOnly = true
|
|
322
|
+
warn('ds-bot: not saving the Bot team: %s', readOnly())
|
|
323
|
+
}
|
|
324
|
+
return false
|
|
325
|
+
}
|
|
326
|
+
const writable = () => {
|
|
327
|
+
if (stuck !== undefined) return warnReadOnly()
|
|
328
|
+
if (lockBroken) return true
|
|
329
|
+
if (holder === undefined && lock.owns()) return true
|
|
330
|
+
// Lost the lock: retake it when nobody else holds it (its file was deleted), else
|
|
331
|
+
// another host took it over and this one stops writing.
|
|
332
|
+
if (holder === undefined) {
|
|
333
|
+
claimLock()
|
|
334
|
+
if (holder === undefined) {
|
|
335
|
+
// A changed file is adopted first; the save queues behind that adopt.
|
|
336
|
+
const pending = reclaimHeld()
|
|
337
|
+
if (pending !== undefined) void pending.catch(() => {})
|
|
338
|
+
return true
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return warnReadOnly()
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const save = ({ required = false } = {}) => {
|
|
345
|
+
if (stopped) return required ? Promise.reject(new Error('The Bot team cannot be saved after this host stopped.')) : writing
|
|
346
|
+
indexSessions()
|
|
347
|
+
// A reading host keeps the file's revision, so its window does not skip the next change.
|
|
348
|
+
if (!writable()) return required ? Promise.reject(new Error(`This host cannot save the Bot team: ${readOnly()}`)) : writing
|
|
349
|
+
const run = writing.then(async () => {
|
|
350
|
+
// Re-check writability once the queued write runs: an adopt queued before
|
|
351
|
+
// it may have made this host read-only (e.g. a file a newer DS Bot wrote).
|
|
352
|
+
if (stuck !== undefined || holder !== undefined) {
|
|
353
|
+
if (required) throw new Error(`This host cannot save the Bot team: ${readOnly()}`)
|
|
354
|
+
warnReadOnly()
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
// The snapshot is taken here, so a save made while an adopt was pending
|
|
358
|
+
// writes the adopted state, not the stale one it had.
|
|
359
|
+
const epoch = lockEpoch
|
|
360
|
+
state.revision += 1
|
|
361
|
+
state.exchanges = state.exchanges.slice(-MAX_EXCHANGES)
|
|
362
|
+
const snapshot = JSON.stringify(state)
|
|
363
|
+
const temp = `${statePath}.${process.pid}.tmp`
|
|
364
|
+
try {
|
|
365
|
+
await mkdir(home, { recursive: true })
|
|
366
|
+
const ensureLock = () => {
|
|
367
|
+
if (lockBroken) return
|
|
368
|
+
const owned = lock.owns()
|
|
369
|
+
// An outside edit under this host's lock goes through the adopt step
|
|
370
|
+
// instead of being written over; a removed file is just written again.
|
|
371
|
+
const changed = owned && stamp() !== '' && stamp() !== seen
|
|
372
|
+
if (changed) void adoptAfterReclaim().catch(() => {})
|
|
373
|
+
// A different epoch means an adopt replaced the state this snapshot was
|
|
374
|
+
// taken from — another host's writes, or an outside edit it adopted.
|
|
375
|
+
if (!owned || changed || lockEpoch !== epoch) {
|
|
376
|
+
const error = new Error(changed ? 'ds-bot: the Bot team file changed under this host\'s lock' : 'ds-bot lost the state lock to another dsh')
|
|
377
|
+
error.code = 'ELOCKLOST'
|
|
378
|
+
throw error
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
ensureLock()
|
|
382
|
+
await writeFile(temp, snapshot, 'utf8')
|
|
383
|
+
ensureLock()
|
|
384
|
+
await replaceFile(temp, statePath, ensureLock)
|
|
385
|
+
seen = stamp()
|
|
386
|
+
rt.capabilitiesStateSaved?.()
|
|
387
|
+
rt.stateSaved?.()
|
|
388
|
+
} catch (error) {
|
|
389
|
+
await unlink(temp).catch(() => {})
|
|
390
|
+
throw error
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
writing = run.catch(() => {})
|
|
394
|
+
return run.catch(error => {
|
|
395
|
+
warn('ds-bot: state write failed: %s', error)
|
|
396
|
+
throw error
|
|
397
|
+
})
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
ctx.effect(() => () => {
|
|
401
|
+
stopped = true
|
|
402
|
+
return writing.finally(() => lock.release())
|
|
403
|
+
}, 'ds-bot: state lock')
|
|
404
|
+
|
|
405
|
+
const botOf = id => (id === undefined ? undefined : own(state.bots, id))
|
|
406
|
+
// A group's own Session moves on to a fresh part when the group's folder no longer
|
|
407
|
+
// matches the one it was made in (parts.js), like a Bot's own chat: `room.id` stays
|
|
408
|
+
// the first Session's id and `room.sessionId` the current one, so the group keeps
|
|
409
|
+
// one identity while its Session moves. `roomOwners` maps the current Session.
|
|
410
|
+
const roomOf = id => (id === undefined ? undefined : own(state.rooms, id) ?? own(state.rooms, roomOwners.get(id)))
|
|
411
|
+
const isMain = id => id !== undefined && state.mainBotIds.includes(id)
|
|
412
|
+
|
|
413
|
+
// Group Sessions. A member speaks in each group from a hidden Session of its own,
|
|
414
|
+
// `room.sessions[botId]`, that renders the same system prompt and tools as its own
|
|
415
|
+
// chat: the provider's prefix cache is shared, and a group turn never re-reads the
|
|
416
|
+
// member's private context. Created on the member's first turn in the group.
|
|
417
|
+
//
|
|
418
|
+
// Segments. A long conversation moves to a fresh Session at a context line (see
|
|
419
|
+
// parts.js), and a group's own Session to a fresh part when the group's folder
|
|
420
|
+
// changes. A Bot's id is the Session id of its first own-chat segment and never
|
|
421
|
+
// changes; `bot.sessionId` is the current one, and `room.sessionId` the group's.
|
|
422
|
+
// Earlier segments, of the own chat (`bot.segments`), of each group Session
|
|
423
|
+
// (`room.segments[botId]`) and of the group's own Session (`room.parts`), are
|
|
424
|
+
// archived and stay readable.
|
|
425
|
+
/** @type {Map<string, string>} */
|
|
426
|
+
const chatOwners = new Map()
|
|
427
|
+
/** @type {Map<string, {botId:string, roomId:string}>} */
|
|
428
|
+
const groupOwners = new Map()
|
|
429
|
+
/** @type {Map<string, string>} */
|
|
430
|
+
const roomOwners = new Map()
|
|
431
|
+
/** @type {Map<string, {botId?:string, roomId?:string}>} */
|
|
432
|
+
const pastOwners = new Map()
|
|
433
|
+
const chatOf = botId => botOf(botId)?.sessionId ?? botId
|
|
434
|
+
const sessionOfRoom = room => (room === undefined ? undefined : room.sessionId ?? room.id)
|
|
435
|
+
const indexSessions = () => {
|
|
436
|
+
chatOwners.clear()
|
|
437
|
+
groupOwners.clear()
|
|
438
|
+
roomOwners.clear()
|
|
439
|
+
pastOwners.clear()
|
|
440
|
+
for (const bot of Object.values(state.bots)) {
|
|
441
|
+
for (const segment of bot.segments ?? []) pastOwners.set(segment.sessionId, { botId: bot.id })
|
|
442
|
+
chatOwners.set(chatOf(bot.id), bot.id)
|
|
443
|
+
}
|
|
444
|
+
for (const room of Object.values(state.rooms)) {
|
|
445
|
+
const sessionId = sessionOfRoom(room)
|
|
446
|
+
if (sessionId !== undefined) roomOwners.set(sessionId, room.id)
|
|
447
|
+
for (const segment of room.parts ?? []) pastOwners.set(segment.sessionId, { roomId: room.id })
|
|
448
|
+
for (const [botId, segments] of Object.entries(room.segments ?? {})) {
|
|
449
|
+
for (const segment of segments) pastOwners.set(segment.sessionId, { botId, roomId: room.id })
|
|
450
|
+
}
|
|
451
|
+
for (const [botId, sessionId] of Object.entries(room.sessions ?? {})) groupOwners.set(sessionId, { botId, roomId: room.id })
|
|
452
|
+
}
|
|
453
|
+
// The agents module owns the DSH Agent session index, and tools.js re-syncs
|
|
454
|
+
// the scoped tools with this ownership (drops Sessions that left, scopes
|
|
455
|
+
// live Agents that joined); both install after this.
|
|
456
|
+
rt.indexAgents?.()
|
|
457
|
+
rt.syncScopedTools?.()
|
|
458
|
+
}
|
|
459
|
+
// The Bot a Session speaks for: its own chat, one of its group Sessions, or an
|
|
460
|
+
// earlier segment of either.
|
|
461
|
+
const selfOf = id => chatOwners.get(id) ?? groupOwners.get(id)?.botId ?? pastOwners.get(id)?.botId
|
|
462
|
+
// A Session where the Bot speaks now, as opposed to an archived segment.
|
|
463
|
+
const isBotSession = id => chatOwners.has(id) || groupOwners.has(id)
|
|
464
|
+
const groupSessionsOf = botId => Object.values(state.rooms).map(room => room.sessions?.[botId]).filter(Boolean)
|
|
465
|
+
const dropGroupSession = (room, botId) => {
|
|
466
|
+
if (room.memberDirs !== undefined) delete room.memberDirs[botId]
|
|
467
|
+
const sessionId = room.sessions?.[botId]
|
|
468
|
+
if (sessionId === undefined) return
|
|
469
|
+
delete room.sessions[botId]
|
|
470
|
+
for (const segment of room.segments?.[botId] ?? []) delete state.switching[segment.sessionId]
|
|
471
|
+
if (room.segments !== undefined) delete room.segments[botId]
|
|
472
|
+
delete state.switching[sessionId]
|
|
473
|
+
groupOwners.delete(sessionId)
|
|
474
|
+
rt.cancelApprovals?.(sessionId)
|
|
475
|
+
// A plain archive refuses a running Session, which would go on working for a group
|
|
476
|
+
// that no longer has it.
|
|
477
|
+
void ctx.workspaceRegistry.archiveSession(sessionId, { stopActivity: true }).catch(() => {})
|
|
478
|
+
}
|
|
479
|
+
// A group Session DSH can no longer open is set aside; the member's earlier parts in
|
|
480
|
+
// the group stay, and its next turn there starts a new Session.
|
|
481
|
+
const retireGroupSession = (room, botId) => {
|
|
482
|
+
const sessionId = room.sessions?.[botId]
|
|
483
|
+
if (sessionId === undefined) return
|
|
484
|
+
delete room.sessions[botId]
|
|
485
|
+
groupOwners.delete(sessionId)
|
|
486
|
+
rt.cancelApprovals?.(sessionId)
|
|
487
|
+
void ctx.workspaceRegistry.archiveSession(sessionId, { stopActivity: true }).catch(() => {})
|
|
488
|
+
}
|
|
489
|
+
const findBot = (ref) => {
|
|
490
|
+
if (ref === undefined) return undefined
|
|
491
|
+
const byId = own(state.bots, ref)
|
|
492
|
+
if (byId) return byId
|
|
493
|
+
const wanted = normalize(unquote(ref)).replace(/^@/, '')
|
|
494
|
+
return Object.values(state.bots).find(bot => normalize(bot.name) === wanted)
|
|
495
|
+
}
|
|
496
|
+
// Every group chat whose id or name matches; callers refuse an ambiguous name.
|
|
497
|
+
const findRooms = (ref) => {
|
|
498
|
+
if (ref === undefined || ref === null) return []
|
|
499
|
+
const byId = own(state.rooms, ref)
|
|
500
|
+
if (byId) return [byId]
|
|
501
|
+
const wanted = normalize(unquote(ref))
|
|
502
|
+
return Object.values(state.rooms).filter(room => normalize(room.name) === wanted)
|
|
503
|
+
}
|
|
504
|
+
const isAdminOf = (room, id) => id !== undefined && room.admin === id
|
|
505
|
+
|
|
506
|
+
Object.assign(rt, {
|
|
507
|
+
state, load, save, refresh, readOnly, own,
|
|
508
|
+
botOf, roomOf, isMain, chatOf, sessionOfRoom, selfOf, isBotSession,
|
|
509
|
+
chatOwners, groupOwners, roomOwners, pastOwners,
|
|
510
|
+
groupSessionsOf, dropGroupSession, retireGroupSession, findBot, findRooms, isAdminOf,
|
|
511
|
+
})
|
|
512
|
+
}
|