@poolzin/pool-bot 2026.2.21 → 2026.2.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -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/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/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 +5 -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 +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -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 +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -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
|
@@ -1,15 +1,49 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { resolveCanvasHostUrl } from "../../infra/canvas-host-url.js";
|
|
3
|
-
import {
|
|
3
|
+
import { removeRemoteNodeInfo } from "../../infra/skills-remote.js";
|
|
4
|
+
import { upsertPresence } from "../../infra/system-presence.js";
|
|
5
|
+
import { truncateUtf16Safe } from "../../utils.js";
|
|
4
6
|
import { isWebchatClient } from "../../utils/message-channel.js";
|
|
5
7
|
import { isLoopbackAddress } from "../net.js";
|
|
6
8
|
import { getHandshakeTimeoutMs } from "../server-constants.js";
|
|
7
9
|
import { formatError } from "../server-utils.js";
|
|
8
10
|
import { logWs } from "../ws-log.js";
|
|
9
|
-
import { getHealthVersion,
|
|
11
|
+
import { getHealthVersion, incrementPresenceVersion } from "./health-state.js";
|
|
12
|
+
import { broadcastPresenceSnapshot } from "./presence-events.js";
|
|
10
13
|
import { attachGatewayWsMessageHandler } from "./ws-connection/message-handler.js";
|
|
14
|
+
const LOG_HEADER_MAX_LEN = 300;
|
|
15
|
+
const LOG_HEADER_FORMAT_REGEX = /\p{Cf}/gu;
|
|
16
|
+
function replaceControlChars(value) {
|
|
17
|
+
let cleaned = "";
|
|
18
|
+
for (const char of value) {
|
|
19
|
+
const codePoint = char.codePointAt(0);
|
|
20
|
+
if (codePoint !== undefined &&
|
|
21
|
+
(codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f))) {
|
|
22
|
+
cleaned += " ";
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
cleaned += char;
|
|
26
|
+
}
|
|
27
|
+
return cleaned;
|
|
28
|
+
}
|
|
29
|
+
const sanitizeLogValue = (value) => {
|
|
30
|
+
if (!value) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
const cleaned = replaceControlChars(value)
|
|
34
|
+
.replace(LOG_HEADER_FORMAT_REGEX, " ")
|
|
35
|
+
.replace(/\s+/g, " ")
|
|
36
|
+
.trim();
|
|
37
|
+
if (!cleaned) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
if (cleaned.length <= LOG_HEADER_MAX_LEN) {
|
|
41
|
+
return cleaned;
|
|
42
|
+
}
|
|
43
|
+
return truncateUtf16Safe(cleaned, LOG_HEADER_MAX_LEN);
|
|
44
|
+
};
|
|
11
45
|
export function attachGatewayWsConnectionHandler(params) {
|
|
12
|
-
const { wss, clients, port, gatewayHost, canvasHostEnabled, canvasHostServerPort, resolvedAuth, gatewayMethods, events, logGateway, logHealth, logWsControl, extraHandlers, broadcast, buildRequestContext, } = params;
|
|
46
|
+
const { wss, clients, port, gatewayHost, canvasHostEnabled, canvasHostServerPort, resolvedAuth, rateLimiter, gatewayMethods, events, logGateway, logHealth, logWsControl, extraHandlers, broadcast, buildRequestContext, } = params;
|
|
13
47
|
wss.on("connection", (socket, upgradeReq) => {
|
|
14
48
|
let client = null;
|
|
15
49
|
let closed = false;
|
|
@@ -40,8 +74,9 @@ export function attachGatewayWsConnectionHandler(params) {
|
|
|
40
74
|
let lastFrameMethod;
|
|
41
75
|
let lastFrameId;
|
|
42
76
|
const setCloseCause = (cause, meta) => {
|
|
43
|
-
if (!closeCause)
|
|
77
|
+
if (!closeCause) {
|
|
44
78
|
closeCause = cause;
|
|
79
|
+
}
|
|
45
80
|
if (meta && Object.keys(meta).length > 0) {
|
|
46
81
|
closeMeta = { ...closeMeta, ...meta };
|
|
47
82
|
}
|
|
@@ -68,12 +103,14 @@ export function attachGatewayWsConnectionHandler(params) {
|
|
|
68
103
|
payload: { nonce: connectNonce, ts: Date.now() },
|
|
69
104
|
});
|
|
70
105
|
const close = (code = 1000, reason) => {
|
|
71
|
-
if (closed)
|
|
106
|
+
if (closed) {
|
|
72
107
|
return;
|
|
108
|
+
}
|
|
73
109
|
closed = true;
|
|
74
110
|
clearTimeout(handshakeTimer);
|
|
75
|
-
if (client)
|
|
111
|
+
if (client) {
|
|
76
112
|
clients.delete(client);
|
|
113
|
+
}
|
|
77
114
|
try {
|
|
78
115
|
socket.close(code, reason);
|
|
79
116
|
}
|
|
@@ -88,6 +125,11 @@ export function attachGatewayWsConnectionHandler(params) {
|
|
|
88
125
|
const isNoisySwiftPmHelperClose = (userAgent, remote) => Boolean(userAgent?.toLowerCase().includes("swiftpm-testing-helper") && isLoopbackAddress(remote));
|
|
89
126
|
socket.once("close", (code, reason) => {
|
|
90
127
|
const durationMs = Date.now() - openedAt;
|
|
128
|
+
const logForwardedFor = sanitizeLogValue(forwardedFor);
|
|
129
|
+
const logOrigin = sanitizeLogValue(requestOrigin);
|
|
130
|
+
const logHost = sanitizeLogValue(requestHost);
|
|
131
|
+
const logUserAgent = sanitizeLogValue(requestUserAgent);
|
|
132
|
+
const logReason = sanitizeLogValue(reason?.toString());
|
|
91
133
|
const closeContext = {
|
|
92
134
|
cause: closeCause,
|
|
93
135
|
handshake: handshakeState,
|
|
@@ -95,43 +137,37 @@ export function attachGatewayWsConnectionHandler(params) {
|
|
|
95
137
|
lastFrameType,
|
|
96
138
|
lastFrameMethod,
|
|
97
139
|
lastFrameId,
|
|
98
|
-
host:
|
|
99
|
-
origin:
|
|
100
|
-
userAgent:
|
|
101
|
-
forwardedFor,
|
|
140
|
+
host: logHost,
|
|
141
|
+
origin: logOrigin,
|
|
142
|
+
userAgent: logUserAgent,
|
|
143
|
+
forwardedFor: logForwardedFor,
|
|
102
144
|
...closeMeta,
|
|
103
145
|
};
|
|
104
146
|
if (!client) {
|
|
105
147
|
const logFn = isNoisySwiftPmHelperClose(requestUserAgent, remoteAddr)
|
|
106
148
|
? logWsControl.debug
|
|
107
149
|
: logWsControl.warn;
|
|
108
|
-
logFn(`closed before connect conn=${connId} remote=${remoteAddr ?? "?"} fwd=${
|
|
150
|
+
logFn(`closed before connect conn=${connId} remote=${remoteAddr ?? "?"} fwd=${logForwardedFor || "n/a"} origin=${logOrigin || "n/a"} host=${logHost || "n/a"} ua=${logUserAgent || "n/a"} code=${code ?? "n/a"} reason=${logReason || "n/a"}`, closeContext);
|
|
109
151
|
}
|
|
110
152
|
if (client && isWebchatClient(client.connect.client)) {
|
|
111
|
-
logWsControl.info(`webchat disconnected code=${code} reason=${
|
|
153
|
+
logWsControl.info(`webchat disconnected code=${code} reason=${logReason || "n/a"} conn=${connId}`);
|
|
112
154
|
}
|
|
113
155
|
if (client?.presenceKey) {
|
|
114
156
|
upsertPresence(client.presenceKey, { reason: "disconnect" });
|
|
115
|
-
incrementPresenceVersion
|
|
116
|
-
broadcast("presence", { presence: listSystemPresence() }, {
|
|
117
|
-
dropIfSlow: true,
|
|
118
|
-
stateVersion: {
|
|
119
|
-
presence: getPresenceVersion(),
|
|
120
|
-
health: getHealthVersion(),
|
|
121
|
-
},
|
|
122
|
-
});
|
|
157
|
+
broadcastPresenceSnapshot({ broadcast, incrementPresenceVersion, getHealthVersion });
|
|
123
158
|
}
|
|
124
159
|
if (client?.connect?.role === "node") {
|
|
125
160
|
const context = buildRequestContext();
|
|
126
161
|
const nodeId = context.nodeRegistry.unregister(connId);
|
|
127
162
|
if (nodeId) {
|
|
163
|
+
removeRemoteNodeInfo(nodeId);
|
|
128
164
|
context.nodeUnsubscribeAll(nodeId);
|
|
129
165
|
}
|
|
130
166
|
}
|
|
131
167
|
logWs("out", "close", {
|
|
132
168
|
connId,
|
|
133
169
|
code,
|
|
134
|
-
reason:
|
|
170
|
+
reason: logReason,
|
|
135
171
|
durationMs,
|
|
136
172
|
cause: closeCause,
|
|
137
173
|
handshake: handshakeState,
|
|
@@ -165,6 +201,7 @@ export function attachGatewayWsConnectionHandler(params) {
|
|
|
165
201
|
canvasHostUrl,
|
|
166
202
|
connectNonce,
|
|
167
203
|
resolvedAuth,
|
|
204
|
+
rateLimiter,
|
|
168
205
|
gatewayMethods,
|
|
169
206
|
events,
|
|
170
207
|
extraHandlers,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MAX_BUFFERED_BYTES } from "./server-constants.js";
|
|
2
|
-
import { logWs, summarizeAgentEventForWsLog } from "./ws-log.js";
|
|
2
|
+
import { logWs, shouldLogWs, summarizeAgentEventForWsLog } from "./ws-log.js";
|
|
3
3
|
const ADMIN_SCOPE = "operator.admin";
|
|
4
4
|
const APPROVALS_SCOPE = "operator.approvals";
|
|
5
5
|
const PAIRING_SCOPE = "operator.pairing";
|
|
@@ -29,6 +29,9 @@ function hasEventScope(client, event) {
|
|
|
29
29
|
export function createGatewayBroadcaster(params) {
|
|
30
30
|
let seq = 0;
|
|
31
31
|
const broadcastInternal = (event, payload, opts, targetConnIds) => {
|
|
32
|
+
if (params.clients.size === 0) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
32
35
|
const isTargeted = Boolean(targetConnIds);
|
|
33
36
|
const eventSeq = isTargeted ? undefined : ++seq;
|
|
34
37
|
const frame = JSON.stringify({
|
|
@@ -38,19 +41,21 @@ export function createGatewayBroadcaster(params) {
|
|
|
38
41
|
seq: eventSeq,
|
|
39
42
|
stateVersion: opts?.stateVersion,
|
|
40
43
|
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
if (shouldLogWs()) {
|
|
45
|
+
const logMeta = {
|
|
46
|
+
event,
|
|
47
|
+
seq: eventSeq ?? "targeted",
|
|
48
|
+
clients: params.clients.size,
|
|
49
|
+
targets: targetConnIds ? targetConnIds.size : undefined,
|
|
50
|
+
dropIfSlow: opts?.dropIfSlow,
|
|
51
|
+
presenceVersion: opts?.stateVersion?.presence,
|
|
52
|
+
healthVersion: opts?.stateVersion?.health,
|
|
53
|
+
};
|
|
54
|
+
if (event === "agent") {
|
|
55
|
+
Object.assign(logMeta, summarizeAgentEventForWsLog(payload));
|
|
56
|
+
}
|
|
57
|
+
logWs("out", "event", logMeta);
|
|
52
58
|
}
|
|
53
|
-
logWs("out", "event", logMeta);
|
|
54
59
|
for (const c of params.clients) {
|
|
55
60
|
if (targetConnIds && !targetConnIds.has(c.connId)) {
|
|
56
61
|
continue;
|
|
@@ -1,22 +1,49 @@
|
|
|
1
1
|
import { resolveDefaultAgentId } from "../agents/agent-scope.js";
|
|
2
2
|
import { loadConfig } from "../config/config.js";
|
|
3
|
-
import { resolveAgentMainSessionKey } from "../config/sessions.js";
|
|
3
|
+
import { canonicalizeMainSessionAlias, resolveAgentIdFromSessionKey, resolveAgentMainSessionKey, } from "../config/sessions.js";
|
|
4
|
+
import { resolveStorePath } from "../config/sessions/paths.js";
|
|
4
5
|
import { runCronIsolatedAgentTurn } from "../cron/isolated-agent.js";
|
|
5
6
|
import { appendCronRunLog, resolveCronRunLogPath } from "../cron/run-log.js";
|
|
6
7
|
import { CronService } from "../cron/service.js";
|
|
7
8
|
import { resolveCronStorePath } from "../cron/store.js";
|
|
9
|
+
import { normalizeHttpWebhookUrl } from "../cron/webhook-url.js";
|
|
10
|
+
import { formatErrorMessage } from "../infra/errors.js";
|
|
8
11
|
import { runHeartbeatOnce } from "../infra/heartbeat-runner.js";
|
|
9
12
|
import { requestHeartbeatNow } from "../infra/heartbeat-wake.js";
|
|
13
|
+
import { fetchWithSsrFGuard } from "../infra/net/fetch-guard.js";
|
|
14
|
+
import { SsrFBlockedError } from "../infra/net/ssrf.js";
|
|
10
15
|
import { enqueueSystemEvent } from "../infra/system-events.js";
|
|
11
16
|
import { getChildLogger } from "../logging.js";
|
|
12
|
-
import { normalizeAgentId } from "../routing/session-key.js";
|
|
17
|
+
import { normalizeAgentId, toAgentStoreSessionKey } from "../routing/session-key.js";
|
|
13
18
|
import { defaultRuntime } from "../runtime.js";
|
|
19
|
+
const CRON_WEBHOOK_TIMEOUT_MS = 10_000;
|
|
20
|
+
function redactWebhookUrl(url) {
|
|
21
|
+
try {
|
|
22
|
+
const parsed = new URL(url);
|
|
23
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return "<invalid-webhook-url>";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function resolveCronWebhookTarget(params) {
|
|
30
|
+
const mode = params.delivery?.mode?.trim().toLowerCase();
|
|
31
|
+
if (mode === "webhook") {
|
|
32
|
+
const url = normalizeHttpWebhookUrl(params.delivery?.to);
|
|
33
|
+
return url ? { url, source: "delivery" } : null;
|
|
34
|
+
}
|
|
35
|
+
if (params.legacyNotify) {
|
|
36
|
+
const legacyUrl = normalizeHttpWebhookUrl(params.legacyWebhook);
|
|
37
|
+
if (legacyUrl) {
|
|
38
|
+
return { url: legacyUrl, source: "legacy" };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
14
43
|
export function buildGatewayCronService(params) {
|
|
15
44
|
const cronLogger = getChildLogger({ module: "cron" });
|
|
16
45
|
const storePath = resolveCronStorePath(params.cfg.cron?.store);
|
|
17
|
-
const cronEnabled = process.env.POOLBOT_SKIP_CRON !== "1" &&
|
|
18
|
-
process.env.CLAWDBOT_SKIP_CRON !== "1" &&
|
|
19
|
-
params.cfg.cron?.enabled !== false;
|
|
46
|
+
const cronEnabled = process.env.POOLBOT_SKIP_CRON !== "1" && params.cfg.cron?.enabled !== false;
|
|
20
47
|
const resolveCronAgent = (requested) => {
|
|
21
48
|
const runtimeConfig = loadConfig();
|
|
22
49
|
const normalized = typeof requested === "string" && requested.trim() ? normalizeAgentId(requested) : undefined;
|
|
@@ -26,23 +53,89 @@ export function buildGatewayCronService(params) {
|
|
|
26
53
|
const agentId = hasAgent ? normalized : resolveDefaultAgentId(runtimeConfig);
|
|
27
54
|
return { agentId, cfg: runtimeConfig };
|
|
28
55
|
};
|
|
56
|
+
const resolveCronSessionKey = (params) => {
|
|
57
|
+
const requested = params.requestedSessionKey?.trim();
|
|
58
|
+
if (!requested) {
|
|
59
|
+
return resolveAgentMainSessionKey({
|
|
60
|
+
cfg: params.runtimeConfig,
|
|
61
|
+
agentId: params.agentId,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const candidate = toAgentStoreSessionKey({
|
|
65
|
+
agentId: params.agentId,
|
|
66
|
+
requestKey: requested,
|
|
67
|
+
mainKey: params.runtimeConfig.session?.mainKey,
|
|
68
|
+
});
|
|
69
|
+
const canonical = canonicalizeMainSessionAlias({
|
|
70
|
+
cfg: params.runtimeConfig,
|
|
71
|
+
agentId: params.agentId,
|
|
72
|
+
sessionKey: candidate,
|
|
73
|
+
});
|
|
74
|
+
if (canonical !== "global") {
|
|
75
|
+
const sessionAgentId = resolveAgentIdFromSessionKey(canonical);
|
|
76
|
+
if (normalizeAgentId(sessionAgentId) !== normalizeAgentId(params.agentId)) {
|
|
77
|
+
return resolveAgentMainSessionKey({
|
|
78
|
+
cfg: params.runtimeConfig,
|
|
79
|
+
agentId: params.agentId,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return canonical;
|
|
84
|
+
};
|
|
85
|
+
const resolveCronWakeTarget = (opts) => {
|
|
86
|
+
const runtimeConfig = loadConfig();
|
|
87
|
+
const requestedAgentId = opts?.agentId ? resolveCronAgent(opts.agentId).agentId : undefined;
|
|
88
|
+
const derivedAgentId = requestedAgentId ??
|
|
89
|
+
(opts?.sessionKey
|
|
90
|
+
? normalizeAgentId(resolveAgentIdFromSessionKey(opts.sessionKey))
|
|
91
|
+
: undefined);
|
|
92
|
+
const agentId = derivedAgentId || undefined;
|
|
93
|
+
const sessionKey = opts?.sessionKey && agentId
|
|
94
|
+
? resolveCronSessionKey({
|
|
95
|
+
runtimeConfig,
|
|
96
|
+
agentId,
|
|
97
|
+
requestedSessionKey: opts.sessionKey,
|
|
98
|
+
})
|
|
99
|
+
: undefined;
|
|
100
|
+
return { runtimeConfig, agentId, sessionKey };
|
|
101
|
+
};
|
|
102
|
+
const defaultAgentId = resolveDefaultAgentId(params.cfg);
|
|
103
|
+
const resolveSessionStorePath = (agentId) => resolveStorePath(params.cfg.session?.store, {
|
|
104
|
+
agentId: agentId ?? defaultAgentId,
|
|
105
|
+
});
|
|
106
|
+
const sessionStorePath = resolveSessionStorePath(defaultAgentId);
|
|
107
|
+
const warnedLegacyWebhookJobs = new Set();
|
|
29
108
|
const cron = new CronService({
|
|
30
109
|
storePath,
|
|
31
110
|
cronEnabled,
|
|
111
|
+
cronConfig: params.cfg.cron,
|
|
112
|
+
defaultAgentId,
|
|
113
|
+
resolveSessionStorePath,
|
|
114
|
+
sessionStorePath,
|
|
32
115
|
enqueueSystemEvent: (text, opts) => {
|
|
33
116
|
const { agentId, cfg: runtimeConfig } = resolveCronAgent(opts?.agentId);
|
|
34
|
-
const sessionKey =
|
|
35
|
-
|
|
117
|
+
const sessionKey = resolveCronSessionKey({
|
|
118
|
+
runtimeConfig,
|
|
119
|
+
agentId,
|
|
120
|
+
requestedSessionKey: opts?.sessionKey,
|
|
121
|
+
});
|
|
122
|
+
enqueueSystemEvent(text, { sessionKey, contextKey: opts?.contextKey });
|
|
123
|
+
},
|
|
124
|
+
requestHeartbeatNow: (opts) => {
|
|
125
|
+
const { agentId, sessionKey } = resolveCronWakeTarget(opts);
|
|
126
|
+
requestHeartbeatNow({
|
|
127
|
+
reason: opts?.reason,
|
|
36
128
|
agentId,
|
|
129
|
+
sessionKey,
|
|
37
130
|
});
|
|
38
|
-
enqueueSystemEvent(text, { sessionKey });
|
|
39
131
|
},
|
|
40
|
-
requestHeartbeatNow,
|
|
41
132
|
runHeartbeatOnce: async (opts) => {
|
|
42
|
-
const runtimeConfig =
|
|
133
|
+
const { runtimeConfig, agentId, sessionKey } = resolveCronWakeTarget(opts);
|
|
43
134
|
return await runHeartbeatOnce({
|
|
44
135
|
cfg: runtimeConfig,
|
|
45
136
|
reason: opts?.reason,
|
|
137
|
+
agentId,
|
|
138
|
+
sessionKey,
|
|
46
139
|
deps: { ...params.deps, runtime: defaultRuntime },
|
|
47
140
|
});
|
|
48
141
|
},
|
|
@@ -62,6 +155,75 @@ export function buildGatewayCronService(params) {
|
|
|
62
155
|
onEvent: (evt) => {
|
|
63
156
|
params.broadcast("cron", evt, { dropIfSlow: true });
|
|
64
157
|
if (evt.action === "finished") {
|
|
158
|
+
const webhookToken = params.cfg.cron?.webhookToken?.trim();
|
|
159
|
+
const legacyWebhook = params.cfg.cron?.webhook?.trim();
|
|
160
|
+
const job = cron.getJob(evt.jobId);
|
|
161
|
+
const legacyNotify = job?.notify === true;
|
|
162
|
+
const webhookTarget = resolveCronWebhookTarget({
|
|
163
|
+
delivery: job?.delivery && typeof job.delivery.mode === "string"
|
|
164
|
+
? { mode: job.delivery.mode, to: job.delivery.to }
|
|
165
|
+
: undefined,
|
|
166
|
+
legacyNotify,
|
|
167
|
+
legacyWebhook,
|
|
168
|
+
});
|
|
169
|
+
if (!webhookTarget && job?.delivery?.mode === "webhook") {
|
|
170
|
+
cronLogger.warn({
|
|
171
|
+
jobId: evt.jobId,
|
|
172
|
+
deliveryTo: job.delivery.to,
|
|
173
|
+
}, "cron: skipped webhook delivery, delivery.to must be a valid http(s) URL");
|
|
174
|
+
}
|
|
175
|
+
if (webhookTarget?.source === "legacy" && !warnedLegacyWebhookJobs.has(evt.jobId)) {
|
|
176
|
+
warnedLegacyWebhookJobs.add(evt.jobId);
|
|
177
|
+
cronLogger.warn({
|
|
178
|
+
jobId: evt.jobId,
|
|
179
|
+
legacyWebhook: redactWebhookUrl(webhookTarget.url),
|
|
180
|
+
}, "cron: deprecated notify+cron.webhook fallback in use, migrate to delivery.mode=webhook with delivery.to");
|
|
181
|
+
}
|
|
182
|
+
if (webhookTarget && evt.summary) {
|
|
183
|
+
const headers = {
|
|
184
|
+
"Content-Type": "application/json",
|
|
185
|
+
};
|
|
186
|
+
if (webhookToken) {
|
|
187
|
+
headers.Authorization = `Bearer ${webhookToken}`;
|
|
188
|
+
}
|
|
189
|
+
const abortController = new AbortController();
|
|
190
|
+
const timeout = setTimeout(() => {
|
|
191
|
+
abortController.abort();
|
|
192
|
+
}, CRON_WEBHOOK_TIMEOUT_MS);
|
|
193
|
+
void (async () => {
|
|
194
|
+
try {
|
|
195
|
+
const result = await fetchWithSsrFGuard({
|
|
196
|
+
url: webhookTarget.url,
|
|
197
|
+
init: {
|
|
198
|
+
method: "POST",
|
|
199
|
+
headers,
|
|
200
|
+
body: JSON.stringify(evt),
|
|
201
|
+
signal: abortController.signal,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
await result.release();
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
if (err instanceof SsrFBlockedError) {
|
|
208
|
+
cronLogger.warn({
|
|
209
|
+
reason: formatErrorMessage(err),
|
|
210
|
+
jobId: evt.jobId,
|
|
211
|
+
webhookUrl: redactWebhookUrl(webhookTarget.url),
|
|
212
|
+
}, "cron: webhook delivery blocked by SSRF guard");
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
cronLogger.warn({
|
|
216
|
+
err: formatErrorMessage(err),
|
|
217
|
+
jobId: evt.jobId,
|
|
218
|
+
webhookUrl: redactWebhookUrl(webhookTarget.url),
|
|
219
|
+
}, "cron: webhook delivery failed");
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
clearTimeout(timeout);
|
|
224
|
+
}
|
|
225
|
+
})();
|
|
226
|
+
}
|
|
65
227
|
const logPath = resolveCronRunLogPath({
|
|
66
228
|
storePath,
|
|
67
229
|
jobId: evt.jobId,
|
|
@@ -73,9 +235,14 @@ export function buildGatewayCronService(params) {
|
|
|
73
235
|
status: evt.status,
|
|
74
236
|
error: evt.error,
|
|
75
237
|
summary: evt.summary,
|
|
238
|
+
sessionId: evt.sessionId,
|
|
239
|
+
sessionKey: evt.sessionKey,
|
|
76
240
|
runAtMs: evt.runAtMs,
|
|
77
241
|
durationMs: evt.durationMs,
|
|
78
242
|
nextRunAtMs: evt.nextRunAtMs,
|
|
243
|
+
model: evt.model,
|
|
244
|
+
provider: evt.provider,
|
|
245
|
+
usage: evt.usage,
|
|
79
246
|
}).catch((err) => {
|
|
80
247
|
cronLogger.warn({ err: String(err), logPath }, "cron: run log append failed");
|
|
81
248
|
});
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { onAgentEvent } from "../../infra/agent-events.js";
|
|
2
2
|
const AGENT_RUN_CACHE_TTL_MS = 10 * 60_000;
|
|
3
|
+
/**
|
|
4
|
+
* Embedded runs can emit transient lifecycle `error` events while auth/model
|
|
5
|
+
* failover is still in progress. Give errors a short grace window so a
|
|
6
|
+
* subsequent `start` event can cancel premature terminal snapshots.
|
|
7
|
+
*/
|
|
8
|
+
const AGENT_RUN_ERROR_RETRY_GRACE_MS = 15_000;
|
|
3
9
|
const agentRunCache = new Map();
|
|
4
10
|
const agentRunStarts = new Map();
|
|
11
|
+
const pendingAgentRunErrors = new Map();
|
|
5
12
|
let agentRunListenerStarted = false;
|
|
6
13
|
function pruneAgentRunCache(now = Date.now()) {
|
|
7
14
|
for (const [runId, entry] of agentRunCache) {
|
|
@@ -14,37 +21,89 @@ function recordAgentRunSnapshot(entry) {
|
|
|
14
21
|
pruneAgentRunCache(entry.ts);
|
|
15
22
|
agentRunCache.set(entry.runId, entry);
|
|
16
23
|
}
|
|
24
|
+
function clearPendingAgentRunError(runId) {
|
|
25
|
+
const pending = pendingAgentRunErrors.get(runId);
|
|
26
|
+
if (!pending) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
clearTimeout(pending.timer);
|
|
30
|
+
pendingAgentRunErrors.delete(runId);
|
|
31
|
+
}
|
|
32
|
+
function schedulePendingAgentRunError(snapshot) {
|
|
33
|
+
clearPendingAgentRunError(snapshot.runId);
|
|
34
|
+
const dueAt = Date.now() + AGENT_RUN_ERROR_RETRY_GRACE_MS;
|
|
35
|
+
const timer = setTimeout(() => {
|
|
36
|
+
const pending = pendingAgentRunErrors.get(snapshot.runId);
|
|
37
|
+
if (!pending) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
pendingAgentRunErrors.delete(snapshot.runId);
|
|
41
|
+
recordAgentRunSnapshot(pending.snapshot);
|
|
42
|
+
}, AGENT_RUN_ERROR_RETRY_GRACE_MS);
|
|
43
|
+
timer.unref?.();
|
|
44
|
+
pendingAgentRunErrors.set(snapshot.runId, { snapshot, dueAt, timer });
|
|
45
|
+
}
|
|
46
|
+
function getPendingAgentRunError(runId) {
|
|
47
|
+
const pending = pendingAgentRunErrors.get(runId);
|
|
48
|
+
if (!pending) {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
snapshot: pending.snapshot,
|
|
53
|
+
dueAt: pending.dueAt,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function createSnapshotFromLifecycleEvent(params) {
|
|
57
|
+
const { runId, phase, data } = params;
|
|
58
|
+
const startedAt = typeof data?.startedAt === "number" ? data.startedAt : agentRunStarts.get(runId);
|
|
59
|
+
const endedAt = typeof data?.endedAt === "number" ? data.endedAt : undefined;
|
|
60
|
+
const error = typeof data?.error === "string" ? data.error : undefined;
|
|
61
|
+
return {
|
|
62
|
+
runId,
|
|
63
|
+
status: phase === "error" ? "error" : data?.aborted ? "timeout" : "ok",
|
|
64
|
+
startedAt,
|
|
65
|
+
endedAt,
|
|
66
|
+
error,
|
|
67
|
+
ts: Date.now(),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
17
70
|
function ensureAgentRunListener() {
|
|
18
|
-
if (agentRunListenerStarted)
|
|
71
|
+
if (agentRunListenerStarted) {
|
|
19
72
|
return;
|
|
73
|
+
}
|
|
20
74
|
agentRunListenerStarted = true;
|
|
21
75
|
onAgentEvent((evt) => {
|
|
22
|
-
if (!evt)
|
|
76
|
+
if (!evt) {
|
|
23
77
|
return;
|
|
24
|
-
|
|
78
|
+
}
|
|
79
|
+
if (evt.stream !== "lifecycle") {
|
|
25
80
|
return;
|
|
81
|
+
}
|
|
26
82
|
const phase = evt.data?.phase;
|
|
27
83
|
if (phase === "start") {
|
|
28
84
|
const startedAt = typeof evt.data?.startedAt === "number" ? evt.data.startedAt : undefined;
|
|
29
85
|
agentRunStarts.set(evt.runId, startedAt ?? Date.now());
|
|
86
|
+
clearPendingAgentRunError(evt.runId);
|
|
87
|
+
// A new start means this run is active again (or retried). Drop stale
|
|
88
|
+
// terminal snapshots so waiters don't resolve from old state.
|
|
89
|
+
agentRunCache.delete(evt.runId);
|
|
30
90
|
return;
|
|
31
91
|
}
|
|
32
|
-
if (phase !== "end" && phase !== "error")
|
|
92
|
+
if (phase !== "end" && phase !== "error") {
|
|
33
93
|
return;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
: agentRunStarts.get(evt.runId);
|
|
37
|
-
const endedAt = typeof evt.data?.endedAt === "number" ? evt.data.endedAt : undefined;
|
|
38
|
-
const error = typeof evt.data?.error === "string" ? evt.data.error : undefined;
|
|
39
|
-
agentRunStarts.delete(evt.runId);
|
|
40
|
-
recordAgentRunSnapshot({
|
|
94
|
+
}
|
|
95
|
+
const snapshot = createSnapshotFromLifecycleEvent({
|
|
41
96
|
runId: evt.runId,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
endedAt,
|
|
45
|
-
error,
|
|
46
|
-
ts: Date.now(),
|
|
97
|
+
phase,
|
|
98
|
+
data: evt.data,
|
|
47
99
|
});
|
|
100
|
+
agentRunStarts.delete(evt.runId);
|
|
101
|
+
if (phase === "error") {
|
|
102
|
+
schedulePendingAgentRunError(snapshot);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
clearPendingAgentRunError(evt.runId);
|
|
106
|
+
recordAgentRunSnapshot(snapshot);
|
|
48
107
|
});
|
|
49
108
|
}
|
|
50
109
|
function getCachedAgentRun(runId) {
|
|
@@ -55,50 +114,84 @@ export async function waitForAgentJob(params) {
|
|
|
55
114
|
const { runId, timeoutMs } = params;
|
|
56
115
|
ensureAgentRunListener();
|
|
57
116
|
const cached = getCachedAgentRun(runId);
|
|
58
|
-
if (cached)
|
|
117
|
+
if (cached) {
|
|
59
118
|
return cached;
|
|
60
|
-
|
|
119
|
+
}
|
|
120
|
+
if (timeoutMs <= 0) {
|
|
61
121
|
return null;
|
|
122
|
+
}
|
|
62
123
|
return await new Promise((resolve) => {
|
|
63
124
|
let settled = false;
|
|
125
|
+
let pendingErrorTimer;
|
|
126
|
+
const clearPendingErrorTimer = () => {
|
|
127
|
+
if (!pendingErrorTimer) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
clearTimeout(pendingErrorTimer);
|
|
131
|
+
pendingErrorTimer = undefined;
|
|
132
|
+
};
|
|
64
133
|
const finish = (entry) => {
|
|
65
|
-
if (settled)
|
|
134
|
+
if (settled) {
|
|
66
135
|
return;
|
|
136
|
+
}
|
|
67
137
|
settled = true;
|
|
68
138
|
clearTimeout(timer);
|
|
139
|
+
clearPendingErrorTimer();
|
|
69
140
|
unsubscribe();
|
|
70
141
|
resolve(entry);
|
|
71
142
|
};
|
|
143
|
+
const scheduleErrorFinish = (snapshot, delayMs = AGENT_RUN_ERROR_RETRY_GRACE_MS) => {
|
|
144
|
+
clearPendingErrorTimer();
|
|
145
|
+
const effectiveDelay = Math.max(1, Math.min(Math.floor(delayMs), 2_147_483_647));
|
|
146
|
+
pendingErrorTimer = setTimeout(() => {
|
|
147
|
+
const latest = getCachedAgentRun(runId);
|
|
148
|
+
if (latest) {
|
|
149
|
+
finish(latest);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
recordAgentRunSnapshot(snapshot);
|
|
153
|
+
finish(snapshot);
|
|
154
|
+
}, effectiveDelay);
|
|
155
|
+
pendingErrorTimer.unref?.();
|
|
156
|
+
};
|
|
157
|
+
const pending = getPendingAgentRunError(runId);
|
|
158
|
+
if (pending) {
|
|
159
|
+
scheduleErrorFinish(pending.snapshot, pending.dueAt - Date.now());
|
|
160
|
+
}
|
|
72
161
|
const unsubscribe = onAgentEvent((evt) => {
|
|
73
|
-
if (!evt || evt.stream !== "lifecycle")
|
|
162
|
+
if (!evt || evt.stream !== "lifecycle") {
|
|
74
163
|
return;
|
|
75
|
-
|
|
164
|
+
}
|
|
165
|
+
if (evt.runId !== runId) {
|
|
76
166
|
return;
|
|
167
|
+
}
|
|
77
168
|
const phase = evt.data?.phase;
|
|
78
|
-
if (phase
|
|
169
|
+
if (phase === "start") {
|
|
170
|
+
clearPendingErrorTimer();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (phase !== "end" && phase !== "error") {
|
|
79
174
|
return;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
175
|
+
}
|
|
176
|
+
const latest = getCachedAgentRun(runId);
|
|
177
|
+
if (latest) {
|
|
178
|
+
finish(latest);
|
|
83
179
|
return;
|
|
84
180
|
}
|
|
85
|
-
const
|
|
86
|
-
? evt.data.startedAt
|
|
87
|
-
: agentRunStarts.get(evt.runId);
|
|
88
|
-
const endedAt = typeof evt.data?.endedAt === "number" ? evt.data.endedAt : undefined;
|
|
89
|
-
const error = typeof evt.data?.error === "string" ? evt.data.error : undefined;
|
|
90
|
-
const snapshot = {
|
|
181
|
+
const snapshot = createSnapshotFromLifecycleEvent({
|
|
91
182
|
runId: evt.runId,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
183
|
+
phase,
|
|
184
|
+
data: evt.data,
|
|
185
|
+
});
|
|
186
|
+
if (phase === "error") {
|
|
187
|
+
scheduleErrorFinish(snapshot);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
98
190
|
recordAgentRunSnapshot(snapshot);
|
|
99
191
|
finish(snapshot);
|
|
100
192
|
});
|
|
101
|
-
const
|
|
193
|
+
const timerDelayMs = Math.max(1, Math.min(Math.floor(timeoutMs), 2_147_483_647));
|
|
194
|
+
const timer = setTimeout(() => finish(null), timerDelayMs);
|
|
102
195
|
});
|
|
103
196
|
}
|
|
104
197
|
ensureAgentRunListener();
|