@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.
- package/assets/chrome-extension/README.md +3 -3
- package/assets/chrome-extension/background.js +5 -5
- package/assets/chrome-extension/manifest.json +3 -3
- package/assets/chrome-extension/options.html +4 -4
- package/assets/chrome-extension/options.js +1 -1
- package/dist/acp/client.js +3 -3
- package/dist/acp/types.js +1 -1
- package/dist/agents/agent-paths.js +3 -3
- package/dist/agents/auth-profiles/paths.js +3 -3
- package/dist/agents/cli-runner/helpers.js +1 -1
- package/dist/agents/cli-runner.js +2 -2
- package/dist/agents/cloudflare-ai-gateway.js +31 -0
- package/dist/agents/compaction.js +16 -2
- package/dist/agents/context-window-guard.js +13 -10
- package/dist/agents/context.js +4 -4
- package/dist/agents/docs-path.js +1 -1
- package/dist/agents/minimax-vlm.js +1 -1
- package/dist/agents/model-auth.js +12 -1
- package/dist/agents/model-catalog.js +4 -4
- package/dist/agents/model-selection.js +10 -4
- package/dist/agents/models-config.js +3 -3
- package/dist/agents/models-config.providers.js +147 -39
- package/dist/agents/pi-embedded-helpers/openai.js +1 -1
- package/dist/agents/pi-embedded-runner/compact.js +8 -8
- package/dist/agents/pi-embedded-runner/model.js +2 -2
- package/dist/agents/pi-embedded-runner/run/attempt.js +6 -6
- package/dist/agents/pi-embedded-runner/run.js +4 -4
- package/dist/agents/pi-embedded-runner/tool-result-truncation.js +275 -0
- package/dist/agents/pi-embedded-runner/utils.js +1 -1
- package/dist/agents/pi-model-discovery.js +10 -0
- package/dist/agents/pi-tool-definition-adapter.js +50 -9
- package/dist/agents/pi-tools.before-tool-call.js +67 -0
- package/dist/agents/pi-tools.js +10 -5
- package/dist/agents/pi-tools.read.js +2 -2
- package/dist/agents/session-file-repair.js +83 -0
- package/dist/agents/session-transcript-repair.js +68 -0
- package/dist/agents/skills/frontmatter.js +1 -1
- package/dist/agents/skills/workspace.js +2 -2
- package/dist/agents/system-prompt.js +28 -4
- package/dist/agents/together-models.js +127 -0
- package/dist/agents/tool-images.js +1 -1
- package/dist/agents/tool-policy.js +1 -1
- package/dist/agents/tools/browser-tool.js +3 -3
- package/dist/agents/tools/image-tool.js +2 -2
- package/dist/agents/tools/memory-tool.js +93 -5
- package/dist/agents/tools/web-search.js +1 -1
- package/dist/auto-reply/commands-registry.data.js +1 -1
- package/dist/auto-reply/reply/commands-context-report.js +2 -2
- package/dist/auto-reply/reply/commands-session.js +2 -2
- package/dist/auto-reply/reply/get-reply-run.js +14 -4
- package/dist/auto-reply/reply/groups.js +1 -1
- package/dist/auto-reply/reply/inbound-context.js +4 -0
- package/dist/auto-reply/reply/inbound-meta.js +130 -0
- package/dist/auto-reply/reply/untrusted-context.js +15 -0
- package/dist/auto-reply/status.js +1 -1
- package/dist/browser/client-fetch.js +1 -1
- package/dist/browser/config.js +1 -1
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/server-context.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui.js +3 -3
- package/dist/channels/plugins/catalog.js +2 -2
- package/dist/channels/plugins/onboarding/imessage.js +1 -1
- package/dist/channels/plugins/onboarding/signal.js +1 -1
- package/dist/channels/plugins/onboarding/slack.js +4 -4
- package/dist/channels/plugins/onboarding/whatsapp.js +3 -3
- package/dist/channels/plugins/pairing-message.js +1 -1
- package/dist/cli/browser-cli-extension.js +2 -2
- package/dist/cli/docs-cli.js +1 -1
- package/dist/cli/gateway-cli/dev.js +1 -1
- package/dist/cli/memory-cli.js +25 -15
- package/dist/cli/nodes-cli/register.canvas.js +1 -1
- package/dist/cli/plugins-cli.js +1 -1
- package/dist/cli/run-main.js +2 -2
- package/dist/cli/security-cli.js +1 -1
- package/dist/cli/tagline.js +1 -1
- package/dist/cli/update-cli.js +4 -4
- package/dist/cli/webhooks-cli.js +5 -5
- package/dist/commands/agents.commands.add.js +1 -1
- package/dist/commands/auth-choice.apply.api-providers.js +305 -17
- package/dist/commands/auth-choice.apply.js +4 -1
- package/dist/commands/auth-choice.apply.plugin-provider.js +2 -2
- package/dist/commands/auth-choice.apply.xai.js +63 -0
- package/dist/commands/auth-choice.preferred-provider.js +7 -1
- package/dist/commands/configure.wizard.js +1 -1
- package/dist/commands/dashboard.js +1 -1
- package/dist/commands/docs.js +1 -1
- package/dist/commands/doctor-gateway-services.js +3 -3
- package/dist/commands/doctor-update.js +3 -3
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/models/list.probe.js +2 -2
- package/dist/commands/models/list.registry.js +4 -4
- package/dist/commands/models/list.status-command.js +2 -2
- package/dist/commands/onboard-auth.config-core.js +366 -28
- package/dist/commands/onboard-auth.credentials.js +71 -9
- package/dist/commands/onboard-auth.js +3 -3
- package/dist/commands/onboard-auth.models.js +26 -24
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +140 -6
- package/dist/commands/status-all/report-lines.js +1 -1
- package/dist/commands/status.command.js +1 -1
- package/dist/commands/uninstall.js +3 -3
- package/dist/compat/legacy-names.js +1 -1
- package/dist/config/io.js +3 -3
- package/dist/config/schema.js +1 -1
- package/dist/config/types.memory.js +1 -0
- package/dist/config/version.js +4 -4
- package/dist/daemon/constants.js +7 -7
- package/dist/daemon/inspect.js +6 -6
- package/dist/daemon/systemd-unit.js +1 -1
- package/dist/gateway/live-image-probe.js +1 -66
- package/dist/gateway/openai-http.js +2 -2
- package/dist/gateway/openresponses-http.js +4 -4
- package/dist/gateway/server-discovery.js +2 -2
- package/dist/gateway/server-http.js +1 -1
- package/dist/gateway/server.impl.js +2 -2
- package/dist/hooks/frontmatter.js +1 -1
- package/dist/hooks/hooks-status.js +1 -1
- package/dist/hooks/install.js +2 -2
- package/dist/hooks/loader.js +1 -1
- package/dist/hooks/workspace.js +3 -3
- package/dist/index.js +2 -2
- package/dist/infra/bonjour.js +3 -3
- package/dist/infra/path-env.js +3 -3
- package/dist/infra/provider-usage.fetch.minimax.js +1 -1
- package/dist/infra/restart.js +1 -1
- package/dist/infra/tailscale.js +1 -1
- package/dist/macos/relay.js +2 -2
- package/dist/media/input-files.js +1 -1
- package/dist/media/mime.js +4 -0
- package/dist/media/png-encode.js +74 -0
- package/dist/media-understanding/providers/image.js +2 -2
- package/dist/memory/backend-config.js +207 -0
- package/dist/memory/embeddings.js +1 -1
- package/dist/memory/manager.js +1 -0
- package/dist/memory/types.js +1 -0
- package/dist/node-host/runner.js +2 -2
- package/dist/pairing/pairing-messages.js +1 -1
- package/dist/plugins/discovery.js +1 -1
- package/dist/plugins/install.js +2 -2
- package/dist/plugins/update.js +1 -1
- package/dist/security/audit.js +2 -2
- package/dist/shared/text/reasoning-tags.js +52 -7
- package/dist/tailscale/detect.js +146 -0
- package/dist/telegram/bot-message-context.js +1 -1
- package/dist/test-helpers/workspace.js +11 -0
- package/dist/test-utils/channel-plugins.js +82 -0
- package/dist/test-utils/ports.js +73 -0
- package/dist/utils/shell-argv.js +61 -0
- package/dist/utils.js +10 -0
- package/dist/web/qr-image.js +1 -61
- package/dist/wizard/onboarding.finalize.js +7 -7
- package/dist/wizard/onboarding.js +3 -3
- package/docs/RELEASE_WORKFOTS_COMPARISON.md +3 -3
- package/docs/_config.yml +2 -2
- package/docs/_layouts/default.html +9 -9
- package/docs/concepts/typebox.md +1 -1
- package/docs/docs.json +1 -1
- package/docs/northflank.mdx +7 -7
- package/docs/railway.mdx +3 -3
- package/docs/render.mdx +5 -5
- package/docs/start/lore.md +2 -2
- package/extensions/bluebubbles/index.ts +2 -2
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/bluebubbles/src/accounts.ts +8 -8
- package/extensions/bluebubbles/src/actions.test.ts +22 -22
- package/extensions/bluebubbles/src/actions.ts +5 -5
- package/extensions/bluebubbles/src/attachments.ts +2 -2
- package/extensions/bluebubbles/src/channel.ts +16 -16
- package/extensions/bluebubbles/src/chat.ts +2 -2
- package/extensions/bluebubbles/src/media-send.ts +2 -2
- package/extensions/bluebubbles/src/monitor.test.ts +46 -46
- package/extensions/bluebubbles/src/monitor.ts +5 -5
- package/extensions/bluebubbles/src/onboarding.ts +7 -7
- package/extensions/bluebubbles/src/reactions.ts +2 -2
- package/extensions/bluebubbles/src/send.ts +2 -2
- package/extensions/copilot-proxy/README.md +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/index.ts +2 -2
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/diagnostics-otel/src/service.ts +3 -3
- package/extensions/discord/index.ts +2 -2
- package/extensions/discord/package.json +1 -1
- package/extensions/google-antigravity-auth/README.md +1 -1
- package/extensions/google-antigravity-auth/index.ts +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/README.md +1 -1
- package/extensions/google-gemini-cli-auth/oauth.ts +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/index.ts +3 -3
- package/extensions/googlechat/package.json +1 -1
- package/extensions/googlechat/src/accounts.ts +8 -8
- package/extensions/googlechat/src/actions.ts +6 -6
- package/extensions/googlechat/src/channel.ts +21 -21
- package/extensions/googlechat/src/monitor.ts +8 -8
- package/extensions/googlechat/src/onboarding.ts +10 -10
- package/extensions/imessage/index.ts +2 -2
- package/extensions/imessage/package.json +1 -1
- package/extensions/line/index.ts +2 -2
- package/extensions/line/package.json +1 -1
- package/extensions/line/src/card-command.ts +2 -2
- package/extensions/line/src/channel.logout.test.ts +4 -4
- package/extensions/line/src/channel.sendPayload.test.ts +8 -8
- package/extensions/line/src/channel.ts +3 -3
- package/extensions/llm-task/README.md +3 -3
- package/extensions/llm-task/index.ts +2 -2
- package/extensions/llm-task/package.json +1 -1
- package/extensions/llm-task/src/llm-task-tool.ts +4 -4
- package/extensions/lobster/README.md +6 -6
- package/extensions/lobster/index.ts +2 -2
- package/extensions/lobster/src/lobster-tool.test.ts +4 -4
- package/extensions/lobster/src/lobster-tool.ts +2 -2
- package/extensions/matrix/index.ts +2 -2
- package/extensions/matrix/package.json +1 -1
- package/extensions/matrix/src/matrix/client/config.ts +1 -1
- package/extensions/matrix/src/matrix/monitor/handler.ts +1 -1
- package/extensions/matrix/src/onboarding.ts +1 -1
- package/extensions/mattermost/index.ts +2 -2
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mattermost/src/mattermost/accounts.ts +8 -8
- package/extensions/mattermost/src/mattermost/monitor-helpers.ts +5 -5
- package/extensions/mattermost/src/mattermost/monitor.ts +2 -2
- package/extensions/mattermost/src/onboarding-helpers.ts +3 -3
- package/extensions/mattermost/src/onboarding.ts +2 -2
- package/extensions/memory-core/index.ts +2 -2
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/index.ts +3 -3
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/msteams/index.ts +2 -2
- package/extensions/msteams/package.json +1 -1
- package/extensions/msteams/src/channel.directory.test.ts +2 -2
- package/extensions/msteams/src/channel.ts +2 -2
- package/extensions/msteams/src/graph-upload.ts +4 -4
- package/extensions/msteams/src/monitor-handler.ts +2 -2
- package/extensions/msteams/src/monitor.ts +2 -2
- package/extensions/msteams/src/onboarding.ts +9 -9
- package/extensions/msteams/src/reply-dispatcher.ts +2 -2
- package/extensions/msteams/src/send-context.ts +2 -2
- package/extensions/msteams/src/send.ts +4 -4
- package/extensions/nextcloud-talk/index.ts +2 -2
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nextcloud-talk/src/channel.ts +7 -7
- package/extensions/nextcloud-talk/src/inbound.ts +7 -7
- package/extensions/nextcloud-talk/src/onboarding.ts +1 -1
- package/extensions/nostr/README.md +2 -2
- package/extensions/nostr/index.ts +5 -5
- package/extensions/nostr/package.json +1 -1
- package/extensions/nostr/src/types.ts +4 -4
- package/extensions/open-prose/index.ts +2 -2
- package/extensions/qwen-portal-auth/README.md +1 -1
- package/extensions/signal/index.ts +2 -2
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/index.ts +2 -2
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/index.ts +2 -2
- package/extensions/telegram/package.json +1 -1
- package/extensions/telegram/src/channel.ts +2 -2
- package/extensions/tlon/README.md +2 -2
- package/extensions/tlon/index.ts +2 -2
- package/extensions/tlon/package.json +1 -1
- package/extensions/tlon/src/channel.ts +13 -13
- package/extensions/tlon/src/monitor/index.ts +3 -3
- package/extensions/tlon/src/onboarding.ts +3 -3
- package/extensions/tlon/src/types.ts +3 -3
- package/extensions/twitch/README.md +1 -1
- package/extensions/twitch/index.ts +2 -2
- package/extensions/twitch/package.json +1 -1
- package/extensions/twitch/src/config.ts +3 -3
- package/extensions/twitch/src/monitor.ts +3 -3
- package/extensions/twitch/src/onboarding.ts +9 -9
- package/extensions/twitch/src/outbound.test.ts +2 -2
- package/extensions/twitch/src/plugin.test.ts +2 -2
- package/extensions/twitch/src/plugin.ts +8 -8
- package/extensions/twitch/src/send.test.ts +2 -2
- package/extensions/twitch/src/send.ts +4 -4
- package/extensions/twitch/src/token.test.ts +8 -8
- package/extensions/twitch/src/token.ts +3 -3
- package/extensions/twitch/src/twitch-client.ts +3 -3
- package/extensions/twitch/src/types.ts +3 -3
- package/extensions/twitch/src/utils/markdown.ts +1 -1
- package/extensions/voice-call/README.md +3 -3
- package/extensions/voice-call/package.json +1 -1
- package/extensions/voice-call/src/core-bridge.ts +2 -2
- package/extensions/voice-call/src/response-generator.ts +1 -1
- package/extensions/whatsapp/index.ts +2 -2
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/README.md +1 -1
- package/extensions/zalo/index.ts +2 -2
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalo/src/accounts.ts +8 -8
- package/extensions/zalo/src/actions.ts +4 -4
- package/extensions/zalo/src/channel.directory.test.ts +2 -2
- package/extensions/zalo/src/channel.ts +18 -18
- package/extensions/zalo/src/monitor.ts +9 -9
- package/extensions/zalo/src/monitor.webhook.test.ts +2 -2
- package/extensions/zalo/src/onboarding.ts +24 -24
- package/extensions/zalo/src/send.ts +2 -2
- package/extensions/zalouser/README.md +2 -2
- package/extensions/zalouser/index.ts +2 -2
- package/extensions/zalouser/package.json +1 -1
- package/extensions/zalouser/src/accounts.ts +9 -9
- package/extensions/zalouser/src/channel.ts +24 -24
- package/extensions/zalouser/src/monitor.ts +4 -4
- package/extensions/zalouser/src/onboarding.ts +28 -28
- package/package.json +13 -251
- package/skills/nano-banana-pro/scripts/generate_image.py +1 -1
- package/skills/tmux/scripts/find-sessions.sh +1 -1
- package/CHANGELOG.md +0 -102
- package/README-header.png +0 -0
- package/git-hooks/pre-commit +0 -4
- package/scripts/format-staged.js +0 -148
- package/scripts/postinstall.js +0 -300
- 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
|
+
}
|
|
@@ -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 (
|
|
22
|
-
|
|
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 (
|
|
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,
|
|
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
|
+
};
|
package/dist/agents/pi-tools.js
CHANGED
|
@@ -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,
|
|
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
|
|
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 [
|
|
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
|
-
?
|
|
292
|
-
:
|
|
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(
|
|
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
|
|
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
|
+
}
|