@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,10 +1,18 @@
|
|
|
1
1
|
import { DEFAULT_AGENT_ID } from "../../routing/session-key.js";
|
|
2
2
|
import { resolveCronDeliveryPlan } from "../delivery.js";
|
|
3
3
|
import { sweepCronRunSessions } from "../session-reaper.js";
|
|
4
|
-
import { computeJobNextRunAtMs, nextWakeAtMs,
|
|
4
|
+
import { computeJobNextRunAtMs, nextWakeAtMs, recomputeNextRunsForMaintenance, resolveJobPayloadTextForMain, } from "./jobs.js";
|
|
5
5
|
import { locked } from "./locked.js";
|
|
6
6
|
import { ensureLoaded, persist } from "./store.js";
|
|
7
7
|
const MAX_TIMER_DELAY_MS = 60_000;
|
|
8
|
+
/**
|
|
9
|
+
* Minimum gap between consecutive fires of the same cron job. This is a
|
|
10
|
+
* safety net that prevents spin-loops when `computeJobNextRunAtMs` returns
|
|
11
|
+
* a value within the same second as the just-completed run. The guard
|
|
12
|
+
* is intentionally generous (2 s) so it never masks a legitimate schedule
|
|
13
|
+
* but always breaks an infinite re-trigger cycle. (See #17821)
|
|
14
|
+
*/
|
|
15
|
+
const MIN_REFIRE_GAP_MS = 2_000;
|
|
8
16
|
/**
|
|
9
17
|
* Maximum wall-clock time for a single job execution. Acts as a safety net
|
|
10
18
|
* on top of the per-provider / per-agent timeouts to prevent one stuck job
|
|
@@ -45,7 +53,7 @@ function applyJobResult(state, job, result) {
|
|
|
45
53
|
else {
|
|
46
54
|
job.state.consecutiveErrors = 0;
|
|
47
55
|
}
|
|
48
|
-
const shouldDelete = job.schedule.kind === "at" &&
|
|
56
|
+
const shouldDelete = job.schedule.kind === "at" && job.deleteAfterRun === true && result.status === "ok";
|
|
49
57
|
if (!shouldDelete) {
|
|
50
58
|
if (job.schedule.kind === "at") {
|
|
51
59
|
// One-shot jobs are always disabled after ANY terminal status
|
|
@@ -78,7 +86,19 @@ function applyJobResult(state, job, result) {
|
|
|
78
86
|
}, "cron: applying error backoff");
|
|
79
87
|
}
|
|
80
88
|
else if (job.enabled) {
|
|
81
|
-
|
|
89
|
+
const naturalNext = computeJobNextRunAtMs(job, result.endedAt);
|
|
90
|
+
if (job.schedule.kind === "cron") {
|
|
91
|
+
// Safety net: ensure the next fire is at least MIN_REFIRE_GAP_MS
|
|
92
|
+
// after the current run ended. Prevents spin-loops when the
|
|
93
|
+
// schedule computation lands in the same second due to
|
|
94
|
+
// timezone/croner edge cases (see #17821).
|
|
95
|
+
const minNext = result.endedAt + MIN_REFIRE_GAP_MS;
|
|
96
|
+
job.state.nextRunAtMs =
|
|
97
|
+
naturalNext !== undefined ? Math.max(naturalNext, minNext) : minNext;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
job.state.nextRunAtMs = naturalNext;
|
|
101
|
+
}
|
|
82
102
|
}
|
|
83
103
|
else {
|
|
84
104
|
job.state.nextRunAtMs = undefined;
|
|
@@ -109,18 +129,36 @@ export function armTimer(state) {
|
|
|
109
129
|
// Wake at least once a minute to avoid schedule drift and recover quickly
|
|
110
130
|
// when the process was paused or wall-clock time jumps.
|
|
111
131
|
const clampedDelay = Math.min(delay, MAX_TIMER_DELAY_MS);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
132
|
+
// Intentionally avoid an `async` timer callback:
|
|
133
|
+
// Vitest's fake-timer helpers can await async callbacks, which would block
|
|
134
|
+
// tests that simulate long-running jobs. Runtime behavior is unchanged.
|
|
135
|
+
state.timer = setTimeout(() => {
|
|
136
|
+
void onTimer(state).catch((err) => {
|
|
117
137
|
state.deps.log.error({ err: String(err) }, "cron: timer tick failed");
|
|
118
|
-
}
|
|
138
|
+
});
|
|
119
139
|
}, clampedDelay);
|
|
120
140
|
state.deps.log.debug({ nextAt, delayMs: clampedDelay, clamped: delay > MAX_TIMER_DELAY_MS }, "cron: timer armed");
|
|
121
141
|
}
|
|
122
142
|
export async function onTimer(state) {
|
|
123
143
|
if (state.running) {
|
|
144
|
+
// Re-arm the timer so the scheduler keeps ticking even when a job is
|
|
145
|
+
// still executing. Without this, a long-running job (e.g. an agentTurn
|
|
146
|
+
// exceeding MAX_TIMER_DELAY_MS) causes the clamped 60 s timer to fire
|
|
147
|
+
// while `running` is true. The early return then leaves no timer set,
|
|
148
|
+
// silently killing the scheduler until the next gateway restart.
|
|
149
|
+
//
|
|
150
|
+
// We use MAX_TIMER_DELAY_MS as a fixed re-check interval to avoid a
|
|
151
|
+
// zero-delay hot-loop when past-due jobs are waiting for the current
|
|
152
|
+
// execution to finish.
|
|
153
|
+
// See: https://github.com/poolbot/poolbot/issues/12025
|
|
154
|
+
if (state.timer) {
|
|
155
|
+
clearTimeout(state.timer);
|
|
156
|
+
}
|
|
157
|
+
state.timer = setTimeout(() => {
|
|
158
|
+
void onTimer(state).catch((err) => {
|
|
159
|
+
state.deps.log.error({ err: String(err) }, "cron: timer tick failed");
|
|
160
|
+
});
|
|
161
|
+
}, MAX_TIMER_DELAY_MS);
|
|
124
162
|
return;
|
|
125
163
|
}
|
|
126
164
|
state.running = true;
|
|
@@ -129,7 +167,10 @@ export async function onTimer(state) {
|
|
|
129
167
|
await ensureLoaded(state, { forceReload: true, skipRecompute: true });
|
|
130
168
|
const due = findDueJobs(state);
|
|
131
169
|
if (due.length === 0) {
|
|
132
|
-
|
|
170
|
+
// Use maintenance-only recompute to avoid advancing past-due nextRunAtMs
|
|
171
|
+
// values without execution. This prevents jobs from being silently skipped
|
|
172
|
+
// when the timer wakes up but findDueJobs returns empty (see #13992).
|
|
173
|
+
const changed = recomputeNextRunsForMaintenance(state);
|
|
133
174
|
if (changed) {
|
|
134
175
|
await persist(state);
|
|
135
176
|
}
|
|
@@ -151,21 +192,37 @@ export async function onTimer(state) {
|
|
|
151
192
|
const startedAt = state.deps.nowMs();
|
|
152
193
|
job.state.runningAtMs = startedAt;
|
|
153
194
|
emit(state, { jobId: job.id, action: "started", runAtMs: startedAt });
|
|
154
|
-
const
|
|
155
|
-
? job.payload.timeoutSeconds * 1_000
|
|
195
|
+
const configuredTimeoutMs = job.payload.kind === "agentTurn" && typeof job.payload.timeoutSeconds === "number"
|
|
196
|
+
? Math.floor(job.payload.timeoutSeconds * 1_000)
|
|
197
|
+
: undefined;
|
|
198
|
+
const jobTimeoutMs = configuredTimeoutMs !== undefined
|
|
199
|
+
? configuredTimeoutMs <= 0
|
|
200
|
+
? undefined
|
|
201
|
+
: configuredTimeoutMs
|
|
156
202
|
: DEFAULT_JOB_TIMEOUT_MS;
|
|
157
203
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
204
|
+
const result = typeof jobTimeoutMs === "number"
|
|
205
|
+
? await (async () => {
|
|
206
|
+
let timeoutId;
|
|
207
|
+
try {
|
|
208
|
+
return await Promise.race([
|
|
209
|
+
executeJobCore(state, job),
|
|
210
|
+
new Promise((_, reject) => {
|
|
211
|
+
timeoutId = setTimeout(() => reject(new Error("cron: job execution timed out")), jobTimeoutMs);
|
|
212
|
+
}),
|
|
213
|
+
]);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
if (timeoutId) {
|
|
217
|
+
clearTimeout(timeoutId);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
})()
|
|
221
|
+
: await executeJobCore(state, job);
|
|
165
222
|
results.push({ jobId: id, ...result, startedAt, endedAt: state.deps.nowMs() });
|
|
166
223
|
}
|
|
167
224
|
catch (err) {
|
|
168
|
-
state.deps.log.warn({ jobId: id, jobName: job.name, timeoutMs: jobTimeoutMs }, `cron: job failed: ${String(err)}`);
|
|
225
|
+
state.deps.log.warn({ jobId: id, jobName: job.name, timeoutMs: jobTimeoutMs ?? null }, `cron: job failed: ${String(err)}`);
|
|
169
226
|
results.push({
|
|
170
227
|
jobId: id,
|
|
171
228
|
status: "error",
|
|
@@ -189,24 +246,18 @@ export async function onTimer(state) {
|
|
|
189
246
|
startedAt: result.startedAt,
|
|
190
247
|
endedAt: result.endedAt,
|
|
191
248
|
});
|
|
192
|
-
|
|
193
|
-
jobId: job.id,
|
|
194
|
-
action: "finished",
|
|
195
|
-
status: result.status,
|
|
196
|
-
error: result.error,
|
|
197
|
-
summary: result.summary,
|
|
198
|
-
sessionId: result.sessionId,
|
|
199
|
-
sessionKey: result.sessionKey,
|
|
200
|
-
runAtMs: result.startedAt,
|
|
201
|
-
durationMs: job.state.lastDurationMs,
|
|
202
|
-
nextRunAtMs: job.state.nextRunAtMs,
|
|
203
|
-
});
|
|
249
|
+
emitJobFinished(state, job, result, result.startedAt);
|
|
204
250
|
if (shouldDelete && state.store) {
|
|
205
251
|
state.store.jobs = state.store.jobs.filter((j) => j.id !== job.id);
|
|
206
252
|
emit(state, { jobId: job.id, action: "removed" });
|
|
207
253
|
}
|
|
208
254
|
}
|
|
209
|
-
|
|
255
|
+
// Use maintenance-only recompute to avoid advancing past-due
|
|
256
|
+
// nextRunAtMs values that became due between findDueJobs and this
|
|
257
|
+
// locked block. The full recomputeNextRuns would silently skip
|
|
258
|
+
// those jobs (advancing nextRunAtMs without execution), causing
|
|
259
|
+
// daily cron schedules to jump 48 h instead of 24 h (#17852).
|
|
260
|
+
recomputeNextRunsForMaintenance(state);
|
|
210
261
|
await persist(state);
|
|
211
262
|
});
|
|
212
263
|
}
|
|
@@ -254,41 +305,49 @@ function findDueJobs(state) {
|
|
|
254
305
|
return [];
|
|
255
306
|
}
|
|
256
307
|
const now = state.deps.nowMs();
|
|
257
|
-
return state
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
return
|
|
269
|
-
}
|
|
308
|
+
return collectRunnableJobs(state, now);
|
|
309
|
+
}
|
|
310
|
+
function isRunnableJob(params) {
|
|
311
|
+
const { job, nowMs } = params;
|
|
312
|
+
if (!job.state) {
|
|
313
|
+
job.state = {};
|
|
314
|
+
}
|
|
315
|
+
if (!job.enabled) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
if (params.skipJobIds?.has(job.id)) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
if (typeof job.state.runningAtMs === "number") {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
if (params.skipAtIfAlreadyRan && job.schedule.kind === "at" && job.state.lastStatus) {
|
|
325
|
+
// Any terminal status (ok, error, skipped) means the job already ran at least once.
|
|
326
|
+
// Don't re-fire it on restart — applyJobResult disables one-shot jobs, but guard
|
|
327
|
+
// here defensively (#13845).
|
|
328
|
+
return false;
|
|
329
|
+
}
|
|
330
|
+
const next = job.state.nextRunAtMs;
|
|
331
|
+
return typeof next === "number" && nowMs >= next;
|
|
332
|
+
}
|
|
333
|
+
function collectRunnableJobs(state, nowMs, opts) {
|
|
334
|
+
if (!state.store) {
|
|
335
|
+
return [];
|
|
336
|
+
}
|
|
337
|
+
return state.store.jobs.filter((job) => isRunnableJob({
|
|
338
|
+
job,
|
|
339
|
+
nowMs,
|
|
340
|
+
skipJobIds: opts?.skipJobIds,
|
|
341
|
+
skipAtIfAlreadyRan: opts?.skipAtIfAlreadyRan,
|
|
342
|
+
}));
|
|
270
343
|
}
|
|
271
|
-
export async function runMissedJobs(state) {
|
|
344
|
+
export async function runMissedJobs(state, opts) {
|
|
272
345
|
if (!state.store) {
|
|
273
346
|
return;
|
|
274
347
|
}
|
|
275
348
|
const now = state.deps.nowMs();
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
j.state = {};
|
|
279
|
-
}
|
|
280
|
-
if (!j.enabled) {
|
|
281
|
-
return false;
|
|
282
|
-
}
|
|
283
|
-
if (typeof j.state.runningAtMs === "number") {
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
const next = j.state.nextRunAtMs;
|
|
287
|
-
if (j.schedule.kind === "at" && j.state.lastStatus === "ok") {
|
|
288
|
-
return false;
|
|
289
|
-
}
|
|
290
|
-
return typeof next === "number" && now >= next;
|
|
291
|
-
});
|
|
349
|
+
const skipJobIds = opts?.skipJobIds;
|
|
350
|
+
const missed = collectRunnableJobs(state, now, { skipJobIds, skipAtIfAlreadyRan: true });
|
|
292
351
|
if (missed.length > 0) {
|
|
293
352
|
state.deps.log.info({ count: missed.length, jobIds: missed.map((j) => j.id) }, "cron: running missed jobs after restart");
|
|
294
353
|
for (const job of missed) {
|
|
@@ -301,19 +360,7 @@ export async function runDueJobs(state) {
|
|
|
301
360
|
return;
|
|
302
361
|
}
|
|
303
362
|
const now = state.deps.nowMs();
|
|
304
|
-
const due = state
|
|
305
|
-
if (!j.state) {
|
|
306
|
-
j.state = {};
|
|
307
|
-
}
|
|
308
|
-
if (!j.enabled) {
|
|
309
|
-
return false;
|
|
310
|
-
}
|
|
311
|
-
if (typeof j.state.runningAtMs === "number") {
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
const next = j.state.nextRunAtMs;
|
|
315
|
-
return typeof next === "number" && now >= next;
|
|
316
|
-
});
|
|
363
|
+
const due = collectRunnableJobs(state, now);
|
|
317
364
|
for (const job of due) {
|
|
318
365
|
await executeJob(state, job, now, { forced: false });
|
|
319
366
|
}
|
|
@@ -330,24 +377,37 @@ async function executeJobCore(state, job) {
|
|
|
330
377
|
: 'main job requires payload.kind="systemEvent"',
|
|
331
378
|
};
|
|
332
379
|
}
|
|
333
|
-
state.deps.enqueueSystemEvent(text, {
|
|
380
|
+
state.deps.enqueueSystemEvent(text, {
|
|
381
|
+
agentId: job.agentId,
|
|
382
|
+
sessionKey: job.sessionKey,
|
|
383
|
+
contextKey: `cron:${job.id}`,
|
|
384
|
+
});
|
|
334
385
|
if (job.wakeMode === "now" && state.deps.runHeartbeatOnce) {
|
|
335
386
|
const reason = `cron:${job.id}`;
|
|
336
387
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
337
|
-
const maxWaitMs = 2 * 60_000;
|
|
388
|
+
const maxWaitMs = state.deps.wakeNowHeartbeatBusyMaxWaitMs ?? 2 * 60_000;
|
|
389
|
+
const retryDelayMs = state.deps.wakeNowHeartbeatBusyRetryDelayMs ?? 250;
|
|
338
390
|
const waitStartedAt = state.deps.nowMs();
|
|
339
391
|
let heartbeatResult;
|
|
340
392
|
for (;;) {
|
|
341
|
-
heartbeatResult = await state.deps.runHeartbeatOnce({
|
|
393
|
+
heartbeatResult = await state.deps.runHeartbeatOnce({
|
|
394
|
+
reason,
|
|
395
|
+
agentId: job.agentId,
|
|
396
|
+
sessionKey: job.sessionKey,
|
|
397
|
+
});
|
|
342
398
|
if (heartbeatResult.status !== "skipped" ||
|
|
343
399
|
heartbeatResult.reason !== "requests-in-flight") {
|
|
344
400
|
break;
|
|
345
401
|
}
|
|
346
402
|
if (state.deps.nowMs() - waitStartedAt > maxWaitMs) {
|
|
347
|
-
state.deps.requestHeartbeatNow({
|
|
403
|
+
state.deps.requestHeartbeatNow({
|
|
404
|
+
reason,
|
|
405
|
+
agentId: job.agentId,
|
|
406
|
+
sessionKey: job.sessionKey,
|
|
407
|
+
});
|
|
348
408
|
return { status: "ok", summary: text };
|
|
349
409
|
}
|
|
350
|
-
await delay(
|
|
410
|
+
await delay(retryDelayMs);
|
|
351
411
|
}
|
|
352
412
|
if (heartbeatResult.status === "ran") {
|
|
353
413
|
return { status: "ok", summary: text };
|
|
@@ -360,7 +420,11 @@ async function executeJobCore(state, job) {
|
|
|
360
420
|
}
|
|
361
421
|
}
|
|
362
422
|
else {
|
|
363
|
-
state.deps.requestHeartbeatNow({
|
|
423
|
+
state.deps.requestHeartbeatNow({
|
|
424
|
+
reason: `cron:${job.id}`,
|
|
425
|
+
agentId: job.agentId,
|
|
426
|
+
sessionKey: job.sessionKey,
|
|
427
|
+
});
|
|
364
428
|
return { status: "ok", summary: text };
|
|
365
429
|
}
|
|
366
430
|
}
|
|
@@ -371,15 +435,28 @@ async function executeJobCore(state, job) {
|
|
|
371
435
|
job,
|
|
372
436
|
message: job.payload.message,
|
|
373
437
|
});
|
|
374
|
-
// Post a short summary back to the main session
|
|
438
|
+
// Post a short summary back to the main session — but only when the
|
|
439
|
+
// isolated run did NOT already deliver its output to the target channel.
|
|
440
|
+
// When `res.delivered` is true the announce flow (or direct outbound
|
|
441
|
+
// delivery) already sent the result, so posting the summary to main
|
|
442
|
+
// would wake the main agent and cause a duplicate message.
|
|
443
|
+
// See: https://github.com/poolbot/poolbot/issues/15692
|
|
375
444
|
const summaryText = res.summary?.trim();
|
|
376
445
|
const deliveryPlan = resolveCronDeliveryPlan(job);
|
|
377
|
-
if (summaryText && deliveryPlan.requested) {
|
|
446
|
+
if (summaryText && deliveryPlan.requested && !res.delivered) {
|
|
378
447
|
const prefix = "Cron";
|
|
379
448
|
const label = res.status === "error" ? `${prefix} (error): ${summaryText}` : `${prefix}: ${summaryText}`;
|
|
380
|
-
state.deps.enqueueSystemEvent(label, {
|
|
449
|
+
state.deps.enqueueSystemEvent(label, {
|
|
450
|
+
agentId: job.agentId,
|
|
451
|
+
sessionKey: job.sessionKey,
|
|
452
|
+
contextKey: `cron:${job.id}`,
|
|
453
|
+
});
|
|
381
454
|
if (job.wakeMode === "now") {
|
|
382
|
-
state.deps.requestHeartbeatNow({
|
|
455
|
+
state.deps.requestHeartbeatNow({
|
|
456
|
+
reason: `cron:${job.id}`,
|
|
457
|
+
agentId: job.agentId,
|
|
458
|
+
sessionKey: job.sessionKey,
|
|
459
|
+
});
|
|
383
460
|
}
|
|
384
461
|
}
|
|
385
462
|
return {
|
|
@@ -388,6 +465,9 @@ async function executeJobCore(state, job) {
|
|
|
388
465
|
summary: res.summary,
|
|
389
466
|
sessionId: res.sessionId,
|
|
390
467
|
sessionKey: res.sessionKey,
|
|
468
|
+
model: res.model,
|
|
469
|
+
provider: res.provider,
|
|
470
|
+
usage: res.usage,
|
|
391
471
|
};
|
|
392
472
|
}
|
|
393
473
|
/**
|
|
@@ -416,22 +496,28 @@ export async function executeJob(state, job, _nowMs, _opts) {
|
|
|
416
496
|
startedAt,
|
|
417
497
|
endedAt,
|
|
418
498
|
});
|
|
499
|
+
emitJobFinished(state, job, coreResult, startedAt);
|
|
500
|
+
if (shouldDelete && state.store) {
|
|
501
|
+
state.store.jobs = state.store.jobs.filter((j) => j.id !== job.id);
|
|
502
|
+
emit(state, { jobId: job.id, action: "removed" });
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function emitJobFinished(state, job, result, runAtMs) {
|
|
419
506
|
emit(state, {
|
|
420
507
|
jobId: job.id,
|
|
421
508
|
action: "finished",
|
|
422
|
-
status:
|
|
423
|
-
error:
|
|
424
|
-
summary:
|
|
425
|
-
sessionId:
|
|
426
|
-
sessionKey:
|
|
427
|
-
runAtMs
|
|
509
|
+
status: result.status,
|
|
510
|
+
error: result.error,
|
|
511
|
+
summary: result.summary,
|
|
512
|
+
sessionId: result.sessionId,
|
|
513
|
+
sessionKey: result.sessionKey,
|
|
514
|
+
runAtMs,
|
|
428
515
|
durationMs: job.state.lastDurationMs,
|
|
429
516
|
nextRunAtMs: job.state.nextRunAtMs,
|
|
517
|
+
model: result.model,
|
|
518
|
+
provider: result.provider,
|
|
519
|
+
usage: result.usage,
|
|
430
520
|
});
|
|
431
|
-
if (shouldDelete && state.store) {
|
|
432
|
-
state.store.jobs = state.store.jobs.filter((j) => j.id !== job.id);
|
|
433
|
-
emit(state, { jobId: job.id, action: "removed" });
|
|
434
|
-
}
|
|
435
521
|
}
|
|
436
522
|
export function wake(state, opts) {
|
|
437
523
|
const text = opts.text.trim();
|
package/dist/cron/service.js
CHANGED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const DEFAULT_TOP_OF_HOUR_STAGGER_MS = 5 * 60 * 1000;
|
|
2
|
+
function parseCronFields(expr) {
|
|
3
|
+
return expr.trim().split(/\s+/).filter(Boolean);
|
|
4
|
+
}
|
|
5
|
+
export function isRecurringTopOfHourCronExpr(expr) {
|
|
6
|
+
const fields = parseCronFields(expr);
|
|
7
|
+
if (fields.length === 5) {
|
|
8
|
+
const [minuteField, hourField] = fields;
|
|
9
|
+
return minuteField === "0" && hourField.includes("*");
|
|
10
|
+
}
|
|
11
|
+
if (fields.length === 6) {
|
|
12
|
+
const [secondField, minuteField, hourField] = fields;
|
|
13
|
+
return secondField === "0" && minuteField === "0" && hourField.includes("*");
|
|
14
|
+
}
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
export function normalizeCronStaggerMs(raw) {
|
|
18
|
+
const numeric = typeof raw === "number"
|
|
19
|
+
? raw
|
|
20
|
+
: typeof raw === "string" && raw.trim()
|
|
21
|
+
? Number(raw)
|
|
22
|
+
: Number.NaN;
|
|
23
|
+
if (!Number.isFinite(numeric)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
return Math.max(0, Math.floor(numeric));
|
|
27
|
+
}
|
|
28
|
+
export function resolveDefaultCronStaggerMs(expr) {
|
|
29
|
+
return isRecurringTopOfHourCronExpr(expr) ? DEFAULT_TOP_OF_HOUR_STAGGER_MS : undefined;
|
|
30
|
+
}
|
|
31
|
+
export function resolveCronStaggerMs(schedule) {
|
|
32
|
+
const explicit = normalizeCronStaggerMs(schedule.staggerMs);
|
|
33
|
+
if (explicit !== undefined) {
|
|
34
|
+
return explicit;
|
|
35
|
+
}
|
|
36
|
+
return resolveDefaultCronStaggerMs(schedule.expr) ?? 0;
|
|
37
|
+
}
|