@poolzin/pool-bot 2026.2.11 → 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 (535) hide show
  1. package/CHANGELOG.md +34 -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/usage.js +22 -0
  5. package/dist/agents/auth-profiles.js +1 -1
  6. package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +23 -0
  7. package/dist/agents/bash-tools.exec-runtime.js +438 -0
  8. package/dist/agents/bash-tools.shared.js +6 -0
  9. package/dist/agents/cli-runner/reliability.js +61 -0
  10. package/dist/agents/cli-watchdog-defaults.js +11 -0
  11. package/dist/agents/command-poll-backoff.js +63 -0
  12. package/dist/agents/current-time.js +16 -0
  13. package/dist/agents/glob-pattern.js +42 -0
  14. package/dist/agents/memory-search.js +33 -0
  15. package/dist/agents/model-alias-lines.js +18 -0
  16. package/dist/agents/model-auth-label.js +61 -0
  17. package/dist/agents/model-fallback.js +59 -8
  18. package/dist/agents/models-config.e2e-harness.js +115 -0
  19. package/dist/agents/ollama-stream.js +11 -3
  20. package/dist/agents/openclaw-tools.js +135 -0
  21. package/dist/agents/pi-auth-json.js +118 -0
  22. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +147 -0
  23. package/dist/agents/pi-embedded-subscribe.e2e-harness.js +90 -0
  24. package/dist/agents/pi-embedded-subscribe.handlers.compaction.js +63 -0
  25. package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +30 -0
  26. package/dist/agents/pi-extensions/session-manager-runtime-registry.js +23 -0
  27. package/dist/agents/pi-tools.before-tool-call.js +145 -4
  28. package/dist/agents/pi-tools.js +29 -9
  29. package/dist/agents/pi-tools.policy.js +85 -92
  30. package/dist/agents/pi-tools.schema.js +54 -27
  31. package/dist/agents/queued-file-writer.js +22 -0
  32. package/dist/agents/sandbox/docker.js +133 -40
  33. package/dist/agents/sandbox/fs-bridge.js +146 -0
  34. package/dist/agents/sandbox/fs-paths.js +205 -0
  35. package/dist/agents/sandbox/hash.js +4 -0
  36. package/dist/agents/sandbox/validate-sandbox-security.js +157 -0
  37. package/dist/agents/sandbox-paths.js +3 -0
  38. package/dist/agents/sandbox-tool-policy.js +26 -0
  39. package/dist/agents/sanitize-for-prompt.js +18 -0
  40. package/dist/agents/session-dirs.js +20 -0
  41. package/dist/agents/session-write-lock.js +203 -39
  42. package/dist/agents/skills/filter.js +24 -0
  43. package/dist/agents/skills/tools-dir.js +9 -0
  44. package/dist/agents/skills-install-download.js +290 -0
  45. package/dist/agents/skills-install-output.js +30 -0
  46. package/dist/agents/skills-install.download-test-utils.js +36 -0
  47. package/dist/agents/skills.e2e-test-helpers.js +13 -0
  48. package/dist/agents/subagent-announce-queue.js +59 -15
  49. package/dist/agents/subagent-depth.js +137 -0
  50. package/dist/agents/subagent-registry.js +448 -96
  51. package/dist/agents/subagent-spawn.js +262 -0
  52. package/dist/agents/system-prompt.js +52 -10
  53. package/dist/agents/test-helpers/fast-tool-stubs.js +18 -0
  54. package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +74 -0
  55. package/dist/agents/tool-display-common.js +782 -0
  56. package/dist/agents/tool-loop-detection.js +466 -0
  57. package/dist/agents/tool-policy.js +6 -0
  58. package/dist/agents/tools/image-tool.js +1 -1
  59. package/dist/agents/tools/sessions-access.js +178 -0
  60. package/dist/agents/tools/sessions-resolution.js +206 -0
  61. package/dist/agents/tools/subagents-tool.js +616 -0
  62. package/dist/agents/workspace-dir.js +18 -0
  63. package/dist/agents/workspace-dirs.js +14 -0
  64. package/dist/agents/workspace.js +70 -0
  65. package/dist/auto-reply/heartbeat-reply-payload.js +18 -0
  66. package/dist/auto-reply/reply/commands-export-session.js +163 -0
  67. package/dist/auto-reply/reply/commands-mesh.js +245 -0
  68. package/dist/auto-reply/reply/commands-setunset.js +28 -0
  69. package/dist/auto-reply/reply/commands-slash-parse.js +31 -0
  70. package/dist/auto-reply/reply/commands-system-prompt.js +117 -0
  71. package/dist/auto-reply/reply/directive-handling.levels.js +17 -0
  72. package/dist/auto-reply/reply/directive-handling.params.js +1 -0
  73. package/dist/auto-reply/reply/directive-parsing.js +36 -0
  74. package/dist/auto-reply/reply/dispatcher-registry.js +43 -0
  75. package/dist/auto-reply/reply/elevated-unavailable.js +20 -0
  76. package/dist/auto-reply/reply/post-compaction-audit.js +96 -0
  77. package/dist/auto-reply/reply/post-compaction-context.js +98 -0
  78. package/dist/auto-reply/reply/reply-delivery.js +92 -0
  79. package/dist/auto-reply/reply/session-reset-prompt.js +1 -0
  80. package/dist/auto-reply/reply/session-run-accounting.js +33 -0
  81. package/dist/auto-reply/reply.directive.directive-behavior.e2e-harness.js +115 -0
  82. package/dist/auto-reply/reply.directive.directive-behavior.e2e-mocks.js +12 -0
  83. package/dist/browser/bridge-auth-registry.js +26 -0
  84. package/dist/browser/client-actions-url.js +10 -0
  85. package/dist/browser/control-auth.js +73 -0
  86. package/dist/browser/csrf.js +64 -0
  87. package/dist/browser/http-auth.js +52 -0
  88. package/dist/browser/paths.js +37 -0
  89. package/dist/browser/proxy-files.js +32 -0
  90. package/dist/browser/pw-ai-state.js +7 -0
  91. package/dist/browser/resolved-config-refresh.js +42 -0
  92. package/dist/browser/routes/path-output.js +1 -0
  93. package/dist/browser/server-context.chrome-test-harness.js +20 -0
  94. package/dist/browser/server-middleware.js +31 -0
  95. package/dist/browser/test-port.js +16 -0
  96. package/dist/build-info.json +3 -3
  97. package/dist/canvas-host/file-resolver.js +43 -0
  98. package/dist/channels/account-summary.js +19 -0
  99. package/dist/channels/draft-stream-loop.js +77 -0
  100. package/dist/channels/plugins/account-helpers.js +26 -0
  101. package/dist/channels/telegram/allow-from.js +10 -0
  102. package/dist/cli/browser-cli-resize.js +22 -0
  103. package/dist/cli/browser-cli-shared.js +8 -0
  104. package/dist/cli/clawbot-cli.js +5 -0
  105. package/dist/cli/completion-cli.js +566 -0
  106. package/dist/cli/config-cli.js +63 -5
  107. package/dist/cli/daemon-cli/lifecycle-core.js +256 -0
  108. package/dist/cli/daemon-cli/register-service-commands.js +60 -0
  109. package/dist/cli/daemon-cli-compat.js +80 -0
  110. package/dist/cli/nodes-cli/pairing-render.js +26 -0
  111. package/dist/cli/program/action-reparse.js +17 -0
  112. package/dist/cli/program/command-registry.js +17 -0
  113. package/dist/cli/program/program-context.js +8 -0
  114. package/dist/cli/program/register.subclis.js +7 -0
  115. package/dist/cli/program/routes.js +233 -0
  116. package/dist/cli/qr-cli.js +132 -0
  117. package/dist/cli/requirements-test-fixtures.js +17 -0
  118. package/dist/cli/respawn-policy.js +4 -0
  119. package/dist/cli/shared/parse-port.js +18 -0
  120. package/dist/cli/skills-cli.format.js +241 -0
  121. package/dist/cli/update-cli/progress.js +121 -0
  122. package/dist/cli/update-cli/restart-helper.js +108 -0
  123. package/dist/cli/update-cli/shared.js +196 -0
  124. package/dist/cli/update-cli/status.js +97 -0
  125. package/dist/cli/update-cli/suppress-deprecations.js +17 -0
  126. package/dist/cli/update-cli/update-command.js +506 -0
  127. package/dist/cli/update-cli/wizard.js +130 -0
  128. package/dist/cli/update-cli.js +3 -9
  129. package/dist/cli/windows-argv.js +69 -0
  130. package/dist/commands/auth-choice-legacy.js +20 -0
  131. package/dist/commands/auth-choice.apply-helpers.js +8 -0
  132. package/dist/commands/channel-test-helpers.js +19 -0
  133. package/dist/commands/cleanup-plan.js +10 -0
  134. package/dist/commands/cleanup-utils.js +7 -0
  135. package/dist/commands/config-validation.js +15 -0
  136. package/dist/commands/doctor-completion.js +112 -0
  137. package/dist/commands/doctor-memory-search.js +119 -0
  138. package/dist/commands/doctor-session-locks.js +73 -0
  139. package/dist/commands/doctor.e2e-harness.js +364 -0
  140. package/dist/commands/gateway-presence.js +19 -0
  141. package/dist/commands/model-default.js +35 -0
  142. package/dist/commands/models/fallbacks-shared.js +102 -0
  143. package/dist/commands/models/shared.js +24 -0
  144. package/dist/commands/onboard-auth.config-gateways.js +64 -0
  145. package/dist/commands/onboard-auth.config-litellm.js +45 -0
  146. package/dist/commands/onboard-auth.config-shared.js +116 -0
  147. package/dist/commands/onboard-config.js +16 -0
  148. package/dist/commands/onboard-non-interactive.test-helpers.js +31 -0
  149. package/dist/commands/onboard-provider-auth-flags.js +136 -0
  150. package/dist/commands/openai-codex-oauth.js +40 -0
  151. package/dist/commands/test-runtime-config-helpers.js +21 -0
  152. package/dist/commands/test-wizard-helpers.js +68 -0
  153. package/dist/commands/vllm-setup.js +66 -0
  154. package/dist/compat/legacy-names.js +2 -0
  155. package/dist/config/backup-rotation.js +19 -0
  156. package/dist/config/env-preserve.js +122 -0
  157. package/dist/config/includes-scan.js +78 -0
  158. package/dist/config/plugins-allowlist.js +13 -0
  159. package/dist/config/schema.help.js +256 -0
  160. package/dist/config/schema.hints.js +189 -0
  161. package/dist/config/schema.irc.js +20 -0
  162. package/dist/config/schema.labels.js +317 -0
  163. package/dist/config/sessions/delivery-info.js +40 -0
  164. package/dist/config/types.irc.js +1 -0
  165. package/dist/config/zod-schema.agent-defaults.js +14 -0
  166. package/dist/config/zod-schema.agent-model.js +10 -0
  167. package/dist/config/zod-schema.agent-runtime.js +14 -0
  168. package/dist/config/zod-schema.allowdeny.js +35 -0
  169. package/dist/config/zod-schema.sensitive.js +4 -0
  170. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  171. package/dist/cron/isolated-agent/skills-snapshot.js +26 -0
  172. package/dist/cron/isolated-agent/subagent-followup.js +127 -0
  173. package/dist/cron/isolated-agent.mocks.js +12 -0
  174. package/dist/cron/isolated-agent.test-setup.js +22 -0
  175. package/dist/cron/legacy-delivery.js +43 -0
  176. package/dist/cron/webhook-url.js +22 -0
  177. package/dist/daemon/arg-split.js +40 -0
  178. package/dist/daemon/exec-file.js +23 -0
  179. package/dist/daemon/output.js +6 -0
  180. package/dist/daemon/runtime-format.js +31 -0
  181. package/dist/daemon/schtasks-exec.js +4 -0
  182. package/dist/daemon/service-audit.js +22 -0
  183. package/dist/discord/client.js +41 -0
  184. package/dist/discord/components-registry.js +57 -0
  185. package/dist/discord/components.js +816 -0
  186. package/dist/discord/guilds.js +12 -0
  187. package/dist/discord/monitor/gateway-plugin.js +48 -0
  188. package/dist/discord/monitor/presence.js +30 -0
  189. package/dist/discord/send.components.js +115 -0
  190. package/dist/discord/send.shared.js +4 -0
  191. package/dist/discord/ui.js +26 -0
  192. package/dist/discord/voice-message.js +254 -0
  193. package/dist/gateway/agent-event-assistant-text.js +5 -0
  194. package/dist/gateway/agent-prompt.js +33 -0
  195. package/dist/gateway/auth-rate-limit.js +136 -0
  196. package/dist/gateway/channel-health-monitor.js +114 -0
  197. package/dist/gateway/control-ui-contract.js +1 -0
  198. package/dist/gateway/control-ui-csp.js +15 -0
  199. package/dist/gateway/gateway-config-prompts.shared.js +25 -0
  200. package/dist/gateway/http-auth-helpers.js +18 -0
  201. package/dist/gateway/http-common.js +18 -0
  202. package/dist/gateway/http-endpoint-helpers.js +27 -0
  203. package/dist/gateway/node-invoke-sanitize.js +11 -0
  204. package/dist/gateway/node-invoke-system-run-approval.js +205 -0
  205. package/dist/gateway/probe-auth.js +21 -0
  206. package/dist/gateway/protocol/index.js +7 -2
  207. package/dist/gateway/protocol/schema/mesh.js +54 -0
  208. package/dist/gateway/protocol/schema/protocol-schemas.js +7 -0
  209. package/dist/gateway/protocol/schema.js +1 -0
  210. package/dist/gateway/server/ws-connection/auth-messages.js +54 -0
  211. package/dist/gateway/server-channels.js +11 -0
  212. package/dist/gateway/server-methods/attachment-normalize.js +16 -0
  213. package/dist/gateway/server-methods/base-hash.js +8 -0
  214. package/dist/gateway/server-methods/mesh.js +700 -0
  215. package/dist/gateway/server-methods/nodes.handlers.invoke-result.js +55 -0
  216. package/dist/gateway/server-methods/restart-request.js +13 -0
  217. package/dist/gateway/server-methods/validation.js +8 -0
  218. package/dist/gateway/server.agent.gateway-server-agent.mocks.js +35 -0
  219. package/dist/gateway/server.e2e-registry-helpers.js +1 -0
  220. package/dist/gateway/server.e2e-ws-harness.js +20 -0
  221. package/dist/gateway/test-helpers.js +2 -0
  222. package/dist/gateway/test-helpers.server.js +3 -1
  223. package/dist/gateway/test-http-response.js +12 -0
  224. package/dist/gateway/test-openai-responses-model.js +20 -0
  225. package/dist/gateway/test-temp-config.js +30 -0
  226. package/dist/gateway/test-with-server.js +32 -0
  227. package/dist/hooks/bundled/bootstrap-extra-files/handler.js +46 -0
  228. package/dist/imessage/monitor/abort-handler.js +23 -0
  229. package/dist/imessage/monitor/inbound-processing.js +346 -0
  230. package/dist/imessage/monitor/parse-notification.js +64 -0
  231. package/dist/imessage/target-parsing-helpers.js +92 -0
  232. package/dist/infra/archive.js +244 -20
  233. package/dist/infra/detect-package-manager.js +26 -0
  234. package/dist/infra/exec-approvals-allowlist.js +257 -0
  235. package/dist/infra/exec-approvals-analysis.js +770 -0
  236. package/dist/infra/exec-approvals.js +13 -0
  237. package/dist/infra/file-lock.js +1 -0
  238. package/dist/infra/gemini-auth.js +39 -0
  239. package/dist/infra/heartbeat-active-hours.js +85 -0
  240. package/dist/infra/heartbeat-events-filter.js +50 -0
  241. package/dist/infra/heartbeat-runner.test-utils.js +39 -0
  242. package/dist/infra/http-body.js +265 -0
  243. package/dist/infra/install-package-dir.js +50 -0
  244. package/dist/infra/install-safe-path.js +49 -0
  245. package/dist/infra/json-files.js +49 -0
  246. package/dist/infra/jsonl-socket.js +52 -0
  247. package/dist/infra/map-size.js +14 -0
  248. package/dist/infra/net/hostname.js +7 -0
  249. package/dist/infra/npm-registry-spec.js +39 -0
  250. package/dist/infra/openclaw-root.js +109 -0
  251. package/dist/infra/outbound/delivery-queue.js +214 -0
  252. package/dist/infra/outbound/identity.js +23 -0
  253. package/dist/infra/outbound/message-action-params.js +307 -0
  254. package/dist/infra/outbound/tool-payload.js +21 -0
  255. package/dist/infra/package-json.js +23 -0
  256. package/dist/infra/pairing-files.js +19 -0
  257. package/dist/infra/pairing-token.js +9 -0
  258. package/dist/infra/path-prepend.js +51 -0
  259. package/dist/infra/path-safety.js +16 -0
  260. package/dist/infra/process-respawn.js +49 -0
  261. package/dist/infra/runtime-status.js +16 -0
  262. package/dist/infra/session-cost-usage.types.js +1 -0
  263. package/dist/infra/session-maintenance-warning.js +89 -0
  264. package/dist/infra/system-run-command.js +78 -0
  265. package/dist/infra/tmp-openclaw-dir.js +81 -0
  266. package/dist/infra/tmp-poolbot-dir.js +2 -0
  267. package/dist/infra/update-channels.js +19 -0
  268. package/dist/line/actions.js +45 -0
  269. package/dist/line/channel-access-token.js +9 -0
  270. package/dist/line/flex-templates/basic-cards.js +332 -0
  271. package/dist/line/flex-templates/common.js +18 -0
  272. package/dist/line/flex-templates/media-control-cards.js +453 -0
  273. package/dist/line/flex-templates/message.js +10 -0
  274. package/dist/line/flex-templates/schedule-cards.js +399 -0
  275. package/dist/line/flex-templates/types.js +1 -0
  276. package/dist/line/webhook-node.js +100 -0
  277. package/dist/line/webhook-utils.js +11 -0
  278. package/dist/logging/diagnostic-session-state.js +73 -0
  279. package/dist/logging/diagnostic.js +22 -0
  280. package/dist/logging/timestamps.js +14 -0
  281. package/dist/markdown/whatsapp.js +62 -0
  282. package/dist/media/base64.js +34 -0
  283. package/dist/media/local-roots.js +32 -0
  284. package/dist/media/outbound-attachment.js +10 -0
  285. package/dist/media/read-response-with-limit.js +41 -0
  286. package/dist/media/sniff-mime-from-base64.js +19 -0
  287. package/dist/media-understanding/audio-preflight.js +67 -0
  288. package/dist/media-understanding/fs.js +13 -0
  289. package/dist/media-understanding/output-extract.js +26 -0
  290. package/dist/media-understanding/providers/audio.test-helpers.js +34 -0
  291. package/dist/media-understanding/providers/google/inline-data.js +64 -0
  292. package/dist/media-understanding/providers/shared.js +7 -0
  293. package/dist/media-understanding/runner.entries.js +459 -0
  294. package/dist/memory/batch-error-utils.js +11 -0
  295. package/dist/memory/batch-http.js +27 -0
  296. package/dist/memory/batch-output.js +29 -0
  297. package/dist/memory/batch-runner.js +22 -0
  298. package/dist/memory/batch-upload.js +23 -0
  299. package/dist/memory/batch-utils.js +26 -0
  300. package/dist/memory/embeddings-debug.js +11 -0
  301. package/dist/memory/embeddings-remote-client.js +22 -0
  302. package/dist/memory/embeddings-remote-fetch.js +14 -0
  303. package/dist/memory/embeddings.js +36 -9
  304. package/dist/memory/hybrid.js +24 -5
  305. package/dist/memory/manager-embedding-ops.js +616 -0
  306. package/dist/memory/manager-sync-ops.js +953 -0
  307. package/dist/memory/manager.js +76 -28
  308. package/dist/memory/mmr.js +164 -0
  309. package/dist/memory/qmd-manager.js +1061 -0
  310. package/dist/memory/qmd-query-parser.js +107 -0
  311. package/dist/memory/qmd-scope.js +93 -0
  312. package/dist/memory/query-expansion.js +331 -0
  313. package/dist/memory/search-manager.js +0 -1
  314. package/dist/memory/sync-index.js +21 -0
  315. package/dist/memory/sync-progress.js +22 -0
  316. package/dist/memory/sync-stale.js +30 -0
  317. package/dist/memory/temporal-decay.js +119 -0
  318. package/dist/memory/test-embeddings-mock.js +16 -0
  319. package/dist/memory/test-manager-helpers.js +14 -0
  320. package/dist/memory/test-runtime-mocks.js +11 -0
  321. package/dist/node-host/invoke-browser.js +177 -0
  322. package/dist/node-host/invoke.js +685 -0
  323. package/dist/pairing/setup-code.js +285 -0
  324. package/dist/plugin-sdk/account-id.js +1 -0
  325. package/dist/plugin-sdk/agent-media-payload.js +13 -0
  326. package/dist/plugin-sdk/allow-from.js +47 -0
  327. package/dist/plugin-sdk/command-auth.js +23 -0
  328. package/dist/plugin-sdk/config-paths.js +9 -0
  329. package/dist/plugin-sdk/file-lock.js +116 -0
  330. package/dist/plugin-sdk/json-store.js +31 -0
  331. package/dist/plugin-sdk/onboarding.js +28 -0
  332. package/dist/plugin-sdk/provider-auth-result.js +29 -0
  333. package/dist/plugin-sdk/slack-message-actions.js +133 -0
  334. package/dist/plugin-sdk/status-helpers.js +35 -0
  335. package/dist/plugin-sdk/text-chunking.js +31 -0
  336. package/dist/plugin-sdk/tool-send.js +12 -0
  337. package/dist/plugin-sdk/webhook-path.js +27 -0
  338. package/dist/plugin-sdk/webhook-targets.js +34 -0
  339. package/dist/plugins/hooks.test-helpers.js +21 -0
  340. package/dist/plugins/uninstall.js +171 -0
  341. package/dist/process/kill-tree.js +98 -0
  342. package/dist/process/supervisor/adapters/child.js +143 -0
  343. package/dist/process/supervisor/adapters/env.js +13 -0
  344. package/dist/process/supervisor/adapters/pty.js +148 -0
  345. package/dist/process/supervisor/index.js +10 -0
  346. package/dist/process/supervisor/registry.js +117 -0
  347. package/dist/process/supervisor/supervisor.js +244 -0
  348. package/dist/process/supervisor/types.js +1 -0
  349. package/dist/providers/google-shared.test-helpers.js +75 -0
  350. package/dist/security/audit-channel.js +419 -0
  351. package/dist/security/audit-tool-policy.js +1 -0
  352. package/dist/security/scan-paths.js +12 -0
  353. package/dist/sessions/input-provenance.js +55 -0
  354. package/dist/sessions/session-key-utils.js +7 -0
  355. package/dist/shared/chat-content.js +31 -0
  356. package/dist/shared/chat-envelope.js +45 -0
  357. package/dist/shared/config-eval.js +117 -0
  358. package/dist/shared/device-auth.js +16 -0
  359. package/dist/shared/entry-metadata.js +9 -0
  360. package/dist/shared/entry-status.js +25 -0
  361. package/dist/shared/frontmatter.js +98 -0
  362. package/dist/shared/model-param-b.js +19 -0
  363. package/dist/shared/net/ipv4.js +17 -0
  364. package/dist/shared/node-match.js +53 -0
  365. package/dist/shared/pid-alive.js +12 -0
  366. package/dist/shared/process-scoped-map.js +10 -0
  367. package/dist/shared/requirements.js +128 -0
  368. package/dist/shared/subagents-format.js +84 -0
  369. package/dist/shared/usage-aggregates.js +28 -0
  370. package/dist/signal/monitor/mentions.js +45 -0
  371. package/dist/signal/rpc-context.js +19 -0
  372. package/dist/slack/blocks-fallback.js +76 -0
  373. package/dist/slack/blocks-input.js +40 -0
  374. package/dist/slack/draft-stream.js +106 -0
  375. package/dist/slack/message-actions.js +51 -0
  376. package/dist/slack/modal-metadata.js +32 -0
  377. package/dist/slack/monitor/events/interactions.js +462 -0
  378. package/dist/slack/monitor/room-context.js +17 -0
  379. package/dist/slack/stream-mode.js +41 -0
  380. package/dist/telegram/bot-native-command-menu.js +64 -0
  381. package/dist/telegram/bot.media.e2e-harness.js +81 -0
  382. package/dist/telegram/button-types.js +1 -0
  383. package/dist/telegram/group-access.js +65 -0
  384. package/dist/telegram/outbound-params.js +21 -0
  385. package/dist/telegram/poll-vote-cache.js +21 -0
  386. package/dist/terminal/health-style.js +36 -0
  387. package/dist/test-utils/chunk-test-helpers.js +21 -0
  388. package/dist/test-utils/env.js +72 -0
  389. package/dist/test-utils/exec-assertions.js +12 -0
  390. package/dist/test-utils/imessage-test-plugin.js +54 -0
  391. package/dist/test-utils/mock-http-response.js +17 -0
  392. package/dist/test-utils/vitest-mock-fn.js +1 -0
  393. package/dist/tts/tts-core.js +550 -0
  394. package/dist/utils/chunk-items.js +10 -0
  395. package/dist/utils/reaction-level.js +52 -0
  396. package/dist/utils/safe-json.js +22 -0
  397. package/dist/utils/with-timeout.js +14 -0
  398. package/dist/web/media.js +17 -5
  399. package/dist/whatsapp/resolve-outbound-target.js +42 -0
  400. package/dist/wizard/onboarding.completion.js +74 -0
  401. package/extensions/bluebubbles/package.json +1 -1
  402. package/extensions/bluebubbles/src/account-resolve.ts +29 -0
  403. package/extensions/bluebubbles/src/monitor-normalize.ts +796 -0
  404. package/extensions/bluebubbles/src/monitor-processing.ts +1007 -0
  405. package/extensions/bluebubbles/src/monitor-reply-cache.ts +185 -0
  406. package/extensions/bluebubbles/src/monitor-shared.ts +51 -0
  407. package/extensions/bluebubbles/src/multipart.ts +32 -0
  408. package/extensions/bluebubbles/src/send-helpers.ts +53 -0
  409. package/extensions/bluebubbles/src/test-harness.ts +50 -0
  410. package/extensions/bluebubbles/src/test-mocks.ts +11 -0
  411. package/extensions/copilot-proxy/package.json +1 -1
  412. package/extensions/device-pair/index.ts +554 -0
  413. package/extensions/diagnostics-otel/package.json +1 -1
  414. package/extensions/discord/package.json +1 -1
  415. package/extensions/discord/src/channel.js +366 -0
  416. package/extensions/discord/src/runtime.js +10 -0
  417. package/extensions/feishu/index.ts +63 -0
  418. package/extensions/feishu/src/accounts.ts +114 -0
  419. package/extensions/feishu/src/bitable.ts +739 -0
  420. package/extensions/feishu/src/bot.ts +965 -0
  421. package/extensions/feishu/src/channel.ts +351 -0
  422. package/extensions/feishu/src/client.ts +118 -0
  423. package/extensions/feishu/src/config-schema.ts +206 -0
  424. package/extensions/feishu/src/dedup.ts +33 -0
  425. package/extensions/feishu/src/directory.ts +177 -0
  426. package/extensions/feishu/src/doc-schema.ts +47 -0
  427. package/extensions/feishu/src/docx.ts +536 -0
  428. package/extensions/feishu/src/drive-schema.ts +46 -0
  429. package/extensions/feishu/src/drive.ts +227 -0
  430. package/extensions/feishu/src/dynamic-agent.ts +131 -0
  431. package/extensions/feishu/src/media.ts +449 -0
  432. package/extensions/feishu/src/mention.ts +126 -0
  433. package/extensions/feishu/src/monitor.ts +330 -0
  434. package/extensions/feishu/src/onboarding.ts +359 -0
  435. package/extensions/feishu/src/outbound.ts +55 -0
  436. package/extensions/feishu/src/perm-schema.ts +52 -0
  437. package/extensions/feishu/src/perm.ts +173 -0
  438. package/extensions/feishu/src/policy.ts +84 -0
  439. package/extensions/feishu/src/probe.ts +44 -0
  440. package/extensions/feishu/src/reactions.ts +160 -0
  441. package/extensions/feishu/src/reply-dispatcher.ts +239 -0
  442. package/extensions/feishu/src/runtime.ts +14 -0
  443. package/extensions/feishu/src/send-result.ts +29 -0
  444. package/extensions/feishu/src/send.ts +335 -0
  445. package/extensions/feishu/src/streaming-card.ts +223 -0
  446. package/extensions/feishu/src/targets.ts +78 -0
  447. package/extensions/feishu/src/tools-config.ts +21 -0
  448. package/extensions/feishu/src/types.ts +81 -0
  449. package/extensions/feishu/src/typing.ts +80 -0
  450. package/extensions/feishu/src/wiki-schema.ts +55 -0
  451. package/extensions/feishu/src/wiki.ts +232 -0
  452. package/extensions/google-antigravity-auth/package.json +1 -1
  453. package/extensions/google-gemini-cli-auth/package.json +1 -1
  454. package/extensions/googlechat/package.json +1 -1
  455. package/extensions/imessage/package.json +1 -1
  456. package/extensions/imessage/src/channel.js +253 -0
  457. package/extensions/imessage/src/runtime.js +10 -0
  458. package/extensions/irc/index.ts +17 -0
  459. package/extensions/irc/src/accounts.ts +268 -0
  460. package/extensions/irc/src/channel.ts +367 -0
  461. package/extensions/irc/src/client.ts +439 -0
  462. package/extensions/irc/src/config-schema.ts +97 -0
  463. package/extensions/irc/src/connect-options.ts +30 -0
  464. package/extensions/irc/src/control-chars.ts +22 -0
  465. package/extensions/irc/src/inbound.ts +334 -0
  466. package/extensions/irc/src/monitor.ts +147 -0
  467. package/extensions/irc/src/normalize.ts +117 -0
  468. package/extensions/irc/src/onboarding.ts +479 -0
  469. package/extensions/irc/src/policy.ts +157 -0
  470. package/extensions/irc/src/probe.ts +53 -0
  471. package/extensions/irc/src/protocol.ts +169 -0
  472. package/extensions/irc/src/runtime.ts +14 -0
  473. package/extensions/irc/src/send.ts +88 -0
  474. package/extensions/irc/src/types.ts +93 -0
  475. package/extensions/line/package.json +1 -1
  476. package/extensions/llm-task/package.json +1 -1
  477. package/extensions/lobster/package.json +1 -1
  478. package/extensions/matrix/CHANGELOG.md +5 -0
  479. package/extensions/matrix/package.json +1 -1
  480. package/extensions/matrix/src/matrix/client-bootstrap.ts +39 -0
  481. package/extensions/mattermost/package.json +1 -1
  482. package/extensions/mattermost/src/mattermost/monitor-onchar.ts +25 -0
  483. package/extensions/mattermost/src/mattermost/monitor-websocket.ts +221 -0
  484. package/extensions/mattermost/src/mattermost/reactions.ts +130 -0
  485. package/extensions/mattermost/src/mattermost/reconnect.ts +103 -0
  486. package/extensions/memory-core/package.json +1 -1
  487. package/extensions/memory-lancedb/package.json +1 -1
  488. package/extensions/minimax-portal-auth/index.ts +161 -0
  489. package/extensions/minimax-portal-auth/oauth.ts +247 -0
  490. package/extensions/msteams/CHANGELOG.md +5 -0
  491. package/extensions/msteams/package.json +1 -1
  492. package/extensions/msteams/src/file-lock.ts +1 -0
  493. package/extensions/msteams/src/graph.ts +92 -0
  494. package/extensions/msteams/src/mentions.ts +114 -0
  495. package/extensions/msteams/src/test-runtime.ts +16 -0
  496. package/extensions/nextcloud-talk/package.json +1 -1
  497. package/extensions/nostr/CHANGELOG.md +5 -0
  498. package/extensions/nostr/package.json +1 -1
  499. package/extensions/open-prose/package.json +1 -1
  500. package/extensions/openai-codex-auth/index.ts +177 -0
  501. package/extensions/phone-control/index.ts +421 -0
  502. package/extensions/shared/resolve-target-test-helpers.ts +66 -0
  503. package/extensions/signal/package.json +1 -1
  504. package/extensions/signal/src/channel.js +273 -0
  505. package/extensions/signal/src/runtime.js +10 -0
  506. package/extensions/slack/package.json +1 -1
  507. package/extensions/slack/src/channel.js +489 -0
  508. package/extensions/slack/src/runtime.js +10 -0
  509. package/extensions/talk-voice/index.ts +150 -0
  510. package/extensions/telegram/package.json +1 -1
  511. package/extensions/telegram/src/channel.js +424 -0
  512. package/extensions/telegram/src/runtime.js +10 -0
  513. package/extensions/thread-ownership/index.ts +133 -0
  514. package/extensions/tlon/package.json +1 -1
  515. package/extensions/tlon/src/account-fields.ts +25 -0
  516. package/extensions/tlon/src/urbit/base-url.ts +57 -0
  517. package/extensions/tlon/src/urbit/channel-client.ts +157 -0
  518. package/extensions/tlon/src/urbit/channel-ops.ts +164 -0
  519. package/extensions/tlon/src/urbit/context.ts +47 -0
  520. package/extensions/tlon/src/urbit/errors.ts +51 -0
  521. package/extensions/tlon/src/urbit/fetch.ts +39 -0
  522. package/extensions/twitch/CHANGELOG.md +5 -0
  523. package/extensions/twitch/package.json +1 -1
  524. package/extensions/twitch/src/test-fixtures.ts +30 -0
  525. package/extensions/voice-call/CHANGELOG.md +5 -0
  526. package/extensions/voice-call/package.json +1 -1
  527. package/extensions/voice-call/src/allowlist.ts +19 -0
  528. package/extensions/whatsapp/package.json +1 -1
  529. package/extensions/whatsapp/src/channel.js +429 -0
  530. package/extensions/whatsapp/src/runtime.js +10 -0
  531. package/extensions/zalo/CHANGELOG.md +5 -0
  532. package/extensions/zalo/package.json +1 -1
  533. package/extensions/zalouser/CHANGELOG.md +5 -0
  534. package/extensions/zalouser/package.json +1 -1
  535. package/package.json +1 -1
@@ -0,0 +1,177 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import {
5
+ emptyPluginConfigSchema,
6
+ type PoolBotPluginApi,
7
+ type ProviderAuthContext,
8
+ type ProviderAuthResult,
9
+ } from "poolbot/plugin-sdk";
10
+
11
+ const PROVIDER_ID = "openai-codex-import";
12
+ const PROVIDER_LABEL = "OpenAI Codex CLI Import";
13
+
14
+ /**
15
+ * Resolve the Codex auth file path, respecting CODEX_HOME env var like core does.
16
+ * Called lazily to pick up env var changes.
17
+ */
18
+ function getAuthFilePath(): string {
19
+ const codexHome = process.env.CODEX_HOME
20
+ ? path.resolve(process.env.CODEX_HOME)
21
+ : path.join(os.homedir(), ".codex");
22
+ return path.join(codexHome, "auth.json");
23
+ }
24
+
25
+ /**
26
+ * OpenAI Codex models available via ChatGPT Plus/Pro subscription.
27
+ * Uses openai-codex/ prefix to match core provider namespace and avoid
28
+ * conflicts with the standard openai/ API key-based provider.
29
+ */
30
+ const CODEX_MODELS = [
31
+ "openai-codex/gpt-5.3-codex",
32
+ "openai-codex/gpt-5.3-codex-spark",
33
+ "openai-codex/gpt-5.2-codex",
34
+ ] as const;
35
+
36
+ const DEFAULT_MODEL = "openai-codex/gpt-5.3-codex";
37
+
38
+ interface CodexAuthTokens {
39
+ access_token: string;
40
+ refresh_token?: string;
41
+ account_id?: string;
42
+ expires_at?: number;
43
+ }
44
+
45
+ interface CodexAuthFile {
46
+ tokens?: CodexAuthTokens;
47
+ }
48
+
49
+ /**
50
+ * Read the Codex CLI auth.json file, respecting CODEX_HOME env var.
51
+ */
52
+ function readCodexAuth(): CodexAuthFile | null {
53
+ try {
54
+ const authFile = getAuthFilePath();
55
+ if (!fs.existsSync(authFile)) return null;
56
+ const content = fs.readFileSync(authFile, "utf-8");
57
+ return JSON.parse(content) as CodexAuthFile;
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Decode JWT expiry timestamp from access token
65
+ */
66
+ function decodeJwtExpiry(token: string): number | undefined {
67
+ try {
68
+ const payload = token.split(".")[1];
69
+ if (!payload) return undefined;
70
+ const decoded = JSON.parse(Buffer.from(payload, "base64").toString()) as { exp?: number };
71
+ return decoded.exp ? decoded.exp * 1000 : undefined;
72
+ } catch {
73
+ return undefined;
74
+ }
75
+ }
76
+
77
+ const openaiCodexPlugin = {
78
+ id: "openai-codex-auth",
79
+ name: "OpenAI Codex Auth",
80
+ description: "Use OpenAI models via Codex CLI authentication (ChatGPT Plus/Pro)",
81
+ configSchema: emptyPluginConfigSchema(),
82
+
83
+ register(api: PoolBotPluginApi) {
84
+ api.registerProvider({
85
+ id: PROVIDER_ID,
86
+ label: PROVIDER_LABEL,
87
+ docsPath: "/providers/models",
88
+ aliases: ["codex-import"],
89
+
90
+ auth: [
91
+ {
92
+ id: "codex-cli",
93
+ label: "Codex CLI Auth",
94
+ hint: "Import existing Codex CLI authentication (respects CODEX_HOME env var)",
95
+ kind: "custom",
96
+
97
+ run: async (ctx: ProviderAuthContext): Promise<ProviderAuthResult> => {
98
+ const spin = ctx.prompter.progress("Reading Codex CLI auth…");
99
+
100
+ try {
101
+ const auth = readCodexAuth();
102
+
103
+ if (!auth?.tokens?.access_token) {
104
+ spin.stop("No Codex auth found");
105
+ await ctx.prompter.note(
106
+ "Run 'codex login' first to authenticate with OpenAI.\n\n" +
107
+ "Install Codex CLI: npm install -g @openai/codex\n" +
108
+ "Then run: codex login",
109
+ "Setup required",
110
+ );
111
+ throw new Error("Codex CLI not authenticated. Run: codex login");
112
+ }
113
+
114
+ spin.stop("Codex auth loaded");
115
+
116
+ const profileId = `openai-codex-import:${auth.tokens.account_id ?? "default"}`;
117
+ const expires = auth.tokens.expires_at
118
+ ? auth.tokens.expires_at * 1000
119
+ : decodeJwtExpiry(auth.tokens.access_token);
120
+
121
+ const modelsConfig: Record<string, object> = {};
122
+ for (const model of CODEX_MODELS) {
123
+ modelsConfig[model] = {};
124
+ }
125
+
126
+ // Validate refresh token - empty/missing refresh tokens cause silent failures
127
+ if (!auth.tokens.refresh_token) {
128
+ spin.stop("Invalid Codex auth");
129
+ await ctx.prompter.note(
130
+ "Your Codex CLI auth is missing a refresh token.\n\n" +
131
+ "Please re-authenticate: codex logout && codex login",
132
+ "Re-authentication required",
133
+ );
134
+ throw new Error(
135
+ "Codex CLI auth missing refresh token. Run: codex logout && codex login",
136
+ );
137
+ }
138
+
139
+ return {
140
+ profiles: [
141
+ {
142
+ profileId,
143
+ credential: {
144
+ type: "oauth",
145
+ provider: PROVIDER_ID,
146
+ access: auth.tokens.access_token,
147
+ refresh: auth.tokens.refresh_token,
148
+ expires: expires ?? Date.now() + 3600000,
149
+ },
150
+ },
151
+ ],
152
+ configPatch: {
153
+ agents: {
154
+ defaults: {
155
+ models: modelsConfig,
156
+ },
157
+ },
158
+ },
159
+ defaultModel: DEFAULT_MODEL,
160
+ notes: [
161
+ `Using Codex CLI auth from ${getAuthFilePath()}`,
162
+ `Available models: ${CODEX_MODELS.join(", ")}`,
163
+ "Tokens auto-refresh when needed.",
164
+ ],
165
+ };
166
+ } catch (err) {
167
+ spin.stop("Failed to load Codex auth");
168
+ throw err;
169
+ }
170
+ },
171
+ },
172
+ ],
173
+ });
174
+ },
175
+ };
176
+
177
+ export default openaiCodexPlugin;
@@ -0,0 +1,421 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import type { PoolBotPluginApi, PoolBotPluginService } from "poolbot/plugin-sdk";
4
+
5
+ type ArmGroup = "camera" | "screen" | "writes" | "all";
6
+
7
+ type ArmStateFileV1 = {
8
+ version: 1;
9
+ armedAtMs: number;
10
+ expiresAtMs: number | null;
11
+ removedFromDeny: string[];
12
+ };
13
+
14
+ type ArmStateFileV2 = {
15
+ version: 2;
16
+ armedAtMs: number;
17
+ expiresAtMs: number | null;
18
+ group: ArmGroup;
19
+ armedCommands: string[];
20
+ addedToAllow: string[];
21
+ removedFromDeny: string[];
22
+ };
23
+
24
+ type ArmStateFile = ArmStateFileV1 | ArmStateFileV2;
25
+
26
+ const STATE_VERSION = 2;
27
+ const STATE_REL_PATH = ["plugins", "phone-control", "armed.json"] as const;
28
+
29
+ const GROUP_COMMANDS: Record<Exclude<ArmGroup, "all">, string[]> = {
30
+ camera: ["camera.snap", "camera.clip"],
31
+ screen: ["screen.record"],
32
+ writes: ["calendar.add", "contacts.add", "reminders.add"],
33
+ };
34
+
35
+ function uniqSorted(values: string[]): string[] {
36
+ return [...new Set(values.map((v) => v.trim()).filter(Boolean))].toSorted();
37
+ }
38
+
39
+ function resolveCommandsForGroup(group: ArmGroup): string[] {
40
+ if (group === "all") {
41
+ return uniqSorted(Object.values(GROUP_COMMANDS).flat());
42
+ }
43
+ return uniqSorted(GROUP_COMMANDS[group]);
44
+ }
45
+
46
+ function formatGroupList(): string {
47
+ return ["camera", "screen", "writes", "all"].join(", ");
48
+ }
49
+
50
+ function parseDurationMs(input: string | undefined): number | null {
51
+ if (!input) {
52
+ return null;
53
+ }
54
+ const raw = input.trim().toLowerCase();
55
+ if (!raw) {
56
+ return null;
57
+ }
58
+ const m = raw.match(/^(\d+)(s|m|h|d)$/);
59
+ if (!m) {
60
+ return null;
61
+ }
62
+ const n = Number.parseInt(m[1] ?? "", 10);
63
+ if (!Number.isFinite(n) || n <= 0) {
64
+ return null;
65
+ }
66
+ const unit = m[2];
67
+ const mult = unit === "s" ? 1000 : unit === "m" ? 60_000 : unit === "h" ? 3_600_000 : 86_400_000;
68
+ return n * mult;
69
+ }
70
+
71
+ function formatDuration(ms: number): string {
72
+ const s = Math.max(0, Math.floor(ms / 1000));
73
+ if (s < 60) {
74
+ return `${s}s`;
75
+ }
76
+ const m = Math.floor(s / 60);
77
+ if (m < 60) {
78
+ return `${m}m`;
79
+ }
80
+ const h = Math.floor(m / 60);
81
+ if (h < 48) {
82
+ return `${h}h`;
83
+ }
84
+ const d = Math.floor(h / 24);
85
+ return `${d}d`;
86
+ }
87
+
88
+ function resolveStatePath(stateDir: string): string {
89
+ return path.join(stateDir, ...STATE_REL_PATH);
90
+ }
91
+
92
+ async function readArmState(statePath: string): Promise<ArmStateFile | null> {
93
+ try {
94
+ const raw = await fs.readFile(statePath, "utf8");
95
+ // Type as unknown record first to allow property access during validation
96
+ const parsed = JSON.parse(raw) as Record<string, unknown>;
97
+ if (parsed.version !== 1 && parsed.version !== 2) {
98
+ return null;
99
+ }
100
+ if (typeof parsed.armedAtMs !== "number") {
101
+ return null;
102
+ }
103
+ if (!(parsed.expiresAtMs === null || typeof parsed.expiresAtMs === "number")) {
104
+ return null;
105
+ }
106
+
107
+ if (parsed.version === 1) {
108
+ if (
109
+ !Array.isArray(parsed.removedFromDeny) ||
110
+ !parsed.removedFromDeny.every((v: unknown) => typeof v === "string")
111
+ ) {
112
+ return null;
113
+ }
114
+ return parsed as unknown as ArmStateFile;
115
+ }
116
+
117
+ const group = typeof parsed.group === "string" ? parsed.group : "";
118
+ if (group !== "camera" && group !== "screen" && group !== "writes" && group !== "all") {
119
+ return null;
120
+ }
121
+ if (
122
+ !Array.isArray(parsed.armedCommands) ||
123
+ !parsed.armedCommands.every((v: unknown) => typeof v === "string")
124
+ ) {
125
+ return null;
126
+ }
127
+ if (
128
+ !Array.isArray(parsed.addedToAllow) ||
129
+ !parsed.addedToAllow.every((v: unknown) => typeof v === "string")
130
+ ) {
131
+ return null;
132
+ }
133
+ if (
134
+ !Array.isArray(parsed.removedFromDeny) ||
135
+ !parsed.removedFromDeny.every((v: unknown) => typeof v === "string")
136
+ ) {
137
+ return null;
138
+ }
139
+ return parsed as unknown as ArmStateFile;
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+
145
+ async function writeArmState(statePath: string, state: ArmStateFile | null): Promise<void> {
146
+ await fs.mkdir(path.dirname(statePath), { recursive: true });
147
+ if (!state) {
148
+ try {
149
+ await fs.unlink(statePath);
150
+ } catch {
151
+ // ignore
152
+ }
153
+ return;
154
+ }
155
+ await fs.writeFile(statePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
156
+ }
157
+
158
+ function normalizeDenyList(cfg: PoolBotPluginApi["config"]): string[] {
159
+ return uniqSorted([...(cfg.gateway?.nodes?.denyCommands ?? [])]);
160
+ }
161
+
162
+ function normalizeAllowList(cfg: PoolBotPluginApi["config"]): string[] {
163
+ return uniqSorted([...(cfg.gateway?.nodes?.allowCommands ?? [])]);
164
+ }
165
+
166
+ function patchConfigNodeLists(
167
+ cfg: PoolBotPluginApi["config"],
168
+ next: { allowCommands: string[]; denyCommands: string[] },
169
+ ): PoolBotPluginApi["config"] {
170
+ return {
171
+ ...cfg,
172
+ gateway: {
173
+ ...cfg.gateway,
174
+ nodes: {
175
+ ...cfg.gateway?.nodes,
176
+ allowCommands: next.allowCommands,
177
+ denyCommands: next.denyCommands,
178
+ },
179
+ },
180
+ };
181
+ }
182
+
183
+ async function disarmNow(params: {
184
+ api: PoolBotPluginApi;
185
+ stateDir: string;
186
+ statePath: string;
187
+ reason: string;
188
+ }): Promise<{ changed: boolean; restored: string[]; removed: string[] }> {
189
+ const { api, stateDir, statePath, reason } = params;
190
+ const state = await readArmState(statePath);
191
+ if (!state) {
192
+ return { changed: false, restored: [], removed: [] };
193
+ }
194
+ const cfg = api.runtime.config.loadConfig();
195
+ const allow = new Set(normalizeAllowList(cfg));
196
+ const deny = new Set(normalizeDenyList(cfg));
197
+ const removed: string[] = [];
198
+ const restored: string[] = [];
199
+
200
+ if (state.version === 1) {
201
+ for (const cmd of state.removedFromDeny) {
202
+ if (!deny.has(cmd)) {
203
+ deny.add(cmd);
204
+ restored.push(cmd);
205
+ }
206
+ }
207
+ } else {
208
+ for (const cmd of state.addedToAllow) {
209
+ if (allow.delete(cmd)) {
210
+ removed.push(cmd);
211
+ }
212
+ }
213
+ for (const cmd of state.removedFromDeny) {
214
+ if (!deny.has(cmd)) {
215
+ deny.add(cmd);
216
+ restored.push(cmd);
217
+ }
218
+ }
219
+ }
220
+
221
+ if (removed.length > 0 || restored.length > 0) {
222
+ const next = patchConfigNodeLists(cfg, {
223
+ allowCommands: uniqSorted([...allow]),
224
+ denyCommands: uniqSorted([...deny]),
225
+ });
226
+ await api.runtime.config.writeConfigFile(next);
227
+ }
228
+ await writeArmState(statePath, null);
229
+ api.logger.info(`phone-control: disarmed (${reason}) stateDir=${stateDir}`);
230
+ return {
231
+ changed: removed.length > 0 || restored.length > 0,
232
+ removed: uniqSorted(removed),
233
+ restored: uniqSorted(restored),
234
+ };
235
+ }
236
+
237
+ function formatHelp(): string {
238
+ return [
239
+ "Phone control commands:",
240
+ "",
241
+ "/phone status",
242
+ "/phone arm <group> [duration]",
243
+ "/phone disarm",
244
+ "",
245
+ "Groups:",
246
+ `- ${formatGroupList()}`,
247
+ "",
248
+ "Duration format: 30s | 10m | 2h | 1d (default: 10m).",
249
+ "",
250
+ "Notes:",
251
+ "- This only toggles what the gateway is allowed to invoke on phone nodes.",
252
+ "- iOS will still ask for permissions (camera, photos, contacts, etc.) on first use.",
253
+ ].join("\n");
254
+ }
255
+
256
+ function parseGroup(raw: string | undefined): ArmGroup | null {
257
+ const value = (raw ?? "").trim().toLowerCase();
258
+ if (!value) {
259
+ return null;
260
+ }
261
+ if (value === "camera" || value === "screen" || value === "writes" || value === "all") {
262
+ return value;
263
+ }
264
+ return null;
265
+ }
266
+
267
+ function formatStatus(state: ArmStateFile | null): string {
268
+ if (!state) {
269
+ return "Phone control: disarmed.";
270
+ }
271
+ const until =
272
+ state.expiresAtMs == null
273
+ ? "manual disarm required"
274
+ : `expires in ${formatDuration(Math.max(0, state.expiresAtMs - Date.now()))}`;
275
+ const cmds = uniqSorted(
276
+ state.version === 1
277
+ ? state.removedFromDeny
278
+ : state.armedCommands.length > 0
279
+ ? state.armedCommands
280
+ : [...state.addedToAllow, ...state.removedFromDeny],
281
+ );
282
+ const cmdLabel = cmds.length > 0 ? cmds.join(", ") : "none";
283
+ return `Phone control: armed (${until}).\nTemporarily allowed: ${cmdLabel}`;
284
+ }
285
+
286
+ export default function register(api: PoolBotPluginApi) {
287
+ let expiryInterval: ReturnType<typeof setInterval> | null = null;
288
+
289
+ const timerService: PoolBotPluginService = {
290
+ id: "phone-control-expiry",
291
+ start: async (ctx) => {
292
+ const statePath = resolveStatePath(ctx.stateDir);
293
+ const tick = async () => {
294
+ const state = await readArmState(statePath);
295
+ if (!state || state.expiresAtMs == null) {
296
+ return;
297
+ }
298
+ if (Date.now() < state.expiresAtMs) {
299
+ return;
300
+ }
301
+ await disarmNow({
302
+ api,
303
+ stateDir: ctx.stateDir,
304
+ statePath,
305
+ reason: "expired",
306
+ });
307
+ };
308
+
309
+ // Best effort; don't crash the gateway if state is corrupt.
310
+ await tick().catch(() => {});
311
+
312
+ expiryInterval = setInterval(() => {
313
+ tick().catch(() => {});
314
+ }, 15_000);
315
+ expiryInterval.unref?.();
316
+
317
+ return;
318
+ },
319
+ stop: async () => {
320
+ if (expiryInterval) {
321
+ clearInterval(expiryInterval);
322
+ expiryInterval = null;
323
+ }
324
+ return;
325
+ },
326
+ };
327
+
328
+ api.registerService(timerService);
329
+
330
+ api.registerCommand({
331
+ name: "phone",
332
+ description: "Arm/disarm high-risk phone node commands (camera/screen/writes).",
333
+ acceptsArgs: true,
334
+ handler: async (ctx) => {
335
+ const args = ctx.args?.trim() ?? "";
336
+ const tokens = args.split(/\s+/).filter(Boolean);
337
+ const action = tokens[0]?.toLowerCase() ?? "";
338
+
339
+ const stateDir = api.runtime.state.resolveStateDir();
340
+ const statePath = resolveStatePath(stateDir);
341
+
342
+ if (!action || action === "help") {
343
+ const state = await readArmState(statePath);
344
+ return { text: `${formatStatus(state)}\n\n${formatHelp()}` };
345
+ }
346
+
347
+ if (action === "status") {
348
+ const state = await readArmState(statePath);
349
+ return { text: formatStatus(state) };
350
+ }
351
+
352
+ if (action === "disarm") {
353
+ const res = await disarmNow({
354
+ api,
355
+ stateDir,
356
+ statePath,
357
+ reason: "manual",
358
+ });
359
+ if (!res.changed) {
360
+ return { text: "Phone control: disarmed." };
361
+ }
362
+ const restoredLabel = res.restored.length > 0 ? res.restored.join(", ") : "none";
363
+ const removedLabel = res.removed.length > 0 ? res.removed.join(", ") : "none";
364
+ return {
365
+ text: `Phone control: disarmed.\nRemoved allowlist: ${removedLabel}\nRestored denylist: ${restoredLabel}`,
366
+ };
367
+ }
368
+
369
+ if (action === "arm") {
370
+ const group = parseGroup(tokens[1]);
371
+ if (!group) {
372
+ return { text: `Usage: /phone arm <group> [duration]\nGroups: ${formatGroupList()}` };
373
+ }
374
+ const durationMs = parseDurationMs(tokens[2]) ?? 10 * 60_000;
375
+ const expiresAtMs = Date.now() + durationMs;
376
+
377
+ const commands = resolveCommandsForGroup(group);
378
+ const cfg = api.runtime.config.loadConfig();
379
+ const allowSet = new Set(normalizeAllowList(cfg));
380
+ const denySet = new Set(normalizeDenyList(cfg));
381
+
382
+ const addedToAllow: string[] = [];
383
+ const removedFromDeny: string[] = [];
384
+ for (const cmd of commands) {
385
+ if (!allowSet.has(cmd)) {
386
+ allowSet.add(cmd);
387
+ addedToAllow.push(cmd);
388
+ }
389
+ if (denySet.delete(cmd)) {
390
+ removedFromDeny.push(cmd);
391
+ }
392
+ }
393
+ const next = patchConfigNodeLists(cfg, {
394
+ allowCommands: uniqSorted([...allowSet]),
395
+ denyCommands: uniqSorted([...denySet]),
396
+ });
397
+ await api.runtime.config.writeConfigFile(next);
398
+
399
+ await writeArmState(statePath, {
400
+ version: STATE_VERSION,
401
+ armedAtMs: Date.now(),
402
+ expiresAtMs,
403
+ group,
404
+ armedCommands: uniqSorted(commands),
405
+ addedToAllow: uniqSorted(addedToAllow),
406
+ removedFromDeny: uniqSorted(removedFromDeny),
407
+ });
408
+
409
+ const allowedLabel = uniqSorted(commands).join(", ");
410
+ return {
411
+ text:
412
+ `Phone control: armed for ${formatDuration(durationMs)}.\n` +
413
+ `Temporarily allowed: ${allowedLabel}\n` +
414
+ `To disarm early: /phone disarm`,
415
+ };
416
+ }
417
+
418
+ return { text: formatHelp() };
419
+ },
420
+ });
421
+ }
@@ -0,0 +1,66 @@
1
+ import { expect, it } from "vitest";
2
+
3
+ type ResolveTargetMode = "explicit" | "implicit" | "heartbeat";
4
+
5
+ type ResolveTargetResult = {
6
+ ok: boolean;
7
+ to?: string;
8
+ error?: unknown;
9
+ };
10
+
11
+ type ResolveTargetFn = (params: {
12
+ to?: string;
13
+ mode: ResolveTargetMode;
14
+ allowFrom: string[];
15
+ }) => ResolveTargetResult;
16
+
17
+ export function installCommonResolveTargetErrorCases(params: {
18
+ resolveTarget: ResolveTargetFn;
19
+ implicitAllowFrom: string[];
20
+ }) {
21
+ const { resolveTarget, implicitAllowFrom } = params;
22
+
23
+ it("should error on normalization failure with allowlist (implicit mode)", () => {
24
+ const result = resolveTarget({
25
+ to: "invalid-target",
26
+ mode: "implicit",
27
+ allowFrom: implicitAllowFrom,
28
+ });
29
+
30
+ expect(result.ok).toBe(false);
31
+ expect(result.error).toBeDefined();
32
+ });
33
+
34
+ it("should error when no target provided with allowlist", () => {
35
+ const result = resolveTarget({
36
+ to: undefined,
37
+ mode: "implicit",
38
+ allowFrom: implicitAllowFrom,
39
+ });
40
+
41
+ expect(result.ok).toBe(false);
42
+ expect(result.error).toBeDefined();
43
+ });
44
+
45
+ it("should error when no target and no allowlist", () => {
46
+ const result = resolveTarget({
47
+ to: undefined,
48
+ mode: "explicit",
49
+ allowFrom: [],
50
+ });
51
+
52
+ expect(result.ok).toBe(false);
53
+ expect(result.error).toBeDefined();
54
+ });
55
+
56
+ it("should handle whitespace-only target", () => {
57
+ const result = resolveTarget({
58
+ to: " ",
59
+ mode: "explicit",
60
+ allowFrom: [],
61
+ });
62
+
63
+ expect(result.ok).toBe(false);
64
+ expect(result.error).toBeDefined();
65
+ });
66
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poolbot/signal",
3
- "version": "2026.1.26",
3
+ "version": "2026.2.17",
4
4
  "type": "module",
5
5
  "description": "Poolbot Signal channel plugin",
6
6
  "poolbot": {