@poolzin/pool-bot 2026.2.21 → 2026.2.23
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/CHANGELOG.md +25 -0
- package/dist/agents/api-key-rotation.js +47 -0
- package/dist/agents/apply-patch-update.js +19 -9
- package/dist/agents/apply-patch.js +72 -47
- package/dist/agents/bash-tools.exec.js +141 -559
- package/dist/agents/cli-backends.js +49 -6
- package/dist/agents/cli-runner/helpers.js +69 -152
- package/dist/agents/cli-runner.js +70 -19
- package/dist/agents/identity.js +20 -1
- package/dist/agents/image-sanitization.js +9 -0
- package/dist/agents/live-auth-keys.js +123 -26
- package/dist/agents/live-model-filter.js +13 -4
- package/dist/agents/model-catalog.js +40 -9
- package/dist/agents/model-forward-compat.js +60 -23
- package/dist/agents/model-selection.js +134 -41
- package/dist/agents/pi-auth-json.js +2 -2
- package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
- package/dist/agents/pi-embedded-helpers/errors.js +140 -15
- package/dist/agents/pi-embedded-helpers/images.js +22 -12
- package/dist/agents/pi-embedded-helpers.js +2 -2
- package/dist/agents/pi-embedded-runner/abort.js +10 -3
- package/dist/agents/pi-embedded-runner/compact.js +230 -32
- package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
- package/dist/agents/pi-embedded-runner/google.js +109 -19
- package/dist/agents/pi-embedded-runner/history.js +35 -17
- package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
- package/dist/agents/pi-embedded-runner/run/images.js +81 -55
- package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
- package/dist/agents/pi-embedded-runner/run.js +193 -25
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
- package/dist/agents/pi-embedded-runner/runs.js +17 -8
- package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
- package/dist/agents/pi-embedded-subscribe.js +37 -0
- package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
- package/dist/agents/pi-model-discovery.js +9 -2
- package/dist/agents/pi-tool-definition-adapter.js +60 -8
- package/dist/agents/pi-tools.before-tool-call.js +1 -1
- package/dist/agents/pi-tools.js +113 -94
- package/dist/agents/pi-tools.read.js +337 -38
- package/dist/agents/poolbot-tools.js +14 -5
- package/dist/agents/sandbox/docker.js +10 -5
- package/dist/agents/sandbox/registry.js +96 -46
- package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
- package/dist/agents/sandbox-paths.js +43 -10
- package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
- package/dist/agents/session-tool-result-guard.js +39 -39
- package/dist/agents/session-transcript-repair.js +36 -33
- package/dist/agents/session-write-lock.js +62 -44
- package/dist/agents/skills/frontmatter.js +49 -88
- package/dist/agents/skills/workspace.js +335 -28
- package/dist/agents/subagent-announce.js +508 -174
- package/dist/agents/subagent-registry.js +45 -4
- package/dist/agents/subagent-spawn.js +16 -33
- package/dist/agents/system-prompt-report.js +27 -10
- package/dist/agents/system-prompt.js +26 -32
- package/dist/agents/tool-call-id.js +69 -17
- package/dist/agents/tool-display-common.js +1 -1
- package/dist/agents/tool-images.js +64 -31
- package/dist/agents/tools/canvas-tool.js +17 -11
- package/dist/agents/tools/common.js +37 -19
- package/dist/agents/tools/cron-tool.js +40 -38
- package/dist/agents/tools/gateway.js +70 -2
- package/dist/agents/tools/message-tool.js +181 -40
- package/dist/agents/tools/nodes-tool.js +128 -36
- package/dist/agents/tools/nodes-utils.js +12 -38
- package/dist/agents/tools/session-status-tool.js +24 -71
- package/dist/agents/tools/sessions-helpers.js +38 -210
- package/dist/agents/tools/sessions-spawn-tool.js +28 -198
- package/dist/agents/tools/telegram-actions.js +58 -7
- package/dist/agents/tools/web-fetch-utils.js +112 -7
- package/dist/agents/tools/web-fetch.js +279 -175
- package/dist/agents/tools/web-shared.js +71 -8
- package/dist/agents/usage.js +25 -16
- package/dist/auto-reply/commands-registry.data.js +85 -11
- package/dist/auto-reply/dispatch.js +40 -21
- package/dist/auto-reply/reply/abort.js +102 -33
- package/dist/auto-reply/reply/commands-core.js +82 -33
- package/dist/auto-reply/reply/commands-export-session.js +1 -1
- package/dist/auto-reply/reply/commands-info.js +41 -12
- package/dist/auto-reply/reply/commands-subagents.js +352 -100
- package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
- package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
- package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
- package/dist/auto-reply/reply/inbound-meta.js +12 -1
- package/dist/auto-reply/reply/mentions.js +18 -11
- package/dist/auto-reply/reply/normalize-reply.js +17 -8
- package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
- package/dist/auto-reply/reply/session.js +102 -21
- package/dist/auto-reply/reply/streaming-directives.js +16 -5
- package/dist/auto-reply/status.js +73 -50
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/http-auth.js +1 -1
- package/dist/browser/paths.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/channels/allowlist-match.js +20 -0
- package/dist/channels/allowlists/resolve-utils.js +65 -2
- package/dist/channels/chat-type.js +8 -4
- package/dist/channels/dock.js +127 -35
- package/dist/channels/draft-stream-loop.js +6 -2
- package/dist/channels/plugins/actions/telegram.js +42 -18
- package/dist/channels/plugins/allowlist-match.js +1 -1
- package/dist/channels/plugins/group-mentions.js +51 -41
- package/dist/channels/plugins/message-action-names.js +2 -0
- package/dist/channels/plugins/message-actions.js +24 -5
- package/dist/channels/plugins/normalize/discord.js +26 -4
- package/dist/channels/plugins/normalize/signal.js +35 -22
- package/dist/channels/plugins/onboarding/helpers.js +8 -26
- package/dist/channels/plugins/outbound/imessage.js +15 -14
- package/dist/channels/registry.js +20 -7
- package/dist/cli/acp-cli.js +7 -5
- package/dist/cli/browser-cli-extension.js +25 -12
- package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
- package/dist/cli/browser-cli-state.js +101 -145
- package/dist/cli/command-options.js +28 -0
- package/dist/cli/completion-cli.js +6 -6
- package/dist/cli/cron-cli/register.cron-add.js +25 -1
- package/dist/cli/cron-cli/register.cron-edit.js +44 -0
- package/dist/cli/cron-cli/shared.js +7 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
- package/dist/cli/daemon-cli/lifecycle.js +23 -247
- package/dist/cli/daemon-cli/register-service-commands.js +25 -4
- package/dist/cli/daemon-cli.js +1 -0
- package/dist/cli/devices-cli.js +33 -20
- package/dist/cli/gateway-cli/register.js +37 -105
- package/dist/cli/gateway-cli/run.js +49 -11
- package/dist/cli/nodes-camera.js +59 -4
- package/dist/cli/nodes-cli/register.camera.js +27 -24
- package/dist/cli/nodes-cli/rpc.js +21 -38
- package/dist/cli/qr-cli.js +2 -2
- package/dist/cli/skills-cli.format.js +2 -2
- package/dist/cli/update-cli/progress.js +2 -2
- package/dist/cli/update-cli/restart-helper.js +28 -7
- package/dist/cli/update-cli/shared.js +7 -7
- package/dist/cli/update-cli/status.js +1 -1
- package/dist/cli/update-cli/update-command.js +14 -8
- package/dist/cli/update-cli/wizard.js +2 -2
- package/dist/cli/update-cli.js +21 -1027
- package/dist/commands/auth-choice.apply.anthropic.js +10 -2
- package/dist/commands/channels/add-mutators.js +3 -35
- package/dist/commands/channels/add.js +39 -51
- package/dist/commands/config-validation.js +1 -1
- package/dist/commands/configure.gateway-auth.js +52 -15
- package/dist/commands/configure.gateway.js +84 -40
- package/dist/commands/doctor-completion.js +3 -3
- package/dist/commands/doctor-config-flow.js +536 -16
- package/dist/commands/doctor-gateway-services.js +103 -79
- package/dist/commands/doctor-memory-search.js +9 -9
- package/dist/commands/doctor-platform-notes.js +57 -30
- package/dist/commands/doctor-prompter.js +26 -15
- package/dist/commands/doctor-session-locks.js +1 -1
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/model-picker.js +120 -95
- package/dist/commands/models/set.js +2 -21
- package/dist/commands/models/shared.js +65 -37
- package/dist/commands/onboard-helpers.js +81 -39
- package/dist/commands/openai-codex-oauth.js +1 -1
- package/dist/commands/sessions.js +52 -53
- package/dist/commands/status.summary.js +52 -34
- package/dist/commands/test-wizard-helpers.js +2 -2
- package/dist/config/defaults.js +79 -42
- package/dist/config/group-policy.js +50 -18
- package/dist/config/includes.js +37 -10
- package/dist/config/schema.help.js +5 -4
- package/dist/config/schema.hints.js +2 -2
- package/dist/config/schema.labels.js +1 -0
- package/dist/config/sessions/group.js +12 -11
- package/dist/config/sessions/paths.js +137 -11
- package/dist/config/sessions/store.js +185 -65
- package/dist/config/sessions/types.js +15 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/telegram-custom-commands.js +3 -2
- package/dist/config/types.js +2 -0
- package/dist/config/zod-schema.agent-defaults.js +6 -27
- package/dist/config/zod-schema.agent-runtime.js +171 -79
- package/dist/config/zod-schema.providers-core.js +138 -65
- package/dist/config/zod-schema.session.js +49 -22
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/run.js +224 -57
- package/dist/cron/normalize.js +48 -45
- package/dist/cron/run-log.js +14 -0
- package/dist/cron/service/jobs.js +190 -28
- package/dist/cron/service/normalize.js +29 -11
- package/dist/cron/service/store.js +30 -44
- package/dist/cron/service/timer.js +182 -96
- package/dist/cron/service.js +3 -0
- package/dist/cron/stagger.js +37 -0
- package/dist/daemon/inspect.js +132 -92
- package/dist/daemon/runtime-paths.js +25 -4
- package/dist/daemon/service-audit.js +47 -16
- package/dist/discord/accounts.js +23 -20
- package/dist/discord/monitor/agent-components.js +1115 -219
- package/dist/discord/monitor/allow-list.js +114 -34
- package/dist/discord/monitor/listeners.js +204 -97
- package/dist/discord/monitor/message-handler.js +21 -10
- package/dist/discord/monitor/message-handler.preflight.js +195 -101
- package/dist/discord/monitor/message-handler.process.js +384 -123
- package/dist/discord/monitor/message-utils.js +86 -23
- package/dist/discord/monitor/native-command.js +77 -57
- package/dist/discord/monitor/provider.js +122 -117
- package/dist/discord/monitor/reply-context.js +20 -16
- package/dist/discord/monitor/reply-delivery.js +40 -8
- package/dist/discord/monitor/rest-fetch.js +22 -0
- package/dist/discord/monitor/threading.js +117 -24
- package/dist/discord/send.js +2 -1
- package/dist/discord/send.outbound.js +124 -11
- package/dist/discord/send.shared.js +112 -72
- package/dist/discord/voice-message.js +3 -3
- package/dist/gateway/auth.js +119 -44
- package/dist/gateway/call.js +76 -34
- package/dist/gateway/channel-health-monitor.js +57 -50
- package/dist/gateway/client.js +63 -29
- package/dist/gateway/control-ui-contract.js +1 -1
- package/dist/gateway/gateway-config-prompts.shared.js +2 -2
- package/dist/gateway/net.js +109 -1
- package/dist/gateway/protocol/index.js +5 -8
- package/dist/gateway/protocol/schema/agent.js +19 -1
- package/dist/gateway/protocol/schema/channels.js +21 -0
- package/dist/gateway/protocol/schema/cron.js +43 -30
- package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
- package/dist/gateway/protocol/schema/sessions.js +5 -1
- package/dist/gateway/protocol/schema.js +0 -1
- package/dist/gateway/server/presence-events.js +12 -0
- package/dist/gateway/server/ws-connection/message-handler.js +203 -212
- package/dist/gateway/server/ws-connection.js +58 -21
- package/dist/gateway/server-broadcast.js +18 -13
- package/dist/gateway/server-cron.js +177 -10
- package/dist/gateway/server-methods/agent-job.js +131 -38
- package/dist/gateway/server-methods/send.js +60 -14
- package/dist/gateway/server-methods/sessions.js +160 -96
- package/dist/gateway/server-methods/system.js +5 -7
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +24 -8
- package/dist/gateway/server-node-events.js +278 -68
- package/dist/gateway/session-utils.fs.js +316 -75
- package/dist/gateway/session-utils.js +224 -70
- package/dist/gateway/sessions-patch.js +63 -20
- package/dist/gateway/test-temp-config.js +1 -1
- package/dist/gateway/tools-invoke-http.js +118 -70
- package/dist/gateway/ws-log.js +135 -107
- package/dist/hooks/frontmatter.js +36 -82
- package/dist/hooks/install.js +149 -139
- package/dist/hooks/internal-hooks.js +29 -4
- package/dist/hooks/plugin-hooks.js +2 -1
- package/dist/imessage/monitor/deliver.js +10 -4
- package/dist/imessage/monitor/monitor-provider.js +138 -375
- package/dist/imessage/monitor/runtime.js +4 -8
- package/dist/imessage/send.js +65 -19
- package/dist/infra/exec-approvals-allowlist.js +7 -0
- package/dist/infra/exec-approvals.js +35 -920
- package/dist/infra/exec-safe-bin-trust.js +64 -0
- package/dist/infra/heartbeat-runner.js +207 -134
- package/dist/infra/heartbeat-wake.js +183 -22
- package/dist/infra/install-source-utils.js +47 -0
- package/dist/infra/net/ssrf.js +170 -36
- package/dist/infra/outbound/deliver.js +224 -58
- package/dist/infra/outbound/message-action-spec.js +12 -5
- package/dist/infra/outbound/outbound-session.js +27 -25
- package/dist/infra/poolbot-root.js +32 -22
- package/dist/infra/ports.js +14 -11
- package/dist/infra/skills-remote.js +48 -37
- package/dist/infra/system-events.js +25 -11
- package/dist/infra/system-presence.js +26 -33
- package/dist/infra/tmp-poolbot-dir.js +81 -2
- package/dist/infra/wsl.js +37 -1
- package/dist/line/bot-message-context.js +163 -191
- package/dist/logging/subsystem.js +59 -22
- package/dist/markdown/ir.js +124 -50
- package/dist/media/store.js +1 -1
- package/dist/media-understanding/runner.entries.js +42 -25
- package/dist/media-understanding/runner.js +53 -488
- package/dist/memory/embeddings-gemini.js +53 -38
- package/dist/memory/manager-embedding-ops.js +48 -69
- package/dist/pairing/pairing-store.js +178 -119
- package/dist/plugin-sdk/index.js +34 -6
- package/dist/plugins/hooks.js +135 -14
- package/dist/plugins/install.js +190 -152
- package/dist/polls.js +11 -0
- package/dist/routing/resolve-route.js +190 -56
- package/dist/routing/session-key.js +38 -22
- package/dist/runtime.js +35 -9
- package/dist/security/audit-channel.js +1 -1
- package/dist/sessions/session-key-utils.js +29 -11
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-types.js +1 -0
- package/dist/shared/string-normalization.js +15 -0
- package/dist/signal/monitor/event-handler.js +68 -36
- package/dist/signal/send.js +29 -37
- package/dist/slack/monitor/allow-list.js +10 -11
- package/dist/slack/monitor/commands.js +14 -3
- package/dist/slack/monitor/events/interactions.js +4 -4
- package/dist/slack/monitor/media.js +224 -16
- package/dist/slack/monitor/message-handler/dispatch.js +247 -13
- package/dist/slack/monitor/message-handler/prepare.js +128 -45
- package/dist/slack/monitor/slash.js +357 -144
- package/dist/slack/streaming.js +77 -0
- package/dist/telegram/accounts.js +40 -13
- package/dist/telegram/allowed-updates.js +3 -0
- package/dist/telegram/bot/delivery.js +129 -66
- package/dist/telegram/bot/helpers.js +136 -122
- package/dist/telegram/bot-handlers.js +600 -339
- package/dist/telegram/bot-message-context.js +115 -73
- package/dist/telegram/bot-message-dispatch.js +235 -104
- package/dist/telegram/bot-native-command-menu.js +3 -1
- package/dist/telegram/bot-native-commands.js +213 -193
- package/dist/telegram/bot.js +24 -132
- package/dist/telegram/draft-stream.js +84 -75
- package/dist/telegram/format.js +150 -6
- package/dist/telegram/send.js +415 -255
- package/dist/telegram/targets.js +21 -2
- package/dist/telegram/update-offset-store.js +19 -3
- package/dist/terminal/restore.js +5 -2
- package/dist/test-utils/fetch-mock.js +5 -0
- package/dist/version.js +18 -5
- package/dist/web/auto-reply/monitor/broadcast.js +7 -3
- package/dist/web/auto-reply/monitor/on-message.js +6 -3
- package/dist/web/inbound/media.js +34 -8
- package/dist/web/inbound/monitor.js +34 -17
- package/dist/web/inbound/send-api.js +18 -17
- package/dist/web/outbound.js +12 -5
- package/dist/wizard/clack-prompter.js +40 -7
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/device-pair/index.ts +2 -2
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/irc/src/accounts.ts +1 -1
- package/extensions/irc/src/onboarding.ts +4 -4
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +10 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +10 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +10 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +10 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +10 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +10 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +10 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/skills/apple-reminders/SKILL.md +100 -49
- package/skills/coding-agent/SKILL.md +34 -28
- package/skills/github/SKILL.md +131 -16
- package/skills/imsg/SKILL.md +112 -15
- package/skills/openhue/SKILL.md +101 -19
- package/skills/tmux/SKILL.md +111 -79
- package/skills/weather/SKILL.md +88 -25
- package/dist/agents/openclaw-tools.js +0 -151
- package/dist/agents/tool-security.js +0 -96
- package/dist/gateway/url-validation.js +0 -94
- package/dist/infra/openclaw-root.js +0 -109
- package/dist/infra/tmp-openclaw-dir.js +0 -81
- package/dist/media/path-sanitization.js +0 -78
|
@@ -1,46 +1,172 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { expandHomePrefix, resolveRequiredHomeDir } from "../../infra/home-dir.js";
|
|
3
4
|
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../routing/session-key.js";
|
|
4
5
|
import { resolveStateDir } from "../paths.js";
|
|
5
|
-
function resolveAgentSessionsDir(agentId, env = process.env, homedir = os.homedir) {
|
|
6
|
+
function resolveAgentSessionsDir(agentId, env = process.env, homedir = () => resolveRequiredHomeDir(env, os.homedir)) {
|
|
6
7
|
const root = resolveStateDir(env, homedir);
|
|
7
8
|
const id = normalizeAgentId(agentId ?? DEFAULT_AGENT_ID);
|
|
8
9
|
return path.join(root, "agents", id, "sessions");
|
|
9
10
|
}
|
|
10
|
-
export function resolveSessionTranscriptsDir(env = process.env, homedir = os.homedir) {
|
|
11
|
+
export function resolveSessionTranscriptsDir(env = process.env, homedir = () => resolveRequiredHomeDir(env, os.homedir)) {
|
|
11
12
|
return resolveAgentSessionsDir(DEFAULT_AGENT_ID, env, homedir);
|
|
12
13
|
}
|
|
13
|
-
export function resolveSessionTranscriptsDirForAgent(agentId, env = process.env, homedir = os.homedir) {
|
|
14
|
+
export function resolveSessionTranscriptsDirForAgent(agentId, env = process.env, homedir = () => resolveRequiredHomeDir(env, os.homedir)) {
|
|
14
15
|
return resolveAgentSessionsDir(agentId, env, homedir);
|
|
15
16
|
}
|
|
16
17
|
export function resolveDefaultSessionStorePath(agentId) {
|
|
17
18
|
return path.join(resolveAgentSessionsDir(agentId), "sessions.json");
|
|
18
19
|
}
|
|
19
|
-
export function
|
|
20
|
+
export function resolveSessionFilePathOptions(params) {
|
|
21
|
+
const agentId = params.agentId?.trim();
|
|
22
|
+
const storePath = params.storePath?.trim();
|
|
23
|
+
if (storePath) {
|
|
24
|
+
const sessionsDir = path.dirname(path.resolve(storePath));
|
|
25
|
+
return agentId ? { sessionsDir, agentId } : { sessionsDir };
|
|
26
|
+
}
|
|
27
|
+
if (agentId) {
|
|
28
|
+
return { agentId };
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
export const SAFE_SESSION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
33
|
+
export function validateSessionId(sessionId) {
|
|
34
|
+
const trimmed = sessionId.trim();
|
|
35
|
+
if (!SAFE_SESSION_ID_RE.test(trimmed)) {
|
|
36
|
+
throw new Error(`Invalid session ID: ${sessionId}`);
|
|
37
|
+
}
|
|
38
|
+
return trimmed;
|
|
39
|
+
}
|
|
40
|
+
function resolveSessionsDir(opts) {
|
|
41
|
+
const sessionsDir = opts?.sessionsDir?.trim();
|
|
42
|
+
if (sessionsDir) {
|
|
43
|
+
return path.resolve(sessionsDir);
|
|
44
|
+
}
|
|
45
|
+
return resolveAgentSessionsDir(opts?.agentId);
|
|
46
|
+
}
|
|
47
|
+
function resolvePathFromAgentSessionsDir(agentSessionsDir, candidateAbsPath) {
|
|
48
|
+
const agentBase = path.resolve(agentSessionsDir);
|
|
49
|
+
const relative = path.relative(agentBase, candidateAbsPath);
|
|
50
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return path.resolve(agentBase, relative);
|
|
54
|
+
}
|
|
55
|
+
function resolveSiblingAgentSessionsDir(baseSessionsDir, agentId) {
|
|
56
|
+
const resolvedBase = path.resolve(baseSessionsDir);
|
|
57
|
+
if (path.basename(resolvedBase) !== "sessions") {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
const baseAgentDir = path.dirname(resolvedBase);
|
|
61
|
+
const baseAgentsDir = path.dirname(baseAgentDir);
|
|
62
|
+
if (path.basename(baseAgentsDir) !== "agents") {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const rootDir = path.dirname(baseAgentsDir);
|
|
66
|
+
return path.join(rootDir, "agents", normalizeAgentId(agentId), "sessions");
|
|
67
|
+
}
|
|
68
|
+
function extractAgentIdFromAbsoluteSessionPath(candidateAbsPath) {
|
|
69
|
+
const normalized = path.normalize(path.resolve(candidateAbsPath));
|
|
70
|
+
const parts = normalized.split(path.sep).filter(Boolean);
|
|
71
|
+
const sessionsIndex = parts.lastIndexOf("sessions");
|
|
72
|
+
if (sessionsIndex < 2 || parts[sessionsIndex - 2] !== "agents") {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
const agentId = parts[sessionsIndex - 1];
|
|
76
|
+
return agentId || undefined;
|
|
77
|
+
}
|
|
78
|
+
function resolvePathWithinSessionsDir(sessionsDir, candidate, opts) {
|
|
79
|
+
const trimmed = candidate.trim();
|
|
80
|
+
if (!trimmed) {
|
|
81
|
+
throw new Error("Session file path must not be empty");
|
|
82
|
+
}
|
|
83
|
+
const resolvedBase = path.resolve(sessionsDir);
|
|
84
|
+
// Normalize absolute paths that are within the sessions directory.
|
|
85
|
+
// Older versions stored absolute sessionFile paths in sessions.json;
|
|
86
|
+
// convert them to relative so the containment check passes.
|
|
87
|
+
const normalized = path.isAbsolute(trimmed) ? path.relative(resolvedBase, trimmed) : trimmed;
|
|
88
|
+
if (normalized.startsWith("..") && path.isAbsolute(trimmed)) {
|
|
89
|
+
const tryAgentFallback = (agentId) => {
|
|
90
|
+
const normalizedAgentId = normalizeAgentId(agentId);
|
|
91
|
+
const siblingSessionsDir = resolveSiblingAgentSessionsDir(resolvedBase, normalizedAgentId);
|
|
92
|
+
if (siblingSessionsDir) {
|
|
93
|
+
const siblingResolved = resolvePathFromAgentSessionsDir(siblingSessionsDir, trimmed);
|
|
94
|
+
if (siblingResolved) {
|
|
95
|
+
return siblingResolved;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return resolvePathFromAgentSessionsDir(resolveAgentSessionsDir(normalizedAgentId), trimmed);
|
|
99
|
+
};
|
|
100
|
+
const explicitAgentId = opts?.agentId?.trim();
|
|
101
|
+
if (explicitAgentId) {
|
|
102
|
+
const resolvedFromAgent = tryAgentFallback(explicitAgentId);
|
|
103
|
+
if (resolvedFromAgent) {
|
|
104
|
+
return resolvedFromAgent;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const extractedAgentId = extractAgentIdFromAbsoluteSessionPath(trimmed);
|
|
108
|
+
if (extractedAgentId) {
|
|
109
|
+
const resolvedFromPath = tryAgentFallback(extractedAgentId);
|
|
110
|
+
if (resolvedFromPath) {
|
|
111
|
+
return resolvedFromPath;
|
|
112
|
+
}
|
|
113
|
+
// The path structurally matches .../agents/<agentId>/sessions/...
|
|
114
|
+
// Accept it even if the root directory differs from the current env
|
|
115
|
+
// (e.g., POOLBOT_STATE_DIR changed between session creation and resolution).
|
|
116
|
+
// The structural pattern provides sufficient containment guarantees.
|
|
117
|
+
return path.resolve(trimmed);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (!normalized || normalized.startsWith("..") || path.isAbsolute(normalized)) {
|
|
121
|
+
throw new Error("Session file path must be within sessions directory");
|
|
122
|
+
}
|
|
123
|
+
return path.resolve(resolvedBase, normalized);
|
|
124
|
+
}
|
|
125
|
+
export function resolveSessionTranscriptPathInDir(sessionId, sessionsDir, topicId) {
|
|
126
|
+
const safeSessionId = validateSessionId(sessionId);
|
|
20
127
|
const safeTopicId = typeof topicId === "string"
|
|
21
128
|
? encodeURIComponent(topicId)
|
|
22
129
|
: typeof topicId === "number"
|
|
23
130
|
? String(topicId)
|
|
24
131
|
: undefined;
|
|
25
|
-
const fileName = safeTopicId !== undefined
|
|
26
|
-
|
|
132
|
+
const fileName = safeTopicId !== undefined
|
|
133
|
+
? `${safeSessionId}-topic-${safeTopicId}.jsonl`
|
|
134
|
+
: `${safeSessionId}.jsonl`;
|
|
135
|
+
return resolvePathWithinSessionsDir(sessionsDir, fileName);
|
|
136
|
+
}
|
|
137
|
+
export function resolveSessionTranscriptPath(sessionId, agentId, topicId) {
|
|
138
|
+
return resolveSessionTranscriptPathInDir(sessionId, resolveAgentSessionsDir(agentId), topicId);
|
|
27
139
|
}
|
|
28
140
|
export function resolveSessionFilePath(sessionId, entry, opts) {
|
|
141
|
+
const sessionsDir = resolveSessionsDir(opts);
|
|
29
142
|
const candidate = entry?.sessionFile?.trim();
|
|
30
|
-
|
|
143
|
+
if (candidate) {
|
|
144
|
+
return resolvePathWithinSessionsDir(sessionsDir, candidate, { agentId: opts?.agentId });
|
|
145
|
+
}
|
|
146
|
+
return resolveSessionTranscriptPathInDir(sessionId, sessionsDir);
|
|
31
147
|
}
|
|
32
148
|
export function resolveStorePath(store, opts) {
|
|
33
149
|
const agentId = normalizeAgentId(opts?.agentId ?? DEFAULT_AGENT_ID);
|
|
34
|
-
if (!store)
|
|
150
|
+
if (!store) {
|
|
35
151
|
return resolveDefaultSessionStorePath(agentId);
|
|
152
|
+
}
|
|
36
153
|
if (store.includes("{agentId}")) {
|
|
37
154
|
const expanded = store.replaceAll("{agentId}", agentId);
|
|
38
155
|
if (expanded.startsWith("~")) {
|
|
39
|
-
return path.resolve(expanded
|
|
156
|
+
return path.resolve(expandHomePrefix(expanded, {
|
|
157
|
+
home: resolveRequiredHomeDir(process.env, os.homedir),
|
|
158
|
+
env: process.env,
|
|
159
|
+
homedir: os.homedir,
|
|
160
|
+
}));
|
|
40
161
|
}
|
|
41
162
|
return path.resolve(expanded);
|
|
42
163
|
}
|
|
43
|
-
if (store.startsWith("~"))
|
|
44
|
-
return path.resolve(store
|
|
164
|
+
if (store.startsWith("~")) {
|
|
165
|
+
return path.resolve(expandHomePrefix(store, {
|
|
166
|
+
home: resolveRequiredHomeDir(process.env, os.homedir),
|
|
167
|
+
env: process.env,
|
|
168
|
+
homedir: os.homedir,
|
|
169
|
+
}));
|
|
170
|
+
}
|
|
45
171
|
return path.resolve(store);
|
|
46
172
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import JSON5 from "json5";
|
|
2
1
|
import crypto from "node:crypto";
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
4
|
+
import { acquireSessionWriteLock } from "../../agents/session-write-lock.js";
|
|
5
5
|
import { parseByteSize } from "../../cli/parse-bytes.js";
|
|
6
6
|
import { parseDurationMs } from "../../cli/parse-duration.js";
|
|
7
|
+
import { archiveSessionTranscripts, cleanupArchivedSessionTranscripts, } from "../../gateway/session-utils.fs.js";
|
|
7
8
|
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
|
8
9
|
import { deliveryContextFromSession, mergeDeliveryContext, normalizeDeliveryContext, normalizeSessionDeliveryFields, } from "../../utils/delivery-context.js";
|
|
9
10
|
import { getFileMtimeMs, isCacheEnabled, resolveCacheTtlMs } from "../cache-utils.js";
|
|
@@ -18,7 +19,7 @@ function isSessionStoreRecord(value) {
|
|
|
18
19
|
}
|
|
19
20
|
function getSessionStoreTtl() {
|
|
20
21
|
return resolveCacheTtlMs({
|
|
21
|
-
envValue: process.env.POOLBOT_SESSION_CACHE_TTL_MS
|
|
22
|
+
envValue: process.env.POOLBOT_SESSION_CACHE_TTL_MS,
|
|
22
23
|
defaultTtlMs: DEFAULT_SESSION_STORE_TTL_MS,
|
|
23
24
|
});
|
|
24
25
|
}
|
|
@@ -84,6 +85,19 @@ function normalizeSessionStore(store) {
|
|
|
84
85
|
}
|
|
85
86
|
export function clearSessionStoreCacheForTest() {
|
|
86
87
|
SESSION_STORE_CACHE.clear();
|
|
88
|
+
for (const queue of LOCK_QUEUES.values()) {
|
|
89
|
+
for (const task of queue.pending) {
|
|
90
|
+
task.reject(new Error("session store queue cleared for test"));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
LOCK_QUEUES.clear();
|
|
94
|
+
}
|
|
95
|
+
/** Expose lock queue size for tests. */
|
|
96
|
+
export function getSessionStoreLockQueueSizeForTest() {
|
|
97
|
+
return LOCK_QUEUES.size;
|
|
98
|
+
}
|
|
99
|
+
export async function withSessionStoreLockForTest(storePath, fn, opts = {}) {
|
|
100
|
+
return await withSessionStoreLock(storePath, fn, opts);
|
|
87
101
|
}
|
|
88
102
|
export function loadSessionStore(storePath, opts = {}) {
|
|
89
103
|
// Check cache first if enabled
|
|
@@ -98,19 +112,39 @@ export function loadSessionStore(storePath, opts = {}) {
|
|
|
98
112
|
invalidateSessionStoreCache(storePath);
|
|
99
113
|
}
|
|
100
114
|
}
|
|
101
|
-
// Cache miss or disabled - load from disk
|
|
115
|
+
// Cache miss or disabled - load from disk.
|
|
116
|
+
// Retry up to 3 times when the file is empty or unparseable. On Windows the
|
|
117
|
+
// temp-file + rename write is not fully atomic: a concurrent reader can briefly
|
|
118
|
+
// observe a 0-byte file (between truncate and write) or a stale/locked state.
|
|
119
|
+
// A short synchronous backoff (50 ms via `Atomics.wait`) is enough for the
|
|
120
|
+
// writer to finish.
|
|
102
121
|
let store = {};
|
|
103
122
|
let mtimeMs = getFileMtimeMs(storePath);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
const maxReadAttempts = process.platform === "win32" ? 3 : 1;
|
|
124
|
+
const retryBuf = maxReadAttempts > 1 ? new Int32Array(new SharedArrayBuffer(4)) : undefined;
|
|
125
|
+
for (let attempt = 0; attempt < maxReadAttempts; attempt++) {
|
|
126
|
+
try {
|
|
127
|
+
const raw = fs.readFileSync(storePath, "utf-8");
|
|
128
|
+
if (raw.length === 0 && attempt < maxReadAttempts - 1) {
|
|
129
|
+
// File is empty — likely caught mid-write; retry after a brief pause.
|
|
130
|
+
Atomics.wait(retryBuf, 0, 0, 50);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const parsed = JSON.parse(raw);
|
|
134
|
+
if (isSessionStoreRecord(parsed)) {
|
|
135
|
+
store = parsed;
|
|
136
|
+
}
|
|
137
|
+
mtimeMs = getFileMtimeMs(storePath) ?? mtimeMs;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// File missing, locked, or transiently corrupt — retry on Windows.
|
|
142
|
+
if (attempt < maxReadAttempts - 1) {
|
|
143
|
+
Atomics.wait(retryBuf, 0, 0, 50);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
// Final attempt failed; proceed with an empty store.
|
|
109
147
|
}
|
|
110
|
-
mtimeMs = getFileMtimeMs(storePath) ?? mtimeMs;
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// ignore missing/invalid store; we'll recreate it
|
|
114
148
|
}
|
|
115
149
|
// Best-effort migration: message provider → channel naming.
|
|
116
150
|
for (const entry of Object.values(store)) {
|
|
@@ -216,6 +250,7 @@ export function pruneStaleEntries(store, overrideMaxAgeMs, opts = {}) {
|
|
|
216
250
|
let pruned = 0;
|
|
217
251
|
for (const [key, entry] of Object.entries(store)) {
|
|
218
252
|
if (entry?.updatedAt != null && entry.updatedAt < cutoffMs) {
|
|
253
|
+
opts.onPruned?.({ key, entry });
|
|
219
254
|
delete store[key];
|
|
220
255
|
pruned++;
|
|
221
256
|
}
|
|
@@ -376,19 +411,71 @@ async function saveSessionStoreUnlocked(storePath, store, opts) {
|
|
|
376
411
|
}
|
|
377
412
|
else {
|
|
378
413
|
// Prune stale entries and cap total count before serializing.
|
|
379
|
-
|
|
414
|
+
const prunedSessionFiles = new Map();
|
|
415
|
+
pruneStaleEntries(store, maintenance.pruneAfterMs, {
|
|
416
|
+
onPruned: ({ entry }) => {
|
|
417
|
+
if (!prunedSessionFiles.has(entry.sessionId) || entry.sessionFile) {
|
|
418
|
+
prunedSessionFiles.set(entry.sessionId, entry.sessionFile);
|
|
419
|
+
}
|
|
420
|
+
},
|
|
421
|
+
});
|
|
380
422
|
capEntryCount(store, maintenance.maxEntries);
|
|
423
|
+
const archivedDirs = new Set();
|
|
424
|
+
for (const [sessionId, sessionFile] of prunedSessionFiles) {
|
|
425
|
+
const archived = archiveSessionTranscripts({
|
|
426
|
+
sessionId,
|
|
427
|
+
storePath,
|
|
428
|
+
sessionFile,
|
|
429
|
+
reason: "deleted",
|
|
430
|
+
});
|
|
431
|
+
for (const archivedPath of archived) {
|
|
432
|
+
archivedDirs.add(path.dirname(archivedPath));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
if (archivedDirs.size > 0) {
|
|
436
|
+
await cleanupArchivedSessionTranscripts({
|
|
437
|
+
directories: [...archivedDirs],
|
|
438
|
+
olderThanMs: maintenance.pruneAfterMs,
|
|
439
|
+
reason: "deleted",
|
|
440
|
+
});
|
|
441
|
+
}
|
|
381
442
|
// Rotate the on-disk file if it exceeds the size threshold.
|
|
382
443
|
await rotateSessionFile(storePath, maintenance.rotateBytes);
|
|
383
444
|
}
|
|
384
445
|
}
|
|
385
446
|
await fs.promises.mkdir(path.dirname(storePath), { recursive: true });
|
|
386
447
|
const json = JSON.stringify(store, null, 2);
|
|
387
|
-
// Windows:
|
|
388
|
-
//
|
|
448
|
+
// Windows: use temp-file + rename for atomic writes, same as other platforms.
|
|
449
|
+
// Direct `writeFile` truncates the target to 0 bytes before writing, which
|
|
450
|
+
// allows concurrent `readFileSync` calls (from unlocked `loadSessionStore`)
|
|
451
|
+
// to observe an empty file and lose the session store contents.
|
|
389
452
|
if (process.platform === "win32") {
|
|
453
|
+
const tmp = `${storePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
390
454
|
try {
|
|
391
|
-
await fs.promises.writeFile(
|
|
455
|
+
await fs.promises.writeFile(tmp, json, "utf-8");
|
|
456
|
+
// Retry rename up to 5 times with increasing backoff — rename can fail
|
|
457
|
+
// on Windows when the target is locked by a concurrent reader. We do
|
|
458
|
+
// NOT fall back to writeFile or copyFile because both use CREATE_ALWAYS
|
|
459
|
+
// on Windows, which truncates the target to 0 bytes before writing —
|
|
460
|
+
// reintroducing the exact race this fix addresses. If all attempts
|
|
461
|
+
// fail, the temp file is cleaned up and the next save cycle (which is
|
|
462
|
+
// serialized by the write lock) will succeed.
|
|
463
|
+
for (let i = 0; i < 5; i++) {
|
|
464
|
+
try {
|
|
465
|
+
await fs.promises.rename(tmp, storePath);
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
catch {
|
|
469
|
+
if (i < 4) {
|
|
470
|
+
await new Promise((r) => setTimeout(r, 50 * (i + 1)));
|
|
471
|
+
}
|
|
472
|
+
// Final attempt failed — skip this save. The write lock ensures
|
|
473
|
+
// the next save will retry with fresh data. Log for diagnostics.
|
|
474
|
+
if (i === 4) {
|
|
475
|
+
console.warn(`[session-store] rename failed after 5 attempts: ${storePath}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
392
479
|
}
|
|
393
480
|
catch (err) {
|
|
394
481
|
const code = err && typeof err === "object" && "code" in err
|
|
@@ -399,6 +486,9 @@ async function saveSessionStoreUnlocked(storePath, store, opts) {
|
|
|
399
486
|
}
|
|
400
487
|
throw err;
|
|
401
488
|
}
|
|
489
|
+
finally {
|
|
490
|
+
await fs.promises.rm(tmp, { force: true }).catch(() => undefined);
|
|
491
|
+
}
|
|
402
492
|
return;
|
|
403
493
|
}
|
|
404
494
|
const tmp = `${storePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
@@ -451,67 +541,97 @@ export async function updateSessionStore(storePath, mutator, opts) {
|
|
|
451
541
|
return result;
|
|
452
542
|
});
|
|
453
543
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
544
|
+
const LOCK_QUEUES = new Map();
|
|
545
|
+
function lockTimeoutError(storePath) {
|
|
546
|
+
return new Error(`timeout waiting for session store lock: ${storePath}`);
|
|
547
|
+
}
|
|
548
|
+
function getOrCreateLockQueue(storePath) {
|
|
549
|
+
const existing = LOCK_QUEUES.get(storePath);
|
|
550
|
+
if (existing) {
|
|
551
|
+
return existing;
|
|
552
|
+
}
|
|
553
|
+
const created = { running: false, pending: [] };
|
|
554
|
+
LOCK_QUEUES.set(storePath, created);
|
|
555
|
+
return created;
|
|
556
|
+
}
|
|
557
|
+
async function drainSessionStoreLockQueue(storePath) {
|
|
558
|
+
const queue = LOCK_QUEUES.get(storePath);
|
|
559
|
+
if (!queue || queue.running) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
queue.running = true;
|
|
563
|
+
try {
|
|
564
|
+
while (queue.pending.length > 0) {
|
|
565
|
+
const task = queue.pending.shift();
|
|
566
|
+
if (!task) {
|
|
567
|
+
continue;
|
|
469
568
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
catch (err) {
|
|
474
|
-
const code = err && typeof err === "object" && "code" in err
|
|
475
|
-
? String(err.code)
|
|
476
|
-
: null;
|
|
477
|
-
if (code === "ENOENT") {
|
|
478
|
-
// Store directory may be deleted/recreated in tests while writes are in-flight.
|
|
479
|
-
// Best-effort: recreate the parent dir and retry until timeout.
|
|
480
|
-
await fs.promises
|
|
481
|
-
.mkdir(path.dirname(storePath), { recursive: true })
|
|
482
|
-
.catch(() => undefined);
|
|
483
|
-
await new Promise((r) => setTimeout(r, pollIntervalMs));
|
|
569
|
+
const remainingTimeoutMs = task.timeoutMs ?? Number.POSITIVE_INFINITY;
|
|
570
|
+
if (task.timeoutMs != null && remainingTimeoutMs <= 0) {
|
|
571
|
+
task.reject(lockTimeoutError(storePath));
|
|
484
572
|
continue;
|
|
485
573
|
}
|
|
486
|
-
|
|
487
|
-
|
|
574
|
+
let lock;
|
|
575
|
+
let result;
|
|
576
|
+
let failed;
|
|
577
|
+
let hasFailure = false;
|
|
578
|
+
try {
|
|
579
|
+
lock = await acquireSessionWriteLock({
|
|
580
|
+
sessionFile: storePath,
|
|
581
|
+
timeoutMs: remainingTimeoutMs,
|
|
582
|
+
staleMs: task.staleMs,
|
|
583
|
+
});
|
|
584
|
+
result = await task.fn();
|
|
488
585
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
586
|
+
catch (err) {
|
|
587
|
+
hasFailure = true;
|
|
588
|
+
failed = err;
|
|
492
589
|
}
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const st = await fs.promises.stat(lockPath);
|
|
496
|
-
const ageMs = now - st.mtimeMs;
|
|
497
|
-
if (ageMs > staleMs) {
|
|
498
|
-
await fs.promises.unlink(lockPath);
|
|
499
|
-
continue;
|
|
500
|
-
}
|
|
590
|
+
finally {
|
|
591
|
+
await lock?.release().catch(() => undefined);
|
|
501
592
|
}
|
|
502
|
-
|
|
503
|
-
|
|
593
|
+
if (hasFailure) {
|
|
594
|
+
task.reject(failed);
|
|
595
|
+
continue;
|
|
504
596
|
}
|
|
505
|
-
|
|
597
|
+
task.resolve(result);
|
|
506
598
|
}
|
|
507
599
|
}
|
|
508
|
-
try {
|
|
509
|
-
return await fn();
|
|
510
|
-
}
|
|
511
600
|
finally {
|
|
512
|
-
|
|
601
|
+
queue.running = false;
|
|
602
|
+
if (queue.pending.length === 0) {
|
|
603
|
+
LOCK_QUEUES.delete(storePath);
|
|
604
|
+
}
|
|
605
|
+
else {
|
|
606
|
+
queueMicrotask(() => {
|
|
607
|
+
void drainSessionStoreLockQueue(storePath);
|
|
608
|
+
});
|
|
609
|
+
}
|
|
513
610
|
}
|
|
514
611
|
}
|
|
612
|
+
async function withSessionStoreLock(storePath, fn, opts = {}) {
|
|
613
|
+
if (!storePath || typeof storePath !== "string") {
|
|
614
|
+
throw new Error(`withSessionStoreLock: storePath must be a non-empty string, got ${JSON.stringify(storePath)}`);
|
|
615
|
+
}
|
|
616
|
+
const timeoutMs = opts.timeoutMs ?? 10_000;
|
|
617
|
+
const staleMs = opts.staleMs ?? 30_000;
|
|
618
|
+
// `pollIntervalMs` is retained for API compatibility with older lock options.
|
|
619
|
+
void opts.pollIntervalMs;
|
|
620
|
+
const hasTimeout = timeoutMs > 0 && Number.isFinite(timeoutMs);
|
|
621
|
+
const queue = getOrCreateLockQueue(storePath);
|
|
622
|
+
const promise = new Promise((resolve, reject) => {
|
|
623
|
+
const task = {
|
|
624
|
+
fn: async () => await fn(),
|
|
625
|
+
resolve: (value) => resolve(value),
|
|
626
|
+
reject,
|
|
627
|
+
timeoutMs: hasTimeout ? timeoutMs : undefined,
|
|
628
|
+
staleMs,
|
|
629
|
+
};
|
|
630
|
+
queue.pending.push(task);
|
|
631
|
+
void drainSessionStoreLockQueue(storePath);
|
|
632
|
+
});
|
|
633
|
+
return await promise;
|
|
634
|
+
}
|
|
515
635
|
export async function updateSessionStoreEntry(params) {
|
|
516
636
|
const { storePath, sessionKey, update } = params;
|
|
517
637
|
return await withSessionStoreLock(storePath, async () => {
|
|
@@ -2,10 +2,24 @@ import crypto from "node:crypto";
|
|
|
2
2
|
export function mergeSessionEntry(existing, patch) {
|
|
3
3
|
const sessionId = patch.sessionId ?? existing?.sessionId ?? crypto.randomUUID();
|
|
4
4
|
const updatedAt = Math.max(existing?.updatedAt ?? 0, patch.updatedAt ?? 0, Date.now());
|
|
5
|
-
if (!existing)
|
|
5
|
+
if (!existing) {
|
|
6
6
|
return { ...patch, sessionId, updatedAt };
|
|
7
|
+
}
|
|
7
8
|
return { ...existing, ...patch, sessionId, updatedAt };
|
|
8
9
|
}
|
|
10
|
+
export function resolveFreshSessionTotalTokens(entry) {
|
|
11
|
+
const total = entry?.totalTokens;
|
|
12
|
+
if (typeof total !== "number" || !Number.isFinite(total) || total < 0) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
if (entry?.totalTokensFresh === false) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
return total;
|
|
19
|
+
}
|
|
20
|
+
export function isSessionTotalTokensFresh(entry) {
|
|
21
|
+
return resolveFreshSessionTotalTokens(entry) !== undefined;
|
|
22
|
+
}
|
|
9
23
|
export const DEFAULT_RESET_TRIGGER = "/new";
|
|
10
24
|
export const DEFAULT_RESET_TRIGGERS = ["/new", "/reset"];
|
|
11
25
|
export const DEFAULT_IDLE_MINUTES = 60;
|
package/dist/config/sessions.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export const TELEGRAM_COMMAND_NAME_PATTERN = /^[a-z0-9_]{1,32}$/;
|
|
2
2
|
export function normalizeTelegramCommandName(value) {
|
|
3
3
|
const trimmed = value.trim();
|
|
4
|
-
if (!trimmed)
|
|
4
|
+
if (!trimmed) {
|
|
5
5
|
return "";
|
|
6
|
+
}
|
|
6
7
|
const withoutSlash = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
|
|
7
|
-
return withoutSlash.trim().toLowerCase();
|
|
8
|
+
return withoutSlash.trim().toLowerCase().replace(/-/g, "_");
|
|
8
9
|
}
|
|
9
10
|
export function normalizeTelegramCommandDescription(value) {
|
|
10
11
|
return value.trim();
|
package/dist/config/types.js
CHANGED
|
@@ -13,6 +13,7 @@ export * from "./types.googlechat.js";
|
|
|
13
13
|
export * from "./types.gateway.js";
|
|
14
14
|
export * from "./types.hooks.js";
|
|
15
15
|
export * from "./types.imessage.js";
|
|
16
|
+
export * from "./types.irc.js";
|
|
16
17
|
export * from "./types.messages.js";
|
|
17
18
|
export * from "./types.models.js";
|
|
18
19
|
export * from "./types.node-host.js";
|
|
@@ -27,3 +28,4 @@ export * from "./types.telegram.js";
|
|
|
27
28
|
export * from "./types.tts.js";
|
|
28
29
|
export * from "./types.tools.js";
|
|
29
30
|
export * from "./types.whatsapp.js";
|
|
31
|
+
export * from "./types.memory.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { HeartbeatSchema,
|
|
2
|
+
import { HeartbeatSchema, AgentSandboxSchema, AgentModelSchema, MemorySearchSchema, } from "./zod-schema.agent-runtime.js";
|
|
3
3
|
import { BlockStreamingChunkSchema, BlockStreamingCoalesceSchema, CliBackendSchema, HumanDelaySchema, } from "./zod-schema.core.js";
|
|
4
4
|
export const AgentDefaultsSchema = z
|
|
5
5
|
.object({
|
|
@@ -32,6 +32,7 @@ export const AgentDefaultsSchema = z
|
|
|
32
32
|
repoRoot: z.string().optional(),
|
|
33
33
|
skipBootstrap: z.boolean().optional(),
|
|
34
34
|
bootstrapMaxChars: z.number().int().positive().optional(),
|
|
35
|
+
bootstrapTotalMaxChars: z.number().int().positive().optional(),
|
|
35
36
|
userTimezone: z.string().optional(),
|
|
36
37
|
timeFormat: z.union([z.literal("auto"), z.literal("12"), z.literal("24")]).optional(),
|
|
37
38
|
envelopeTimezone: z.string().optional(),
|
|
@@ -111,6 +112,7 @@ export const AgentDefaultsSchema = z
|
|
|
111
112
|
humanDelay: HumanDelaySchema.optional(),
|
|
112
113
|
timeoutSeconds: z.number().int().positive().optional(),
|
|
113
114
|
mediaMaxMb: z.number().positive().optional(),
|
|
115
|
+
imageMaxDimensionPx: z.number().int().positive().optional(),
|
|
114
116
|
typingIntervalSeconds: z.number().int().positive().optional(),
|
|
115
117
|
typingMode: z
|
|
116
118
|
.union([
|
|
@@ -138,37 +140,14 @@ export const AgentDefaultsSchema = z
|
|
|
138
140
|
.min(1)
|
|
139
141
|
.max(20)
|
|
140
142
|
.optional()
|
|
141
|
-
.describe("Maximum active children a single
|
|
143
|
+
.describe("Maximum number of active children a single agent session can spawn (default: 5)."),
|
|
142
144
|
archiveAfterMinutes: z.number().int().positive().optional(),
|
|
143
|
-
model:
|
|
144
|
-
.union([
|
|
145
|
-
z.string(),
|
|
146
|
-
z
|
|
147
|
-
.object({
|
|
148
|
-
primary: z.string().optional(),
|
|
149
|
-
fallbacks: z.array(z.string()).optional(),
|
|
150
|
-
})
|
|
151
|
-
.strict(),
|
|
152
|
-
])
|
|
153
|
-
.optional(),
|
|
145
|
+
model: AgentModelSchema.optional(),
|
|
154
146
|
thinking: z.string().optional(),
|
|
155
147
|
})
|
|
156
148
|
.strict()
|
|
157
149
|
.optional(),
|
|
158
|
-
sandbox:
|
|
159
|
-
.object({
|
|
160
|
-
mode: z.union([z.literal("off"), z.literal("non-main"), z.literal("all")]).optional(),
|
|
161
|
-
workspaceAccess: z.union([z.literal("none"), z.literal("ro"), z.literal("rw")]).optional(),
|
|
162
|
-
sessionToolsVisibility: z.union([z.literal("spawned"), z.literal("all")]).optional(),
|
|
163
|
-
scope: z.union([z.literal("session"), z.literal("agent"), z.literal("shared")]).optional(),
|
|
164
|
-
perSession: z.boolean().optional(),
|
|
165
|
-
workspaceRoot: z.string().optional(),
|
|
166
|
-
docker: SandboxDockerSchema,
|
|
167
|
-
browser: SandboxBrowserSchema,
|
|
168
|
-
prune: SandboxPruneSchema,
|
|
169
|
-
})
|
|
170
|
-
.strict()
|
|
171
|
-
.optional(),
|
|
150
|
+
sandbox: AgentSandboxSchema,
|
|
172
151
|
})
|
|
173
152
|
.strict()
|
|
174
153
|
.optional();
|