@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.
Files changed (272) hide show
  1. package/.dsh-plugin/bot-product.mjs +81 -5
  2. package/.dsh-plugin/client.js +17490 -1791
  3. package/README.i18n.yaml +4 -4
  4. package/README.md +409 -364
  5. package/README.zh.md +409 -364
  6. package/apps/integration-client/README.md +31 -7
  7. package/apps/integration-client/api.mjs +14 -3
  8. package/apps/integration-client/binding-editor.jsx +308 -109
  9. package/apps/integration-client/binding-model.mjs +262 -0
  10. package/apps/integration-client/binding.css +144 -0
  11. package/apps/integration-client/bot-model-status.mjs +18 -0
  12. package/apps/integration-client/bot-profile-identity.jsx +197 -0
  13. package/apps/integration-client/bot-profile.css +57 -0
  14. package/apps/integration-client/bot-profile.mjs +97 -0
  15. package/apps/integration-client/bot-settings-dialog.css +18 -0
  16. package/apps/integration-client/bot-settings-dialog.jsx +111 -0
  17. package/apps/integration-client/bot-thread-header-bridge.css +20 -0
  18. package/apps/integration-client/bot-thread-header.jsx +278 -0
  19. package/apps/integration-client/bot-threads-panel.jsx +378 -0
  20. package/apps/integration-client/bot-threads.css +169 -0
  21. package/apps/integration-client/chat-design.mjs +250 -0
  22. package/apps/integration-client/chat-ui.jsx +175 -0
  23. package/apps/integration-client/chat.css +477 -0
  24. package/apps/integration-client/composer-draft-binding.mjs +50 -0
  25. package/apps/integration-client/composer-draft-bound.jsx +14 -0
  26. package/apps/integration-client/composer-draft-host.jsx +142 -0
  27. package/apps/integration-client/composer-draft-session.jsx +287 -0
  28. package/apps/integration-client/composer-draft-witness.mjs +115 -0
  29. package/apps/integration-client/composer-draft.css +4 -0
  30. package/apps/integration-client/composer-draft.mjs +322 -0
  31. package/apps/integration-client/contact-attention.jsx +54 -0
  32. package/apps/integration-client/data-panel.jsx +439 -0
  33. package/apps/integration-client/data.css +69 -0
  34. package/apps/integration-client/desktop-bridge.mjs +1 -0
  35. package/apps/integration-client/desktop-http-socket.mjs +49 -0
  36. package/apps/integration-client/desktop-keyboard.mjs +32 -0
  37. package/apps/integration-client/desktop-pane.jsx +99 -30
  38. package/apps/integration-client/desktop-stream.mjs +13 -3
  39. package/apps/integration-client/desktop-tools.jsx +19 -0
  40. package/apps/integration-client/desktop-touch.mjs +27 -0
  41. package/apps/integration-client/desktop.css +41 -0
  42. package/apps/integration-client/independent-chat-controller.mjs +224 -0
  43. package/apps/integration-client/independent-chat-entry.jsx +61 -0
  44. package/apps/integration-client/independent-chat-navigation.mjs +21 -0
  45. package/apps/integration-client/independent-chat-page.jsx +148 -0
  46. package/apps/integration-client/independent-chat-rich.jsx +64 -0
  47. package/apps/integration-client/independent-chat-rich.mjs +30 -0
  48. package/apps/integration-client/independent-chat.css +73 -0
  49. package/apps/integration-client/index.jsx +24 -16
  50. package/apps/integration-client/library-panel.jsx +4 -1
  51. package/apps/integration-client/licenses/jsqr-1.4.0-LICENSE.txt +202 -0
  52. package/apps/integration-client/main-route-editor.jsx +94 -0
  53. package/apps/integration-client/main-route-model.mjs +100 -0
  54. package/apps/integration-client/model-picker.css +37 -0
  55. package/apps/integration-client/model-picker.jsx +83 -0
  56. package/apps/integration-client/native-activity.css +141 -0
  57. package/apps/integration-client/native-activity.jsx +276 -0
  58. package/apps/integration-client/native-activity.mjs +384 -0
  59. package/apps/integration-client/notifications-panel.jsx +186 -0
  60. package/apps/integration-client/notifications.css +126 -0
  61. package/apps/integration-client/pairing-qr.jsx +79 -0
  62. package/apps/integration-client/panels.jsx +126 -115
  63. package/apps/integration-client/product-slots.jsx +46 -0
  64. package/apps/integration-client/routine-recorder.mjs +23 -2
  65. package/apps/integration-client/secure-pairing.jsx +84 -0
  66. package/apps/integration-client/tasks-panel.jsx +32 -10
  67. package/apps/integration-client/ui-icons.jsx +17 -0
  68. package/apps/integration-client/usage-explorer.jsx +43 -0
  69. package/apps/integration-client/usage-report.mjs +71 -0
  70. package/apps/integration-client/usage.css +34 -0
  71. package/apps/integration-client/web-panel-model.mjs +140 -0
  72. package/apps/integration-client/web-panel-page.jsx +221 -0
  73. package/apps/integration-client/web-panel-pins-live.jsx +28 -0
  74. package/apps/integration-client/web-panel.css +139 -0
  75. package/apps/integration-client/workspace-model.mjs +67 -0
  76. package/apps/integration-client/workspace-navigation.mjs +56 -0
  77. package/apps/integration-client/workspace-ui.jsx +173 -0
  78. package/apps/integration-client/workspace.css +332 -0
  79. package/modules/bot-runtime/SOURCE.json +176 -50
  80. package/modules/bot-runtime/index.mjs +169 -13
  81. package/modules/bot-runtime/product-overlay/OVERLAY.json +128 -2
  82. package/modules/bot-runtime/product-overlay/base/src/client/header.js +244 -0
  83. package/modules/bot-runtime/product-overlay/base/src/client/learning-card.js +202 -0
  84. package/modules/bot-runtime/product-overlay/base/src/client/model-menu.js +371 -0
  85. package/modules/bot-runtime/product-overlay/base/src/client/secret-card.js +363 -0
  86. package/modules/bot-runtime/product-overlay/base/src/client/sources.js +68 -0
  87. package/modules/bot-runtime/product-overlay/base/src/client/turn-error.js +260 -0
  88. package/modules/bot-runtime/product-overlay/base/src/host/channel.js +647 -0
  89. package/modules/bot-runtime/product-overlay/base/src/host/memory.js +2050 -0
  90. package/modules/bot-runtime/product-overlay/base/src/host/models.js +314 -0
  91. package/modules/bot-runtime/product-overlay/base/src/host/parts.js +1473 -0
  92. package/modules/bot-runtime/product-overlay/base/src/host/privacy.js +368 -0
  93. package/modules/bot-runtime/product-overlay/base/src/host/roster.js +776 -0
  94. package/modules/bot-runtime/product-overlay/base/src/host/store.js +512 -0
  95. package/modules/bot-runtime/product-overlay/base/src/host/usage.js +653 -0
  96. package/modules/bot-runtime/product-overlay/patches/src/client/details-drawer.js.patch +48 -2
  97. package/modules/bot-runtime/product-overlay/patches/src/client/header.js.patch +41 -0
  98. package/modules/bot-runtime/product-overlay/patches/src/client/index.js.patch +33 -1
  99. package/modules/bot-runtime/product-overlay/patches/src/client/learning-card.js.patch +20 -0
  100. package/modules/bot-runtime/product-overlay/patches/src/client/model-menu.js.patch +24 -0
  101. package/modules/bot-runtime/product-overlay/patches/src/client/secret-card.js.patch +25 -0
  102. package/modules/bot-runtime/product-overlay/patches/src/client/sources.js.patch +52 -0
  103. package/modules/bot-runtime/product-overlay/patches/src/client/turn-error.js.patch +23 -0
  104. package/modules/bot-runtime/product-overlay/patches/src/host/channel.js.patch +46 -0
  105. package/modules/bot-runtime/product-overlay/patches/src/host/memory.js.patch +11 -0
  106. package/modules/bot-runtime/product-overlay/patches/src/host/models.js.patch +16 -0
  107. package/modules/bot-runtime/product-overlay/patches/src/host/parts.js.patch +95 -0
  108. package/modules/bot-runtime/product-overlay/patches/src/host/privacy.js.patch +11 -0
  109. package/modules/bot-runtime/product-overlay/patches/src/host/roster.js.patch +145 -0
  110. package/modules/bot-runtime/product-overlay/patches/src/host/store.js.patch +68 -0
  111. package/modules/bot-runtime/product-overlay/patches/src/host/usage.js.patch +11 -0
  112. package/modules/bot-runtime/read-model.mjs +19 -4
  113. package/modules/bot-runtime/vendor/ds-bot/src/client/details-drawer.js +15 -5
  114. package/modules/bot-runtime/vendor/ds-bot/src/client/header.js +9 -2
  115. package/modules/bot-runtime/vendor/ds-bot/src/client/index.js +9 -2
  116. package/modules/bot-runtime/vendor/ds-bot/src/client/learning-card.js +2 -2
  117. package/modules/bot-runtime/vendor/ds-bot/src/client/model-menu.js +5 -3
  118. package/modules/bot-runtime/vendor/ds-bot/src/client/secret-card.js +3 -3
  119. package/modules/bot-runtime/vendor/ds-bot/src/client/sources.js +25 -2
  120. package/modules/bot-runtime/vendor/ds-bot/src/client/turn-error.js +3 -2
  121. package/modules/bot-runtime/vendor/ds-bot/src/host/channel.js +14 -2
  122. package/modules/bot-runtime/vendor/ds-bot/src/host/memory.js +1 -1
  123. package/modules/bot-runtime/vendor/ds-bot/src/host/models.js +7 -0
  124. package/modules/bot-runtime/vendor/ds-bot/src/host/parts.js +26 -9
  125. package/modules/bot-runtime/vendor/ds-bot/src/host/privacy.js +1 -1
  126. package/modules/bot-runtime/vendor/ds-bot/src/host/roster.js +112 -3
  127. package/modules/bot-runtime/vendor/ds-bot/src/host/store.js +34 -1
  128. package/modules/bot-runtime/vendor/ds-bot/src/host/usage.js +1 -1
  129. package/modules/independent-runtime/index.mjs +152 -62
  130. package/modules/independent-runtime/remote-tools.mjs +8 -5
  131. package/modules/openmaus-protocol/NOTICE +10 -0
  132. package/modules/openmaus-protocol/README.md +15 -0
  133. package/modules/openmaus-protocol/SOURCE.json +68 -0
  134. package/modules/openmaus-protocol/dist/protocol.mjs +1203 -0
  135. package/modules/openmaus-protocol/index.mjs +1 -0
  136. package/modules/openmaus-protocol/transport.mjs +77 -0
  137. package/modules/openmaus-protocol/vendor/LICENSE +202 -0
  138. package/modules/openmaus-protocol/vendor/companion/src/e2e/README.md +405 -0
  139. package/modules/openmaus-protocol/vendor/companion/src/e2e/credentials.ts +128 -0
  140. package/modules/openmaus-protocol/vendor/companion/src/e2e/descriptor.ts +84 -0
  141. package/modules/openmaus-protocol/vendor/companion/src/e2e/noise.ts +456 -0
  142. package/modules/openmaus-protocol/vendor/companion/src/e2e/phone.ts +104 -0
  143. package/modules/openmaus-protocol/vendor/companion/src/e2e/session.ts +417 -0
  144. package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.json +2565 -0
  145. package/modules/openmaus-protocol/vendor/companion/src/e2e/vectors/v1.sha256 +1 -0
  146. package/modules/openmaus-protocol/vendor/companion/src/e2e/yamux.ts +335 -0
  147. package/package.json +20 -6
  148. package/packages/bot-acp/README.md +156 -0
  149. package/packages/bot-acp/licenses/LICENSE-OpenMausBot +202 -0
  150. package/packages/bot-acp/licenses/NOTICE.md +27 -0
  151. package/packages/bot-acp/src/acp-adapter.mjs +795 -0
  152. package/packages/bot-acp/src/acp-host.mjs +148 -0
  153. package/packages/bot-acp/src/native-launch.mjs +121 -0
  154. package/packages/bot-acp/src/permission-policy.mjs +283 -0
  155. package/packages/bot-acp/src/rpc-codec.mjs +265 -0
  156. package/packages/bot-acp/src/session-core.mjs +581 -0
  157. package/packages/bot-acp/src/stdio-rpc.mjs +329 -0
  158. package/packages/bot-engines/README.md +281 -0
  159. package/packages/bot-engines/licenses/LICENSE-OpenMausBot +202 -0
  160. package/packages/bot-engines/licenses/NOTICE.md +32 -0
  161. package/packages/bot-engines/src/claude-engine.mjs +772 -0
  162. package/packages/bot-engines/src/claude-llm-adapter.mjs +797 -0
  163. package/packages/bot-engines/src/claude-llm-turn.mjs +292 -0
  164. package/packages/bot-engines/src/claude-permission-broker.mjs +130 -0
  165. package/packages/bot-engines/src/claude-permission-proxy.mjs +87 -0
  166. package/packages/bot-engines/src/claude-protocol.mjs +177 -0
  167. package/packages/bot-engines/src/driver-catalog.mjs +334 -0
  168. package/packages/bot-engines/src/host-loop-request.mjs +95 -0
  169. package/packages/bot-engines/src/host-registrations.mjs +105 -0
  170. package/packages/capability-broker/src/cli-bootstrap.mjs +86 -16
  171. package/packages/capability-broker/src/cli-bridge-response.mjs +80 -0
  172. package/packages/capability-broker/src/cli-stdio-bridge.mjs +21 -8
  173. package/packages/cli-runtime/src/codex-session-core.mjs +4 -0
  174. package/packages/cli-runtime/src/native-cli-output.mjs +57 -0
  175. package/packages/cli-runtime/src/native-peer.ps1 +51 -3
  176. package/packages/cli-runtime/src/native-process.mjs +77 -14
  177. package/packages/model-router-plugin/README.md +3 -3
  178. package/packages/model-router-plugin/package.json +1 -1
  179. package/packages/orchestration/src/binding-product.mjs +19 -0
  180. package/packages/orchestration/src/bot-data-actions.mjs +45 -0
  181. package/packages/orchestration/src/bot-data-chart.mjs +109 -0
  182. package/packages/orchestration/src/bot-data-context.mjs +44 -0
  183. package/packages/orchestration/src/bot-data-engine.mjs +809 -0
  184. package/packages/orchestration/src/bot-data-io.mjs +186 -0
  185. package/packages/orchestration/src/bot-data-sql.mjs +195 -0
  186. package/packages/orchestration/src/bot-data-surface.mjs +144 -0
  187. package/packages/orchestration/src/bot-data-tools.mjs +68 -0
  188. package/packages/orchestration/src/bot-data-worker.mjs +228 -0
  189. package/packages/orchestration/src/bot-independent-execution.mjs +195 -0
  190. package/packages/orchestration/src/bot-independent-stream.mjs +74 -0
  191. package/packages/orchestration/src/bot-loop.mjs +152 -19
  192. package/packages/orchestration/src/bot-notifications.mjs +159 -0
  193. package/packages/orchestration/src/bot-policy.mjs +43 -4
  194. package/packages/orchestration/src/bot-profile.mjs +171 -0
  195. package/packages/orchestration/src/bot-routine.mjs +4 -4
  196. package/packages/orchestration/src/bot-runtime-assembly.mjs +255 -8
  197. package/packages/orchestration/src/bot-runtime-switch.mjs +13 -2
  198. package/packages/orchestration/src/bot-serving.mjs +164 -0
  199. package/packages/orchestration/src/bot-session-store.mjs +105 -9
  200. package/packages/orchestration/src/bot-settings.mjs +50 -11
  201. package/packages/orchestration/src/bot-thread-admission.mjs +84 -0
  202. package/packages/orchestration/src/bot-thread-host.mjs +187 -0
  203. package/packages/orchestration/src/bot-threads.mjs +790 -0
  204. package/packages/orchestration/src/data-product-access.mjs +37 -0
  205. package/packages/orchestration/src/data-tool-contract.mjs +291 -0
  206. package/packages/orchestration/src/desktop-http-carrier.mjs +120 -0
  207. package/packages/orchestration/src/desktop-proxy.mjs +311 -0
  208. package/packages/orchestration/src/desktop-sessions.mjs +43 -9
  209. package/packages/orchestration/src/ds-lifecycle.mjs +31 -4
  210. package/packages/orchestration/src/grant-refresh-queue.mjs +17 -0
  211. package/packages/orchestration/src/independent-chat-page.mjs +39 -0
  212. package/packages/orchestration/src/independent-chat-service.mjs +308 -0
  213. package/packages/orchestration/src/independent-context-state.mjs +44 -0
  214. package/packages/orchestration/src/independent-message-wire.mjs +84 -0
  215. package/packages/orchestration/src/independent-tool-catalog.mjs +175 -0
  216. package/packages/orchestration/src/independent-tool-families.mjs +73 -0
  217. package/packages/orchestration/src/independent-tool-host.mjs +344 -0
  218. package/packages/orchestration/src/main-bot-grant.mjs +15 -0
  219. package/packages/orchestration/src/main-model-routes.mjs +208 -0
  220. package/packages/orchestration/src/model-accounting.mjs +99 -35
  221. package/packages/orchestration/src/model-auxiliary-dispatch.mjs +187 -0
  222. package/packages/orchestration/src/model-loop-frame.mjs +179 -0
  223. package/packages/orchestration/src/model-prepared-generation.mjs +175 -0
  224. package/packages/orchestration/src/model-request-bounds.mjs +111 -0
  225. package/packages/orchestration/src/native-activity-contract.mjs +491 -0
  226. package/packages/orchestration/src/native-activity-service.mjs +102 -0
  227. package/packages/orchestration/src/native-activity-store.mjs +249 -0
  228. package/packages/orchestration/src/native-conversation-tools.mjs +78 -0
  229. package/packages/orchestration/src/native-data-tools.mjs +100 -0
  230. package/packages/orchestration/src/native-remote-actions.mjs +742 -0
  231. package/packages/orchestration/src/native-team-tools.mjs +73 -0
  232. package/packages/orchestration/src/pairing-invite.mjs +37 -0
  233. package/packages/orchestration/src/product-composition.mjs +1559 -1001
  234. package/packages/orchestration/src/remote-machines.mjs +82 -14
  235. package/packages/orchestration/src/routine-execution.mjs +5 -3
  236. package/packages/orchestration/src/routine-session.mjs +19 -13
  237. package/packages/orchestration/src/secure-pairing-host.mjs +112 -0
  238. package/packages/orchestration/src/task-journal.mjs +29 -0
  239. package/packages/orchestration/src/task-product.mjs +17 -0
  240. package/packages/orchestration/src/team-assignment-store.mjs +120 -0
  241. package/packages/orchestration/src/team-bridge.mjs +282 -0
  242. package/packages/orchestration/src/team-scheduler.mjs +83 -0
  243. package/packages/orchestration/src/team-sdk-tools.mjs +84 -0
  244. package/packages/orchestration/src/team-tool-contract.mjs +284 -0
  245. package/packages/orchestration/src/ui-host-namespace.mjs +23 -0
  246. package/packages/orchestration/src/web-panel-actions.mjs +103 -0
  247. package/packages/orchestration/src/web-panel-framing.mjs +227 -0
  248. package/packages/orchestration/src/web-panel-store.mjs +206 -0
  249. package/remote-computer-agent/README.md +39 -29
  250. package/remote-computer-agent/build.mjs +3 -0
  251. package/remote-computer-agent/dist/OPENMAUSBOT_LICENSE +202 -0
  252. package/remote-computer-agent/dist/OPENMAUSBOT_NOTICE +10 -0
  253. package/remote-computer-agent/dist/OPENMAUSBOT_SOURCE.json +68 -0
  254. package/remote-computer-agent/dist/cli-stdio-bridge.mjs +215 -25
  255. package/remote-computer-agent/dist/cli-stdio-bridge.mjs.map +4 -4
  256. package/remote-computer-agent/dist/cli.mjs +1688 -168
  257. package/remote-computer-agent/dist/cli.mjs.map +4 -4
  258. package/remote-computer-agent/dist/index.mjs +1579 -122
  259. package/remote-computer-agent/dist/index.mjs.map +4 -4
  260. package/remote-computer-agent/dist/native-peer.ps1 +51 -3
  261. package/remote-computer-agent/package-lock.json +308 -2
  262. package/remote-computer-agent/package.json +2 -1
  263. package/remote-computer-agent/pair-device.ps1 +20 -0
  264. package/remote-computer-agent/setup.ps1 +38 -16
  265. package/remote-computer-agent/src/cli.ts +24 -3
  266. package/remote-computer-agent/src/desktop-session.ts +11 -3
  267. package/remote-computer-agent/src/e2e-server.ts +81 -0
  268. package/remote-computer-agent/src/pairing-page.ts +35 -0
  269. package/remote-computer-agent/src/protocol.ts +25 -25
  270. package/remote-computer-agent/src/secure-pairing.ts +105 -0
  271. package/remote-computer-agent/src/store.ts +20 -5
  272. 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
+ }