@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,368 @@
|
|
|
1
|
+
// Private Bots. A private Bot reads the team like any Bot, but no other Bot reads it, and
|
|
2
|
+
// what it sends out waits for the user. DSH does not fence reads, so this closes the
|
|
3
|
+
// tools DS Bot owns and checks the paths that the file tools and the shell are given;
|
|
4
|
+
// a shell can still reach any file the account can (see docs/plans).
|
|
5
|
+
import { join, resolve } from 'node:path'
|
|
6
|
+
import { within } from './folders.js'
|
|
7
|
+
import { unquote } from './text.js'
|
|
8
|
+
|
|
9
|
+
const FILE_TOOLS = { read: 'file_path', write: 'file_path', edit: 'file_path', read_image: 'file_path', str_replace_editor: 'path' }
|
|
10
|
+
const WRITE_TOOLS = ['write', 'edit', 'str_replace_editor']
|
|
11
|
+
const SEARCH_TOOLS = ['glob', 'grep']
|
|
12
|
+
const SHELL_TOOLS = ['bash', 'pwsh', 'terminal_open', 'terminal_send']
|
|
13
|
+
const SESSION_TOOLS = ['session_search', 'session_event_search', 'session_trace', 'session_event_trace', 'session_event_read']
|
|
14
|
+
|
|
15
|
+
// Windows and macOS file systems ignore case.
|
|
16
|
+
const fold = text => (process.platform === 'linux' ? text : text.toLowerCase())
|
|
17
|
+
const overlap = (a, b) => within(a, b) || within(b, a)
|
|
18
|
+
const strings = value => (typeof value === 'string' ? [value] : Array.isArray(value) ? value.flatMap(strings) : value && typeof value === 'object' ? Object.values(value).flatMap(strings) : [])
|
|
19
|
+
// The same path as a shell writes it: forward slashes, and on Windows both drive forms.
|
|
20
|
+
const asShell = (path) => {
|
|
21
|
+
const slashed = path.replace(/\\/g, '/')
|
|
22
|
+
const drive = /^([a-z]):\//i.exec(slashed)
|
|
23
|
+
return drive ? [slashed, `/${drive[1]}${slashed.slice(2)}`] : [slashed]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function install(rt) {
|
|
27
|
+
const { ctx, state } = rt
|
|
28
|
+
|
|
29
|
+
const isPrivate = id => rt.botOf(id)?.private === true
|
|
30
|
+
const privateBots = () => Object.values(state.bots).filter(bot => bot.private === true)
|
|
31
|
+
// Deleting a private Bot does not make the files and memory it leaves behind public.
|
|
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.chatOf(bot.id), ...(bot.segments ?? []).map(segment => segment.sessionId), ...rt.groupSessionsOf(bot.id)])])]
|
|
34
|
+
const dataRoots = () => [rt.dshHome, rt.home, process.env.DS_BOT_USER_DSH_HOME?.trim()].filter(Boolean)
|
|
35
|
+
|
|
36
|
+
// A path as it is and as the file system resolves it, folded for comparing. realBeyond
|
|
37
|
+
// also resolves a link above a file that does not exist yet.
|
|
38
|
+
const spellings = async (path) => [...new Set([fold(resolve(path)), fold(await rt.realBeyond(resolve(path)))])]
|
|
39
|
+
|
|
40
|
+
// The Bot a call speaks for. A subagent speaks for the Bot at the root of its
|
|
41
|
+
// parent chain, however many subagents sit between them.
|
|
42
|
+
const callerOf = async (exec) => {
|
|
43
|
+
const own = rt.selfOf(exec.agent?.id) ?? rt.selfOf(exec.agent?.session?.id)
|
|
44
|
+
if (own !== undefined) return own
|
|
45
|
+
let parent = exec.agent?.session?.header?.parentSession
|
|
46
|
+
const seen = new Set()
|
|
47
|
+
while (parent !== undefined && !seen.has(parent)) {
|
|
48
|
+
seen.add(parent)
|
|
49
|
+
const owner = rt.selfOf(parent)
|
|
50
|
+
if (owner !== undefined) return owner
|
|
51
|
+
const agent = await rt.resolveAgent(parent).catch(() => undefined)
|
|
52
|
+
parent = agent?.session?.header?.parentSession
|
|
53
|
+
}
|
|
54
|
+
return undefined
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Where a private Bot keeps what must stay closed: its folder and its memory. Its
|
|
58
|
+
// Sessions and its id are in file names under DSH's and the team's data.
|
|
59
|
+
const regionsOf = async bot => (await Promise.all([
|
|
60
|
+
bot.workDir,
|
|
61
|
+
join(rt.home, 'memory', 'bots', bot.id),
|
|
62
|
+
join(rt.home, 'browser-profiles', bot.id),
|
|
63
|
+
join(rt.home, 'browser-downloads', bot.id),
|
|
64
|
+
join(rt.home, 'side', `${bot.id}.json`),
|
|
65
|
+
join(rt.home, 'btw', bot.id),
|
|
66
|
+
bot.archivePath,
|
|
67
|
+
].filter(Boolean).map(spellings))).flat()
|
|
68
|
+
const sharedFiles = async () => (await Promise.all([
|
|
69
|
+
rt.statePath,
|
|
70
|
+
join(rt.home, 'chat-index.sqlite'),
|
|
71
|
+
join(rt.home, 'chat-index.sqlite-wal'),
|
|
72
|
+
join(rt.home, 'chat-index.sqlite-shm'),
|
|
73
|
+
join(rt.home, 'usage.sqlite'),
|
|
74
|
+
join(rt.home, 'usage.sqlite-wal'),
|
|
75
|
+
join(rt.home, 'usage.sqlite-shm'),
|
|
76
|
+
join(rt.home, 'inflight.json'),
|
|
77
|
+
join(rt.home, 'learning', 'state.json'),
|
|
78
|
+
join(rt.home, 'ocr.json'),
|
|
79
|
+
join(rt.home, 'secrets.json'),
|
|
80
|
+
join(rt.home, 'memory', 'journal.jsonl'),
|
|
81
|
+
join(rt.home, 'memory', 'journal.torn'),
|
|
82
|
+
join(rt.home, 'memory', 'pending.json'),
|
|
83
|
+
join(rt.home, 'logs', 'diagnostics.jsonl'),
|
|
84
|
+
].filter(Boolean).map(spellings))).flat()
|
|
85
|
+
|
|
86
|
+
const teamFolder = () => rt.teamFolder() ?? resolve(rt.defaultTeamFolder())
|
|
87
|
+
const teamMemory = join(rt.home, 'memory', 'team')
|
|
88
|
+
// The memory of deleted Bots and groups. What a private Bot left there is a region of
|
|
89
|
+
// its own (archivePath) and is checked first; the rest was never closed, and the
|
|
90
|
+
// memory of a Bot whose protection the user lifted is in it.
|
|
91
|
+
const memoryArchive = join(rt.home, 'memory', 'archive')
|
|
92
|
+
|
|
93
|
+
// The private Bot that `path` belongs to, other than `self`; undefined when it is open.
|
|
94
|
+
// `search` is for tools that walk a folder: a folder that holds a closed one is closed
|
|
95
|
+
// too, and so are the data folders, where the walk cannot be told which files to skip.
|
|
96
|
+
async function ownerOfPath(path, { self, search = false, base } = {}) {
|
|
97
|
+
const bots = protectedBots()
|
|
98
|
+
if (bots.length === 0) return undefined
|
|
99
|
+
const keys = await spellings(base === undefined ? path : resolve(base, path))
|
|
100
|
+
for (const bot of bots) {
|
|
101
|
+
if (bot.id === self) continue
|
|
102
|
+
const needles = sessionsOf(bot).map(fold)
|
|
103
|
+
const regions = await regionsOf(bot)
|
|
104
|
+
for (const key of keys) {
|
|
105
|
+
if (regions.some(region => within(key, region) || (search && within(region, key)))) return bot
|
|
106
|
+
if (needles.some(needle => key.includes(needle))) return bot
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const other = bots.find(bot => bot.id !== self)
|
|
110
|
+
for (const file of await sharedFiles()) if (other !== undefined && keys.some(key => key.startsWith(file))) return other
|
|
111
|
+
if (other !== undefined) {
|
|
112
|
+
const ownBot = rt.botOf(self)
|
|
113
|
+
const ownIds = ownBot === undefined ? [] : sessionsOf(ownBot).map(fold)
|
|
114
|
+
const ownRegions = ownBot === undefined ? [] : await regionsOf(ownBot)
|
|
115
|
+
// A walk of the archive stays closed while a private Bot's memory is on its way
|
|
116
|
+
// there: until the path is known, the walk cannot be told which folder to skip.
|
|
117
|
+
const moving = search && bots.some(bot => bot.archiving === true)
|
|
118
|
+
const open = [rt.workDirOf({ botId: self }), teamFolder(), teamMemory, moving ? undefined : memoryArchive, ...ownRegions].filter(Boolean)
|
|
119
|
+
const folders = (await Promise.all(open.map(spellings))).flat()
|
|
120
|
+
const roots = (await Promise.all(dataRoots().map(spellings))).flat()
|
|
121
|
+
for (const key of keys) {
|
|
122
|
+
if ((!search && ownIds.some(id => key.includes(id))) || folders.some(folder => within(key, folder))) continue
|
|
123
|
+
// Direct reads are closed too, not only walks: an index, pending record or log
|
|
124
|
+
// can hold the same private text without naming the Session in its file name.
|
|
125
|
+
if (roots.some(root => within(key, root) || (search && within(root, key)))) return other
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Text a shell runs: a closed path anywhere in it closes the command. A path that the
|
|
132
|
+
// command builds, or reaches by walking up from a folder, is not seen.
|
|
133
|
+
async function ownerInText(text, self) {
|
|
134
|
+
const flat = fold(text.replace(/\\/g, '/'))
|
|
135
|
+
const bots = protectedBots()
|
|
136
|
+
for (const bot of bots) {
|
|
137
|
+
if (bot.id === self) continue
|
|
138
|
+
const spelled = (await regionsOf(bot)).flatMap(asShell)
|
|
139
|
+
if (spelled.some(region => flat.includes(region)) || sessionsOf(bot).some(id => flat.includes(fold(id)))) return bot
|
|
140
|
+
}
|
|
141
|
+
const other = bots.find(bot => bot.id !== self)
|
|
142
|
+
for (const file of await sharedFiles()) if (other !== undefined && asShell(file).some(spelled => flat.includes(spelled))) return other
|
|
143
|
+
return undefined
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const closed = (bot, what) => new Error(`${bot.name} is a private Bot, and other Bots cannot reach its ${what}. Ask the user if ${bot.name} should share something.`)
|
|
147
|
+
|
|
148
|
+
// What a private Bot sends out is shown to the user first, as the user sees it.
|
|
149
|
+
async function approve(exec, { en, zh }) {
|
|
150
|
+
const approval = ctx.get?.('approval')
|
|
151
|
+
if (approval === undefined || exec.agent?.session === undefined) throw new Error('A private Bot needs the user\'s approval for this, and there is no way to ask for it here, so it was not done.')
|
|
152
|
+
const outcome = await approval.request({
|
|
153
|
+
agent: exec.agent,
|
|
154
|
+
toolName: exec.name,
|
|
155
|
+
...(exec.callId !== undefined ? { callId: exec.callId } : {}),
|
|
156
|
+
reason: en,
|
|
157
|
+
displayReason: { en, zh },
|
|
158
|
+
...(exec.signal ? { signal: exec.signal } : {}),
|
|
159
|
+
})
|
|
160
|
+
if (outcome === 'allowed-once') return
|
|
161
|
+
throw new Error(outcome === 'rejected' ? 'The user did not allow it, so it was not done.'
|
|
162
|
+
: outcome === 'cancelled' ? 'The approval was cancelled, so it was not done.'
|
|
163
|
+
: 'The approval could not be answered, so it was not done.')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// The Bot a tool argument names, as the tools and the memory scopes read it.
|
|
167
|
+
const botNamed = ref => (typeof ref === 'string' && ref !== '' ? rt.findBot(ref) ?? rt.findBot(unquote(ref)) : undefined)
|
|
168
|
+
|
|
169
|
+
// Tools between Bots. Other Bots may not reach a private Bot; what a private Bot sends
|
|
170
|
+
// to them is the user's to allow. A private Bot is no member of a group, which the
|
|
171
|
+
// roster checks as the members are written.
|
|
172
|
+
async function guardTeam(exec, self) {
|
|
173
|
+
const args = exec.arguments ?? {}
|
|
174
|
+
const target = (ref) => {
|
|
175
|
+
const bot = botNamed(ref)
|
|
176
|
+
return bot !== undefined && isPrivate(bot.id) && bot.id !== self ? bot : undefined
|
|
177
|
+
}
|
|
178
|
+
switch (exec.name) {
|
|
179
|
+
case 'message_bot': {
|
|
180
|
+
const bot = target(args.to)
|
|
181
|
+
if (bot) throw new Error(`Not sent: ${bot.name} is a private Bot, and other Bots cannot message it. Ask the user to pass it on.`)
|
|
182
|
+
const to = botNamed(args.to)
|
|
183
|
+
if (isPrivate(self) && to !== undefined) await approve(exec, { en: `Let ${rt.botOf(self).name} send this to ${to.name}?\n\n${String(args.message ?? '')}`, zh: `允许 ${rt.botOf(self).name} 把下面的内容发给 ${to.name} 吗?\n\n${String(args.message ?? '')}` })
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
// Every Bot reads the shared capabilities, so saving one shares what is in it.
|
|
187
|
+
case 'save_capability': {
|
|
188
|
+
if (!isPrivate(self)) return
|
|
189
|
+
const shown = JSON.stringify(args, null, 2)
|
|
190
|
+
await approve(exec, { en: `Let ${rt.botOf(self).name} share this with the team?\n\n${shown}`, zh: `允许 ${rt.botOf(self).name} 把下面的内容共享给团队吗?\n\n${shown}` })
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
case 'stop_bot': {
|
|
194
|
+
const bot = target(args.bot)
|
|
195
|
+
if (bot) throw closed(bot, 'turns')
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
case 'attach_capability':
|
|
199
|
+
case 'detach_capability': {
|
|
200
|
+
const bot = target(args.bot)
|
|
201
|
+
if (bot) throw closed(bot, 'capabilities')
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
case 'update_bot': {
|
|
205
|
+
const bot = target(args.bot)
|
|
206
|
+
if (bot) throw closed(bot, 'settings')
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
case 'recall':
|
|
210
|
+
case 'remember':
|
|
211
|
+
case 'forget': {
|
|
212
|
+
const bot = target(args.scope)
|
|
213
|
+
if (bot) throw closed(bot, 'memory')
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
default:
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// A Session started under a private Bot is closed too, even when its own id is not in
|
|
221
|
+
// the Bot's chat history. Walk the same parent chain as the caller check.
|
|
222
|
+
async function ownerOfSession(id, self) {
|
|
223
|
+
const bots = protectedBots()
|
|
224
|
+
const owns = (sessionId, bot) => bot.id !== self && sessionsOf(bot).includes(sessionId)
|
|
225
|
+
const direct = bots.find(bot => owns(id, bot))
|
|
226
|
+
if (direct) return direct
|
|
227
|
+
const seen = new Set([id])
|
|
228
|
+
let current = id
|
|
229
|
+
for (;;) {
|
|
230
|
+
const agent = await rt.resolveAgent(current).catch(() => undefined)
|
|
231
|
+
const parent = agent?.session?.header?.parentSession
|
|
232
|
+
if (parent === undefined || seen.has(parent)) return undefined
|
|
233
|
+
const owner = bots.find(bot => owns(parent, bot))
|
|
234
|
+
if (owner) return owner
|
|
235
|
+
seen.add(parent)
|
|
236
|
+
current = parent
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// The file tools, the shell and the session search: where the path or text names a
|
|
241
|
+
// closed place, the call fails before it runs.
|
|
242
|
+
async function guardFiles(exec, self) {
|
|
243
|
+
const args = exec.arguments ?? {}
|
|
244
|
+
const folder = () => rt.sessionFolder(exec.agent?.session ?? exec.agent)
|
|
245
|
+
const refuse = (bot) => { throw closed(bot, 'files') }
|
|
246
|
+
const name = exec.name
|
|
247
|
+
if (name in FILE_TOOLS) {
|
|
248
|
+
const path = args[FILE_TOOLS[name]]
|
|
249
|
+
if (typeof path !== 'string' || path === '') return
|
|
250
|
+
const bot = await ownerOfPath(path, { self, base: folder() })
|
|
251
|
+
if (bot) refuse(bot)
|
|
252
|
+
// A private Bot's file changes stay in its own folder unless the user allows them.
|
|
253
|
+
if (isPrivate(self) && WRITE_TOOLS.includes(name)) {
|
|
254
|
+
const own = await spellings(rt.botOf(self).workDir)
|
|
255
|
+
const base = folder()
|
|
256
|
+
const target = await spellings(base === undefined ? path : resolve(base, path))
|
|
257
|
+
if (!target.every(key => own.some(root => within(key, root)))) await approve(exec, { en: `Let ${rt.botOf(self).name} change ${path}? It is outside its own folder.`, zh: `允许 ${rt.botOf(self).name} 修改 ${path} 吗?它在自己的文件夹之外。` })
|
|
258
|
+
}
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
if (SEARCH_TOOLS.includes(name)) {
|
|
262
|
+
const where = typeof args.path === 'string' && args.path !== '' ? args.path : '.'
|
|
263
|
+
const bot = await ownerOfPath(where, { self, search: true, base: folder() })
|
|
264
|
+
?? (typeof args.pattern === 'string' && /^([a-z]:|[\\/]|~)/i.test(args.pattern) ? await ownerOfPath(args.pattern.replace(/[*?[{].*$/, ''), { self, search: true, base: folder() }) : undefined)
|
|
265
|
+
if (bot) refuse(bot)
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
if (SHELL_TOOLS.includes(name)) {
|
|
269
|
+
const bot = await ownerInText(strings(args).join('\n'), self)
|
|
270
|
+
if (bot) refuse(bot)
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
if ((name === 'subagent' || name === 'subagent_fork') && typeof args.cwd === 'string') {
|
|
274
|
+
const bot = await ownerOfPath(args.cwd, { self, search: true, base: folder() })
|
|
275
|
+
if (bot) refuse(bot)
|
|
276
|
+
return
|
|
277
|
+
}
|
|
278
|
+
if (name === 'working_directory' && typeof args.cd === 'string') {
|
|
279
|
+
const bot = await ownerOfPath(args.cd, { self, search: true, base: folder() })
|
|
280
|
+
if (bot) refuse(bot)
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
if (SESSION_TOOLS.includes(name)) {
|
|
284
|
+
const asked = strings([args.session_id, args.session_ids, args.parent_session_ids])
|
|
285
|
+
for (const id of asked) {
|
|
286
|
+
const bot = await ownerOfSession(id, self)
|
|
287
|
+
if (bot) refuse(bot)
|
|
288
|
+
}
|
|
289
|
+
// DSH scopes Session search to the caller's cwd. A subagent can have another cwd,
|
|
290
|
+
// so an unfiltered search is closed too; explicit safe ids still narrow it.
|
|
291
|
+
if (name === 'session_search' && args.session_ids === undefined && args.parent_session_ids === undefined) {
|
|
292
|
+
const closedBot = protectedBots().find(found => found.id !== self)
|
|
293
|
+
if (closedBot) throw closed(closedBot, 'Sessions')
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Before the tools' own hooks, so that a closed call never reaches a card they show.
|
|
299
|
+
ctx.on('tools/execute', async (exec, next) => {
|
|
300
|
+
if (protectedBots().length === 0) return next()
|
|
301
|
+
const self = await callerOf(exec)
|
|
302
|
+
await guardTeam(exec, self)
|
|
303
|
+
await guardFiles(exec, self)
|
|
304
|
+
return next()
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
// A folder that a private Bot's folder would overlap, or that overlaps one.
|
|
308
|
+
async function privateClash(path, { except } = {}) {
|
|
309
|
+
const keys = await spellings(path)
|
|
310
|
+
for (const bot of protectedBots()) {
|
|
311
|
+
if (bot.id === except || bot.workDir === undefined) continue
|
|
312
|
+
const dirs = await spellings(bot.workDir)
|
|
313
|
+
if (keys.some(key => dirs.some(dir => overlap(key, dir)))) return bot
|
|
314
|
+
}
|
|
315
|
+
return undefined
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Checks a change of `private` or of the folder of a Bot before it is written. A
|
|
319
|
+
// private Bot's folder is no other Bot's, no group's and not the team workspace —
|
|
320
|
+
// a member's own pick in a group counts as that member's folder — because DSH
|
|
321
|
+
// cannot be told which files of a shared folder to hide.
|
|
322
|
+
async function checkChange(bot, { flag, moving, workDir }) {
|
|
323
|
+
const makes = flag === undefined ? bot.private === true : flag
|
|
324
|
+
const dir = moving ? workDir : bot.workDir
|
|
325
|
+
if (!makes) {
|
|
326
|
+
const clash = moving && dir !== undefined ? await privateClash(dir, { except: bot.id }) : undefined
|
|
327
|
+
if (clash) throw new Error(`${dir} is in or around the folder of ${clash.name}, a private Bot, so ${bot.name} cannot work there`)
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
if (rt.isMain(bot.id)) throw new Error(`${bot.name} is a Main Bot, so it cannot be private`)
|
|
331
|
+
if (dir === undefined) throw new Error(`${bot.name} needs a folder of its own to be private`)
|
|
332
|
+
if (Object.values(state.rooms).some(room => room.members.includes(bot.id))) throw new Error(`${bot.name} is in a group chat, where every member reads what is said. Remove it from its groups first`)
|
|
333
|
+
const clash = await privateClash(dir, { except: bot.id })
|
|
334
|
+
if (clash) throw new Error(`${dir} is in or around the folder of ${clash.name}, a private Bot, so ${bot.name} cannot work there`)
|
|
335
|
+
const keys = await spellings(dir)
|
|
336
|
+
const others = [
|
|
337
|
+
['the team workspace', teamFolder()],
|
|
338
|
+
...Object.values(state.bots).filter(other => other.id !== bot.id && other.workDir).map(other => [`the folder of ${other.name}`, other.workDir]),
|
|
339
|
+
...Object.values(state.rooms).filter(room => room.workDir).map(room => [`the folder of "${room.name}"`, room.workDir]),
|
|
340
|
+
...Object.values(state.rooms).flatMap(room => Object.entries(room.memberDirs ?? {})
|
|
341
|
+
.filter(([member, dir]) => room.members.includes(member) && dir !== null)
|
|
342
|
+
.map(([member, dir]) => [`the folder of ${rt.botOf(member)?.name ?? 'a member'} in "${room.name}"`, dir])),
|
|
343
|
+
]
|
|
344
|
+
for (const [what, path] of others) {
|
|
345
|
+
const spelled = await spellings(path)
|
|
346
|
+
if (keys.some(key => spelled.some(other => overlap(key, other)))) throw new Error(`${dir} is in or around ${what}, so ${bot.name} cannot be private there. Give it a folder of its own`)
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const checkNoPrivate = (ids) => {
|
|
351
|
+
const found = ids.map(id => rt.botOf(id)).find(bot => bot?.private === true)
|
|
352
|
+
if (found) throw new Error(`${found.name} is a private Bot and cannot be in a group chat: what is said in a group is read by every member`)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Deleting a private Bot keeps its places closed (see roster.js deleteBot); only the
|
|
356
|
+
// user opens them again, one Bot at a time. The files stay where they are.
|
|
357
|
+
async function releaseArchive(id) {
|
|
358
|
+
const kept = state.privateArchives ?? []
|
|
359
|
+
if (!kept.some(entry => entry.id === id)) throw new Error('Nothing kept closed for this Bot')
|
|
360
|
+
state.privateArchives = kept.filter(entry => entry.id !== id)
|
|
361
|
+
await rt.save()
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// What the settings list shows. Session ids stay on the host.
|
|
365
|
+
const archivesView = () => (state.privateArchives ?? []).map(({ id, name, workDir }) => ({ id, name, ...(workDir === undefined ? {} : { workDir }) }))
|
|
366
|
+
|
|
367
|
+
Object.assign(rt, { isPrivate, privateClash, checkPrivacyChange: checkChange, checkNoPrivate, releaseArchive, archivesView })
|
|
368
|
+
}
|