@poolzin/pool-bot 2026.2.21 → 2026.2.22

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 (369) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/agents/api-key-rotation.js +47 -0
  3. package/dist/agents/apply-patch-update.js +19 -9
  4. package/dist/agents/apply-patch.js +72 -47
  5. package/dist/agents/bash-tools.exec.js +141 -559
  6. package/dist/agents/cli-backends.js +49 -6
  7. package/dist/agents/cli-runner/helpers.js +69 -152
  8. package/dist/agents/cli-runner.js +70 -19
  9. package/dist/agents/identity.js +20 -1
  10. package/dist/agents/image-sanitization.js +9 -0
  11. package/dist/agents/live-auth-keys.js +123 -26
  12. package/dist/agents/live-model-filter.js +13 -4
  13. package/dist/agents/model-catalog.js +40 -9
  14. package/dist/agents/model-forward-compat.js +60 -23
  15. package/dist/agents/model-selection.js +134 -41
  16. package/dist/agents/pi-auth-json.js +2 -2
  17. package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
  18. package/dist/agents/pi-embedded-helpers/errors.js +140 -15
  19. package/dist/agents/pi-embedded-helpers/images.js +22 -12
  20. package/dist/agents/pi-embedded-helpers.js +2 -2
  21. package/dist/agents/pi-embedded-runner/abort.js +10 -3
  22. package/dist/agents/pi-embedded-runner/compact.js +230 -32
  23. package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
  24. package/dist/agents/pi-embedded-runner/google.js +109 -19
  25. package/dist/agents/pi-embedded-runner/history.js +35 -17
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
  27. package/dist/agents/pi-embedded-runner/run/images.js +81 -55
  28. package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
  29. package/dist/agents/pi-embedded-runner/run.js +193 -25
  30. package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
  31. package/dist/agents/pi-embedded-runner/runs.js +17 -8
  32. package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
  33. package/dist/agents/pi-embedded-runner.js +1 -1
  34. package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
  35. package/dist/agents/pi-embedded-subscribe.js +37 -0
  36. package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
  37. package/dist/agents/pi-model-discovery.js +9 -2
  38. package/dist/agents/pi-tool-definition-adapter.js +60 -8
  39. package/dist/agents/pi-tools.before-tool-call.js +1 -1
  40. package/dist/agents/pi-tools.js +113 -94
  41. package/dist/agents/pi-tools.read.js +337 -38
  42. package/dist/agents/poolbot-tools.js +14 -5
  43. package/dist/agents/sandbox/docker.js +10 -5
  44. package/dist/agents/sandbox/registry.js +96 -46
  45. package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
  46. package/dist/agents/sandbox-paths.js +43 -10
  47. package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
  48. package/dist/agents/session-tool-result-guard.js +39 -39
  49. package/dist/agents/session-transcript-repair.js +36 -33
  50. package/dist/agents/session-write-lock.js +62 -44
  51. package/dist/agents/skills/frontmatter.js +49 -88
  52. package/dist/agents/skills/workspace.js +335 -28
  53. package/dist/agents/subagent-announce.js +508 -174
  54. package/dist/agents/subagent-registry.js +45 -4
  55. package/dist/agents/subagent-spawn.js +16 -33
  56. package/dist/agents/system-prompt-report.js +27 -10
  57. package/dist/agents/system-prompt.js +26 -32
  58. package/dist/agents/tool-call-id.js +69 -17
  59. package/dist/agents/tool-display-common.js +1 -1
  60. package/dist/agents/tool-images.js +64 -31
  61. package/dist/agents/tools/canvas-tool.js +17 -11
  62. package/dist/agents/tools/common.js +37 -19
  63. package/dist/agents/tools/cron-tool.js +40 -38
  64. package/dist/agents/tools/gateway.js +70 -2
  65. package/dist/agents/tools/message-tool.js +181 -40
  66. package/dist/agents/tools/nodes-tool.js +128 -36
  67. package/dist/agents/tools/nodes-utils.js +12 -38
  68. package/dist/agents/tools/session-status-tool.js +24 -71
  69. package/dist/agents/tools/sessions-helpers.js +38 -210
  70. package/dist/agents/tools/sessions-spawn-tool.js +28 -198
  71. package/dist/agents/tools/telegram-actions.js +58 -7
  72. package/dist/agents/tools/web-fetch-utils.js +112 -7
  73. package/dist/agents/tools/web-fetch.js +279 -175
  74. package/dist/agents/tools/web-shared.js +71 -8
  75. package/dist/agents/usage.js +25 -16
  76. package/dist/auto-reply/commands-registry.data.js +85 -11
  77. package/dist/auto-reply/dispatch.js +40 -21
  78. package/dist/auto-reply/reply/abort.js +102 -33
  79. package/dist/auto-reply/reply/commands-core.js +82 -33
  80. package/dist/auto-reply/reply/commands-export-session.js +1 -1
  81. package/dist/auto-reply/reply/commands-info.js +41 -12
  82. package/dist/auto-reply/reply/commands-subagents.js +352 -100
  83. package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
  84. package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
  85. package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
  86. package/dist/auto-reply/reply/inbound-meta.js +12 -1
  87. package/dist/auto-reply/reply/mentions.js +18 -11
  88. package/dist/auto-reply/reply/normalize-reply.js +17 -8
  89. package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
  90. package/dist/auto-reply/reply/session.js +102 -21
  91. package/dist/auto-reply/reply/streaming-directives.js +16 -5
  92. package/dist/auto-reply/status.js +73 -50
  93. package/dist/browser/extension-relay.js +3 -3
  94. package/dist/browser/http-auth.js +1 -1
  95. package/dist/browser/paths.js +2 -2
  96. package/dist/build-info.json +3 -3
  97. package/dist/channels/allowlist-match.js +20 -0
  98. package/dist/channels/allowlists/resolve-utils.js +65 -2
  99. package/dist/channels/chat-type.js +8 -4
  100. package/dist/channels/dock.js +127 -35
  101. package/dist/channels/draft-stream-loop.js +6 -2
  102. package/dist/channels/plugins/actions/telegram.js +42 -18
  103. package/dist/channels/plugins/allowlist-match.js +1 -1
  104. package/dist/channels/plugins/group-mentions.js +51 -41
  105. package/dist/channels/plugins/message-action-names.js +2 -0
  106. package/dist/channels/plugins/message-actions.js +24 -5
  107. package/dist/channels/plugins/normalize/discord.js +26 -4
  108. package/dist/channels/plugins/normalize/signal.js +35 -22
  109. package/dist/channels/plugins/onboarding/helpers.js +8 -26
  110. package/dist/channels/plugins/outbound/imessage.js +15 -14
  111. package/dist/channels/registry.js +20 -7
  112. package/dist/cli/acp-cli.js +7 -5
  113. package/dist/cli/browser-cli-extension.js +25 -12
  114. package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
  115. package/dist/cli/browser-cli-state.js +101 -145
  116. package/dist/cli/command-options.js +28 -0
  117. package/dist/cli/completion-cli.js +6 -6
  118. package/dist/cli/cron-cli/register.cron-add.js +25 -1
  119. package/dist/cli/cron-cli/register.cron-edit.js +44 -0
  120. package/dist/cli/cron-cli/shared.js +7 -1
  121. package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
  122. package/dist/cli/daemon-cli/lifecycle.js +23 -247
  123. package/dist/cli/daemon-cli/register-service-commands.js +25 -4
  124. package/dist/cli/daemon-cli.js +1 -0
  125. package/dist/cli/devices-cli.js +33 -20
  126. package/dist/cli/gateway-cli/register.js +37 -105
  127. package/dist/cli/gateway-cli/run.js +49 -11
  128. package/dist/cli/nodes-camera.js +59 -4
  129. package/dist/cli/nodes-cli/register.camera.js +27 -24
  130. package/dist/cli/nodes-cli/rpc.js +21 -38
  131. package/dist/cli/qr-cli.js +2 -2
  132. package/dist/cli/skills-cli.format.js +2 -2
  133. package/dist/cli/update-cli/progress.js +2 -2
  134. package/dist/cli/update-cli/restart-helper.js +28 -7
  135. package/dist/cli/update-cli/shared.js +7 -7
  136. package/dist/cli/update-cli/status.js +1 -1
  137. package/dist/cli/update-cli/update-command.js +14 -8
  138. package/dist/cli/update-cli/wizard.js +2 -2
  139. package/dist/cli/update-cli.js +21 -1027
  140. package/dist/commands/auth-choice.apply.anthropic.js +10 -2
  141. package/dist/commands/channels/add-mutators.js +3 -35
  142. package/dist/commands/channels/add.js +39 -51
  143. package/dist/commands/config-validation.js +1 -1
  144. package/dist/commands/configure.gateway-auth.js +52 -15
  145. package/dist/commands/configure.gateway.js +84 -40
  146. package/dist/commands/doctor-completion.js +3 -3
  147. package/dist/commands/doctor-config-flow.js +536 -16
  148. package/dist/commands/doctor-gateway-services.js +103 -79
  149. package/dist/commands/doctor-memory-search.js +9 -9
  150. package/dist/commands/doctor-platform-notes.js +57 -30
  151. package/dist/commands/doctor-prompter.js +26 -15
  152. package/dist/commands/doctor-session-locks.js +1 -1
  153. package/dist/commands/doctor.js +21 -9
  154. package/dist/commands/model-picker.js +120 -95
  155. package/dist/commands/models/set.js +2 -21
  156. package/dist/commands/models/shared.js +65 -37
  157. package/dist/commands/onboard-helpers.js +81 -39
  158. package/dist/commands/openai-codex-oauth.js +1 -1
  159. package/dist/commands/sessions.js +52 -53
  160. package/dist/commands/status.summary.js +52 -34
  161. package/dist/commands/test-wizard-helpers.js +2 -2
  162. package/dist/config/defaults.js +79 -42
  163. package/dist/config/group-policy.js +50 -18
  164. package/dist/config/includes.js +37 -10
  165. package/dist/config/schema.help.js +5 -4
  166. package/dist/config/schema.hints.js +2 -2
  167. package/dist/config/schema.labels.js +1 -0
  168. package/dist/config/sessions/group.js +12 -11
  169. package/dist/config/sessions/paths.js +137 -11
  170. package/dist/config/sessions/store.js +185 -65
  171. package/dist/config/sessions/types.js +15 -1
  172. package/dist/config/sessions.js +1 -0
  173. package/dist/config/telegram-custom-commands.js +3 -2
  174. package/dist/config/types.js +2 -0
  175. package/dist/config/zod-schema.agent-defaults.js +6 -27
  176. package/dist/config/zod-schema.agent-runtime.js +171 -79
  177. package/dist/config/zod-schema.providers-core.js +138 -65
  178. package/dist/config/zod-schema.session.js +49 -22
  179. package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
  180. package/dist/cron/isolated-agent/run.js +224 -57
  181. package/dist/cron/normalize.js +48 -45
  182. package/dist/cron/run-log.js +14 -0
  183. package/dist/cron/service/jobs.js +190 -28
  184. package/dist/cron/service/normalize.js +29 -11
  185. package/dist/cron/service/store.js +30 -44
  186. package/dist/cron/service/timer.js +182 -96
  187. package/dist/cron/service.js +3 -0
  188. package/dist/cron/stagger.js +37 -0
  189. package/dist/daemon/inspect.js +132 -92
  190. package/dist/daemon/runtime-paths.js +25 -4
  191. package/dist/daemon/service-audit.js +47 -16
  192. package/dist/discord/accounts.js +23 -20
  193. package/dist/discord/monitor/agent-components.js +1115 -219
  194. package/dist/discord/monitor/allow-list.js +114 -34
  195. package/dist/discord/monitor/listeners.js +204 -97
  196. package/dist/discord/monitor/message-handler.js +21 -10
  197. package/dist/discord/monitor/message-handler.preflight.js +195 -101
  198. package/dist/discord/monitor/message-handler.process.js +384 -123
  199. package/dist/discord/monitor/message-utils.js +86 -23
  200. package/dist/discord/monitor/native-command.js +77 -57
  201. package/dist/discord/monitor/provider.js +122 -117
  202. package/dist/discord/monitor/reply-context.js +20 -16
  203. package/dist/discord/monitor/reply-delivery.js +40 -8
  204. package/dist/discord/monitor/rest-fetch.js +22 -0
  205. package/dist/discord/monitor/threading.js +117 -24
  206. package/dist/discord/send.js +2 -1
  207. package/dist/discord/send.outbound.js +124 -11
  208. package/dist/discord/send.shared.js +112 -72
  209. package/dist/discord/voice-message.js +3 -3
  210. package/dist/gateway/auth.js +119 -44
  211. package/dist/gateway/call.js +76 -34
  212. package/dist/gateway/channel-health-monitor.js +57 -50
  213. package/dist/gateway/client.js +63 -29
  214. package/dist/gateway/control-ui-contract.js +1 -1
  215. package/dist/gateway/gateway-config-prompts.shared.js +2 -2
  216. package/dist/gateway/net.js +109 -1
  217. package/dist/gateway/protocol/index.js +5 -8
  218. package/dist/gateway/protocol/schema/agent.js +19 -1
  219. package/dist/gateway/protocol/schema/channels.js +21 -0
  220. package/dist/gateway/protocol/schema/cron.js +43 -30
  221. package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
  222. package/dist/gateway/protocol/schema/sessions.js +5 -1
  223. package/dist/gateway/protocol/schema.js +0 -1
  224. package/dist/gateway/server/presence-events.js +12 -0
  225. package/dist/gateway/server/ws-connection/message-handler.js +203 -212
  226. package/dist/gateway/server/ws-connection.js +58 -21
  227. package/dist/gateway/server-broadcast.js +18 -13
  228. package/dist/gateway/server-cron.js +177 -10
  229. package/dist/gateway/server-methods/agent-job.js +131 -38
  230. package/dist/gateway/server-methods/send.js +60 -14
  231. package/dist/gateway/server-methods/sessions.js +160 -96
  232. package/dist/gateway/server-methods/system.js +5 -7
  233. package/dist/gateway/server-methods-list.js +8 -0
  234. package/dist/gateway/server-methods.js +24 -8
  235. package/dist/gateway/server-node-events.js +278 -68
  236. package/dist/gateway/session-utils.fs.js +316 -75
  237. package/dist/gateway/session-utils.js +224 -70
  238. package/dist/gateway/sessions-patch.js +63 -20
  239. package/dist/gateway/test-temp-config.js +1 -1
  240. package/dist/gateway/tools-invoke-http.js +118 -70
  241. package/dist/gateway/ws-log.js +135 -107
  242. package/dist/hooks/frontmatter.js +36 -82
  243. package/dist/hooks/install.js +149 -139
  244. package/dist/hooks/internal-hooks.js +29 -4
  245. package/dist/hooks/plugin-hooks.js +2 -1
  246. package/dist/imessage/monitor/deliver.js +10 -4
  247. package/dist/imessage/monitor/monitor-provider.js +138 -375
  248. package/dist/imessage/monitor/runtime.js +4 -8
  249. package/dist/imessage/send.js +65 -19
  250. package/dist/infra/exec-approvals-allowlist.js +7 -0
  251. package/dist/infra/exec-approvals.js +35 -920
  252. package/dist/infra/exec-safe-bin-trust.js +64 -0
  253. package/dist/infra/heartbeat-runner.js +207 -134
  254. package/dist/infra/heartbeat-wake.js +183 -22
  255. package/dist/infra/install-source-utils.js +47 -0
  256. package/dist/infra/net/ssrf.js +170 -36
  257. package/dist/infra/outbound/deliver.js +224 -58
  258. package/dist/infra/outbound/message-action-spec.js +12 -5
  259. package/dist/infra/outbound/outbound-session.js +27 -25
  260. package/dist/infra/poolbot-root.js +32 -22
  261. package/dist/infra/ports.js +14 -11
  262. package/dist/infra/skills-remote.js +48 -37
  263. package/dist/infra/system-events.js +25 -11
  264. package/dist/infra/system-presence.js +26 -33
  265. package/dist/infra/tmp-poolbot-dir.js +81 -2
  266. package/dist/infra/wsl.js +37 -1
  267. package/dist/line/bot-message-context.js +163 -191
  268. package/dist/logging/subsystem.js +59 -22
  269. package/dist/markdown/ir.js +124 -50
  270. package/dist/media/store.js +1 -1
  271. package/dist/media-understanding/runner.entries.js +42 -25
  272. package/dist/media-understanding/runner.js +53 -488
  273. package/dist/memory/embeddings-gemini.js +53 -38
  274. package/dist/memory/manager-embedding-ops.js +48 -69
  275. package/dist/pairing/pairing-store.js +178 -119
  276. package/dist/plugin-sdk/index.js +34 -6
  277. package/dist/plugins/hooks.js +135 -14
  278. package/dist/plugins/install.js +190 -152
  279. package/dist/polls.js +11 -0
  280. package/dist/routing/resolve-route.js +190 -56
  281. package/dist/routing/session-key.js +38 -22
  282. package/dist/runtime.js +35 -9
  283. package/dist/security/audit-channel.js +1 -1
  284. package/dist/sessions/session-key-utils.js +29 -11
  285. package/dist/shared/frontmatter.js +5 -5
  286. package/dist/shared/node-list-types.js +1 -0
  287. package/dist/shared/string-normalization.js +15 -0
  288. package/dist/signal/monitor/event-handler.js +68 -36
  289. package/dist/signal/send.js +29 -37
  290. package/dist/slack/monitor/allow-list.js +10 -11
  291. package/dist/slack/monitor/commands.js +14 -3
  292. package/dist/slack/monitor/events/interactions.js +4 -4
  293. package/dist/slack/monitor/media.js +224 -16
  294. package/dist/slack/monitor/message-handler/dispatch.js +247 -13
  295. package/dist/slack/monitor/message-handler/prepare.js +128 -45
  296. package/dist/slack/monitor/slash.js +357 -144
  297. package/dist/slack/streaming.js +77 -0
  298. package/dist/telegram/accounts.js +40 -13
  299. package/dist/telegram/allowed-updates.js +3 -0
  300. package/dist/telegram/bot/delivery.js +129 -66
  301. package/dist/telegram/bot/helpers.js +136 -122
  302. package/dist/telegram/bot-handlers.js +600 -339
  303. package/dist/telegram/bot-message-context.js +115 -73
  304. package/dist/telegram/bot-message-dispatch.js +235 -104
  305. package/dist/telegram/bot-native-command-menu.js +3 -1
  306. package/dist/telegram/bot-native-commands.js +213 -193
  307. package/dist/telegram/bot.js +24 -132
  308. package/dist/telegram/draft-stream.js +84 -75
  309. package/dist/telegram/format.js +150 -6
  310. package/dist/telegram/send.js +415 -255
  311. package/dist/telegram/targets.js +21 -2
  312. package/dist/telegram/update-offset-store.js +19 -3
  313. package/dist/terminal/restore.js +5 -2
  314. package/dist/test-utils/fetch-mock.js +5 -0
  315. package/dist/version.js +18 -5
  316. package/dist/web/auto-reply/monitor/broadcast.js +7 -3
  317. package/dist/web/auto-reply/monitor/on-message.js +6 -3
  318. package/dist/web/inbound/media.js +34 -8
  319. package/dist/web/inbound/monitor.js +34 -17
  320. package/dist/web/inbound/send-api.js +18 -17
  321. package/dist/web/outbound.js +12 -5
  322. package/dist/wizard/clack-prompter.js +40 -7
  323. package/extensions/bluebubbles/package.json +1 -1
  324. package/extensions/copilot-proxy/package.json +1 -1
  325. package/extensions/diagnostics-otel/package.json +1 -1
  326. package/extensions/discord/package.json +1 -1
  327. package/extensions/feishu/package.json +1 -1
  328. package/extensions/google-antigravity-auth/package.json +1 -1
  329. package/extensions/google-gemini-cli-auth/package.json +1 -1
  330. package/extensions/googlechat/package.json +1 -1
  331. package/extensions/imessage/package.json +1 -1
  332. package/extensions/irc/package.json +1 -1
  333. package/extensions/line/package.json +1 -1
  334. package/extensions/llm-task/package.json +1 -1
  335. package/extensions/lobster/package.json +1 -1
  336. package/extensions/matrix/CHANGELOG.md +5 -0
  337. package/extensions/matrix/package.json +1 -1
  338. package/extensions/mattermost/package.json +1 -1
  339. package/extensions/memory-core/package.json +1 -1
  340. package/extensions/memory-lancedb/package.json +1 -1
  341. package/extensions/minimax-portal-auth/package.json +1 -1
  342. package/extensions/msteams/CHANGELOG.md +5 -0
  343. package/extensions/msteams/package.json +1 -1
  344. package/extensions/nextcloud-talk/package.json +1 -1
  345. package/extensions/nostr/CHANGELOG.md +5 -0
  346. package/extensions/nostr/package.json +1 -1
  347. package/extensions/open-prose/package.json +1 -1
  348. package/extensions/openai-codex-auth/package.json +1 -1
  349. package/extensions/signal/package.json +1 -1
  350. package/extensions/slack/package.json +1 -1
  351. package/extensions/telegram/package.json +1 -1
  352. package/extensions/tlon/package.json +1 -1
  353. package/extensions/twitch/CHANGELOG.md +5 -0
  354. package/extensions/twitch/package.json +1 -1
  355. package/extensions/voice-call/CHANGELOG.md +5 -0
  356. package/extensions/voice-call/package.json +1 -1
  357. package/extensions/whatsapp/package.json +1 -1
  358. package/extensions/zalo/CHANGELOG.md +5 -0
  359. package/extensions/zalo/package.json +1 -1
  360. package/extensions/zalouser/CHANGELOG.md +5 -0
  361. package/extensions/zalouser/package.json +1 -1
  362. package/package.json +1 -1
  363. package/skills/apple-reminders/SKILL.md +100 -49
  364. package/skills/coding-agent/SKILL.md +34 -28
  365. package/skills/github/SKILL.md +131 -16
  366. package/skills/imsg/SKILL.md +112 -15
  367. package/skills/openhue/SKILL.md +101 -19
  368. package/skills/tmux/SKILL.md +111 -79
  369. package/skills/weather/SKILL.md +88 -25
@@ -1,19 +1,43 @@
1
1
  import fs from "node:fs";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import { formatSkillsForPrompt, loadSkillsFromDir, } from "@mariozechner/pi-coding-agent";
4
5
  import { createSubsystemLogger } from "../../logging/subsystem.js";
5
6
  import { CONFIG_DIR, resolveUserPath } from "../../utils.js";
7
+ import { resolveSandboxPath } from "../sandbox-paths.js";
6
8
  import { resolveBundledSkillsDir } from "./bundled-dir.js";
7
9
  import { shouldIncludeSkill } from "./config.js";
10
+ import { normalizeSkillFilter } from "./filter.js";
8
11
  import { parseFrontmatter, resolvePoolbotMetadata, resolveSkillInvocationPolicy, } from "./frontmatter.js";
9
12
  import { resolvePluginSkillDirs } from "./plugin-skills.js";
10
13
  import { serializeByKey } from "./serialize.js";
11
14
  const fsp = fs.promises;
12
15
  const skillsLogger = createSubsystemLogger("skills");
13
16
  const skillCommandDebugOnce = new Set();
17
+ /**
18
+ * Replace the user's home directory prefix with `~` in skill file paths
19
+ * to reduce system prompt token usage. Models understand `~` expansion,
20
+ * and the read tool resolves `~` to the home directory.
21
+ *
22
+ * Example: `/Users/alice/.bun/.../skills/github/SKILL.md`
23
+ * → `~/.bun/.../skills/github/SKILL.md`
24
+ *
25
+ * Saves ~5–6 tokens per skill path × N skills ≈ 400–600 tokens total.
26
+ */
27
+ function compactSkillPaths(skills) {
28
+ const home = os.homedir();
29
+ if (!home)
30
+ return skills;
31
+ const prefix = home.endsWith(path.sep) ? home : home + path.sep;
32
+ return skills.map((s) => ({
33
+ ...s,
34
+ filePath: s.filePath.startsWith(prefix) ? "~/" + s.filePath.slice(prefix.length) : s.filePath,
35
+ }));
36
+ }
14
37
  function debugSkillCommandOnce(messageKey, message, meta) {
15
- if (skillCommandDebugOnce.has(messageKey))
38
+ if (skillCommandDebugOnce.has(messageKey)) {
16
39
  return;
40
+ }
17
41
  skillCommandDebugOnce.add(messageKey);
18
42
  skillsLogger.debug(message, meta);
19
43
  }
@@ -21,14 +45,14 @@ function filterSkillEntries(entries, config, skillFilter, eligibility) {
21
45
  let filtered = entries.filter((entry) => shouldIncludeSkill({ entry, config, eligibility }));
22
46
  // If skillFilter is provided, only include skills in the filter list.
23
47
  if (skillFilter !== undefined) {
24
- const normalized = skillFilter.map((entry) => String(entry).trim()).filter(Boolean);
48
+ const normalized = normalizeSkillFilter(skillFilter) ?? [];
25
49
  const label = normalized.length > 0 ? normalized.join(", ") : "(none)";
26
- console.log(`[skills] Applying skill filter: ${label}`);
50
+ skillsLogger.debug(`Applying skill filter: ${label}`);
27
51
  filtered =
28
52
  normalized.length > 0
29
53
  ? filtered.filter((entry) => normalized.includes(entry.skill.name))
30
54
  : [];
31
- console.log(`[skills] After filter: ${filtered.map((entry) => entry.skill.name).join(", ")}`);
55
+ skillsLogger.debug(`After skill filter: ${filtered.map((entry) => entry.skill.name).join(", ") || "(none)"}`);
32
56
  }
33
57
  return filtered;
34
58
  }
@@ -36,6 +60,11 @@ const SKILL_COMMAND_MAX_LENGTH = 32;
36
60
  const SKILL_COMMAND_FALLBACK = "skill";
37
61
  // Discord command descriptions must be ≤100 characters
38
62
  const SKILL_COMMAND_DESCRIPTION_MAX_LENGTH = 100;
63
+ const DEFAULT_MAX_CANDIDATES_PER_ROOT = 300;
64
+ const DEFAULT_MAX_SKILLS_LOADED_PER_SOURCE = 200;
65
+ const DEFAULT_MAX_SKILLS_IN_PROMPT = 150;
66
+ const DEFAULT_MAX_SKILLS_PROMPT_CHARS = 30_000;
67
+ const DEFAULT_MAX_SKILL_FILE_BYTES = 256_000;
39
68
  function sanitizeSkillCommandName(raw) {
40
69
  const normalized = raw
41
70
  .toLowerCase()
@@ -47,35 +76,186 @@ function sanitizeSkillCommandName(raw) {
47
76
  }
48
77
  function resolveUniqueSkillCommandName(base, used) {
49
78
  const normalizedBase = base.toLowerCase();
50
- if (!used.has(normalizedBase))
79
+ if (!used.has(normalizedBase)) {
51
80
  return base;
81
+ }
52
82
  for (let index = 2; index < 1000; index += 1) {
53
83
  const suffix = `_${index}`;
54
84
  const maxBaseLength = Math.max(1, SKILL_COMMAND_MAX_LENGTH - suffix.length);
55
85
  const trimmedBase = base.slice(0, maxBaseLength);
56
86
  const candidate = `${trimmedBase}${suffix}`;
57
87
  const candidateKey = candidate.toLowerCase();
58
- if (!used.has(candidateKey))
88
+ if (!used.has(candidateKey)) {
59
89
  return candidate;
90
+ }
60
91
  }
61
92
  const fallback = `${base.slice(0, Math.max(1, SKILL_COMMAND_MAX_LENGTH - 2))}_x`;
62
93
  return fallback;
63
94
  }
95
+ function resolveSkillsLimits(config) {
96
+ const limits = config?.skills?.limits;
97
+ return {
98
+ maxCandidatesPerRoot: limits?.maxCandidatesPerRoot ?? DEFAULT_MAX_CANDIDATES_PER_ROOT,
99
+ maxSkillsLoadedPerSource: limits?.maxSkillsLoadedPerSource ?? DEFAULT_MAX_SKILLS_LOADED_PER_SOURCE,
100
+ maxSkillsInPrompt: limits?.maxSkillsInPrompt ?? DEFAULT_MAX_SKILLS_IN_PROMPT,
101
+ maxSkillsPromptChars: limits?.maxSkillsPromptChars ?? DEFAULT_MAX_SKILLS_PROMPT_CHARS,
102
+ maxSkillFileBytes: limits?.maxSkillFileBytes ?? DEFAULT_MAX_SKILL_FILE_BYTES,
103
+ };
104
+ }
105
+ function listChildDirectories(dir) {
106
+ try {
107
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
108
+ const dirs = [];
109
+ for (const entry of entries) {
110
+ if (entry.name.startsWith("."))
111
+ continue;
112
+ if (entry.name === "node_modules")
113
+ continue;
114
+ const fullPath = path.join(dir, entry.name);
115
+ if (entry.isDirectory()) {
116
+ dirs.push(entry.name);
117
+ continue;
118
+ }
119
+ if (entry.isSymbolicLink()) {
120
+ try {
121
+ if (fs.statSync(fullPath).isDirectory()) {
122
+ dirs.push(entry.name);
123
+ }
124
+ }
125
+ catch {
126
+ // ignore broken symlinks
127
+ }
128
+ }
129
+ }
130
+ return dirs;
131
+ }
132
+ catch {
133
+ return [];
134
+ }
135
+ }
136
+ function resolveNestedSkillsRoot(dir, opts) {
137
+ const nested = path.join(dir, "skills");
138
+ try {
139
+ if (!fs.existsSync(nested) || !fs.statSync(nested).isDirectory()) {
140
+ return { baseDir: dir };
141
+ }
142
+ }
143
+ catch {
144
+ return { baseDir: dir };
145
+ }
146
+ // Heuristic: if `dir/skills/*/SKILL.md` exists for any entry, treat `dir/skills` as the real root.
147
+ // Note: don't stop at 25, but keep a cap to avoid pathological scans.
148
+ const nestedDirs = listChildDirectories(nested);
149
+ const scanLimit = Math.max(0, opts?.maxEntriesToScan ?? 100);
150
+ const toScan = scanLimit === 0 ? [] : nestedDirs.slice(0, Math.min(nestedDirs.length, scanLimit));
151
+ for (const name of toScan) {
152
+ const skillMd = path.join(nested, name, "SKILL.md");
153
+ if (fs.existsSync(skillMd)) {
154
+ return { baseDir: nested, note: `Detected nested skills root at ${nested}` };
155
+ }
156
+ }
157
+ return { baseDir: dir };
158
+ }
159
+ function unwrapLoadedSkills(loaded) {
160
+ if (Array.isArray(loaded)) {
161
+ return loaded;
162
+ }
163
+ if (loaded && typeof loaded === "object" && "skills" in loaded) {
164
+ const skills = loaded.skills;
165
+ if (Array.isArray(skills)) {
166
+ return skills;
167
+ }
168
+ }
169
+ return [];
170
+ }
64
171
  function loadSkillEntries(workspaceDir, opts) {
172
+ const limits = resolveSkillsLimits(opts?.config);
65
173
  const loadSkills = (params) => {
66
- const loaded = loadSkillsFromDir(params);
67
- if (Array.isArray(loaded))
68
- return loaded;
69
- if (loaded &&
70
- typeof loaded === "object" &&
71
- "skills" in loaded &&
72
- Array.isArray(loaded.skills)) {
73
- return loaded.skills;
174
+ const resolved = resolveNestedSkillsRoot(params.dir, {
175
+ maxEntriesToScan: limits.maxCandidatesPerRoot,
176
+ });
177
+ const baseDir = resolved.baseDir;
178
+ // If the root itself is a skill directory, just load it directly (but enforce size cap).
179
+ const rootSkillMd = path.join(baseDir, "SKILL.md");
180
+ if (fs.existsSync(rootSkillMd)) {
181
+ try {
182
+ const size = fs.statSync(rootSkillMd).size;
183
+ if (size > limits.maxSkillFileBytes) {
184
+ skillsLogger.warn("Skipping skills root due to oversized SKILL.md.", {
185
+ dir: baseDir,
186
+ filePath: rootSkillMd,
187
+ size,
188
+ maxSkillFileBytes: limits.maxSkillFileBytes,
189
+ });
190
+ return [];
191
+ }
192
+ }
193
+ catch {
194
+ return [];
195
+ }
196
+ const loaded = loadSkillsFromDir({ dir: baseDir, source: params.source });
197
+ return unwrapLoadedSkills(loaded);
74
198
  }
75
- return [];
199
+ const childDirs = listChildDirectories(baseDir);
200
+ const suspicious = childDirs.length > limits.maxCandidatesPerRoot;
201
+ const maxCandidates = Math.max(0, limits.maxSkillsLoadedPerSource);
202
+ const limitedChildren = childDirs.slice().sort().slice(0, maxCandidates);
203
+ if (suspicious) {
204
+ skillsLogger.warn("Skills root looks suspiciously large, truncating discovery.", {
205
+ dir: params.dir,
206
+ baseDir,
207
+ childDirCount: childDirs.length,
208
+ maxCandidatesPerRoot: limits.maxCandidatesPerRoot,
209
+ maxSkillsLoadedPerSource: limits.maxSkillsLoadedPerSource,
210
+ });
211
+ }
212
+ else if (childDirs.length > maxCandidates) {
213
+ skillsLogger.warn("Skills root has many entries, truncating discovery.", {
214
+ dir: params.dir,
215
+ baseDir,
216
+ childDirCount: childDirs.length,
217
+ maxSkillsLoadedPerSource: limits.maxSkillsLoadedPerSource,
218
+ });
219
+ }
220
+ const loadedSkills = [];
221
+ // Only consider immediate subfolders that look like skills (have SKILL.md) and are under size cap.
222
+ for (const name of limitedChildren) {
223
+ const skillDir = path.join(baseDir, name);
224
+ const skillMd = path.join(skillDir, "SKILL.md");
225
+ if (!fs.existsSync(skillMd)) {
226
+ continue;
227
+ }
228
+ try {
229
+ const size = fs.statSync(skillMd).size;
230
+ if (size > limits.maxSkillFileBytes) {
231
+ skillsLogger.warn("Skipping skill due to oversized SKILL.md.", {
232
+ skill: name,
233
+ filePath: skillMd,
234
+ size,
235
+ maxSkillFileBytes: limits.maxSkillFileBytes,
236
+ });
237
+ continue;
238
+ }
239
+ }
240
+ catch {
241
+ continue;
242
+ }
243
+ const loaded = loadSkillsFromDir({ dir: skillDir, source: params.source });
244
+ loadedSkills.push(...unwrapLoadedSkills(loaded));
245
+ if (loadedSkills.length >= limits.maxSkillsLoadedPerSource) {
246
+ break;
247
+ }
248
+ }
249
+ if (loadedSkills.length > limits.maxSkillsLoadedPerSource) {
250
+ return loadedSkills
251
+ .slice()
252
+ .sort((a, b) => a.name.localeCompare(b.name))
253
+ .slice(0, limits.maxSkillsLoadedPerSource);
254
+ }
255
+ return loadedSkills;
76
256
  };
77
257
  const managedSkillsDir = opts?.managedSkillsDir ?? path.join(CONFIG_DIR, "skills");
78
- const workspaceSkillsDir = path.join(workspaceDir, "skills");
258
+ const workspaceSkillsDir = path.resolve(workspaceDir, "skills");
79
259
  const bundledSkillsDir = opts?.bundledSkillsDir ?? resolveBundledSkillsDir();
80
260
  const extraDirsRaw = opts?.config?.skills?.load?.extraDirs ?? [];
81
261
  const extraDirs = extraDirsRaw
@@ -103,20 +283,40 @@ function loadSkillEntries(workspaceDir, opts) {
103
283
  dir: managedSkillsDir,
104
284
  source: "poolbot-managed",
105
285
  });
286
+ const personalAgentsSkillsDir = path.resolve(os.homedir(), ".agents", "skills");
287
+ const personalAgentsSkills = loadSkills({
288
+ dir: personalAgentsSkillsDir,
289
+ source: "agents-skills-personal",
290
+ });
291
+ const projectAgentsSkillsDir = path.resolve(workspaceDir, ".agents", "skills");
292
+ const projectAgentsSkills = loadSkills({
293
+ dir: projectAgentsSkillsDir,
294
+ source: "agents-skills-project",
295
+ });
106
296
  const workspaceSkills = loadSkills({
107
297
  dir: workspaceSkillsDir,
108
298
  source: "poolbot-workspace",
109
299
  });
110
300
  const merged = new Map();
111
- // Precedence: extra < bundled < managed < workspace
112
- for (const skill of extraSkills)
301
+ // Precedence: extra < bundled < managed < agents-skills-personal < agents-skills-project < workspace
302
+ for (const skill of extraSkills) {
113
303
  merged.set(skill.name, skill);
114
- for (const skill of bundledSkills)
304
+ }
305
+ for (const skill of bundledSkills) {
115
306
  merged.set(skill.name, skill);
116
- for (const skill of managedSkills)
307
+ }
308
+ for (const skill of managedSkills) {
309
+ merged.set(skill.name, skill);
310
+ }
311
+ for (const skill of personalAgentsSkills) {
117
312
  merged.set(skill.name, skill);
118
- for (const skill of workspaceSkills)
313
+ }
314
+ for (const skill of projectAgentsSkills) {
315
+ merged.set(skill.name, skill);
316
+ }
317
+ for (const skill of workspaceSkills) {
119
318
  merged.set(skill.name, skill);
319
+ }
120
320
  const skillEntries = Array.from(merged.values()).map((skill) => {
121
321
  let frontmatter = {};
122
322
  try {
@@ -135,19 +335,64 @@ function loadSkillEntries(workspaceDir, opts) {
135
335
  });
136
336
  return skillEntries;
137
337
  }
338
+ function applySkillsPromptLimits(params) {
339
+ const limits = resolveSkillsLimits(params.config);
340
+ const total = params.skills.length;
341
+ const byCount = params.skills.slice(0, Math.max(0, limits.maxSkillsInPrompt));
342
+ let skillsForPrompt = byCount;
343
+ let truncated = total > byCount.length;
344
+ let truncatedReason = truncated ? "count" : null;
345
+ const fits = (skills) => {
346
+ const block = formatSkillsForPrompt(skills);
347
+ return block.length <= limits.maxSkillsPromptChars;
348
+ };
349
+ if (!fits(skillsForPrompt)) {
350
+ // Binary search the largest prefix that fits in the char budget.
351
+ let lo = 0;
352
+ let hi = skillsForPrompt.length;
353
+ while (lo < hi) {
354
+ const mid = Math.ceil((lo + hi) / 2);
355
+ if (fits(skillsForPrompt.slice(0, mid))) {
356
+ lo = mid;
357
+ }
358
+ else {
359
+ hi = mid - 1;
360
+ }
361
+ }
362
+ skillsForPrompt = skillsForPrompt.slice(0, lo);
363
+ truncated = true;
364
+ truncatedReason = "chars";
365
+ }
366
+ return { skillsForPrompt, truncated, truncatedReason };
367
+ }
138
368
  export function buildWorkspaceSkillSnapshot(workspaceDir, opts) {
139
369
  const skillEntries = opts?.entries ?? loadSkillEntries(workspaceDir, opts);
140
370
  const eligible = filterSkillEntries(skillEntries, opts?.config, opts?.skillFilter, opts?.eligibility);
141
371
  const promptEntries = eligible.filter((entry) => entry.invocation?.disableModelInvocation !== true);
142
372
  const resolvedSkills = promptEntries.map((entry) => entry.skill);
143
373
  const remoteNote = opts?.eligibility?.remote?.note?.trim();
144
- const prompt = [remoteNote, formatSkillsForPrompt(resolvedSkills)].filter(Boolean).join("\n");
374
+ const { skillsForPrompt, truncated } = applySkillsPromptLimits({
375
+ skills: resolvedSkills,
376
+ config: opts?.config,
377
+ });
378
+ const truncationNote = truncated
379
+ ? `⚠️ Skills truncated: included ${skillsForPrompt.length} of ${resolvedSkills.length}. Run \`poolbot skills check\` to audit.`
380
+ : "";
381
+ const prompt = [
382
+ remoteNote,
383
+ truncationNote,
384
+ formatSkillsForPrompt(compactSkillPaths(skillsForPrompt)),
385
+ ]
386
+ .filter(Boolean)
387
+ .join("\n");
388
+ const skillFilter = normalizeSkillFilter(opts?.skillFilter);
145
389
  return {
146
390
  prompt,
147
391
  skills: eligible.map((entry) => ({
148
392
  name: entry.skill.name,
149
393
  primaryEnv: entry.metadata?.primaryEnv,
150
394
  })),
395
+ ...(skillFilter === undefined ? {} : { skillFilter }),
151
396
  resolvedSkills,
152
397
  version: opts?.snapshotVersion,
153
398
  };
@@ -157,14 +402,23 @@ export function buildWorkspaceSkillsPrompt(workspaceDir, opts) {
157
402
  const eligible = filterSkillEntries(skillEntries, opts?.config, opts?.skillFilter, opts?.eligibility);
158
403
  const promptEntries = eligible.filter((entry) => entry.invocation?.disableModelInvocation !== true);
159
404
  const remoteNote = opts?.eligibility?.remote?.note?.trim();
160
- return [remoteNote, formatSkillsForPrompt(promptEntries.map((entry) => entry.skill))]
405
+ const resolvedSkills = promptEntries.map((entry) => entry.skill);
406
+ const { skillsForPrompt, truncated } = applySkillsPromptLimits({
407
+ skills: resolvedSkills,
408
+ config: opts?.config,
409
+ });
410
+ const truncationNote = truncated
411
+ ? `⚠️ Skills truncated: included ${skillsForPrompt.length} of ${resolvedSkills.length}. Run \`poolbot skills check\` to audit.`
412
+ : "";
413
+ return [remoteNote, truncationNote, formatSkillsForPrompt(compactSkillPaths(skillsForPrompt))]
161
414
  .filter(Boolean)
162
415
  .join("\n");
163
416
  }
164
417
  export function resolveSkillsPromptForRun(params) {
165
418
  const snapshotPrompt = params.skillsSnapshot?.prompt?.trim();
166
- if (snapshotPrompt)
419
+ if (snapshotPrompt) {
167
420
  return snapshotPrompt;
421
+ }
168
422
  if (params.entries && params.entries.length > 0) {
169
423
  const prompt = buildWorkspaceSkillsPrompt(params.workspaceDir, {
170
424
  entries: params.entries,
@@ -177,11 +431,45 @@ export function resolveSkillsPromptForRun(params) {
177
431
  export function loadWorkspaceSkillEntries(workspaceDir, opts) {
178
432
  return loadSkillEntries(workspaceDir, opts);
179
433
  }
434
+ function resolveUniqueSyncedSkillDirName(base, used) {
435
+ if (!used.has(base)) {
436
+ used.add(base);
437
+ return base;
438
+ }
439
+ for (let index = 2; index < 10_000; index += 1) {
440
+ const candidate = `${base}-${index}`;
441
+ if (!used.has(candidate)) {
442
+ used.add(candidate);
443
+ return candidate;
444
+ }
445
+ }
446
+ let fallbackIndex = 10_000;
447
+ let fallback = `${base}-${fallbackIndex}`;
448
+ while (used.has(fallback)) {
449
+ fallbackIndex += 1;
450
+ fallback = `${base}-${fallbackIndex}`;
451
+ }
452
+ used.add(fallback);
453
+ return fallback;
454
+ }
455
+ function resolveSyncedSkillDestinationPath(params) {
456
+ const sourceDirName = path.basename(params.entry.skill.baseDir).trim();
457
+ if (!sourceDirName || sourceDirName === "." || sourceDirName === "..") {
458
+ return null;
459
+ }
460
+ const uniqueDirName = resolveUniqueSyncedSkillDirName(sourceDirName, params.usedDirNames);
461
+ return resolveSandboxPath({
462
+ filePath: uniqueDirName,
463
+ cwd: params.targetSkillsDir,
464
+ root: params.targetSkillsDir,
465
+ }).resolved;
466
+ }
180
467
  export async function syncSkillsToWorkspace(params) {
181
468
  const sourceDir = resolveUserPath(params.sourceWorkspaceDir);
182
469
  const targetDir = resolveUserPath(params.targetWorkspaceDir);
183
- if (sourceDir === targetDir)
470
+ if (sourceDir === targetDir) {
184
471
  return;
472
+ }
185
473
  await serializeByKey(`syncSkills:${targetDir}`, async () => {
186
474
  const targetSkillsDir = path.join(targetDir, "skills");
187
475
  const entries = loadSkillEntries(sourceDir, {
@@ -191,8 +479,25 @@ export async function syncSkillsToWorkspace(params) {
191
479
  });
192
480
  await fsp.rm(targetSkillsDir, { recursive: true, force: true });
193
481
  await fsp.mkdir(targetSkillsDir, { recursive: true });
482
+ const usedDirNames = new Set();
194
483
  for (const entry of entries) {
195
- const dest = path.join(targetSkillsDir, entry.skill.name);
484
+ let dest = null;
485
+ try {
486
+ dest = resolveSyncedSkillDestinationPath({
487
+ targetSkillsDir,
488
+ entry,
489
+ usedDirNames,
490
+ });
491
+ }
492
+ catch (error) {
493
+ const message = error instanceof Error ? error.message : JSON.stringify(error);
494
+ console.warn(`[skills] Failed to resolve safe destination for ${entry.skill.name}: ${message}`);
495
+ continue;
496
+ }
497
+ if (!dest) {
498
+ console.warn(`[skills] Failed to resolve safe destination for ${entry.skill.name}: invalid source directory name`);
499
+ continue;
500
+ }
196
501
  try {
197
502
  await fsp.cp(entry.skill.baseDir, dest, {
198
503
  recursive: true,
@@ -239,10 +544,12 @@ export function buildWorkspaceSkillCommandSpecs(workspaceDir, opts) {
239
544
  "")
240
545
  .trim()
241
546
  .toLowerCase();
242
- if (!kindRaw)
547
+ if (!kindRaw) {
243
548
  return undefined;
244
- if (kindRaw !== "tool")
549
+ }
550
+ if (kindRaw !== "tool") {
245
551
  return undefined;
552
+ }
246
553
  const toolName = (entry.frontmatter?.["command-tool"] ??
247
554
  entry.frontmatter?.["command_tool"] ??
248
555
  "").trim();