@poolzin/pool-bot 2026.1.39 → 2026.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (312) hide show
  1. package/assets/chrome-extension/README.md +3 -3
  2. package/assets/chrome-extension/background.js +5 -5
  3. package/assets/chrome-extension/manifest.json +3 -3
  4. package/assets/chrome-extension/options.html +4 -4
  5. package/assets/chrome-extension/options.js +1 -1
  6. package/dist/acp/client.js +3 -3
  7. package/dist/acp/types.js +1 -1
  8. package/dist/agents/agent-paths.js +3 -3
  9. package/dist/agents/auth-profiles/paths.js +3 -3
  10. package/dist/agents/cli-runner/helpers.js +1 -1
  11. package/dist/agents/cli-runner.js +2 -2
  12. package/dist/agents/cloudflare-ai-gateway.js +31 -0
  13. package/dist/agents/compaction.js +16 -2
  14. package/dist/agents/context-window-guard.js +13 -10
  15. package/dist/agents/context.js +4 -4
  16. package/dist/agents/docs-path.js +1 -1
  17. package/dist/agents/minimax-vlm.js +1 -1
  18. package/dist/agents/model-auth.js +12 -1
  19. package/dist/agents/model-catalog.js +4 -4
  20. package/dist/agents/model-selection.js +10 -4
  21. package/dist/agents/models-config.js +3 -3
  22. package/dist/agents/models-config.providers.js +147 -39
  23. package/dist/agents/pi-embedded-helpers/openai.js +1 -1
  24. package/dist/agents/pi-embedded-runner/compact.js +8 -8
  25. package/dist/agents/pi-embedded-runner/model.js +2 -2
  26. package/dist/agents/pi-embedded-runner/run/attempt.js +6 -6
  27. package/dist/agents/pi-embedded-runner/run.js +4 -4
  28. package/dist/agents/pi-embedded-runner/tool-result-truncation.js +275 -0
  29. package/dist/agents/pi-embedded-runner/utils.js +1 -1
  30. package/dist/agents/pi-model-discovery.js +10 -0
  31. package/dist/agents/pi-tool-definition-adapter.js +50 -9
  32. package/dist/agents/pi-tools.before-tool-call.js +67 -0
  33. package/dist/agents/pi-tools.js +10 -5
  34. package/dist/agents/pi-tools.read.js +2 -2
  35. package/dist/agents/session-file-repair.js +83 -0
  36. package/dist/agents/session-transcript-repair.js +68 -0
  37. package/dist/agents/skills/frontmatter.js +1 -1
  38. package/dist/agents/skills/workspace.js +2 -2
  39. package/dist/agents/system-prompt.js +28 -4
  40. package/dist/agents/together-models.js +127 -0
  41. package/dist/agents/tool-images.js +1 -1
  42. package/dist/agents/tool-policy.js +1 -1
  43. package/dist/agents/tools/browser-tool.js +3 -3
  44. package/dist/agents/tools/image-tool.js +2 -2
  45. package/dist/agents/tools/memory-tool.js +93 -5
  46. package/dist/agents/tools/web-search.js +1 -1
  47. package/dist/auto-reply/commands-registry.data.js +1 -1
  48. package/dist/auto-reply/reply/commands-context-report.js +2 -2
  49. package/dist/auto-reply/reply/commands-session.js +2 -2
  50. package/dist/auto-reply/reply/get-reply-run.js +14 -4
  51. package/dist/auto-reply/reply/groups.js +1 -1
  52. package/dist/auto-reply/reply/inbound-context.js +4 -0
  53. package/dist/auto-reply/reply/inbound-meta.js +130 -0
  54. package/dist/auto-reply/reply/untrusted-context.js +15 -0
  55. package/dist/auto-reply/status.js +1 -1
  56. package/dist/browser/client-fetch.js +1 -1
  57. package/dist/browser/config.js +1 -1
  58. package/dist/browser/extension-relay.js +3 -3
  59. package/dist/browser/server-context.js +2 -2
  60. package/dist/build-info.json +3 -3
  61. package/dist/canvas-host/a2ui.js +3 -3
  62. package/dist/channels/plugins/catalog.js +2 -2
  63. package/dist/channels/plugins/onboarding/imessage.js +1 -1
  64. package/dist/channels/plugins/onboarding/signal.js +1 -1
  65. package/dist/channels/plugins/onboarding/slack.js +4 -4
  66. package/dist/channels/plugins/onboarding/whatsapp.js +3 -3
  67. package/dist/channels/plugins/pairing-message.js +1 -1
  68. package/dist/cli/browser-cli-extension.js +2 -2
  69. package/dist/cli/docs-cli.js +1 -1
  70. package/dist/cli/gateway-cli/dev.js +1 -1
  71. package/dist/cli/memory-cli.js +25 -15
  72. package/dist/cli/nodes-cli/register.canvas.js +1 -1
  73. package/dist/cli/plugins-cli.js +1 -1
  74. package/dist/cli/run-main.js +2 -2
  75. package/dist/cli/security-cli.js +1 -1
  76. package/dist/cli/tagline.js +1 -1
  77. package/dist/cli/update-cli.js +4 -4
  78. package/dist/cli/webhooks-cli.js +5 -5
  79. package/dist/commands/agents.commands.add.js +1 -1
  80. package/dist/commands/auth-choice.apply.api-providers.js +305 -17
  81. package/dist/commands/auth-choice.apply.js +4 -1
  82. package/dist/commands/auth-choice.apply.plugin-provider.js +2 -2
  83. package/dist/commands/auth-choice.apply.xai.js +63 -0
  84. package/dist/commands/auth-choice.preferred-provider.js +7 -1
  85. package/dist/commands/configure.wizard.js +1 -1
  86. package/dist/commands/dashboard.js +1 -1
  87. package/dist/commands/docs.js +1 -1
  88. package/dist/commands/doctor-gateway-services.js +3 -3
  89. package/dist/commands/doctor-update.js +3 -3
  90. package/dist/commands/doctor.js +1 -1
  91. package/dist/commands/models/list.probe.js +2 -2
  92. package/dist/commands/models/list.registry.js +4 -4
  93. package/dist/commands/models/list.status-command.js +2 -2
  94. package/dist/commands/onboard-auth.config-core.js +366 -28
  95. package/dist/commands/onboard-auth.credentials.js +71 -9
  96. package/dist/commands/onboard-auth.js +3 -3
  97. package/dist/commands/onboard-auth.models.js +26 -24
  98. package/dist/commands/onboard-non-interactive/local/auth-choice.js +140 -6
  99. package/dist/commands/status-all/report-lines.js +1 -1
  100. package/dist/commands/status.command.js +1 -1
  101. package/dist/commands/uninstall.js +3 -3
  102. package/dist/compat/legacy-names.js +1 -1
  103. package/dist/config/io.js +3 -3
  104. package/dist/config/schema.js +1 -1
  105. package/dist/config/types.memory.js +1 -0
  106. package/dist/config/version.js +4 -4
  107. package/dist/daemon/constants.js +7 -7
  108. package/dist/daemon/inspect.js +6 -6
  109. package/dist/daemon/systemd-unit.js +1 -1
  110. package/dist/gateway/live-image-probe.js +1 -66
  111. package/dist/gateway/openai-http.js +2 -2
  112. package/dist/gateway/openresponses-http.js +4 -4
  113. package/dist/gateway/server-discovery.js +2 -2
  114. package/dist/gateway/server-http.js +1 -1
  115. package/dist/gateway/server.impl.js +2 -2
  116. package/dist/hooks/frontmatter.js +1 -1
  117. package/dist/hooks/hooks-status.js +1 -1
  118. package/dist/hooks/install.js +2 -2
  119. package/dist/hooks/loader.js +1 -1
  120. package/dist/hooks/workspace.js +3 -3
  121. package/dist/index.js +2 -2
  122. package/dist/infra/bonjour.js +3 -3
  123. package/dist/infra/path-env.js +3 -3
  124. package/dist/infra/provider-usage.fetch.minimax.js +1 -1
  125. package/dist/infra/restart.js +1 -1
  126. package/dist/infra/tailscale.js +1 -1
  127. package/dist/macos/relay.js +2 -2
  128. package/dist/media/input-files.js +1 -1
  129. package/dist/media/mime.js +4 -0
  130. package/dist/media/png-encode.js +74 -0
  131. package/dist/media-understanding/providers/image.js +2 -2
  132. package/dist/memory/backend-config.js +207 -0
  133. package/dist/memory/embeddings.js +1 -1
  134. package/dist/memory/manager.js +1 -0
  135. package/dist/memory/types.js +1 -0
  136. package/dist/node-host/runner.js +2 -2
  137. package/dist/pairing/pairing-messages.js +1 -1
  138. package/dist/plugins/discovery.js +1 -1
  139. package/dist/plugins/install.js +2 -2
  140. package/dist/plugins/update.js +1 -1
  141. package/dist/security/audit.js +2 -2
  142. package/dist/shared/text/reasoning-tags.js +52 -7
  143. package/dist/tailscale/detect.js +146 -0
  144. package/dist/telegram/bot-message-context.js +1 -1
  145. package/dist/test-helpers/workspace.js +11 -0
  146. package/dist/test-utils/channel-plugins.js +82 -0
  147. package/dist/test-utils/ports.js +73 -0
  148. package/dist/utils/shell-argv.js +61 -0
  149. package/dist/utils.js +10 -0
  150. package/dist/web/qr-image.js +1 -61
  151. package/dist/wizard/onboarding.finalize.js +7 -7
  152. package/dist/wizard/onboarding.js +3 -3
  153. package/docs/RELEASE_WORKFOTS_COMPARISON.md +3 -3
  154. package/docs/_config.yml +2 -2
  155. package/docs/_layouts/default.html +9 -9
  156. package/docs/concepts/typebox.md +1 -1
  157. package/docs/docs.json +1 -1
  158. package/docs/northflank.mdx +7 -7
  159. package/docs/railway.mdx +3 -3
  160. package/docs/render.mdx +5 -5
  161. package/docs/start/lore.md +2 -2
  162. package/extensions/bluebubbles/index.ts +2 -2
  163. package/extensions/bluebubbles/package.json +1 -1
  164. package/extensions/bluebubbles/src/accounts.ts +8 -8
  165. package/extensions/bluebubbles/src/actions.test.ts +22 -22
  166. package/extensions/bluebubbles/src/actions.ts +5 -5
  167. package/extensions/bluebubbles/src/attachments.ts +2 -2
  168. package/extensions/bluebubbles/src/channel.ts +16 -16
  169. package/extensions/bluebubbles/src/chat.ts +2 -2
  170. package/extensions/bluebubbles/src/media-send.ts +2 -2
  171. package/extensions/bluebubbles/src/monitor.test.ts +46 -46
  172. package/extensions/bluebubbles/src/monitor.ts +5 -5
  173. package/extensions/bluebubbles/src/onboarding.ts +7 -7
  174. package/extensions/bluebubbles/src/reactions.ts +2 -2
  175. package/extensions/bluebubbles/src/send.ts +2 -2
  176. package/extensions/copilot-proxy/README.md +1 -1
  177. package/extensions/copilot-proxy/package.json +1 -1
  178. package/extensions/diagnostics-otel/index.ts +2 -2
  179. package/extensions/diagnostics-otel/package.json +1 -1
  180. package/extensions/diagnostics-otel/src/service.ts +3 -3
  181. package/extensions/discord/index.ts +2 -2
  182. package/extensions/discord/package.json +1 -1
  183. package/extensions/google-antigravity-auth/README.md +1 -1
  184. package/extensions/google-antigravity-auth/index.ts +1 -1
  185. package/extensions/google-antigravity-auth/package.json +1 -1
  186. package/extensions/google-gemini-cli-auth/README.md +1 -1
  187. package/extensions/google-gemini-cli-auth/oauth.ts +1 -1
  188. package/extensions/google-gemini-cli-auth/package.json +1 -1
  189. package/extensions/googlechat/index.ts +3 -3
  190. package/extensions/googlechat/package.json +1 -1
  191. package/extensions/googlechat/src/accounts.ts +8 -8
  192. package/extensions/googlechat/src/actions.ts +6 -6
  193. package/extensions/googlechat/src/channel.ts +21 -21
  194. package/extensions/googlechat/src/monitor.ts +8 -8
  195. package/extensions/googlechat/src/onboarding.ts +10 -10
  196. package/extensions/imessage/index.ts +2 -2
  197. package/extensions/imessage/package.json +1 -1
  198. package/extensions/line/index.ts +2 -2
  199. package/extensions/line/package.json +1 -1
  200. package/extensions/line/src/card-command.ts +2 -2
  201. package/extensions/line/src/channel.logout.test.ts +4 -4
  202. package/extensions/line/src/channel.sendPayload.test.ts +8 -8
  203. package/extensions/line/src/channel.ts +3 -3
  204. package/extensions/llm-task/README.md +3 -3
  205. package/extensions/llm-task/index.ts +2 -2
  206. package/extensions/llm-task/package.json +1 -1
  207. package/extensions/llm-task/src/llm-task-tool.ts +4 -4
  208. package/extensions/lobster/README.md +6 -6
  209. package/extensions/lobster/index.ts +2 -2
  210. package/extensions/lobster/src/lobster-tool.test.ts +4 -4
  211. package/extensions/lobster/src/lobster-tool.ts +2 -2
  212. package/extensions/matrix/index.ts +2 -2
  213. package/extensions/matrix/package.json +1 -1
  214. package/extensions/matrix/src/matrix/client/config.ts +1 -1
  215. package/extensions/matrix/src/matrix/monitor/handler.ts +1 -1
  216. package/extensions/matrix/src/onboarding.ts +1 -1
  217. package/extensions/mattermost/index.ts +2 -2
  218. package/extensions/mattermost/package.json +1 -1
  219. package/extensions/mattermost/src/mattermost/accounts.ts +8 -8
  220. package/extensions/mattermost/src/mattermost/monitor-helpers.ts +5 -5
  221. package/extensions/mattermost/src/mattermost/monitor.ts +2 -2
  222. package/extensions/mattermost/src/onboarding-helpers.ts +3 -3
  223. package/extensions/mattermost/src/onboarding.ts +2 -2
  224. package/extensions/memory-core/index.ts +2 -2
  225. package/extensions/memory-core/package.json +1 -1
  226. package/extensions/memory-lancedb/index.ts +3 -3
  227. package/extensions/memory-lancedb/package.json +1 -1
  228. package/extensions/msteams/index.ts +2 -2
  229. package/extensions/msteams/package.json +1 -1
  230. package/extensions/msteams/src/channel.directory.test.ts +2 -2
  231. package/extensions/msteams/src/channel.ts +2 -2
  232. package/extensions/msteams/src/graph-upload.ts +4 -4
  233. package/extensions/msteams/src/monitor-handler.ts +2 -2
  234. package/extensions/msteams/src/monitor.ts +2 -2
  235. package/extensions/msteams/src/onboarding.ts +9 -9
  236. package/extensions/msteams/src/reply-dispatcher.ts +2 -2
  237. package/extensions/msteams/src/send-context.ts +2 -2
  238. package/extensions/msteams/src/send.ts +4 -4
  239. package/extensions/nextcloud-talk/index.ts +2 -2
  240. package/extensions/nextcloud-talk/package.json +1 -1
  241. package/extensions/nextcloud-talk/src/channel.ts +7 -7
  242. package/extensions/nextcloud-talk/src/inbound.ts +7 -7
  243. package/extensions/nextcloud-talk/src/onboarding.ts +1 -1
  244. package/extensions/nostr/README.md +2 -2
  245. package/extensions/nostr/index.ts +5 -5
  246. package/extensions/nostr/package.json +1 -1
  247. package/extensions/nostr/src/types.ts +4 -4
  248. package/extensions/open-prose/index.ts +2 -2
  249. package/extensions/qwen-portal-auth/README.md +1 -1
  250. package/extensions/signal/index.ts +2 -2
  251. package/extensions/signal/package.json +1 -1
  252. package/extensions/slack/index.ts +2 -2
  253. package/extensions/slack/package.json +1 -1
  254. package/extensions/telegram/index.ts +2 -2
  255. package/extensions/telegram/package.json +1 -1
  256. package/extensions/telegram/src/channel.ts +2 -2
  257. package/extensions/tlon/README.md +2 -2
  258. package/extensions/tlon/index.ts +2 -2
  259. package/extensions/tlon/package.json +1 -1
  260. package/extensions/tlon/src/channel.ts +13 -13
  261. package/extensions/tlon/src/monitor/index.ts +3 -3
  262. package/extensions/tlon/src/onboarding.ts +3 -3
  263. package/extensions/tlon/src/types.ts +3 -3
  264. package/extensions/twitch/README.md +1 -1
  265. package/extensions/twitch/index.ts +2 -2
  266. package/extensions/twitch/package.json +1 -1
  267. package/extensions/twitch/src/config.ts +3 -3
  268. package/extensions/twitch/src/monitor.ts +3 -3
  269. package/extensions/twitch/src/onboarding.ts +9 -9
  270. package/extensions/twitch/src/outbound.test.ts +2 -2
  271. package/extensions/twitch/src/plugin.test.ts +2 -2
  272. package/extensions/twitch/src/plugin.ts +8 -8
  273. package/extensions/twitch/src/send.test.ts +2 -2
  274. package/extensions/twitch/src/send.ts +4 -4
  275. package/extensions/twitch/src/token.test.ts +8 -8
  276. package/extensions/twitch/src/token.ts +3 -3
  277. package/extensions/twitch/src/twitch-client.ts +3 -3
  278. package/extensions/twitch/src/types.ts +3 -3
  279. package/extensions/twitch/src/utils/markdown.ts +1 -1
  280. package/extensions/voice-call/README.md +3 -3
  281. package/extensions/voice-call/package.json +1 -1
  282. package/extensions/voice-call/src/core-bridge.ts +2 -2
  283. package/extensions/voice-call/src/response-generator.ts +1 -1
  284. package/extensions/whatsapp/index.ts +2 -2
  285. package/extensions/whatsapp/package.json +1 -1
  286. package/extensions/zalo/README.md +1 -1
  287. package/extensions/zalo/index.ts +2 -2
  288. package/extensions/zalo/package.json +1 -1
  289. package/extensions/zalo/src/accounts.ts +8 -8
  290. package/extensions/zalo/src/actions.ts +4 -4
  291. package/extensions/zalo/src/channel.directory.test.ts +2 -2
  292. package/extensions/zalo/src/channel.ts +18 -18
  293. package/extensions/zalo/src/monitor.ts +9 -9
  294. package/extensions/zalo/src/monitor.webhook.test.ts +2 -2
  295. package/extensions/zalo/src/onboarding.ts +24 -24
  296. package/extensions/zalo/src/send.ts +2 -2
  297. package/extensions/zalouser/README.md +2 -2
  298. package/extensions/zalouser/index.ts +2 -2
  299. package/extensions/zalouser/package.json +1 -1
  300. package/extensions/zalouser/src/accounts.ts +9 -9
  301. package/extensions/zalouser/src/channel.ts +24 -24
  302. package/extensions/zalouser/src/monitor.ts +4 -4
  303. package/extensions/zalouser/src/onboarding.ts +28 -28
  304. package/package.json +13 -251
  305. package/skills/nano-banana-pro/scripts/generate_image.py +1 -1
  306. package/skills/tmux/scripts/find-sessions.sh +1 -1
  307. package/CHANGELOG.md +0 -102
  308. package/README-header.png +0 -0
  309. package/git-hooks/pre-commit +0 -4
  310. package/scripts/format-staged.js +0 -148
  311. package/scripts/postinstall.js +0 -300
  312. package/scripts/setup-git-hooks.js +0 -96
@@ -0,0 +1,275 @@
1
+ import { SessionManager } from "@mariozechner/pi-coding-agent";
2
+ import { log } from "./logger.js";
3
+ /**
4
+ * Maximum share of the context window a single tool result should occupy.
5
+ * This is intentionally conservative – a single tool result should not
6
+ * consume more than 30% of the context window even without other messages.
7
+ */
8
+ const MAX_TOOL_RESULT_CONTEXT_SHARE = 0.3;
9
+ /**
10
+ * Hard character limit for a single tool result text block.
11
+ * Even for the largest context windows (~2M tokens), a single tool result
12
+ * should not exceed ~400K characters (~100K tokens).
13
+ * This acts as a safety net when we don't know the context window size.
14
+ */
15
+ export const HARD_MAX_TOOL_RESULT_CHARS = 400_000;
16
+ /**
17
+ * Minimum characters to keep when truncating.
18
+ * We always keep at least the first portion so the model understands
19
+ * what was in the content.
20
+ */
21
+ const MIN_KEEP_CHARS = 2_000;
22
+ /**
23
+ * Suffix appended to truncated tool results.
24
+ */
25
+ const TRUNCATION_SUFFIX = "\n\n⚠️ [Content truncated — original was too large for the model's context window. " +
26
+ "The content above is a partial view. If you need more, request specific sections or use " +
27
+ "offset/limit parameters to read smaller chunks.]";
28
+ /**
29
+ * Truncate a single text string to fit within maxChars, preserving the beginning.
30
+ */
31
+ export function truncateToolResultText(text, maxChars) {
32
+ if (text.length <= maxChars) {
33
+ return text;
34
+ }
35
+ const keepChars = Math.max(MIN_KEEP_CHARS, maxChars - TRUNCATION_SUFFIX.length);
36
+ // Try to break at a newline boundary to avoid cutting mid-line
37
+ let cutPoint = keepChars;
38
+ const lastNewline = text.lastIndexOf("\n", keepChars);
39
+ if (lastNewline > keepChars * 0.8) {
40
+ cutPoint = lastNewline;
41
+ }
42
+ return text.slice(0, cutPoint) + TRUNCATION_SUFFIX;
43
+ }
44
+ /**
45
+ * Calculate the maximum allowed characters for a single tool result
46
+ * based on the model's context window tokens.
47
+ *
48
+ * Uses a rough 4 chars ≈ 1 token heuristic (conservative for English text;
49
+ * actual ratio varies by tokenizer).
50
+ */
51
+ export function calculateMaxToolResultChars(contextWindowTokens) {
52
+ const maxTokens = Math.floor(contextWindowTokens * MAX_TOOL_RESULT_CONTEXT_SHARE);
53
+ // Rough conversion: ~4 chars per token on average
54
+ const maxChars = maxTokens * 4;
55
+ return Math.min(maxChars, HARD_MAX_TOOL_RESULT_CHARS);
56
+ }
57
+ /**
58
+ * Get the total character count of text content blocks in a tool result message.
59
+ */
60
+ function getToolResultTextLength(msg) {
61
+ if (!msg || msg.role !== "toolResult") {
62
+ return 0;
63
+ }
64
+ const content = msg.content;
65
+ if (!Array.isArray(content)) {
66
+ return 0;
67
+ }
68
+ let totalLength = 0;
69
+ for (const block of content) {
70
+ if (block && typeof block === "object" && block.type === "text") {
71
+ const text = block.text;
72
+ if (typeof text === "string") {
73
+ totalLength += text.length;
74
+ }
75
+ }
76
+ }
77
+ return totalLength;
78
+ }
79
+ /**
80
+ * Truncate a tool result message's text content blocks to fit within maxChars.
81
+ * Returns a new message (does not mutate the original).
82
+ */
83
+ function truncateToolResultMessage(msg, maxChars) {
84
+ const content = msg.content;
85
+ if (!Array.isArray(content)) {
86
+ return msg;
87
+ }
88
+ // Calculate total text size
89
+ const totalTextChars = getToolResultTextLength(msg);
90
+ if (totalTextChars <= maxChars) {
91
+ return msg;
92
+ }
93
+ // Distribute the budget proportionally among text blocks
94
+ const newContent = content.map((block) => {
95
+ if (!block || typeof block !== "object" || block.type !== "text") {
96
+ return block; // Keep non-text blocks (images) as-is
97
+ }
98
+ const textBlock = block;
99
+ if (typeof textBlock.text !== "string") {
100
+ return block;
101
+ }
102
+ // Proportional budget for this block
103
+ const blockShare = textBlock.text.length / totalTextChars;
104
+ const blockBudget = Math.max(MIN_KEEP_CHARS, Math.floor(maxChars * blockShare));
105
+ return {
106
+ ...textBlock,
107
+ text: truncateToolResultText(textBlock.text, blockBudget),
108
+ };
109
+ });
110
+ return { ...msg, content: newContent };
111
+ }
112
+ /**
113
+ * Find oversized tool result entries in a session and truncate them.
114
+ *
115
+ * This operates on the session file by:
116
+ * 1. Opening the session manager
117
+ * 2. Walking the current branch to find oversized tool results
118
+ * 3. Branching from before the first oversized tool result
119
+ * 4. Re-appending all entries from that point with truncated tool results
120
+ *
121
+ * @returns Object indicating whether any truncation was performed
122
+ */
123
+ export async function truncateOversizedToolResultsInSession(params) {
124
+ const { sessionFile, contextWindowTokens } = params;
125
+ const maxChars = calculateMaxToolResultChars(contextWindowTokens);
126
+ try {
127
+ const sessionManager = SessionManager.open(sessionFile);
128
+ const branch = sessionManager.getBranch();
129
+ if (branch.length === 0) {
130
+ return { truncated: false, truncatedCount: 0, reason: "empty session" };
131
+ }
132
+ // Find oversized tool result entries and their indices in the branch
133
+ const oversizedIndices = [];
134
+ for (let i = 0; i < branch.length; i++) {
135
+ const entry = branch[i];
136
+ if (entry.type !== "message") {
137
+ continue;
138
+ }
139
+ const msg = entry.message;
140
+ if (msg.role !== "toolResult") {
141
+ continue;
142
+ }
143
+ const textLength = getToolResultTextLength(msg);
144
+ if (textLength > maxChars) {
145
+ oversizedIndices.push(i);
146
+ log.info(`[tool-result-truncation] Found oversized tool result: ` +
147
+ `entry=${entry.id} chars=${textLength} maxChars=${maxChars} ` +
148
+ `sessionKey=${params.sessionKey ?? params.sessionId ?? "unknown"}`);
149
+ }
150
+ }
151
+ if (oversizedIndices.length === 0) {
152
+ return { truncated: false, truncatedCount: 0, reason: "no oversized tool results" };
153
+ }
154
+ // Branch from the parent of the first oversized entry
155
+ const firstOversizedIdx = oversizedIndices[0];
156
+ const firstOversizedEntry = branch[firstOversizedIdx];
157
+ const branchFromId = firstOversizedEntry.parentId;
158
+ if (!branchFromId) {
159
+ // The oversized entry is the root - very unusual but handle it
160
+ sessionManager.resetLeaf();
161
+ }
162
+ else {
163
+ sessionManager.branch(branchFromId);
164
+ }
165
+ // Re-append all entries from the first oversized one onwards,
166
+ // with truncated tool results
167
+ const oversizedSet = new Set(oversizedIndices);
168
+ let truncatedCount = 0;
169
+ for (let i = firstOversizedIdx; i < branch.length; i++) {
170
+ const entry = branch[i];
171
+ if (entry.type === "message") {
172
+ let message = entry.message;
173
+ if (oversizedSet.has(i)) {
174
+ message = truncateToolResultMessage(message, maxChars);
175
+ truncatedCount++;
176
+ const newLength = getToolResultTextLength(message);
177
+ log.info(`[tool-result-truncation] Truncated tool result: ` +
178
+ `originalEntry=${entry.id} newChars=${newLength} ` +
179
+ `sessionKey=${params.sessionKey ?? params.sessionId ?? "unknown"}`);
180
+ }
181
+ // appendMessage expects Message | CustomMessage | BashExecutionMessage
182
+ sessionManager.appendMessage(message);
183
+ }
184
+ else if (entry.type === "compaction") {
185
+ sessionManager.appendCompaction(entry.summary, entry.firstKeptEntryId, entry.tokensBefore, entry.details, entry.fromHook);
186
+ }
187
+ else if (entry.type === "thinking_level_change") {
188
+ sessionManager.appendThinkingLevelChange(entry.thinkingLevel);
189
+ }
190
+ else if (entry.type === "model_change") {
191
+ sessionManager.appendModelChange(entry.provider, entry.modelId);
192
+ }
193
+ else if (entry.type === "custom") {
194
+ sessionManager.appendCustomEntry(entry.customType, entry.data);
195
+ }
196
+ else if (entry.type === "custom_message") {
197
+ sessionManager.appendCustomMessageEntry(entry.customType, entry.content, entry.display, entry.details);
198
+ }
199
+ else if (entry.type === "branch_summary") {
200
+ // Branch summaries reference specific entry IDs - skip to avoid inconsistency
201
+ continue;
202
+ }
203
+ else if (entry.type === "label") {
204
+ // Labels reference specific entry IDs - skip to avoid inconsistency
205
+ continue;
206
+ }
207
+ else if (entry.type === "session_info") {
208
+ if (entry.name) {
209
+ sessionManager.appendSessionInfo(entry.name);
210
+ }
211
+ }
212
+ }
213
+ log.info(`[tool-result-truncation] Truncated ${truncatedCount} tool result(s) in session ` +
214
+ `(contextWindow=${contextWindowTokens} maxChars=${maxChars}) ` +
215
+ `sessionKey=${params.sessionKey ?? params.sessionId ?? "unknown"}`);
216
+ return { truncated: true, truncatedCount };
217
+ }
218
+ catch (err) {
219
+ const errMsg = err instanceof Error ? err.message : String(err);
220
+ log.warn(`[tool-result-truncation] Failed to truncate: ${errMsg}`);
221
+ return { truncated: false, truncatedCount: 0, reason: errMsg };
222
+ }
223
+ }
224
+ /**
225
+ * Truncate oversized tool results in an array of messages (in-memory).
226
+ * Returns a new array with truncated messages.
227
+ *
228
+ * This is used as a pre-emptive guard before sending messages to the LLM,
229
+ * without modifying the session file.
230
+ */
231
+ export function truncateOversizedToolResultsInMessages(messages, contextWindowTokens) {
232
+ const maxChars = calculateMaxToolResultChars(contextWindowTokens);
233
+ let truncatedCount = 0;
234
+ const result = messages.map((msg) => {
235
+ if (msg.role !== "toolResult") {
236
+ return msg;
237
+ }
238
+ const textLength = getToolResultTextLength(msg);
239
+ if (textLength <= maxChars) {
240
+ return msg;
241
+ }
242
+ truncatedCount++;
243
+ return truncateToolResultMessage(msg, maxChars);
244
+ });
245
+ return { messages: result, truncatedCount };
246
+ }
247
+ /**
248
+ * Check if a tool result message exceeds the size limit for a given context window.
249
+ */
250
+ export function isOversizedToolResult(msg, contextWindowTokens) {
251
+ if (msg.role !== "toolResult") {
252
+ return false;
253
+ }
254
+ const maxChars = calculateMaxToolResultChars(contextWindowTokens);
255
+ return getToolResultTextLength(msg) > maxChars;
256
+ }
257
+ /**
258
+ * Estimate whether the session likely has oversized tool results that caused
259
+ * a context overflow. Used as a heuristic to decide whether to attempt
260
+ * tool result truncation before giving up.
261
+ */
262
+ export function sessionLikelyHasOversizedToolResults(params) {
263
+ const { messages, contextWindowTokens } = params;
264
+ const maxChars = calculateMaxToolResultChars(contextWindowTokens);
265
+ for (const msg of messages) {
266
+ if (msg.role !== "toolResult") {
267
+ continue;
268
+ }
269
+ const textLength = getToolResultTextLength(msg);
270
+ if (textLength > maxChars) {
271
+ return true;
272
+ }
273
+ }
274
+ return false;
275
+ }
@@ -1,5 +1,5 @@
1
1
  export function mapThinkingLevel(level) {
2
- // pi-agent-core supports "xhigh"; Moltbot enables it for specific models.
2
+ // pi-agent-core supports "xhigh"; Poolbot enables it for specific models.
3
3
  if (!level)
4
4
  return "off";
5
5
  return level;
@@ -0,0 +1,10 @@
1
+ import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
2
+ import path from "node:path";
3
+ export { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
4
+ // Compatibility helpers for pi-coding-agent 0.50+ (discover* helpers removed).
5
+ export function discoverAuthStorage(agentDir) {
6
+ return new AuthStorage(path.join(agentDir, "auth.json"));
7
+ }
8
+ export function discoverModels(authStorage, agentDir) {
9
+ return new ModelRegistry(authStorage, path.join(agentDir, "models.json"));
10
+ }
@@ -1,6 +1,16 @@
1
1
  import { logDebug, logError } from "../logger.js";
2
+ import { isPlainObject } from "../utils.js";
3
+ import { runBeforeToolCallHook } from "./pi-tools.before-tool-call.js";
2
4
  import { normalizeToolName } from "./tool-policy.js";
3
5
  import { jsonResult } from "./tools/common.js";
6
+ function isAbortSignal(value) {
7
+ return typeof value === "object" && value !== null && "aborted" in value;
8
+ }
9
+ function isLegacyToolExecuteArgs(args) {
10
+ const third = args[2];
11
+ const fourth = args[3];
12
+ return isAbortSignal(third) || typeof fourth === "function";
13
+ }
4
14
  function describeToolExecutionError(err) {
5
15
  if (err instanceof Error) {
6
16
  const message = err.message?.trim() ? err.message : String(err);
@@ -8,6 +18,24 @@ function describeToolExecutionError(err) {
8
18
  }
9
19
  return { message: String(err) };
10
20
  }
21
+ function splitToolExecuteArgs(args) {
22
+ if (isLegacyToolExecuteArgs(args)) {
23
+ const [toolCallId, params, signal, onUpdate] = args;
24
+ return {
25
+ toolCallId,
26
+ params,
27
+ onUpdate,
28
+ signal,
29
+ };
30
+ }
31
+ const [toolCallId, params, onUpdate, _ctx, signal] = args;
32
+ return {
33
+ toolCallId,
34
+ params,
35
+ onUpdate,
36
+ signal,
37
+ };
38
+ }
11
39
  export function toToolDefinitions(tools) {
12
40
  return tools.map((tool) => {
13
41
  const name = tool.name || "tool";
@@ -16,22 +44,22 @@ export function toToolDefinitions(tools) {
16
44
  name,
17
45
  label: tool.label ?? name,
18
46
  description: tool.description ?? "",
19
- // biome-ignore lint/suspicious/noExplicitAny: TypeBox schema from pi-agent-core uses a different module instance.
20
47
  parameters: tool.parameters,
21
- execute: async (toolCallId, params, onUpdate, _ctx, signal) => {
22
- // KNOWN: pi-coding-agent `ToolDefinition.execute` has a different signature/order
23
- // than pi-agent-core `AgentTool.execute`. This adapter keeps our existing tools intact.
48
+ execute: async (...args) => {
49
+ const { toolCallId, params, onUpdate, signal } = splitToolExecuteArgs(args);
24
50
  try {
25
51
  return await tool.execute(toolCallId, params, signal, onUpdate);
26
52
  }
27
53
  catch (err) {
28
- if (signal?.aborted)
54
+ if (signal?.aborted) {
29
55
  throw err;
56
+ }
30
57
  const name = err && typeof err === "object" && "name" in err
31
58
  ? String(err.name)
32
59
  : "";
33
- if (name === "AbortError")
60
+ if (name === "AbortError") {
34
61
  throw err;
62
+ }
35
63
  const described = describeToolExecutionError(err);
36
64
  if (described.stack && described.stack !== described.message) {
37
65
  logDebug(`tools: ${normalizedName} failed stack:\n${described.stack}`);
@@ -49,18 +77,31 @@ export function toToolDefinitions(tools) {
49
77
  }
50
78
  // Convert client tools (OpenResponses hosted tools) to ToolDefinition format
51
79
  // These tools are intercepted to return a "pending" result instead of executing
52
- export function toClientToolDefinitions(tools, onClientToolCall) {
80
+ export function toClientToolDefinitions(tools, onClientToolCall, hookContext) {
53
81
  return tools.map((tool) => {
54
82
  const func = tool.function;
55
83
  return {
56
84
  name: func.name,
57
85
  label: func.name,
58
86
  description: func.description ?? "",
87
+ // oxlint-disable-next-line typescript/no-explicit-any
59
88
  parameters: func.parameters,
60
- execute: async (toolCallId, params, _onUpdate, _ctx, _signal) => {
89
+ execute: async (...args) => {
90
+ const { toolCallId, params } = splitToolExecuteArgs(args);
91
+ const outcome = await runBeforeToolCallHook({
92
+ toolName: func.name,
93
+ params,
94
+ toolCallId,
95
+ ctx: hookContext,
96
+ });
97
+ if (outcome.blocked) {
98
+ throw new Error(outcome.reason);
99
+ }
100
+ const adjustedParams = outcome.params;
101
+ const paramsRecord = isPlainObject(adjustedParams) ? adjustedParams : {};
61
102
  // Notify handler that a client tool was called
62
103
  if (onClientToolCall) {
63
- onClientToolCall(func.name, params);
104
+ onClientToolCall(func.name, paramsRecord);
64
105
  }
65
106
  // Return a pending result - the client will execute this tool
66
107
  return jsonResult({
@@ -0,0 +1,67 @@
1
+ import { createSubsystemLogger } from "../logging/subsystem.js";
2
+ import { getGlobalHookRunner } from "../plugins/hook-runner-global.js";
3
+ import { isPlainObject } from "../utils.js";
4
+ import { normalizeToolName } from "./tool-policy.js";
5
+ const log = createSubsystemLogger("agents/tools");
6
+ export async function runBeforeToolCallHook(args) {
7
+ const hookRunner = getGlobalHookRunner();
8
+ if (!hookRunner?.hasHooks("before_tool_call")) {
9
+ return { blocked: false, params: args.params };
10
+ }
11
+ const toolName = normalizeToolName(args.toolName || "tool");
12
+ const params = args.params;
13
+ try {
14
+ const normalizedParams = isPlainObject(params) ? params : {};
15
+ const hookResult = await hookRunner.runBeforeToolCall({
16
+ toolName,
17
+ params: normalizedParams,
18
+ }, {
19
+ toolName,
20
+ agentId: args.ctx?.agentId,
21
+ sessionKey: args.ctx?.sessionKey,
22
+ });
23
+ if (hookResult?.block) {
24
+ return {
25
+ blocked: true,
26
+ reason: hookResult.blockReason || "Tool call blocked by plugin hook",
27
+ };
28
+ }
29
+ if (hookResult?.params && isPlainObject(hookResult.params)) {
30
+ if (isPlainObject(params)) {
31
+ return { blocked: false, params: { ...params, ...hookResult.params } };
32
+ }
33
+ return { blocked: false, params: hookResult.params };
34
+ }
35
+ }
36
+ catch (err) {
37
+ const toolCallId = args.toolCallId ? ` toolCallId=${args.toolCallId}` : "";
38
+ log.warn(`before_tool_call hook failed: tool=${toolName}${toolCallId} error=${String(err)}`);
39
+ }
40
+ return { blocked: false, params };
41
+ }
42
+ export function wrapToolWithBeforeToolCallHook(tool, ctx) {
43
+ const execute = tool.execute;
44
+ if (!execute) {
45
+ return tool;
46
+ }
47
+ const toolName = tool.name || "tool";
48
+ return {
49
+ ...tool,
50
+ execute: async (toolCallId, params, signal, onUpdate) => {
51
+ const outcome = await runBeforeToolCallHook({
52
+ toolName,
53
+ params,
54
+ toolCallId,
55
+ ctx,
56
+ });
57
+ if (outcome.blocked) {
58
+ throw new Error(outcome.reason);
59
+ }
60
+ return await execute(toolCallId, outcome.params, signal, onUpdate);
61
+ },
62
+ };
63
+ }
64
+ export const __testing = {
65
+ runBeforeToolCallHook,
66
+ isPlainObject,
67
+ };
@@ -6,8 +6,9 @@ import { createExecTool, createProcessTool, } from "./bash-tools.js";
6
6
  import { listChannelAgentTools } from "./channel-tools.js";
7
7
  import { createPoolBotTools } from "./poolbot-tools.js";
8
8
  import { wrapToolWithAbortSignal } from "./pi-tools.abort.js";
9
+ import { wrapToolWithBeforeToolCallHook } from "./pi-tools.before-tool-call.js";
9
10
  import { filterToolsByPolicy, isToolAllowedByPolicies, resolveEffectiveToolPolicy, resolveGroupToolPolicy, resolveSubagentToolPolicy, } from "./pi-tools.policy.js";
10
- import { assertRequiredParams, CLAUDE_PARAM_GROUPS, createMoltbotReadTool, createSandboxedEditTool, createSandboxedReadTool, createSandboxedWriteTool, normalizeToolParams, patchToolSchemaForClaudeCompatibility, wrapToolParamNormalization, } from "./pi-tools.read.js";
11
+ import { assertRequiredParams, CLAUDE_PARAM_GROUPS, createPoolbotReadTool, createSandboxedEditTool, createSandboxedReadTool, createSandboxedWriteTool, normalizeToolParams, patchToolSchemaForClaudeCompatibility, wrapToolParamNormalization, } from "./pi-tools.read.js";
11
12
  import { cleanToolSchemaForGemini, normalizeToolParameters } from "./pi-tools.schema.js";
12
13
  import { buildPluginToolGroups, collectExplicitAllowlist, expandPolicyWithPluginGroups, normalizeToolName, resolveToolProfilePolicy, stripPluginOnlyAllowlist, } from "./tool-policy.js";
13
14
  import { getPluginToolMeta } from "../plugins/tools.js";
@@ -59,7 +60,7 @@ export const __testing = {
59
60
  wrapToolParamNormalization,
60
61
  assertRequiredParams,
61
62
  };
62
- export function createMoltbotCodingTools(options) {
63
+ export function createPoolbotCodingTools(options) {
63
64
  const execToolName = "exec";
64
65
  const sandbox = options?.sandbox?.enabled ? options.sandbox : undefined;
65
66
  const { agentId, globalPolicy, globalProviderPolicy, agentPolicy, agentProviderPolicy, profile, providerProfile, profileAlsoAllow, providerProfileAlsoAllow, } = resolveEffectiveToolPolicy({
@@ -124,7 +125,7 @@ export function createMoltbotCodingTools(options) {
124
125
  return [createSandboxedReadTool(sandboxRoot)];
125
126
  }
126
127
  const freshReadTool = createReadTool(workspaceRoot);
127
- return [createMoltbotReadTool(freshReadTool)];
128
+ return [createPoolbotReadTool(freshReadTool)];
128
129
  }
129
130
  if (tool.name === "bash" || tool.name === execToolName)
130
131
  return [];
@@ -287,9 +288,13 @@ export function createMoltbotCodingTools(options) {
287
288
  // Always normalize tool JSON Schemas before handing them to pi-agent/pi-ai.
288
289
  // Without this, some providers (notably OpenAI) will reject root-level union schemas.
289
290
  const normalized = subagentFiltered.map(normalizeToolParameters);
291
+ const withHooks = normalized.map((tool) => wrapToolWithBeforeToolCallHook(tool, {
292
+ agentId,
293
+ sessionKey: options?.sessionKey,
294
+ }));
290
295
  const withAbort = options?.abortSignal
291
- ? normalized.map((tool) => wrapToolWithAbortSignal(tool, options.abortSignal))
292
- : normalized;
296
+ ? withHooks.map((tool) => wrapToolWithAbortSignal(tool, options.abortSignal))
297
+ : withHooks;
293
298
  // NOTE: Keep canonical (lowercase) tool names here.
294
299
  // pi-ai's Anthropic OAuth transport remaps tool names to Claude Code-style names
295
300
  // on the wire and maps them back for tool dispatch.
@@ -199,7 +199,7 @@ function wrapSandboxPathGuard(tool, root) {
199
199
  }
200
200
  export function createSandboxedReadTool(root) {
201
201
  const base = createReadTool(root);
202
- return wrapSandboxPathGuard(createMoltbotReadTool(base), root);
202
+ return wrapSandboxPathGuard(createPoolbotReadTool(base), root);
203
203
  }
204
204
  export function createSandboxedWriteTool(root) {
205
205
  const base = createWriteTool(root);
@@ -209,7 +209,7 @@ export function createSandboxedEditTool(root) {
209
209
  const base = createEditTool(root);
210
210
  return wrapSandboxPathGuard(wrapToolParamNormalization(base, CLAUDE_PARAM_GROUPS.edit), root);
211
211
  }
212
- export function createMoltbotReadTool(base) {
212
+ export function createPoolbotReadTool(base) {
213
213
  const patched = patchToolSchemaForClaudeCompatibility(base);
214
214
  return {
215
215
  ...patched,
@@ -0,0 +1,83 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ function isSessionHeader(entry) {
4
+ if (!entry || typeof entry !== "object") {
5
+ return false;
6
+ }
7
+ const record = entry;
8
+ return record.type === "session" && typeof record.id === "string" && record.id.length > 0;
9
+ }
10
+ export async function repairSessionFileIfNeeded(params) {
11
+ const sessionFile = params.sessionFile.trim();
12
+ if (!sessionFile) {
13
+ return { repaired: false, droppedLines: 0, reason: "missing session file" };
14
+ }
15
+ let content;
16
+ try {
17
+ content = await fs.readFile(sessionFile, "utf-8");
18
+ }
19
+ catch (err) {
20
+ const code = err?.code;
21
+ if (code === "ENOENT") {
22
+ return { repaired: false, droppedLines: 0, reason: "missing session file" };
23
+ }
24
+ const reason = `failed to read session file: ${err instanceof Error ? err.message : "unknown error"}`;
25
+ params.warn?.(`session file repair skipped: ${reason} (${path.basename(sessionFile)})`);
26
+ return { repaired: false, droppedLines: 0, reason };
27
+ }
28
+ const lines = content.split(/\r?\n/);
29
+ const entries = [];
30
+ let droppedLines = 0;
31
+ for (const line of lines) {
32
+ if (!line.trim()) {
33
+ continue;
34
+ }
35
+ try {
36
+ const entry = JSON.parse(line);
37
+ entries.push(entry);
38
+ }
39
+ catch {
40
+ droppedLines += 1;
41
+ }
42
+ }
43
+ if (entries.length === 0) {
44
+ return { repaired: false, droppedLines, reason: "empty session file" };
45
+ }
46
+ if (!isSessionHeader(entries[0])) {
47
+ params.warn?.(`session file repair skipped: invalid session header (${path.basename(sessionFile)})`);
48
+ return { repaired: false, droppedLines, reason: "invalid session header" };
49
+ }
50
+ if (droppedLines === 0) {
51
+ return { repaired: false, droppedLines: 0 };
52
+ }
53
+ const cleaned = `${entries.map((entry) => JSON.stringify(entry)).join("\n")}\n`;
54
+ const backupPath = `${sessionFile}.bak-${process.pid}-${Date.now()}`;
55
+ const tmpPath = `${sessionFile}.repair-${process.pid}-${Date.now()}.tmp`;
56
+ try {
57
+ const stat = await fs.stat(sessionFile).catch(() => null);
58
+ await fs.writeFile(backupPath, content, "utf-8");
59
+ if (stat) {
60
+ await fs.chmod(backupPath, stat.mode);
61
+ }
62
+ await fs.writeFile(tmpPath, cleaned, "utf-8");
63
+ if (stat) {
64
+ await fs.chmod(tmpPath, stat.mode);
65
+ }
66
+ await fs.rename(tmpPath, sessionFile);
67
+ }
68
+ catch (err) {
69
+ try {
70
+ await fs.unlink(tmpPath);
71
+ }
72
+ catch (cleanupErr) {
73
+ params.warn?.(`session file repair cleanup failed: ${cleanupErr instanceof Error ? cleanupErr.message : "unknown error"} (${path.basename(tmpPath)})`);
74
+ }
75
+ return {
76
+ repaired: false,
77
+ droppedLines,
78
+ reason: `repair failed: ${err instanceof Error ? err.message : "unknown error"}`,
79
+ };
80
+ }
81
+ params.warn?.(`session file repaired: dropped ${droppedLines} malformed line(s) (${path.basename(sessionFile)})`);
82
+ return { repaired: true, droppedLines, backupPath };
83
+ }