@poolzin/pool-bot 2026.2.0 → 2026.2.2
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 +118 -0
- package/README-header.png +0 -0
- package/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/context.js +1 -1
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-catalog.js +1 -1
- package/dist/agents/model-selection.js +21 -0
- package/dist/agents/pi-embedded-block-chunker.js +117 -42
- package/dist/agents/pi-embedded-helpers/errors.js +183 -78
- package/dist/agents/pi-embedded-helpers.js +1 -1
- package/dist/agents/pi-embedded-runner/compact.js +8 -10
- package/dist/agents/pi-embedded-runner/model.js +62 -3
- package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
- package/dist/agents/pi-embedded-runner/run.js +199 -46
- package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
- package/dist/agents/pi-embedded-subscribe.js +118 -29
- package/dist/agents/pi-tools.js +10 -5
- package/dist/agents/poolbot-tools.js +15 -10
- package/dist/agents/sandbox-paths.js +31 -0
- package/dist/agents/session-tool-result-guard.js +94 -15
- package/dist/agents/shell-utils.js +51 -0
- package/dist/agents/skills/bundled-context.js +23 -0
- package/dist/agents/skills/bundled-dir.js +41 -7
- package/dist/agents/skills-install.js +60 -23
- package/dist/agents/subagent-announce.js +79 -34
- package/dist/agents/tool-policy.conformance.js +14 -0
- package/dist/agents/tool-policy.js +24 -0
- package/dist/agents/tools/cron-tool.js +166 -19
- package/dist/agents/tools/discord-actions-presence.js +78 -0
- package/dist/agents/tools/image-tool.js +1 -1
- package/dist/agents/tools/message-tool.js +56 -2
- package/dist/agents/tools/sessions-history-tool.js +69 -1
- package/dist/agents/tools/web-search.js +211 -42
- package/dist/agents/usage.js +23 -1
- package/dist/agents/workspace-run.js +67 -0
- package/dist/agents/workspace-templates.js +44 -0
- package/dist/auto-reply/command-auth.js +121 -6
- package/dist/auto-reply/envelope.js +74 -82
- package/dist/auto-reply/reply/commands-compact.js +1 -0
- package/dist/auto-reply/reply/commands-context-report.js +1 -0
- package/dist/auto-reply/reply/commands-context.js +1 -0
- package/dist/auto-reply/reply/commands-models.js +107 -60
- package/dist/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/reply/get-reply-run.js +2 -1
- package/dist/auto-reply/reply/inbound-context.js +5 -1
- package/dist/auto-reply/reply/mentions.js +1 -1
- package/dist/auto-reply/reply/model-selection.js +3 -3
- package/dist/auto-reply/thinking.js +88 -43
- package/dist/browser/bridge-server.js +13 -0
- package/dist/browser/cdp.helpers.js +38 -24
- package/dist/browser/client-fetch.js +50 -7
- package/dist/browser/config.js +1 -10
- package/dist/browser/extension-relay.js +101 -40
- package/dist/browser/pw-ai.js +1 -1
- package/dist/browser/pw-session.js +143 -8
- package/dist/browser/pw-tools-core.interactions.js +125 -27
- package/dist/browser/pw-tools-core.responses.js +1 -1
- package/dist/browser/pw-tools-core.state.js +1 -1
- package/dist/browser/routes/agent.act.js +86 -41
- package/dist/browser/routes/dispatcher.js +4 -4
- package/dist/browser/screenshot.js +1 -1
- package/dist/browser/server.js +13 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/index.html +28 -28
- package/dist/channels/reply-prefix.js +8 -1
- package/dist/cli/cron-cli/register.cron-add.js +61 -40
- package/dist/cli/cron-cli/register.cron-edit.js +60 -34
- package/dist/cli/cron-cli/shared.js +56 -41
- package/dist/cli/dns-cli.js +26 -14
- package/dist/cli/gateway-cli/register.js +37 -19
- package/dist/cli/memory-cli.js +5 -5
- package/dist/cli/parse-bytes.js +37 -0
- package/dist/cli/update-cli.js +173 -52
- package/dist/commands/agent.js +1 -0
- package/dist/commands/auth-choice.apply.oauth.js +1 -1
- package/dist/commands/doctor-config-flow.js +61 -5
- package/dist/commands/doctor-state-migrations.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/model-allowlist.js +29 -0
- package/dist/commands/model-picker.js +2 -1
- package/dist/commands/models/list.registry.js +1 -1
- package/dist/commands/models/list.status-command.js +43 -23
- package/dist/commands/models/shared.js +15 -0
- package/dist/commands/onboard-custom.js +384 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
- package/dist/commands/onboard-skills.js +63 -38
- package/dist/commands/openai-model-default.js +41 -0
- package/dist/compat/legacy-names.js +2 -0
- package/dist/config/defaults.js +3 -2
- package/dist/config/paths.js +136 -35
- package/dist/config/plugin-auto-enable.js +21 -5
- package/dist/config/redact-snapshot.js +153 -0
- package/dist/config/schema.field-metadata.js +590 -0
- package/dist/config/schema.js +2 -2
- package/dist/config/sessions/store.js +291 -23
- package/dist/config/zod-schema.agent-defaults.js +3 -0
- package/dist/config/zod-schema.agent-runtime.js +13 -2
- package/dist/config/zod-schema.providers-core.js +142 -0
- package/dist/config/zod-schema.session.js +3 -0
- package/dist/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
- package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
- package/dist/control-ui/index.html +4 -4
- package/dist/cron/delivery.js +57 -0
- package/dist/cron/isolated-agent/delivery-target.js +18 -3
- package/dist/cron/isolated-agent/helpers.js +22 -5
- package/dist/cron/isolated-agent/run.js +172 -63
- package/dist/cron/isolated-agent/session.js +2 -0
- package/dist/cron/normalize.js +356 -28
- package/dist/cron/parse.js +10 -5
- package/dist/cron/run-log.js +35 -10
- package/dist/cron/schedule.js +41 -6
- package/dist/cron/service/jobs.js +208 -35
- package/dist/cron/service/ops.js +72 -16
- package/dist/cron/service/state.js +2 -0
- package/dist/cron/service/store.js +386 -14
- package/dist/cron/service/timer.js +390 -147
- package/dist/cron/session-reaper.js +86 -0
- package/dist/cron/store.js +23 -8
- package/dist/cron/validate-timestamp.js +43 -0
- package/dist/discord/monitor/agent-components.js +438 -0
- package/dist/discord/monitor/allow-list.js +28 -5
- package/dist/discord/monitor/gateway-registry.js +29 -0
- package/dist/discord/monitor/native-command.js +44 -23
- package/dist/discord/monitor/sender-identity.js +45 -0
- package/dist/discord/pluralkit.js +27 -0
- package/dist/discord/send.outbound.js +92 -5
- package/dist/discord/send.shared.js +60 -23
- package/dist/discord/targets.js +84 -1
- package/dist/entry.js +15 -9
- package/dist/extensionAPI.js +8 -0
- package/dist/gateway/control-ui.js +8 -1
- package/dist/gateway/hooks-mapping.js +3 -0
- package/dist/gateway/hooks.js +65 -0
- package/dist/gateway/net.js +96 -31
- package/dist/gateway/node-command-policy.js +50 -15
- package/dist/gateway/origin-check.js +56 -0
- package/dist/gateway/protocol/client-info.js +9 -0
- package/dist/gateway/protocol/index.js +9 -2
- package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
- package/dist/gateway/protocol/schema/cron.js +22 -10
- package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
- package/dist/gateway/protocol/schema/sessions.js +12 -0
- package/dist/gateway/server/hooks.js +1 -1
- package/dist/gateway/server-broadcast.js +26 -9
- package/dist/gateway/server-chat.js +112 -23
- package/dist/gateway/server-discovery-runtime.js +10 -2
- package/dist/gateway/server-http.js +109 -11
- package/dist/gateway/server-methods/agent-timestamp.js +60 -0
- package/dist/gateway/server-methods/agents.js +321 -2
- package/dist/gateway/server-methods/usage.js +559 -16
- package/dist/gateway/server-runtime-state.js +22 -8
- package/dist/gateway/server-startup-memory.js +16 -0
- package/dist/gateway/server.impl.js +5 -1
- package/dist/gateway/session-utils.fs.js +23 -25
- package/dist/gateway/session-utils.js +20 -10
- package/dist/gateway/sessions-patch.js +7 -22
- package/dist/gateway/test-helpers.mocks.js +11 -7
- package/dist/gateway/test-helpers.server.js +35 -2
- package/dist/imessage/constants.js +2 -0
- package/dist/imessage/monitor/deliver.js +4 -1
- package/dist/imessage/monitor/monitor-provider.js +51 -1
- package/dist/infra/bonjour-discovery.js +131 -70
- package/dist/infra/control-ui-assets.js +134 -12
- package/dist/infra/errors.js +12 -0
- package/dist/infra/exec-approvals.js +266 -57
- package/dist/infra/format-time/format-datetime.js +79 -0
- package/dist/infra/format-time/format-duration.js +81 -0
- package/dist/infra/format-time/format-relative.js +80 -0
- package/dist/infra/heartbeat-runner.js +140 -49
- package/dist/infra/home-dir.js +54 -0
- package/dist/infra/net/fetch-guard.js +122 -0
- package/dist/infra/net/ssrf.js +65 -29
- package/dist/infra/outbound/abort.js +14 -0
- package/dist/infra/outbound/message-action-runner.js +77 -13
- package/dist/infra/outbound/outbound-session.js +143 -37
- package/dist/infra/poolbot-root.js +43 -1
- package/dist/infra/session-cost-usage.js +631 -41
- package/dist/infra/state-migrations.js +317 -47
- package/dist/infra/update-global.js +35 -0
- package/dist/infra/update-runner.js +149 -43
- package/dist/infra/warning-filter.js +65 -0
- package/dist/infra/widearea-dns.js +30 -9
- package/dist/logging/redact-identifier.js +12 -0
- package/dist/media/fetch.js +81 -58
- package/dist/media/store.js +2 -0
- package/dist/media-understanding/apply.js +403 -3
- package/dist/media-understanding/attachments.js +38 -27
- package/dist/media-understanding/defaults.js +16 -0
- package/dist/media-understanding/providers/deepgram/audio.js +22 -14
- package/dist/media-understanding/providers/google/audio.js +24 -17
- package/dist/media-understanding/providers/google/video.js +24 -17
- package/dist/media-understanding/providers/image.js +3 -3
- package/dist/media-understanding/providers/index.js +4 -1
- package/dist/media-understanding/providers/openai/audio.js +22 -14
- package/dist/media-understanding/providers/shared.js +16 -11
- package/dist/media-understanding/providers/zai/index.js +6 -0
- package/dist/media-understanding/runner.js +158 -90
- package/dist/memory/batch-voyage.js +277 -0
- package/dist/memory/embeddings-voyage.js +75 -0
- package/dist/memory/embeddings.js +28 -16
- package/dist/memory/internal.js +101 -18
- package/dist/memory/manager.js +154 -48
- package/dist/memory/search-manager.js +173 -0
- package/dist/memory/session-files.js +9 -3
- package/dist/node-host/runner.js +34 -24
- package/dist/node-host/with-timeout.js +27 -0
- package/dist/plugins/commands.js +5 -1
- package/dist/plugins/config-state.js +86 -7
- package/dist/plugins/source-display.js +51 -0
- package/dist/process/exec.js +20 -2
- package/dist/routing/resolve-route.js +12 -0
- package/dist/routing/session-key.js +15 -0
- package/dist/runtime.js +2 -0
- package/dist/security/audit-extra.async.js +601 -0
- package/dist/security/audit-extra.js +2 -830
- package/dist/security/audit-extra.sync.js +505 -0
- package/dist/security/channel-metadata.js +34 -0
- package/dist/security/external-content.js +88 -6
- package/dist/security/skill-scanner.js +330 -0
- package/dist/sessions/session-key-utils.js +7 -0
- package/dist/signal/monitor/event-handler.js +80 -1
- package/dist/slack/monitor/media.js +85 -15
- package/dist/tailscale/detect.js +1 -2
- package/dist/telegram/bot/helpers.js +109 -28
- package/dist/telegram/bot-handlers.js +144 -3
- package/dist/telegram/bot-message-context.js +37 -10
- package/dist/telegram/bot-message-dispatch.js +54 -17
- package/dist/telegram/bot-native-commands.js +86 -29
- package/dist/telegram/bot.js +30 -29
- package/dist/telegram/model-buttons.js +163 -0
- package/dist/telegram/monitor.js +110 -85
- package/dist/telegram/send.js +129 -47
- package/dist/terminal/restore.js +45 -0
- package/dist/test-helpers/state-dir-env.js +16 -0
- package/dist/tts/tts.js +12 -6
- package/dist/tui/tui-session-actions.js +166 -54
- package/dist/utils/fetch-timeout.js +20 -0
- package/dist/utils/normalize-secret-input.js +19 -0
- package/dist/utils/transcript-tools.js +58 -0
- package/dist/utils.js +45 -14
- package/dist/version.js +42 -5
- package/dist/wizard/clack-prompter.js +9 -6
- package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
- package/extensions/googlechat/package.json +2 -2
- package/extensions/line/node_modules/.bin/poolbot +21 -0
- package/extensions/line/package.json +1 -1
- package/extensions/matrix/node_modules/.bin/poolbot +21 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
- package/extensions/memory-core/package.json +4 -1
- package/extensions/twitch/node_modules/.bin/poolbot +21 -0
- package/extensions/twitch/package.json +1 -1
- package/package.json +183 -24
- package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
+
import { parseAbsoluteTimeMs } from "../parse.js";
|
|
2
3
|
import { computeNextRunAtMs } from "../schedule.js";
|
|
3
4
|
import { normalizeOptionalAgentId, normalizeOptionalText, normalizePayloadToSystemText, normalizeRequiredName, } from "./normalize.js";
|
|
4
5
|
const STUCK_RUN_MS = 2 * 60 * 60 * 1000;
|
|
6
|
+
function resolveEveryAnchorMs(params) {
|
|
7
|
+
const raw = params.schedule.anchorMs;
|
|
8
|
+
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
9
|
+
return Math.max(0, Math.floor(raw));
|
|
10
|
+
}
|
|
11
|
+
return Math.max(0, Math.floor(params.fallbackAnchorMs));
|
|
12
|
+
}
|
|
5
13
|
export function assertSupportedJobSpec(job) {
|
|
6
14
|
if (job.sessionTarget === "main" && job.payload.kind !== "systemEvent") {
|
|
7
15
|
throw new Error('main cron jobs require payload.kind="systemEvent"');
|
|
@@ -10,100 +18,189 @@ export function assertSupportedJobSpec(job) {
|
|
|
10
18
|
throw new Error('isolated cron jobs require payload.kind="agentTurn"');
|
|
11
19
|
}
|
|
12
20
|
}
|
|
21
|
+
function assertDeliverySupport(job) {
|
|
22
|
+
if (job.delivery && job.sessionTarget !== "isolated") {
|
|
23
|
+
throw new Error('cron delivery config is only supported for sessionTarget="isolated"');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
13
26
|
export function findJobOrThrow(state, id) {
|
|
14
27
|
const job = state.store?.jobs.find((j) => j.id === id);
|
|
15
|
-
if (!job)
|
|
28
|
+
if (!job) {
|
|
16
29
|
throw new Error(`unknown cron job id: ${id}`);
|
|
30
|
+
}
|
|
17
31
|
return job;
|
|
18
32
|
}
|
|
19
33
|
export function computeJobNextRunAtMs(job, nowMs) {
|
|
20
|
-
if (!job.enabled)
|
|
34
|
+
if (!job.enabled) {
|
|
21
35
|
return undefined;
|
|
36
|
+
}
|
|
37
|
+
if (job.schedule.kind === "every") {
|
|
38
|
+
const anchorMs = resolveEveryAnchorMs({
|
|
39
|
+
schedule: job.schedule,
|
|
40
|
+
fallbackAnchorMs: job.createdAtMs,
|
|
41
|
+
});
|
|
42
|
+
return computeNextRunAtMs({ ...job.schedule, anchorMs }, nowMs);
|
|
43
|
+
}
|
|
22
44
|
if (job.schedule.kind === "at") {
|
|
23
45
|
// One-shot jobs stay due until they successfully finish.
|
|
24
|
-
if (job.state.lastStatus === "ok" && job.state.lastRunAtMs)
|
|
46
|
+
if (job.state.lastStatus === "ok" && job.state.lastRunAtMs) {
|
|
25
47
|
return undefined;
|
|
26
|
-
|
|
48
|
+
}
|
|
49
|
+
// Handle both canonical `at` (string) and legacy `atMs` (number) fields.
|
|
50
|
+
// The store migration should convert atMs→at, but be defensive in case
|
|
51
|
+
// the migration hasn't run yet or was bypassed.
|
|
52
|
+
const schedule = job.schedule;
|
|
53
|
+
const atMs = typeof schedule.atMs === "number" && Number.isFinite(schedule.atMs) && schedule.atMs > 0
|
|
54
|
+
? schedule.atMs
|
|
55
|
+
: typeof schedule.atMs === "string"
|
|
56
|
+
? parseAbsoluteTimeMs(schedule.atMs)
|
|
57
|
+
: typeof schedule.at === "string"
|
|
58
|
+
? parseAbsoluteTimeMs(schedule.at)
|
|
59
|
+
: null;
|
|
60
|
+
return atMs !== null ? atMs : undefined;
|
|
27
61
|
}
|
|
28
62
|
return computeNextRunAtMs(job.schedule, nowMs);
|
|
29
63
|
}
|
|
30
64
|
export function recomputeNextRuns(state) {
|
|
31
|
-
if (!state.store)
|
|
32
|
-
return;
|
|
65
|
+
if (!state.store) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
let changed = false;
|
|
33
69
|
const now = state.deps.nowMs();
|
|
34
70
|
for (const job of state.store.jobs) {
|
|
35
|
-
if (!job.state)
|
|
71
|
+
if (!job.state) {
|
|
36
72
|
job.state = {};
|
|
73
|
+
changed = true;
|
|
74
|
+
}
|
|
37
75
|
if (!job.enabled) {
|
|
38
|
-
job.state.nextRunAtMs
|
|
39
|
-
|
|
76
|
+
if (job.state.nextRunAtMs !== undefined) {
|
|
77
|
+
job.state.nextRunAtMs = undefined;
|
|
78
|
+
changed = true;
|
|
79
|
+
}
|
|
80
|
+
if (job.state.runningAtMs !== undefined) {
|
|
81
|
+
job.state.runningAtMs = undefined;
|
|
82
|
+
changed = true;
|
|
83
|
+
}
|
|
40
84
|
continue;
|
|
41
85
|
}
|
|
42
86
|
const runningAt = job.state.runningAtMs;
|
|
43
87
|
if (typeof runningAt === "number" && now - runningAt > STUCK_RUN_MS) {
|
|
44
88
|
state.deps.log.warn({ jobId: job.id, runningAtMs: runningAt }, "cron: clearing stuck running marker");
|
|
45
89
|
job.state.runningAtMs = undefined;
|
|
90
|
+
changed = true;
|
|
91
|
+
}
|
|
92
|
+
// Only recompute if nextRunAtMs is missing or already past-due.
|
|
93
|
+
// Preserving a still-future nextRunAtMs avoids accidentally advancing
|
|
94
|
+
// a job that hasn't fired yet (e.g. during restart recovery).
|
|
95
|
+
const nextRun = job.state.nextRunAtMs;
|
|
96
|
+
const isDueOrMissing = nextRun === undefined || now >= nextRun;
|
|
97
|
+
if (isDueOrMissing) {
|
|
98
|
+
const newNext = computeJobNextRunAtMs(job, now);
|
|
99
|
+
if (job.state.nextRunAtMs !== newNext) {
|
|
100
|
+
job.state.nextRunAtMs = newNext;
|
|
101
|
+
changed = true;
|
|
102
|
+
}
|
|
46
103
|
}
|
|
47
|
-
job.state.nextRunAtMs = computeJobNextRunAtMs(job, now);
|
|
48
104
|
}
|
|
105
|
+
return changed;
|
|
49
106
|
}
|
|
50
107
|
export function nextWakeAtMs(state) {
|
|
51
108
|
const jobs = state.store?.jobs ?? [];
|
|
52
109
|
const enabled = jobs.filter((j) => j.enabled && typeof j.state.nextRunAtMs === "number");
|
|
53
|
-
if (enabled.length === 0)
|
|
110
|
+
if (enabled.length === 0) {
|
|
54
111
|
return undefined;
|
|
112
|
+
}
|
|
55
113
|
return enabled.reduce((min, j) => Math.min(min, j.state.nextRunAtMs), enabled[0].state.nextRunAtMs);
|
|
56
114
|
}
|
|
57
115
|
export function createJob(state, input) {
|
|
58
116
|
const now = state.deps.nowMs();
|
|
59
117
|
const id = crypto.randomUUID();
|
|
118
|
+
const schedule = input.schedule.kind === "every"
|
|
119
|
+
? {
|
|
120
|
+
...input.schedule,
|
|
121
|
+
anchorMs: resolveEveryAnchorMs({
|
|
122
|
+
schedule: input.schedule,
|
|
123
|
+
fallbackAnchorMs: now,
|
|
124
|
+
}),
|
|
125
|
+
}
|
|
126
|
+
: input.schedule;
|
|
127
|
+
const deleteAfterRun = typeof input.deleteAfterRun === "boolean"
|
|
128
|
+
? input.deleteAfterRun
|
|
129
|
+
: schedule.kind === "at"
|
|
130
|
+
? true
|
|
131
|
+
: undefined;
|
|
132
|
+
const enabled = typeof input.enabled === "boolean" ? input.enabled : true;
|
|
60
133
|
const job = {
|
|
61
134
|
id,
|
|
62
135
|
agentId: normalizeOptionalAgentId(input.agentId),
|
|
63
136
|
name: normalizeRequiredName(input.name),
|
|
64
137
|
description: normalizeOptionalText(input.description),
|
|
65
|
-
enabled
|
|
66
|
-
deleteAfterRun
|
|
138
|
+
enabled,
|
|
139
|
+
deleteAfterRun,
|
|
67
140
|
createdAtMs: now,
|
|
68
141
|
updatedAtMs: now,
|
|
69
|
-
schedule
|
|
142
|
+
schedule,
|
|
70
143
|
sessionTarget: input.sessionTarget,
|
|
71
144
|
wakeMode: input.wakeMode,
|
|
72
145
|
payload: input.payload,
|
|
73
|
-
|
|
146
|
+
delivery: input.delivery,
|
|
74
147
|
state: {
|
|
75
148
|
...input.state,
|
|
76
149
|
},
|
|
77
150
|
};
|
|
78
151
|
assertSupportedJobSpec(job);
|
|
152
|
+
assertDeliverySupport(job);
|
|
79
153
|
job.state.nextRunAtMs = computeJobNextRunAtMs(job, now);
|
|
80
154
|
return job;
|
|
81
155
|
}
|
|
82
156
|
export function applyJobPatch(job, patch) {
|
|
83
|
-
if ("name" in patch)
|
|
157
|
+
if ("name" in patch) {
|
|
84
158
|
job.name = normalizeRequiredName(patch.name);
|
|
85
|
-
|
|
159
|
+
}
|
|
160
|
+
if ("description" in patch) {
|
|
86
161
|
job.description = normalizeOptionalText(patch.description);
|
|
87
|
-
|
|
162
|
+
}
|
|
163
|
+
if (typeof patch.enabled === "boolean") {
|
|
88
164
|
job.enabled = patch.enabled;
|
|
89
|
-
|
|
165
|
+
}
|
|
166
|
+
if (typeof patch.deleteAfterRun === "boolean") {
|
|
90
167
|
job.deleteAfterRun = patch.deleteAfterRun;
|
|
91
|
-
|
|
168
|
+
}
|
|
169
|
+
if (patch.schedule) {
|
|
92
170
|
job.schedule = patch.schedule;
|
|
93
|
-
|
|
171
|
+
}
|
|
172
|
+
if (patch.sessionTarget) {
|
|
94
173
|
job.sessionTarget = patch.sessionTarget;
|
|
95
|
-
|
|
174
|
+
}
|
|
175
|
+
if (patch.wakeMode) {
|
|
96
176
|
job.wakeMode = patch.wakeMode;
|
|
97
|
-
|
|
177
|
+
}
|
|
178
|
+
if (patch.payload) {
|
|
98
179
|
job.payload = mergeCronPayload(job.payload, patch.payload);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
180
|
+
}
|
|
181
|
+
if (!patch.delivery && patch.payload?.kind === "agentTurn") {
|
|
182
|
+
// Back-compat: legacy clients still update delivery via payload fields.
|
|
183
|
+
const legacyDeliveryPatch = buildLegacyDeliveryPatch(patch.payload);
|
|
184
|
+
if (legacyDeliveryPatch &&
|
|
185
|
+
job.sessionTarget === "isolated" &&
|
|
186
|
+
job.payload.kind === "agentTurn") {
|
|
187
|
+
job.delivery = mergeCronDelivery(job.delivery, legacyDeliveryPatch);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (patch.delivery) {
|
|
191
|
+
job.delivery = mergeCronDelivery(job.delivery, patch.delivery);
|
|
192
|
+
}
|
|
193
|
+
if (job.sessionTarget === "main" && job.delivery) {
|
|
194
|
+
job.delivery = undefined;
|
|
195
|
+
}
|
|
196
|
+
if (patch.state) {
|
|
102
197
|
job.state = { ...job.state, ...patch.state };
|
|
198
|
+
}
|
|
103
199
|
if ("agentId" in patch) {
|
|
104
200
|
job.agentId = normalizeOptionalAgentId(patch.agentId);
|
|
105
201
|
}
|
|
106
202
|
assertSupportedJobSpec(job);
|
|
203
|
+
assertDeliverySupport(job);
|
|
107
204
|
}
|
|
108
205
|
function mergeCronPayload(existing, patch) {
|
|
109
206
|
if (patch.kind !== existing.kind) {
|
|
@@ -120,25 +217,69 @@ function mergeCronPayload(existing, patch) {
|
|
|
120
217
|
return buildPayloadFromPatch(patch);
|
|
121
218
|
}
|
|
122
219
|
const next = { ...existing };
|
|
123
|
-
if (typeof patch.message === "string")
|
|
220
|
+
if (typeof patch.message === "string") {
|
|
124
221
|
next.message = patch.message;
|
|
125
|
-
|
|
222
|
+
}
|
|
223
|
+
if (typeof patch.model === "string") {
|
|
126
224
|
next.model = patch.model;
|
|
127
|
-
|
|
225
|
+
}
|
|
226
|
+
if (typeof patch.thinking === "string") {
|
|
128
227
|
next.thinking = patch.thinking;
|
|
129
|
-
|
|
228
|
+
}
|
|
229
|
+
if (typeof patch.timeoutSeconds === "number") {
|
|
130
230
|
next.timeoutSeconds = patch.timeoutSeconds;
|
|
131
|
-
|
|
231
|
+
}
|
|
232
|
+
if (typeof patch.allowUnsafeExternalContent === "boolean") {
|
|
233
|
+
next.allowUnsafeExternalContent = patch.allowUnsafeExternalContent;
|
|
234
|
+
}
|
|
235
|
+
if (typeof patch.deliver === "boolean") {
|
|
132
236
|
next.deliver = patch.deliver;
|
|
133
|
-
|
|
237
|
+
}
|
|
238
|
+
if (typeof patch.channel === "string") {
|
|
134
239
|
next.channel = patch.channel;
|
|
135
|
-
|
|
240
|
+
}
|
|
241
|
+
if (typeof patch.to === "string") {
|
|
136
242
|
next.to = patch.to;
|
|
243
|
+
}
|
|
137
244
|
if (typeof patch.bestEffortDeliver === "boolean") {
|
|
138
245
|
next.bestEffortDeliver = patch.bestEffortDeliver;
|
|
139
246
|
}
|
|
140
247
|
return next;
|
|
141
248
|
}
|
|
249
|
+
function buildLegacyDeliveryPatch(payload) {
|
|
250
|
+
const deliver = payload.deliver;
|
|
251
|
+
const toRaw = typeof payload.to === "string" ? payload.to.trim() : "";
|
|
252
|
+
const hasLegacyHints = typeof deliver === "boolean" ||
|
|
253
|
+
typeof payload.bestEffortDeliver === "boolean" ||
|
|
254
|
+
Boolean(toRaw);
|
|
255
|
+
if (!hasLegacyHints) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
const patch = {};
|
|
259
|
+
let hasPatch = false;
|
|
260
|
+
if (deliver === false) {
|
|
261
|
+
patch.mode = "none";
|
|
262
|
+
hasPatch = true;
|
|
263
|
+
}
|
|
264
|
+
else if (deliver === true || toRaw) {
|
|
265
|
+
patch.mode = "announce";
|
|
266
|
+
hasPatch = true;
|
|
267
|
+
}
|
|
268
|
+
if (typeof payload.channel === "string") {
|
|
269
|
+
const channel = payload.channel.trim().toLowerCase();
|
|
270
|
+
patch.channel = channel ? channel : undefined;
|
|
271
|
+
hasPatch = true;
|
|
272
|
+
}
|
|
273
|
+
if (typeof payload.to === "string") {
|
|
274
|
+
patch.to = payload.to.trim();
|
|
275
|
+
hasPatch = true;
|
|
276
|
+
}
|
|
277
|
+
if (typeof payload.bestEffortDeliver === "boolean") {
|
|
278
|
+
patch.bestEffort = payload.bestEffortDeliver;
|
|
279
|
+
hasPatch = true;
|
|
280
|
+
}
|
|
281
|
+
return hasPatch ? patch : null;
|
|
282
|
+
}
|
|
142
283
|
function buildPayloadFromPatch(patch) {
|
|
143
284
|
if (patch.kind === "systemEvent") {
|
|
144
285
|
if (typeof patch.text !== "string" || patch.text.length === 0) {
|
|
@@ -155,20 +296,52 @@ function buildPayloadFromPatch(patch) {
|
|
|
155
296
|
model: patch.model,
|
|
156
297
|
thinking: patch.thinking,
|
|
157
298
|
timeoutSeconds: patch.timeoutSeconds,
|
|
299
|
+
allowUnsafeExternalContent: patch.allowUnsafeExternalContent,
|
|
158
300
|
deliver: patch.deliver,
|
|
159
301
|
channel: patch.channel,
|
|
160
302
|
to: patch.to,
|
|
161
303
|
bestEffortDeliver: patch.bestEffortDeliver,
|
|
162
304
|
};
|
|
163
305
|
}
|
|
306
|
+
function mergeCronDelivery(existing, patch) {
|
|
307
|
+
const next = {
|
|
308
|
+
mode: existing?.mode ?? "none",
|
|
309
|
+
channel: existing?.channel,
|
|
310
|
+
to: existing?.to,
|
|
311
|
+
bestEffort: existing?.bestEffort,
|
|
312
|
+
};
|
|
313
|
+
if (typeof patch.mode === "string") {
|
|
314
|
+
next.mode = patch.mode === "deliver" ? "announce" : patch.mode;
|
|
315
|
+
}
|
|
316
|
+
if ("channel" in patch) {
|
|
317
|
+
const channel = typeof patch.channel === "string" ? patch.channel.trim() : "";
|
|
318
|
+
next.channel = channel ? channel : undefined;
|
|
319
|
+
}
|
|
320
|
+
if ("to" in patch) {
|
|
321
|
+
const to = typeof patch.to === "string" ? patch.to.trim() : "";
|
|
322
|
+
next.to = to ? to : undefined;
|
|
323
|
+
}
|
|
324
|
+
if (typeof patch.bestEffort === "boolean") {
|
|
325
|
+
next.bestEffort = patch.bestEffort;
|
|
326
|
+
}
|
|
327
|
+
return next;
|
|
328
|
+
}
|
|
164
329
|
export function isJobDue(job, nowMs, opts) {
|
|
165
|
-
if (
|
|
330
|
+
if (!job.state) {
|
|
331
|
+
job.state = {};
|
|
332
|
+
}
|
|
333
|
+
if (typeof job.state.runningAtMs === "number") {
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
if (opts.forced) {
|
|
166
337
|
return true;
|
|
338
|
+
}
|
|
167
339
|
return job.enabled && typeof job.state.nextRunAtMs === "number" && nowMs >= job.state.nextRunAtMs;
|
|
168
340
|
}
|
|
169
341
|
export function resolveJobPayloadTextForMain(job) {
|
|
170
|
-
if (job.payload.kind !== "systemEvent")
|
|
342
|
+
if (job.payload.kind !== "systemEvent") {
|
|
171
343
|
return undefined;
|
|
344
|
+
}
|
|
172
345
|
const text = normalizePayloadToSystemText(job.payload);
|
|
173
346
|
return text.trim() ? text : undefined;
|
|
174
347
|
}
|
package/dist/cron/service/ops.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { applyJobPatch, computeJobNextRunAtMs, createJob, findJobOrThrow, isJobDue, nextWakeAtMs, recomputeNextRuns, } from "./jobs.js";
|
|
2
2
|
import { locked } from "./locked.js";
|
|
3
3
|
import { ensureLoaded, persist, warnIfDisabled } from "./store.js";
|
|
4
|
-
import { armTimer, emit, executeJob, stopTimer, wake } from "./timer.js";
|
|
4
|
+
import { armTimer, emit, executeJob, runMissedJobs, stopTimer, wake } from "./timer.js";
|
|
5
5
|
export async function start(state) {
|
|
6
6
|
await locked(state, async () => {
|
|
7
7
|
if (!state.deps.cronEnabled) {
|
|
8
8
|
state.deps.log.info({ enabled: false }, "cron: disabled");
|
|
9
9
|
return;
|
|
10
10
|
}
|
|
11
|
-
await ensureLoaded(state);
|
|
11
|
+
await ensureLoaded(state, { skipRecompute: true });
|
|
12
|
+
const jobs = state.store?.jobs ?? [];
|
|
13
|
+
for (const job of jobs) {
|
|
14
|
+
if (typeof job.state.runningAtMs === "number") {
|
|
15
|
+
state.deps.log.warn({ jobId: job.id, runningAtMs: job.state.runningAtMs }, "cron: clearing stale running marker on startup");
|
|
16
|
+
job.state.runningAtMs = undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
await runMissedJobs(state);
|
|
12
20
|
recomputeNextRuns(state);
|
|
13
21
|
await persist(state);
|
|
14
22
|
armTimer(state);
|
|
@@ -24,21 +32,33 @@ export function stop(state) {
|
|
|
24
32
|
}
|
|
25
33
|
export async function status(state) {
|
|
26
34
|
return await locked(state, async () => {
|
|
27
|
-
await ensureLoaded(state);
|
|
35
|
+
await ensureLoaded(state, { skipRecompute: true });
|
|
36
|
+
if (state.store) {
|
|
37
|
+
const changed = recomputeNextRuns(state);
|
|
38
|
+
if (changed) {
|
|
39
|
+
await persist(state);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
28
42
|
return {
|
|
29
43
|
enabled: state.deps.cronEnabled,
|
|
30
44
|
storePath: state.deps.storePath,
|
|
31
45
|
jobs: state.store?.jobs.length ?? 0,
|
|
32
|
-
nextWakeAtMs: state.deps.cronEnabled
|
|
46
|
+
nextWakeAtMs: state.deps.cronEnabled ? (nextWakeAtMs(state) ?? null) : null,
|
|
33
47
|
};
|
|
34
48
|
});
|
|
35
49
|
}
|
|
36
50
|
export async function list(state, opts) {
|
|
37
51
|
return await locked(state, async () => {
|
|
38
|
-
await ensureLoaded(state);
|
|
52
|
+
await ensureLoaded(state, { skipRecompute: true });
|
|
53
|
+
if (state.store) {
|
|
54
|
+
const changed = recomputeNextRuns(state);
|
|
55
|
+
if (changed) {
|
|
56
|
+
await persist(state);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
39
59
|
const includeDisabled = opts?.includeDisabled === true;
|
|
40
60
|
const jobs = (state.store?.jobs ?? []).filter((j) => includeDisabled || j.enabled);
|
|
41
|
-
return jobs.
|
|
61
|
+
return jobs.toSorted((a, b) => (a.state.nextRunAtMs ?? 0) - (b.state.nextRunAtMs ?? 0));
|
|
42
62
|
});
|
|
43
63
|
}
|
|
44
64
|
export async function add(state, input) {
|
|
@@ -47,8 +67,18 @@ export async function add(state, input) {
|
|
|
47
67
|
await ensureLoaded(state);
|
|
48
68
|
const job = createJob(state, input);
|
|
49
69
|
state.store?.jobs.push(job);
|
|
70
|
+
// Defensive: recompute all next-run times to ensure consistency
|
|
71
|
+
recomputeNextRuns(state);
|
|
50
72
|
await persist(state);
|
|
51
73
|
armTimer(state);
|
|
74
|
+
state.deps.log.info({
|
|
75
|
+
jobId: job.id,
|
|
76
|
+
jobName: job.name,
|
|
77
|
+
nextRunAtMs: job.state.nextRunAtMs,
|
|
78
|
+
schedulerNextWakeAtMs: nextWakeAtMs(state) ?? null,
|
|
79
|
+
timerArmed: state.timer !== null,
|
|
80
|
+
cronEnabled: state.deps.cronEnabled,
|
|
81
|
+
}, "cron: job added");
|
|
52
82
|
emit(state, {
|
|
53
83
|
jobId: job.id,
|
|
54
84
|
action: "added",
|
|
@@ -64,13 +94,32 @@ export async function update(state, id, patch) {
|
|
|
64
94
|
const job = findJobOrThrow(state, id);
|
|
65
95
|
const now = state.deps.nowMs();
|
|
66
96
|
applyJobPatch(job, patch);
|
|
67
|
-
job.
|
|
68
|
-
|
|
69
|
-
|
|
97
|
+
if (job.schedule.kind === "every") {
|
|
98
|
+
const anchor = job.schedule.anchorMs;
|
|
99
|
+
if (typeof anchor !== "number" || !Number.isFinite(anchor)) {
|
|
100
|
+
const patchSchedule = patch.schedule;
|
|
101
|
+
const fallbackAnchorMs = patchSchedule?.kind === "every"
|
|
102
|
+
? now
|
|
103
|
+
: typeof job.createdAtMs === "number" && Number.isFinite(job.createdAtMs)
|
|
104
|
+
? job.createdAtMs
|
|
105
|
+
: now;
|
|
106
|
+
job.schedule = {
|
|
107
|
+
...job.schedule,
|
|
108
|
+
anchorMs: Math.max(0, Math.floor(fallbackAnchorMs)),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
70
111
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
112
|
+
const scheduleChanged = patch.schedule !== undefined;
|
|
113
|
+
const enabledChanged = patch.enabled !== undefined;
|
|
114
|
+
job.updatedAtMs = now;
|
|
115
|
+
if (scheduleChanged || enabledChanged) {
|
|
116
|
+
if (job.enabled) {
|
|
117
|
+
job.state.nextRunAtMs = computeJobNextRunAtMs(job, now);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
job.state.nextRunAtMs = undefined;
|
|
121
|
+
job.state.runningAtMs = undefined;
|
|
122
|
+
}
|
|
74
123
|
}
|
|
75
124
|
await persist(state);
|
|
76
125
|
armTimer(state);
|
|
@@ -87,27 +136,34 @@ export async function remove(state, id) {
|
|
|
87
136
|
warnIfDisabled(state, "remove");
|
|
88
137
|
await ensureLoaded(state);
|
|
89
138
|
const before = state.store?.jobs.length ?? 0;
|
|
90
|
-
if (!state.store)
|
|
139
|
+
if (!state.store) {
|
|
91
140
|
return { ok: false, removed: false };
|
|
141
|
+
}
|
|
92
142
|
state.store.jobs = state.store.jobs.filter((j) => j.id !== id);
|
|
93
143
|
const removed = (state.store.jobs.length ?? 0) !== before;
|
|
94
144
|
await persist(state);
|
|
95
145
|
armTimer(state);
|
|
96
|
-
if (removed)
|
|
146
|
+
if (removed) {
|
|
97
147
|
emit(state, { jobId: id, action: "removed" });
|
|
148
|
+
}
|
|
98
149
|
return { ok: true, removed };
|
|
99
150
|
});
|
|
100
151
|
}
|
|
101
152
|
export async function run(state, id, mode) {
|
|
102
153
|
return await locked(state, async () => {
|
|
103
154
|
warnIfDisabled(state, "run");
|
|
104
|
-
await ensureLoaded(state);
|
|
155
|
+
await ensureLoaded(state, { skipRecompute: true });
|
|
105
156
|
const job = findJobOrThrow(state, id);
|
|
157
|
+
if (typeof job.state.runningAtMs === "number") {
|
|
158
|
+
return { ok: true, ran: false, reason: "already-running" };
|
|
159
|
+
}
|
|
106
160
|
const now = state.deps.nowMs();
|
|
107
161
|
const due = isJobDue(job, now, { forced: mode === "force" });
|
|
108
|
-
if (!due)
|
|
162
|
+
if (!due) {
|
|
109
163
|
return { ok: true, ran: false, reason: "not-due" };
|
|
164
|
+
}
|
|
110
165
|
await executeJob(state, job, now, { forced: mode === "force" });
|
|
166
|
+
recomputeNextRuns(state);
|
|
111
167
|
await persist(state);
|
|
112
168
|
armTimer(state);
|
|
113
169
|
return { ok: true, ran: true };
|