@poolzin/pool-bot 2026.2.17 → 2026.2.18

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 (469) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/agent-scope.js +4 -0
  3. package/dist/agents/announce-idempotency.js +14 -0
  4. package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +23 -0
  5. package/dist/agents/bash-tools.exec-runtime.js +438 -0
  6. package/dist/agents/bash-tools.shared.js +6 -0
  7. package/dist/agents/cli-runner/reliability.js +61 -0
  8. package/dist/agents/cli-watchdog-defaults.js +11 -0
  9. package/dist/agents/command-poll-backoff.js +63 -0
  10. package/dist/agents/current-time.js +16 -0
  11. package/dist/agents/model-alias-lines.js +18 -0
  12. package/dist/agents/model-auth-label.js +61 -0
  13. package/dist/agents/models-config.e2e-harness.js +115 -0
  14. package/dist/agents/ollama-stream.js +11 -3
  15. package/dist/agents/openclaw-tools.js +135 -0
  16. package/dist/agents/pi-auth-json.js +118 -0
  17. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +147 -0
  18. package/dist/agents/pi-embedded-subscribe.e2e-harness.js +90 -0
  19. package/dist/agents/pi-embedded-subscribe.handlers.compaction.js +63 -0
  20. package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +30 -0
  21. package/dist/agents/pi-extensions/session-manager-runtime-registry.js +23 -0
  22. package/dist/agents/pi-tools.js +2 -0
  23. package/dist/agents/queued-file-writer.js +22 -0
  24. package/dist/agents/sandbox/docker.js +133 -40
  25. package/dist/agents/sandbox/fs-bridge.js +146 -0
  26. package/dist/agents/sandbox/fs-paths.js +205 -0
  27. package/dist/agents/sandbox/hash.js +4 -0
  28. package/dist/agents/sandbox-paths.js +3 -0
  29. package/dist/agents/session-dirs.js +20 -0
  30. package/dist/agents/skills/filter.js +24 -0
  31. package/dist/agents/skills/tools-dir.js +9 -0
  32. package/dist/agents/skills-install-download.js +290 -0
  33. package/dist/agents/skills-install-output.js +30 -0
  34. package/dist/agents/skills-install.download-test-utils.js +36 -0
  35. package/dist/agents/skills.e2e-test-helpers.js +13 -0
  36. package/dist/agents/subagent-announce-queue.js +59 -15
  37. package/dist/agents/subagent-depth.js +137 -0
  38. package/dist/agents/subagent-registry.js +448 -96
  39. package/dist/agents/subagent-spawn.js +262 -0
  40. package/dist/agents/test-helpers/fast-tool-stubs.js +18 -0
  41. package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +74 -0
  42. package/dist/agents/tool-display-common.js +782 -0
  43. package/dist/agents/tools/image-tool.js +1 -1
  44. package/dist/agents/tools/sessions-access.js +178 -0
  45. package/dist/agents/tools/sessions-resolution.js +206 -0
  46. package/dist/agents/tools/subagents-tool.js +616 -0
  47. package/dist/agents/workspace-dir.js +18 -0
  48. package/dist/agents/workspace-dirs.js +14 -0
  49. package/dist/agents/workspace.js +70 -0
  50. package/dist/auto-reply/heartbeat-reply-payload.js +18 -0
  51. package/dist/auto-reply/reply/commands-export-session.js +163 -0
  52. package/dist/auto-reply/reply/commands-mesh.js +245 -0
  53. package/dist/auto-reply/reply/commands-setunset.js +28 -0
  54. package/dist/auto-reply/reply/commands-slash-parse.js +31 -0
  55. package/dist/auto-reply/reply/commands-system-prompt.js +117 -0
  56. package/dist/auto-reply/reply/directive-handling.levels.js +17 -0
  57. package/dist/auto-reply/reply/directive-handling.params.js +1 -0
  58. package/dist/auto-reply/reply/directive-parsing.js +36 -0
  59. package/dist/auto-reply/reply/dispatcher-registry.js +43 -0
  60. package/dist/auto-reply/reply/elevated-unavailable.js +20 -0
  61. package/dist/auto-reply/reply/reply-delivery.js +92 -0
  62. package/dist/auto-reply/reply/session-reset-prompt.js +1 -0
  63. package/dist/auto-reply/reply/session-run-accounting.js +33 -0
  64. package/dist/auto-reply/reply.directive.directive-behavior.e2e-harness.js +115 -0
  65. package/dist/auto-reply/reply.directive.directive-behavior.e2e-mocks.js +12 -0
  66. package/dist/browser/bridge-auth-registry.js +26 -0
  67. package/dist/browser/client-actions-url.js +10 -0
  68. package/dist/browser/control-auth.js +73 -0
  69. package/dist/browser/csrf.js +64 -0
  70. package/dist/browser/http-auth.js +52 -0
  71. package/dist/browser/paths.js +37 -0
  72. package/dist/browser/proxy-files.js +32 -0
  73. package/dist/browser/pw-ai-state.js +7 -0
  74. package/dist/browser/resolved-config-refresh.js +42 -0
  75. package/dist/browser/routes/path-output.js +1 -0
  76. package/dist/browser/server-context.chrome-test-harness.js +20 -0
  77. package/dist/browser/server-middleware.js +31 -0
  78. package/dist/browser/test-port.js +16 -0
  79. package/dist/build-info.json +3 -3
  80. package/dist/canvas-host/file-resolver.js +43 -0
  81. package/dist/channels/account-summary.js +19 -0
  82. package/dist/channels/draft-stream-loop.js +77 -0
  83. package/dist/channels/plugins/account-helpers.js +26 -0
  84. package/dist/channels/telegram/allow-from.js +10 -0
  85. package/dist/cli/browser-cli-resize.js +22 -0
  86. package/dist/cli/browser-cli-shared.js +8 -0
  87. package/dist/cli/clawbot-cli.js +5 -0
  88. package/dist/cli/completion-cli.js +566 -0
  89. package/dist/cli/config-cli.js +63 -5
  90. package/dist/cli/daemon-cli/lifecycle-core.js +256 -0
  91. package/dist/cli/daemon-cli/register-service-commands.js +60 -0
  92. package/dist/cli/daemon-cli-compat.js +80 -0
  93. package/dist/cli/nodes-cli/pairing-render.js +26 -0
  94. package/dist/cli/program/action-reparse.js +17 -0
  95. package/dist/cli/program/command-registry.js +17 -0
  96. package/dist/cli/program/program-context.js +8 -0
  97. package/dist/cli/program/register.subclis.js +7 -0
  98. package/dist/cli/program/routes.js +233 -0
  99. package/dist/cli/qr-cli.js +132 -0
  100. package/dist/cli/requirements-test-fixtures.js +17 -0
  101. package/dist/cli/respawn-policy.js +4 -0
  102. package/dist/cli/shared/parse-port.js +18 -0
  103. package/dist/cli/skills-cli.format.js +241 -0
  104. package/dist/cli/update-cli/progress.js +121 -0
  105. package/dist/cli/update-cli/restart-helper.js +108 -0
  106. package/dist/cli/update-cli/shared.js +196 -0
  107. package/dist/cli/update-cli/status.js +97 -0
  108. package/dist/cli/update-cli/suppress-deprecations.js +17 -0
  109. package/dist/cli/update-cli/update-command.js +506 -0
  110. package/dist/cli/update-cli/wizard.js +130 -0
  111. package/dist/cli/update-cli.js +3 -9
  112. package/dist/cli/windows-argv.js +69 -0
  113. package/dist/commands/auth-choice-legacy.js +20 -0
  114. package/dist/commands/auth-choice.apply-helpers.js +8 -0
  115. package/dist/commands/channel-test-helpers.js +19 -0
  116. package/dist/commands/cleanup-plan.js +10 -0
  117. package/dist/commands/cleanup-utils.js +7 -0
  118. package/dist/commands/config-validation.js +15 -0
  119. package/dist/commands/doctor-completion.js +112 -0
  120. package/dist/commands/doctor-memory-search.js +119 -0
  121. package/dist/commands/doctor-session-locks.js +73 -0
  122. package/dist/commands/doctor.e2e-harness.js +364 -0
  123. package/dist/commands/gateway-presence.js +19 -0
  124. package/dist/commands/model-default.js +35 -0
  125. package/dist/commands/models/fallbacks-shared.js +102 -0
  126. package/dist/commands/models/shared.js +24 -0
  127. package/dist/commands/onboard-auth.config-gateways.js +64 -0
  128. package/dist/commands/onboard-auth.config-litellm.js +45 -0
  129. package/dist/commands/onboard-auth.config-shared.js +116 -0
  130. package/dist/commands/onboard-config.js +16 -0
  131. package/dist/commands/onboard-non-interactive.test-helpers.js +31 -0
  132. package/dist/commands/onboard-provider-auth-flags.js +136 -0
  133. package/dist/commands/openai-codex-oauth.js +40 -0
  134. package/dist/commands/test-runtime-config-helpers.js +21 -0
  135. package/dist/commands/test-wizard-helpers.js +68 -0
  136. package/dist/commands/vllm-setup.js +66 -0
  137. package/dist/compat/legacy-names.js +2 -0
  138. package/dist/config/backup-rotation.js +19 -0
  139. package/dist/config/env-preserve.js +122 -0
  140. package/dist/config/includes-scan.js +78 -0
  141. package/dist/config/plugins-allowlist.js +13 -0
  142. package/dist/config/schema.help.js +256 -0
  143. package/dist/config/schema.hints.js +189 -0
  144. package/dist/config/schema.irc.js +20 -0
  145. package/dist/config/schema.labels.js +317 -0
  146. package/dist/config/sessions/delivery-info.js +40 -0
  147. package/dist/config/types.irc.js +1 -0
  148. package/dist/config/zod-schema.agent-model.js +10 -0
  149. package/dist/config/zod-schema.allowdeny.js +35 -0
  150. package/dist/config/zod-schema.sensitive.js +4 -0
  151. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  152. package/dist/cron/isolated-agent/skills-snapshot.js +26 -0
  153. package/dist/cron/isolated-agent/subagent-followup.js +127 -0
  154. package/dist/cron/isolated-agent.mocks.js +12 -0
  155. package/dist/cron/isolated-agent.test-setup.js +22 -0
  156. package/dist/cron/legacy-delivery.js +43 -0
  157. package/dist/cron/webhook-url.js +22 -0
  158. package/dist/daemon/arg-split.js +40 -0
  159. package/dist/daemon/exec-file.js +23 -0
  160. package/dist/daemon/output.js +6 -0
  161. package/dist/daemon/runtime-format.js +31 -0
  162. package/dist/daemon/schtasks-exec.js +4 -0
  163. package/dist/daemon/service-audit.js +22 -0
  164. package/dist/discord/client.js +41 -0
  165. package/dist/discord/components-registry.js +57 -0
  166. package/dist/discord/components.js +816 -0
  167. package/dist/discord/guilds.js +12 -0
  168. package/dist/discord/monitor/gateway-plugin.js +48 -0
  169. package/dist/discord/monitor/presence.js +30 -0
  170. package/dist/discord/send.components.js +115 -0
  171. package/dist/discord/send.shared.js +4 -0
  172. package/dist/discord/ui.js +26 -0
  173. package/dist/discord/voice-message.js +254 -0
  174. package/dist/gateway/agent-event-assistant-text.js +5 -0
  175. package/dist/gateway/agent-prompt.js +33 -0
  176. package/dist/gateway/auth-rate-limit.js +136 -0
  177. package/dist/gateway/channel-health-monitor.js +114 -0
  178. package/dist/gateway/control-ui-contract.js +1 -0
  179. package/dist/gateway/control-ui-csp.js +15 -0
  180. package/dist/gateway/gateway-config-prompts.shared.js +25 -0
  181. package/dist/gateway/http-auth-helpers.js +18 -0
  182. package/dist/gateway/http-common.js +18 -0
  183. package/dist/gateway/http-endpoint-helpers.js +27 -0
  184. package/dist/gateway/node-invoke-sanitize.js +11 -0
  185. package/dist/gateway/node-invoke-system-run-approval.js +205 -0
  186. package/dist/gateway/probe-auth.js +21 -0
  187. package/dist/gateway/protocol/index.js +7 -2
  188. package/dist/gateway/protocol/schema/mesh.js +54 -0
  189. package/dist/gateway/protocol/schema/protocol-schemas.js +7 -0
  190. package/dist/gateway/protocol/schema.js +1 -0
  191. package/dist/gateway/server/ws-connection/auth-messages.js +54 -0
  192. package/dist/gateway/server-channels.js +11 -0
  193. package/dist/gateway/server-methods/attachment-normalize.js +16 -0
  194. package/dist/gateway/server-methods/base-hash.js +8 -0
  195. package/dist/gateway/server-methods/mesh.js +700 -0
  196. package/dist/gateway/server-methods/nodes.handlers.invoke-result.js +55 -0
  197. package/dist/gateway/server-methods/restart-request.js +13 -0
  198. package/dist/gateway/server-methods/validation.js +8 -0
  199. package/dist/gateway/server.agent.gateway-server-agent.mocks.js +35 -0
  200. package/dist/gateway/server.e2e-registry-helpers.js +1 -0
  201. package/dist/gateway/server.e2e-ws-harness.js +20 -0
  202. package/dist/gateway/test-helpers.js +2 -0
  203. package/dist/gateway/test-helpers.server.js +3 -1
  204. package/dist/gateway/test-http-response.js +12 -0
  205. package/dist/gateway/test-openai-responses-model.js +20 -0
  206. package/dist/gateway/test-temp-config.js +30 -0
  207. package/dist/gateway/test-with-server.js +32 -0
  208. package/dist/hooks/bundled/bootstrap-extra-files/handler.js +46 -0
  209. package/dist/imessage/monitor/abort-handler.js +23 -0
  210. package/dist/imessage/monitor/inbound-processing.js +346 -0
  211. package/dist/imessage/monitor/parse-notification.js +64 -0
  212. package/dist/imessage/target-parsing-helpers.js +92 -0
  213. package/dist/infra/archive.js +244 -20
  214. package/dist/infra/detect-package-manager.js +26 -0
  215. package/dist/infra/exec-approvals-allowlist.js +257 -0
  216. package/dist/infra/exec-approvals-analysis.js +770 -0
  217. package/dist/infra/exec-approvals.js +13 -0
  218. package/dist/infra/file-lock.js +1 -0
  219. package/dist/infra/gemini-auth.js +39 -0
  220. package/dist/infra/heartbeat-active-hours.js +85 -0
  221. package/dist/infra/heartbeat-events-filter.js +50 -0
  222. package/dist/infra/heartbeat-runner.test-utils.js +39 -0
  223. package/dist/infra/http-body.js +265 -0
  224. package/dist/infra/install-package-dir.js +50 -0
  225. package/dist/infra/install-safe-path.js +49 -0
  226. package/dist/infra/json-files.js +49 -0
  227. package/dist/infra/jsonl-socket.js +52 -0
  228. package/dist/infra/map-size.js +14 -0
  229. package/dist/infra/net/hostname.js +7 -0
  230. package/dist/infra/npm-registry-spec.js +39 -0
  231. package/dist/infra/openclaw-root.js +109 -0
  232. package/dist/infra/outbound/delivery-queue.js +214 -0
  233. package/dist/infra/outbound/identity.js +23 -0
  234. package/dist/infra/outbound/message-action-params.js +307 -0
  235. package/dist/infra/outbound/tool-payload.js +21 -0
  236. package/dist/infra/package-json.js +23 -0
  237. package/dist/infra/pairing-files.js +19 -0
  238. package/dist/infra/pairing-token.js +9 -0
  239. package/dist/infra/path-prepend.js +51 -0
  240. package/dist/infra/process-respawn.js +49 -0
  241. package/dist/infra/runtime-status.js +16 -0
  242. package/dist/infra/session-cost-usage.types.js +1 -0
  243. package/dist/infra/session-maintenance-warning.js +89 -0
  244. package/dist/infra/system-run-command.js +78 -0
  245. package/dist/infra/tmp-openclaw-dir.js +81 -0
  246. package/dist/infra/tmp-poolbot-dir.js +2 -0
  247. package/dist/infra/update-channels.js +19 -0
  248. package/dist/line/actions.js +45 -0
  249. package/dist/line/channel-access-token.js +9 -0
  250. package/dist/line/flex-templates/basic-cards.js +332 -0
  251. package/dist/line/flex-templates/common.js +18 -0
  252. package/dist/line/flex-templates/media-control-cards.js +453 -0
  253. package/dist/line/flex-templates/message.js +10 -0
  254. package/dist/line/flex-templates/schedule-cards.js +399 -0
  255. package/dist/line/flex-templates/types.js +1 -0
  256. package/dist/line/webhook-node.js +100 -0
  257. package/dist/line/webhook-utils.js +11 -0
  258. package/dist/logging/timestamps.js +14 -0
  259. package/dist/markdown/whatsapp.js +62 -0
  260. package/dist/media/base64.js +34 -0
  261. package/dist/media/local-roots.js +32 -0
  262. package/dist/media/outbound-attachment.js +10 -0
  263. package/dist/media/read-response-with-limit.js +41 -0
  264. package/dist/media/sniff-mime-from-base64.js +19 -0
  265. package/dist/media-understanding/audio-preflight.js +67 -0
  266. package/dist/media-understanding/fs.js +13 -0
  267. package/dist/media-understanding/output-extract.js +26 -0
  268. package/dist/media-understanding/providers/audio.test-helpers.js +34 -0
  269. package/dist/media-understanding/providers/google/inline-data.js +64 -0
  270. package/dist/media-understanding/providers/shared.js +7 -0
  271. package/dist/media-understanding/runner.entries.js +459 -0
  272. package/dist/memory/batch-error-utils.js +11 -0
  273. package/dist/memory/batch-http.js +27 -0
  274. package/dist/memory/batch-output.js +29 -0
  275. package/dist/memory/batch-runner.js +22 -0
  276. package/dist/memory/batch-upload.js +23 -0
  277. package/dist/memory/batch-utils.js +26 -0
  278. package/dist/memory/embeddings-debug.js +11 -0
  279. package/dist/memory/embeddings-remote-client.js +22 -0
  280. package/dist/memory/embeddings-remote-fetch.js +14 -0
  281. package/dist/memory/manager-embedding-ops.js +616 -0
  282. package/dist/memory/manager-sync-ops.js +953 -0
  283. package/dist/memory/qmd-manager.js +1061 -0
  284. package/dist/memory/qmd-query-parser.js +107 -0
  285. package/dist/memory/qmd-scope.js +93 -0
  286. package/dist/memory/search-manager.js +0 -1
  287. package/dist/memory/sync-index.js +21 -0
  288. package/dist/memory/sync-progress.js +22 -0
  289. package/dist/memory/sync-stale.js +30 -0
  290. package/dist/memory/test-embeddings-mock.js +16 -0
  291. package/dist/memory/test-manager-helpers.js +14 -0
  292. package/dist/memory/test-runtime-mocks.js +11 -0
  293. package/dist/node-host/invoke-browser.js +177 -0
  294. package/dist/node-host/invoke.js +685 -0
  295. package/dist/pairing/setup-code.js +285 -0
  296. package/dist/plugin-sdk/account-id.js +1 -0
  297. package/dist/plugin-sdk/agent-media-payload.js +13 -0
  298. package/dist/plugin-sdk/allow-from.js +47 -0
  299. package/dist/plugin-sdk/command-auth.js +23 -0
  300. package/dist/plugin-sdk/config-paths.js +9 -0
  301. package/dist/plugin-sdk/file-lock.js +116 -0
  302. package/dist/plugin-sdk/json-store.js +31 -0
  303. package/dist/plugin-sdk/onboarding.js +28 -0
  304. package/dist/plugin-sdk/provider-auth-result.js +29 -0
  305. package/dist/plugin-sdk/slack-message-actions.js +133 -0
  306. package/dist/plugin-sdk/status-helpers.js +35 -0
  307. package/dist/plugin-sdk/text-chunking.js +31 -0
  308. package/dist/plugin-sdk/tool-send.js +12 -0
  309. package/dist/plugin-sdk/webhook-path.js +27 -0
  310. package/dist/plugin-sdk/webhook-targets.js +34 -0
  311. package/dist/plugins/hooks.test-helpers.js +21 -0
  312. package/dist/plugins/uninstall.js +171 -0
  313. package/dist/process/supervisor/adapters/child.js +143 -0
  314. package/dist/process/supervisor/adapters/env.js +13 -0
  315. package/dist/process/supervisor/adapters/pty.js +148 -0
  316. package/dist/process/supervisor/index.js +10 -0
  317. package/dist/process/supervisor/registry.js +117 -0
  318. package/dist/process/supervisor/supervisor.js +244 -0
  319. package/dist/process/supervisor/types.js +1 -0
  320. package/dist/providers/google-shared.test-helpers.js +75 -0
  321. package/dist/security/audit-channel.js +419 -0
  322. package/dist/security/audit-tool-policy.js +1 -0
  323. package/dist/security/scan-paths.js +12 -0
  324. package/dist/sessions/input-provenance.js +55 -0
  325. package/dist/sessions/session-key-utils.js +7 -0
  326. package/dist/shared/chat-content.js +31 -0
  327. package/dist/shared/chat-envelope.js +45 -0
  328. package/dist/shared/config-eval.js +117 -0
  329. package/dist/shared/device-auth.js +16 -0
  330. package/dist/shared/entry-metadata.js +9 -0
  331. package/dist/shared/entry-status.js +25 -0
  332. package/dist/shared/frontmatter.js +98 -0
  333. package/dist/shared/model-param-b.js +19 -0
  334. package/dist/shared/net/ipv4.js +17 -0
  335. package/dist/shared/node-match.js +53 -0
  336. package/dist/shared/requirements.js +128 -0
  337. package/dist/shared/subagents-format.js +84 -0
  338. package/dist/shared/usage-aggregates.js +28 -0
  339. package/dist/signal/monitor/mentions.js +45 -0
  340. package/dist/signal/rpc-context.js +19 -0
  341. package/dist/slack/blocks-fallback.js +76 -0
  342. package/dist/slack/blocks-input.js +40 -0
  343. package/dist/slack/draft-stream.js +106 -0
  344. package/dist/slack/message-actions.js +51 -0
  345. package/dist/slack/modal-metadata.js +32 -0
  346. package/dist/slack/monitor/events/interactions.js +462 -0
  347. package/dist/slack/monitor/room-context.js +17 -0
  348. package/dist/slack/stream-mode.js +41 -0
  349. package/dist/telegram/bot-native-command-menu.js +64 -0
  350. package/dist/telegram/bot.media.e2e-harness.js +81 -0
  351. package/dist/telegram/button-types.js +1 -0
  352. package/dist/telegram/group-access.js +65 -0
  353. package/dist/telegram/outbound-params.js +21 -0
  354. package/dist/telegram/poll-vote-cache.js +21 -0
  355. package/dist/terminal/health-style.js +36 -0
  356. package/dist/test-utils/chunk-test-helpers.js +21 -0
  357. package/dist/test-utils/env.js +72 -0
  358. package/dist/test-utils/exec-assertions.js +12 -0
  359. package/dist/test-utils/imessage-test-plugin.js +54 -0
  360. package/dist/test-utils/mock-http-response.js +17 -0
  361. package/dist/test-utils/vitest-mock-fn.js +1 -0
  362. package/dist/tts/tts-core.js +550 -0
  363. package/dist/utils/chunk-items.js +10 -0
  364. package/dist/utils/reaction-level.js +52 -0
  365. package/dist/utils/safe-json.js +22 -0
  366. package/dist/utils/with-timeout.js +14 -0
  367. package/dist/web/media.js +17 -5
  368. package/dist/whatsapp/resolve-outbound-target.js +42 -0
  369. package/dist/wizard/onboarding.completion.js +74 -0
  370. package/extensions/bluebubbles/src/account-resolve.ts +29 -0
  371. package/extensions/bluebubbles/src/monitor-normalize.ts +796 -0
  372. package/extensions/bluebubbles/src/monitor-processing.ts +1007 -0
  373. package/extensions/bluebubbles/src/monitor-reply-cache.ts +185 -0
  374. package/extensions/bluebubbles/src/monitor-shared.ts +51 -0
  375. package/extensions/bluebubbles/src/multipart.ts +32 -0
  376. package/extensions/bluebubbles/src/send-helpers.ts +53 -0
  377. package/extensions/bluebubbles/src/test-harness.ts +50 -0
  378. package/extensions/bluebubbles/src/test-mocks.ts +11 -0
  379. package/extensions/device-pair/index.ts +554 -0
  380. package/extensions/discord/src/channel.js +366 -0
  381. package/extensions/discord/src/runtime.js +10 -0
  382. package/extensions/feishu/index.ts +63 -0
  383. package/extensions/feishu/src/accounts.ts +114 -0
  384. package/extensions/feishu/src/bitable.ts +739 -0
  385. package/extensions/feishu/src/bot.ts +965 -0
  386. package/extensions/feishu/src/channel.ts +351 -0
  387. package/extensions/feishu/src/client.ts +118 -0
  388. package/extensions/feishu/src/config-schema.ts +206 -0
  389. package/extensions/feishu/src/dedup.ts +33 -0
  390. package/extensions/feishu/src/directory.ts +177 -0
  391. package/extensions/feishu/src/doc-schema.ts +47 -0
  392. package/extensions/feishu/src/docx.ts +536 -0
  393. package/extensions/feishu/src/drive-schema.ts +46 -0
  394. package/extensions/feishu/src/drive.ts +227 -0
  395. package/extensions/feishu/src/dynamic-agent.ts +131 -0
  396. package/extensions/feishu/src/media.ts +449 -0
  397. package/extensions/feishu/src/mention.ts +126 -0
  398. package/extensions/feishu/src/monitor.ts +330 -0
  399. package/extensions/feishu/src/onboarding.ts +359 -0
  400. package/extensions/feishu/src/outbound.ts +55 -0
  401. package/extensions/feishu/src/perm-schema.ts +52 -0
  402. package/extensions/feishu/src/perm.ts +173 -0
  403. package/extensions/feishu/src/policy.ts +84 -0
  404. package/extensions/feishu/src/probe.ts +44 -0
  405. package/extensions/feishu/src/reactions.ts +160 -0
  406. package/extensions/feishu/src/reply-dispatcher.ts +239 -0
  407. package/extensions/feishu/src/runtime.ts +14 -0
  408. package/extensions/feishu/src/send-result.ts +29 -0
  409. package/extensions/feishu/src/send.ts +335 -0
  410. package/extensions/feishu/src/streaming-card.ts +223 -0
  411. package/extensions/feishu/src/targets.ts +78 -0
  412. package/extensions/feishu/src/tools-config.ts +21 -0
  413. package/extensions/feishu/src/types.ts +81 -0
  414. package/extensions/feishu/src/typing.ts +80 -0
  415. package/extensions/feishu/src/wiki-schema.ts +55 -0
  416. package/extensions/feishu/src/wiki.ts +232 -0
  417. package/extensions/imessage/src/channel.js +253 -0
  418. package/extensions/imessage/src/runtime.js +10 -0
  419. package/extensions/irc/index.ts +17 -0
  420. package/extensions/irc/src/accounts.ts +268 -0
  421. package/extensions/irc/src/channel.ts +367 -0
  422. package/extensions/irc/src/client.ts +439 -0
  423. package/extensions/irc/src/config-schema.ts +97 -0
  424. package/extensions/irc/src/connect-options.ts +30 -0
  425. package/extensions/irc/src/control-chars.ts +22 -0
  426. package/extensions/irc/src/inbound.ts +334 -0
  427. package/extensions/irc/src/monitor.ts +147 -0
  428. package/extensions/irc/src/normalize.ts +117 -0
  429. package/extensions/irc/src/onboarding.ts +479 -0
  430. package/extensions/irc/src/policy.ts +157 -0
  431. package/extensions/irc/src/probe.ts +53 -0
  432. package/extensions/irc/src/protocol.ts +169 -0
  433. package/extensions/irc/src/runtime.ts +14 -0
  434. package/extensions/irc/src/send.ts +88 -0
  435. package/extensions/irc/src/types.ts +93 -0
  436. package/extensions/matrix/src/matrix/client-bootstrap.ts +39 -0
  437. package/extensions/mattermost/src/mattermost/monitor-onchar.ts +25 -0
  438. package/extensions/mattermost/src/mattermost/monitor-websocket.ts +221 -0
  439. package/extensions/mattermost/src/mattermost/reactions.ts +130 -0
  440. package/extensions/mattermost/src/mattermost/reconnect.ts +103 -0
  441. package/extensions/minimax-portal-auth/index.ts +161 -0
  442. package/extensions/minimax-portal-auth/oauth.ts +247 -0
  443. package/extensions/msteams/src/file-lock.ts +1 -0
  444. package/extensions/msteams/src/graph.ts +92 -0
  445. package/extensions/msteams/src/mentions.ts +114 -0
  446. package/extensions/msteams/src/test-runtime.ts +16 -0
  447. package/extensions/openai-codex-auth/index.ts +177 -0
  448. package/extensions/phone-control/index.ts +421 -0
  449. package/extensions/shared/resolve-target-test-helpers.ts +66 -0
  450. package/extensions/signal/src/channel.js +273 -0
  451. package/extensions/signal/src/runtime.js +10 -0
  452. package/extensions/slack/src/channel.js +489 -0
  453. package/extensions/slack/src/runtime.js +10 -0
  454. package/extensions/talk-voice/index.ts +150 -0
  455. package/extensions/telegram/src/channel.js +424 -0
  456. package/extensions/telegram/src/runtime.js +10 -0
  457. package/extensions/thread-ownership/index.ts +133 -0
  458. package/extensions/tlon/src/account-fields.ts +25 -0
  459. package/extensions/tlon/src/urbit/base-url.ts +57 -0
  460. package/extensions/tlon/src/urbit/channel-client.ts +157 -0
  461. package/extensions/tlon/src/urbit/channel-ops.ts +164 -0
  462. package/extensions/tlon/src/urbit/context.ts +47 -0
  463. package/extensions/tlon/src/urbit/errors.ts +51 -0
  464. package/extensions/tlon/src/urbit/fetch.ts +39 -0
  465. package/extensions/twitch/src/test-fixtures.ts +30 -0
  466. package/extensions/voice-call/src/allowlist.ts +19 -0
  467. package/extensions/whatsapp/src/channel.js +429 -0
  468. package/extensions/whatsapp/src/runtime.js +10 -0
  469. package/package.json +1 -1
@@ -0,0 +1,90 @@
1
+ import { expect } from "vitest";
2
+ import { subscribeEmbeddedPiSession } from "./pi-embedded-subscribe.js";
3
+ export function createStubSessionHarness() {
4
+ let handler;
5
+ const session = {
6
+ subscribe: (fn) => {
7
+ handler = fn;
8
+ return () => { };
9
+ },
10
+ };
11
+ return { session, emit: (evt) => handler?.(evt) };
12
+ }
13
+ export function createSubscribedSessionHarness(params) {
14
+ const { sessionExtras, ...subscribeParams } = params;
15
+ const { session, emit } = createStubSessionHarness();
16
+ const mergedSession = Object.assign(session, sessionExtras ?? {});
17
+ const subscription = subscribeEmbeddedPiSession({
18
+ ...subscribeParams,
19
+ session: mergedSession,
20
+ });
21
+ return { emit, session: mergedSession, subscription };
22
+ }
23
+ export function createParagraphChunkedBlockReplyHarness(params) {
24
+ const onBlockReply = params.onBlockReply ?? (() => { });
25
+ const { emit, subscription } = createSubscribedSessionHarness({
26
+ runId: params.runId ?? "run",
27
+ onBlockReply,
28
+ blockReplyBreak: "message_end",
29
+ blockReplyChunking: {
30
+ ...params.chunking,
31
+ breakPreference: "paragraph",
32
+ },
33
+ });
34
+ return { emit, onBlockReply, subscription };
35
+ }
36
+ export function extractAgentEventPayloads(calls) {
37
+ return calls
38
+ .map((call) => {
39
+ const first = call?.[0];
40
+ const data = first?.data;
41
+ return data && typeof data === "object" ? data : undefined;
42
+ })
43
+ .filter((value) => Boolean(value));
44
+ }
45
+ export function extractTextPayloads(calls) {
46
+ return calls
47
+ .map((call) => {
48
+ const payload = call?.[0];
49
+ return typeof payload?.text === "string" ? payload.text : undefined;
50
+ })
51
+ .filter((text) => Boolean(text));
52
+ }
53
+ export function emitMessageStartAndEndForAssistantText(params) {
54
+ const assistantMessage = {
55
+ role: "assistant",
56
+ content: [{ type: "text", text: params.text }],
57
+ };
58
+ params.emit({ type: "message_start", message: assistantMessage });
59
+ params.emit({ type: "message_end", message: assistantMessage });
60
+ }
61
+ export function emitAssistantTextDeltaAndEnd(params) {
62
+ params.emit({
63
+ type: "message_update",
64
+ message: { role: "assistant" },
65
+ assistantMessageEvent: {
66
+ type: "text_delta",
67
+ delta: params.text,
68
+ },
69
+ });
70
+ const assistantMessage = {
71
+ role: "assistant",
72
+ content: [{ type: "text", text: params.text }],
73
+ };
74
+ params.emit({ type: "message_end", message: assistantMessage });
75
+ }
76
+ export function expectFencedChunks(calls, expectedPrefix) {
77
+ expect(calls.length).toBeGreaterThan(1);
78
+ for (const call of calls) {
79
+ const chunk = call[0]?.text;
80
+ expect(typeof chunk === "string" && chunk.startsWith(expectedPrefix)).toBe(true);
81
+ const fenceCount = typeof chunk === "string" ? (chunk.match(/```/g)?.length ?? 0) : 0;
82
+ expect(fenceCount).toBeGreaterThanOrEqual(2);
83
+ }
84
+ }
85
+ export function expectSingleAgentEventText(calls, text) {
86
+ const payloads = extractAgentEventPayloads(calls);
87
+ expect(payloads).toHaveLength(1);
88
+ expect(payloads[0]?.text).toBe(text);
89
+ expect(payloads[0]?.delta).toBe(text);
90
+ }
@@ -0,0 +1,63 @@
1
+ import { emitAgentEvent } from "../infra/agent-events.js";
2
+ import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
3
+ export function handleAutoCompactionStart(ctx) {
4
+ ctx.state.compactionInFlight = true;
5
+ ctx.incrementCompactionCount();
6
+ ctx.ensureCompactionPromise();
7
+ ctx.log.debug(`embedded run compaction start: runId=${ctx.params.runId}`);
8
+ emitAgentEvent({
9
+ runId: ctx.params.runId,
10
+ stream: "compaction",
11
+ data: { phase: "start" },
12
+ });
13
+ void ctx.params.onAgentEvent?.({
14
+ stream: "compaction",
15
+ data: { phase: "start" },
16
+ });
17
+ // Run before_compaction plugin hook (fire-and-forget)
18
+ const hookRunner = getGlobalHookRunner();
19
+ if (hookRunner?.hasHooks("before_compaction")) {
20
+ void hookRunner
21
+ .runBeforeCompaction({
22
+ messageCount: ctx.params.session.messages?.length ?? 0,
23
+ }, {})
24
+ .catch((err) => {
25
+ ctx.log.warn(`before_compaction hook failed: ${String(err)}`);
26
+ });
27
+ }
28
+ }
29
+ export function handleAutoCompactionEnd(ctx, evt) {
30
+ ctx.state.compactionInFlight = false;
31
+ const willRetry = Boolean(evt.willRetry);
32
+ if (willRetry) {
33
+ ctx.noteCompactionRetry();
34
+ ctx.resetForCompactionRetry();
35
+ ctx.log.debug(`embedded run compaction retry: runId=${ctx.params.runId}`);
36
+ }
37
+ else {
38
+ ctx.maybeResolveCompactionWait();
39
+ }
40
+ emitAgentEvent({
41
+ runId: ctx.params.runId,
42
+ stream: "compaction",
43
+ data: { phase: "end", willRetry },
44
+ });
45
+ void ctx.params.onAgentEvent?.({
46
+ stream: "compaction",
47
+ data: { phase: "end", willRetry },
48
+ });
49
+ // Run after_compaction plugin hook (fire-and-forget)
50
+ if (!willRetry) {
51
+ const hookRunnerEnd = getGlobalHookRunner();
52
+ if (hookRunnerEnd?.hasHooks("after_compaction")) {
53
+ void hookRunnerEnd
54
+ .runAfterCompaction({
55
+ messageCount: ctx.params.session.messages?.length ?? 0,
56
+ compactedCount: ctx.getCompactionCount(),
57
+ }, {})
58
+ .catch((err) => {
59
+ ctx.log.warn(`after_compaction hook failed: ${String(err)}`);
60
+ });
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,30 @@
1
+ import { handleToolExecutionEnd, handleToolExecutionStart, } from "./pi-embedded-subscribe.handlers.tools.js";
2
+ /** Type-safe bridge: narrows parameter type so callers avoid assertions. */
3
+ function asFullContext(ctx) {
4
+ return ctx;
5
+ }
6
+ /** Typed wrapper around {@link handleToolExecutionStart}. */
7
+ export function callToolExecutionStart(ctx, evt) {
8
+ return handleToolExecutionStart(asFullContext(ctx), evt);
9
+ }
10
+ /** Typed wrapper around {@link handleToolExecutionEnd}. */
11
+ export async function callToolExecutionEnd(ctx, evt) {
12
+ return handleToolExecutionEnd(asFullContext(ctx), evt);
13
+ }
14
+ /**
15
+ * Check whether a mock-call argument is an object containing `mediaUrls`
16
+ * but NOT `text` (i.e. a "direct media" emission).
17
+ */
18
+ export function isDirectMediaCall(call) {
19
+ const arg = call[0];
20
+ if (!arg || typeof arg !== "object") {
21
+ return false;
22
+ }
23
+ return "mediaUrls" in arg && !("text" in arg);
24
+ }
25
+ /**
26
+ * Filter a vi.fn() mock's call log to only direct-media emissions.
27
+ */
28
+ export function filterDirectMediaCalls(mock) {
29
+ return mock.mock.calls.filter(isDirectMediaCall);
30
+ }
@@ -0,0 +1,23 @@
1
+ export function createSessionManagerRuntimeRegistry() {
2
+ // Session-scoped runtime registry keyed by object identity.
3
+ // The SessionManager instance must stay stable across set/get calls.
4
+ const registry = new WeakMap();
5
+ const set = (sessionManager, value) => {
6
+ if (!sessionManager || typeof sessionManager !== "object") {
7
+ return;
8
+ }
9
+ const key = sessionManager;
10
+ if (value === null) {
11
+ registry.delete(key);
12
+ return;
13
+ }
14
+ registry.set(key, value);
15
+ };
16
+ const get = (sessionManager) => {
17
+ if (!sessionManager || typeof sessionManager !== "object") {
18
+ return null;
19
+ }
20
+ return registry.get(sessionManager) ?? null;
21
+ };
22
+ return { set, get };
23
+ }
@@ -81,6 +81,8 @@ export const __testing = {
81
81
  wrapToolParamNormalization,
82
82
  assertRequiredParams,
83
83
  };
84
+ /** Alias for upstream compatibility — callers importing the upstream name get the pool-bot version. */
85
+ export const createOpenClawCodingTools = createPoolbotCodingTools;
84
86
  export function createPoolbotCodingTools(options) {
85
87
  const execToolName = "exec";
86
88
  const sandbox = options?.sandbox?.enabled ? options.sandbox : undefined;
@@ -0,0 +1,22 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export function getQueuedFileWriter(writers, filePath) {
4
+ const existing = writers.get(filePath);
5
+ if (existing) {
6
+ return existing;
7
+ }
8
+ const dir = path.dirname(filePath);
9
+ const ready = fs.mkdir(dir, { recursive: true }).catch(() => undefined);
10
+ let queue = Promise.resolve();
11
+ const writer = {
12
+ filePath,
13
+ write: (line) => {
14
+ queue = queue
15
+ .then(() => ready)
16
+ .then(() => fs.appendFile(filePath, line, "utf8"))
17
+ .catch(() => undefined);
18
+ },
19
+ };
20
+ writers.set(filePath, writer);
21
+ return writer;
22
+ }
@@ -1,44 +1,118 @@
1
1
  import { spawn } from "node:child_process";
2
- import { defaultRuntime } from "../../runtime.js";
3
- import { formatCliCommand } from "../../cli/command-format.js";
4
- import { DEFAULT_SANDBOX_IMAGE, SANDBOX_AGENT_WORKSPACE_MOUNT } from "./constants.js";
5
- import { readRegistry, updateRegistry } from "./registry.js";
6
- import { computeSandboxConfigHash } from "./config-hash.js";
7
- import { resolveSandboxAgentId, resolveSandboxScopeKey, slugifySessionKey } from "./shared.js";
8
- const HOT_CONTAINER_WINDOW_MS = 5 * 60 * 1000;
9
- export function execDocker(args, opts) {
2
+ function createAbortError() {
3
+ const err = new Error("Aborted");
4
+ err.name = "AbortError";
5
+ return err;
6
+ }
7
+ export function execDockerRaw(args, opts) {
10
8
  return new Promise((resolve, reject) => {
11
9
  const child = spawn("docker", args, {
12
- stdio: ["ignore", "pipe", "pipe"],
10
+ stdio: ["pipe", "pipe", "pipe"],
13
11
  });
14
- let stdout = "";
15
- let stderr = "";
12
+ const stdoutChunks = [];
13
+ const stderrChunks = [];
14
+ let aborted = false;
15
+ const signal = opts?.signal;
16
+ const handleAbort = () => {
17
+ if (aborted) {
18
+ return;
19
+ }
20
+ aborted = true;
21
+ child.kill("SIGTERM");
22
+ };
23
+ if (signal) {
24
+ if (signal.aborted) {
25
+ handleAbort();
26
+ }
27
+ else {
28
+ signal.addEventListener("abort", handleAbort);
29
+ }
30
+ }
16
31
  child.stdout?.on("data", (chunk) => {
17
- stdout += chunk.toString();
32
+ stdoutChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
18
33
  });
19
34
  child.stderr?.on("data", (chunk) => {
20
- stderr += chunk.toString();
35
+ stderrChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
36
+ });
37
+ child.on("error", (error) => {
38
+ if (signal) {
39
+ signal.removeEventListener("abort", handleAbort);
40
+ }
41
+ reject(error);
21
42
  });
22
43
  child.on("close", (code) => {
44
+ if (signal) {
45
+ signal.removeEventListener("abort", handleAbort);
46
+ }
47
+ const stdout = Buffer.concat(stdoutChunks);
48
+ const stderr = Buffer.concat(stderrChunks);
49
+ if (aborted || signal?.aborted) {
50
+ reject(createAbortError());
51
+ return;
52
+ }
23
53
  const exitCode = code ?? 0;
24
54
  if (exitCode !== 0 && !opts?.allowFailure) {
25
- reject(new Error(stderr.trim() || `docker ${args.join(" ")} failed`));
55
+ const message = stderr.length > 0 ? stderr.toString("utf8").trim() : "";
56
+ const error = Object.assign(new Error(message || `docker ${args.join(" ")} failed`), {
57
+ code: exitCode,
58
+ stdout,
59
+ stderr,
60
+ });
61
+ reject(error);
26
62
  return;
27
63
  }
28
64
  resolve({ stdout, stderr, code: exitCode });
29
65
  });
66
+ const stdin = child.stdin;
67
+ if (stdin) {
68
+ if (opts?.input !== undefined) {
69
+ stdin.end(opts.input);
70
+ }
71
+ else {
72
+ stdin.end();
73
+ }
74
+ }
30
75
  });
31
76
  }
77
+ import { formatCliCommand } from "../../cli/command-format.js";
78
+ import { defaultRuntime } from "../../runtime.js";
79
+ import { computeSandboxConfigHash } from "./config-hash.js";
80
+ import { DEFAULT_SANDBOX_IMAGE, SANDBOX_AGENT_WORKSPACE_MOUNT } from "./constants.js";
81
+ import { readRegistry, updateRegistry } from "./registry.js";
82
+ import { resolveSandboxAgentId, resolveSandboxScopeKey, slugifySessionKey } from "./shared.js";
83
+ import { validateSandboxSecurity } from "./validate-sandbox-security.js";
84
+ const HOT_CONTAINER_WINDOW_MS = 5 * 60 * 1000;
85
+ export async function execDocker(args, opts) {
86
+ const result = await execDockerRaw(args, opts);
87
+ return {
88
+ stdout: result.stdout.toString("utf8"),
89
+ stderr: result.stderr.toString("utf8"),
90
+ code: result.code,
91
+ };
92
+ }
93
+ export async function readDockerContainerLabel(containerName, label) {
94
+ const result = await execDocker(["inspect", "-f", `{{ index .Config.Labels "${label}" }}`, containerName], { allowFailure: true });
95
+ if (result.code !== 0) {
96
+ return null;
97
+ }
98
+ const raw = result.stdout.trim();
99
+ if (!raw || raw === "<no value>") {
100
+ return null;
101
+ }
102
+ return raw;
103
+ }
32
104
  export async function readDockerPort(containerName, port) {
33
105
  const result = await execDocker(["port", containerName, `${port}/tcp`], {
34
106
  allowFailure: true,
35
107
  });
36
- if (result.code !== 0)
108
+ if (result.code !== 0) {
37
109
  return null;
110
+ }
38
111
  const line = result.stdout.trim().split(/\r?\n/)[0] ?? "";
39
112
  const match = line.match(/:(\d+)\s*$/);
40
- if (!match)
113
+ if (!match) {
41
114
  return null;
115
+ }
42
116
  const mapped = Number.parseInt(match[1] ?? "", 10);
43
117
  return Number.isFinite(mapped) ? mapped : null;
44
118
  }
@@ -46,8 +120,9 @@ async function dockerImageExists(image) {
46
120
  const result = await execDocker(["image", "inspect", image], {
47
121
  allowFailure: true,
48
122
  });
49
- if (result.code === 0)
123
+ if (result.code === 0) {
50
124
  return true;
125
+ }
51
126
  const stderr = result.stderr.trim();
52
127
  if (stderr.includes("No such image")) {
53
128
  return false;
@@ -56,8 +131,9 @@ async function dockerImageExists(image) {
56
131
  }
57
132
  export async function ensureDockerImage(image) {
58
133
  const exists = await dockerImageExists(image);
59
- if (exists)
134
+ if (exists) {
60
135
  return;
136
+ }
61
137
  if (image === DEFAULT_SANDBOX_IMAGE) {
62
138
  await execDocker(["pull", "debian:bookworm-slim"]);
63
139
  await execDocker(["tag", "debian:bookworm-slim", DEFAULT_SANDBOX_IMAGE]);
@@ -69,13 +145,15 @@ export async function dockerContainerState(name) {
69
145
  const result = await execDocker(["inspect", "-f", "{{.State.Running}}", name], {
70
146
  allowFailure: true,
71
147
  });
72
- if (result.code !== 0)
148
+ if (result.code !== 0) {
73
149
  return { exists: false, running: false };
150
+ }
74
151
  return { exists: true, running: result.stdout.trim() === "true" };
75
152
  }
76
153
  function normalizeDockerLimit(value) {
77
- if (value === undefined || value === null)
154
+ if (value === undefined || value === null) {
78
155
  return undefined;
156
+ }
79
157
  if (typeof value === "number") {
80
158
  return Number.isFinite(value) ? String(value) : undefined;
81
159
  }
@@ -83,23 +161,29 @@ function normalizeDockerLimit(value) {
83
161
  return trimmed ? trimmed : undefined;
84
162
  }
85
163
  function formatUlimitValue(name, value) {
86
- if (!name.trim())
164
+ if (!name.trim()) {
87
165
  return null;
166
+ }
88
167
  if (typeof value === "number" || typeof value === "string") {
89
168
  const raw = String(value).trim();
90
169
  return raw ? `${name}=${raw}` : null;
91
170
  }
92
171
  const soft = typeof value.soft === "number" ? Math.max(0, value.soft) : undefined;
93
172
  const hard = typeof value.hard === "number" ? Math.max(0, value.hard) : undefined;
94
- if (soft === undefined && hard === undefined)
173
+ if (soft === undefined && hard === undefined) {
95
174
  return null;
96
- if (soft === undefined)
175
+ }
176
+ if (soft === undefined) {
97
177
  return `${name}=${hard}`;
98
- if (hard === undefined)
178
+ }
179
+ if (hard === undefined) {
99
180
  return `${name}=${soft}`;
181
+ }
100
182
  return `${name}=${soft}:${hard}`;
101
183
  }
102
184
  export function buildSandboxCreateArgs(params) {
185
+ // Runtime security validation: blocks dangerous bind mounts, network modes, and profiles.
186
+ validateSandboxSecurity(params.cfg);
103
187
  const createdAtMs = params.createdAtMs ?? Date.now();
104
188
  const args = ["create", "--name", params.name];
105
189
  args.push("--label", "poolbot.sandbox=1");
@@ -109,18 +193,28 @@ export function buildSandboxCreateArgs(params) {
109
193
  args.push("--label", `poolbot.configHash=${params.configHash}`);
110
194
  }
111
195
  for (const [key, value] of Object.entries(params.labels ?? {})) {
112
- if (key && value)
196
+ if (key && value) {
113
197
  args.push("--label", `${key}=${value}`);
198
+ }
114
199
  }
115
- if (params.cfg.readOnlyRoot)
200
+ if (params.cfg.readOnlyRoot) {
116
201
  args.push("--read-only");
202
+ }
117
203
  for (const entry of params.cfg.tmpfs) {
118
204
  args.push("--tmpfs", entry);
119
205
  }
120
- if (params.cfg.network)
206
+ if (params.cfg.network) {
121
207
  args.push("--network", params.cfg.network);
122
- if (params.cfg.user)
208
+ }
209
+ if (params.cfg.user) {
123
210
  args.push("--user", params.cfg.user);
211
+ }
212
+ for (const [key, value] of Object.entries(params.cfg.env ?? {})) {
213
+ if (!key.trim()) {
214
+ continue;
215
+ }
216
+ args.push("--env", key + "=" + value);
217
+ }
124
218
  for (const cap of params.cfg.capDrop) {
125
219
  args.push("--cap-drop", cap);
126
220
  }
@@ -132,29 +226,34 @@ export function buildSandboxCreateArgs(params) {
132
226
  args.push("--security-opt", `apparmor=${params.cfg.apparmorProfile}`);
133
227
  }
134
228
  for (const entry of params.cfg.dns ?? []) {
135
- if (entry.trim())
229
+ if (entry.trim()) {
136
230
  args.push("--dns", entry);
231
+ }
137
232
  }
138
233
  for (const entry of params.cfg.extraHosts ?? []) {
139
- if (entry.trim())
234
+ if (entry.trim()) {
140
235
  args.push("--add-host", entry);
236
+ }
141
237
  }
142
238
  if (typeof params.cfg.pidsLimit === "number" && params.cfg.pidsLimit > 0) {
143
239
  args.push("--pids-limit", String(params.cfg.pidsLimit));
144
240
  }
145
241
  const memory = normalizeDockerLimit(params.cfg.memory);
146
- if (memory)
242
+ if (memory) {
147
243
  args.push("--memory", memory);
244
+ }
148
245
  const memorySwap = normalizeDockerLimit(params.cfg.memorySwap);
149
- if (memorySwap)
246
+ if (memorySwap) {
150
247
  args.push("--memory-swap", memorySwap);
248
+ }
151
249
  if (typeof params.cfg.cpus === "number" && params.cfg.cpus > 0) {
152
250
  args.push("--cpus", String(params.cfg.cpus));
153
251
  }
154
252
  for (const [name, value] of Object.entries(params.cfg.ulimits ?? {})) {
155
253
  const formatted = formatUlimitValue(name, value);
156
- if (formatted)
254
+ if (formatted) {
157
255
  args.push("--ulimit", formatted);
256
+ }
158
257
  }
159
258
  if (params.cfg.binds?.length) {
160
259
  for (const bind of params.cfg.binds) {
@@ -187,13 +286,7 @@ async function createSandboxContainer(params) {
187
286
  }
188
287
  }
189
288
  async function readContainerConfigHash(containerName) {
190
- const result = await execDocker(["inspect", "-f", '{{ index .Config.Labels "poolbot.configHash" }}', containerName], { allowFailure: true });
191
- if (result.code !== 0)
192
- return null;
193
- const raw = result.stdout.trim();
194
- if (!raw || raw === "<no value>")
195
- return null;
196
- return raw;
289
+ return await readDockerContainerLabel(containerName, "poolbot.configHash");
197
290
  }
198
291
  function formatSandboxRecreateHint(params) {
199
292
  if (params.scope === "session") {
@@ -0,0 +1,146 @@
1
+ import { execDockerRaw } from "./docker.js";
2
+ import { buildSandboxFsMounts, resolveSandboxFsPathWithMounts, } from "./fs-paths.js";
3
+ export function createSandboxFsBridge(params) {
4
+ return new SandboxFsBridgeImpl(params.sandbox);
5
+ }
6
+ class SandboxFsBridgeImpl {
7
+ sandbox;
8
+ mounts;
9
+ constructor(sandbox) {
10
+ this.sandbox = sandbox;
11
+ this.mounts = buildSandboxFsMounts(sandbox);
12
+ }
13
+ resolvePath(params) {
14
+ const target = this.resolveResolvedPath(params);
15
+ return {
16
+ hostPath: target.hostPath,
17
+ relativePath: target.relativePath,
18
+ containerPath: target.containerPath,
19
+ };
20
+ }
21
+ async readFile(params) {
22
+ const target = this.resolveResolvedPath(params);
23
+ const result = await this.runCommand('set -eu; cat -- "$1"', {
24
+ args: [target.containerPath],
25
+ signal: params.signal,
26
+ });
27
+ return result.stdout;
28
+ }
29
+ async writeFile(params) {
30
+ const target = this.resolveResolvedPath(params);
31
+ this.ensureWriteAccess(target, "write files");
32
+ const buffer = Buffer.isBuffer(params.data)
33
+ ? params.data
34
+ : Buffer.from(params.data, params.encoding ?? "utf8");
35
+ const script = params.mkdir === false
36
+ ? 'set -eu; cat >"$1"'
37
+ : 'set -eu; dir=$(dirname -- "$1"); if [ "$dir" != "." ]; then mkdir -p -- "$dir"; fi; cat >"$1"';
38
+ await this.runCommand(script, {
39
+ args: [target.containerPath],
40
+ stdin: buffer,
41
+ signal: params.signal,
42
+ });
43
+ }
44
+ async mkdirp(params) {
45
+ const target = this.resolveResolvedPath(params);
46
+ this.ensureWriteAccess(target, "create directories");
47
+ await this.runCommand('set -eu; mkdir -p -- "$1"', {
48
+ args: [target.containerPath],
49
+ signal: params.signal,
50
+ });
51
+ }
52
+ async remove(params) {
53
+ const target = this.resolveResolvedPath(params);
54
+ this.ensureWriteAccess(target, "remove files");
55
+ const flags = [params.force === false ? "" : "-f", params.recursive ? "-r" : ""].filter(Boolean);
56
+ const rmCommand = flags.length > 0 ? `rm ${flags.join(" ")}` : "rm";
57
+ await this.runCommand(`set -eu; ${rmCommand} -- "$1"`, {
58
+ args: [target.containerPath],
59
+ signal: params.signal,
60
+ });
61
+ }
62
+ async rename(params) {
63
+ const from = this.resolveResolvedPath({ filePath: params.from, cwd: params.cwd });
64
+ const to = this.resolveResolvedPath({ filePath: params.to, cwd: params.cwd });
65
+ this.ensureWriteAccess(from, "rename files");
66
+ this.ensureWriteAccess(to, "rename files");
67
+ await this.runCommand('set -eu; dir=$(dirname -- "$2"); if [ "$dir" != "." ]; then mkdir -p -- "$dir"; fi; mv -- "$1" "$2"', {
68
+ args: [from.containerPath, to.containerPath],
69
+ signal: params.signal,
70
+ });
71
+ }
72
+ async stat(params) {
73
+ const target = this.resolveResolvedPath(params);
74
+ const result = await this.runCommand('set -eu; stat -c "%F|%s|%Y" -- "$1"', {
75
+ args: [target.containerPath],
76
+ signal: params.signal,
77
+ allowFailure: true,
78
+ });
79
+ if (result.code !== 0) {
80
+ const stderr = result.stderr.toString("utf8");
81
+ if (stderr.includes("No such file or directory")) {
82
+ return null;
83
+ }
84
+ const message = stderr.trim() || `stat failed with code ${result.code}`;
85
+ throw new Error(`stat failed for ${target.containerPath}: ${message}`);
86
+ }
87
+ const text = result.stdout.toString("utf8").trim();
88
+ const [typeRaw, sizeRaw, mtimeRaw] = text.split("|");
89
+ const size = Number.parseInt(sizeRaw ?? "0", 10);
90
+ const mtime = Number.parseInt(mtimeRaw ?? "0", 10) * 1000;
91
+ return {
92
+ type: coerceStatType(typeRaw),
93
+ size: Number.isFinite(size) ? size : 0,
94
+ mtimeMs: Number.isFinite(mtime) ? mtime : 0,
95
+ };
96
+ }
97
+ async runCommand(script, options = {}) {
98
+ const dockerArgs = [
99
+ "exec",
100
+ "-i",
101
+ this.sandbox.containerName,
102
+ "sh",
103
+ "-c",
104
+ script,
105
+ "moltbot-sandbox-fs",
106
+ ];
107
+ if (options.args?.length) {
108
+ dockerArgs.push(...options.args);
109
+ }
110
+ return execDockerRaw(dockerArgs, {
111
+ input: options.stdin,
112
+ allowFailure: options.allowFailure,
113
+ signal: options.signal,
114
+ });
115
+ }
116
+ ensureWriteAccess(target, action) {
117
+ if (!allowsWrites(this.sandbox.workspaceAccess) || !target.writable) {
118
+ throw new Error(`Sandbox path is read-only; cannot ${action}: ${target.containerPath}`);
119
+ }
120
+ }
121
+ resolveResolvedPath(params) {
122
+ return resolveSandboxFsPathWithMounts({
123
+ filePath: params.filePath,
124
+ cwd: params.cwd ?? this.sandbox.workspaceDir,
125
+ defaultWorkspaceRoot: this.sandbox.workspaceDir,
126
+ defaultContainerRoot: this.sandbox.containerWorkdir,
127
+ mounts: this.mounts,
128
+ });
129
+ }
130
+ }
131
+ function allowsWrites(access) {
132
+ return access === "rw";
133
+ }
134
+ function coerceStatType(typeRaw) {
135
+ if (!typeRaw) {
136
+ return "other";
137
+ }
138
+ const normalized = typeRaw.trim().toLowerCase();
139
+ if (normalized.includes("directory")) {
140
+ return "directory";
141
+ }
142
+ if (normalized.includes("file")) {
143
+ return "file";
144
+ }
145
+ return "other";
146
+ }