@capybara-hq/kernel 0.1.0-rc.23 → 0.1.0-rc.24
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/bootstrap.json +25 -25
- package/dist/admin/admin.module.d.ts +2 -1
- package/dist/admin/admin.module.js +7 -3
- package/dist/admin/outbox-dead.controller.d.ts +23 -0
- package/dist/admin/outbox-dead.controller.js +62 -0
- package/dist/admin/outbox-dead.schemas.d.ts +8 -0
- package/dist/admin/outbox-dead.schemas.js +18 -0
- package/dist/admin/outbox-dead.service.d.ts +66 -0
- package/dist/admin/outbox-dead.service.js +179 -0
- package/dist/admin/overview.service.d.ts +40 -5
- package/dist/admin/overview.service.js +41 -7
- package/dist/agents/from-template.js +1 -1
- package/dist/background/finish.d.ts +5 -2
- package/dist/background/finish.js +14 -3
- package/dist/background/launch.d.ts +2 -1
- package/dist/background/launch.js +5 -2
- package/dist/background/store.d.ts +8 -0
- package/dist/background/store.js +24 -0
- package/dist/catalog/admin-catalog.controller.d.ts +21 -2
- package/dist/catalog/admin-catalog.controller.js +45 -6
- package/dist/catalog/admin-catalog.schemas.d.ts +5 -0
- package/dist/catalog/admin-catalog.schemas.js +8 -0
- package/dist/catalog/catalog.module.d.ts +2 -1
- package/dist/catalog/catalog.module.js +5 -1
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/platform-import-update.d.ts +50 -0
- package/dist/catalog/platform-import-update.js +116 -0
- package/dist/catalog/plugin-import.d.ts +36 -1
- package/dist/catalog/plugin-import.js +23 -1
- package/dist/catalog/plugin-upgrade.d.ts +18 -0
- package/dist/catalog/plugin-upgrade.js +19 -0
- package/dist/channels/channels.module.d.ts +5 -2
- package/dist/channels/channels.module.js +9 -2
- package/dist/channels/deliver-job.d.ts +25 -0
- package/dist/channels/deliver-job.js +25 -0
- package/dist/channels/inbound.d.ts +1 -1
- package/dist/channels/inbound.js +2 -15
- package/dist/channels/jobs.d.ts +28 -18
- package/dist/channels/jobs.js +63 -26
- package/dist/config/schema.d.ts +0 -3
- package/dist/config/schema.js +11 -20
- package/dist/conversations/agent-tree.d.ts +16 -0
- package/dist/conversations/agent-tree.js +36 -0
- package/dist/conversations/conversations.service.d.ts +44 -14
- package/dist/conversations/conversations.service.js +52 -38
- package/dist/conversations/event-log.d.ts +7 -10
- package/dist/conversations/event-log.js +24 -15
- package/dist/conversations/events.js +2 -1
- package/dist/conversations/queue.service.d.ts +8 -3
- package/dist/conversations/queue.service.js +37 -13
- package/dist/conversations/titles.d.ts +21 -8
- package/dist/conversations/titles.js +42 -15
- package/dist/db/kernel-schema.d.ts +26 -8
- package/dist/devices/devices.service.d.ts +12 -3
- package/dist/devices/devices.service.js +18 -3
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +11 -0
- package/dist/evals/run-replay.js +14 -8
- package/dist/extensions/extensions.module.js +3 -0
- package/dist/extensions/lifecycle.d.ts +6 -1
- package/dist/extensions/lifecycle.js +9 -2
- package/dist/extensions/usage-subscribers.d.ts +43 -0
- package/dist/extensions/usage-subscribers.js +108 -0
- package/dist/http/api.module.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/infra/cli/kernel-cli.module.js +5 -0
- package/dist/infra/events/kernel-events.d.ts +9 -35
- package/dist/infra/outbox/outbox-relay.module.d.ts +6 -0
- package/dist/infra/outbox/outbox-relay.module.js +18 -0
- package/dist/infra/outbox/outbox.dispatcher.d.ts +103 -0
- package/dist/infra/outbox/outbox.dispatcher.js +310 -0
- package/dist/infra/outbox/outbox.module.d.ts +24 -0
- package/dist/infra/outbox/outbox.module.js +50 -0
- package/dist/infra/outbox/outbox.relay.d.ts +31 -0
- package/dist/infra/outbox/outbox.relay.js +76 -0
- package/dist/infra/outbox/outbox.service.d.ts +51 -0
- package/dist/infra/outbox/outbox.service.js +131 -0
- package/dist/infra/outbox/registry.d.ts +120 -0
- package/dist/infra/outbox/registry.js +108 -0
- package/dist/infra/queue/queue-names.d.ts +8 -0
- package/dist/infra/queue/queue-names.js +16 -0
- package/dist/kernel.module.js +3 -0
- package/dist/local-devices/device-policy.service.d.ts +4 -0
- package/dist/local-devices/device-policy.service.js +61 -3
- package/dist/local-devices/machines.d.ts +13 -5
- package/dist/local-devices/machines.js +45 -7
- package/dist/local-devices/policy-summary.d.ts +44 -1
- package/dist/local-devices/policy-summary.js +69 -0
- package/dist/models/embeddings.d.ts +9 -9
- package/dist/models/embeddings.js +11 -18
- package/dist/models/models.module.js +3 -0
- package/dist/orgs/orgs.service.js +7 -0
- package/dist/overrides.d.ts +20 -3
- package/dist/overrides.js +10 -2
- package/dist/platform/exclusions.js +5 -0
- package/dist/platform/ops/admin.ops.js +18 -2
- package/dist/platform/ops/catalog-admin.ops.d.ts +3 -1
- package/dist/platform/ops/catalog-admin.ops.js +32 -4
- package/dist/platform/ops/outbox.ops.d.ts +21 -0
- package/dist/platform/ops/outbox.ops.js +125 -0
- package/dist/platform/ops/schedules.ops.js +40 -1
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +3 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +9 -4
- package/dist/runtime/langgraph-loop.js +13 -5
- package/dist/runtime/limits.d.ts +15 -0
- package/dist/runtime/limits.js +25 -0
- package/dist/runtime/loop.d.ts +8 -3
- package/dist/runtime/loop.js +2 -1
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +17 -2
- package/dist/runtime/model-call.d.ts +14 -16
- package/dist/runtime/model-call.js +14 -24
- package/dist/runtime/queue-lost-jobs.d.ts +24 -0
- package/dist/runtime/queue-lost-jobs.js +51 -0
- package/dist/runtime/run-control.d.ts +42 -5
- package/dist/runtime/run-control.js +66 -12
- package/dist/runtime/run-finish.d.ts +39 -12
- package/dist/runtime/run-finish.js +97 -14
- package/dist/runtime/run-jobs.d.ts +95 -13
- package/dist/runtime/run-jobs.js +199 -28
- package/dist/runtime/run-orphan-check.job.d.ts +142 -0
- package/dist/runtime/run-orphan-check.job.js +339 -0
- package/dist/runtime/run-processors.d.ts +16 -9
- package/dist/runtime/run-processors.js +1 -1
- package/dist/runtime/run-queue.d.ts +64 -55
- package/dist/runtime/run-queue.js +147 -284
- package/dist/runtime/run-steps.d.ts +1 -2
- package/dist/runtime/run-steps.js +8 -11
- package/dist/runtime/run-worker.d.ts +6 -17
- package/dist/runtime/run-worker.js +32 -34
- package/dist/runtime/runtime.module.d.ts +2 -3
- package/dist/runtime/runtime.module.js +5 -6
- package/dist/runtime/step-log.d.ts +16 -4
- package/dist/runtime/step-log.js +26 -6
- package/dist/runtime/stream.controller.d.ts +2 -4
- package/dist/runtime/stream.controller.js +5 -11
- package/dist/runtime/subagents/child.js +1 -1
- package/dist/runtime/subagents/definition.d.ts +2 -1
- package/dist/runtime/subagents/definition.js +4 -2
- package/dist/runtime/subagents/deliveries.d.ts +6 -3
- package/dist/runtime/subagents/deliveries.js +16 -7
- package/dist/runtime/subagents/dispatch.d.ts +12 -12
- package/dist/runtime/subagents/dispatch.js +13 -38
- package/dist/runtime/subagents/messages.d.ts +5 -1
- package/dist/runtime/subagents/messages.js +11 -3
- package/dist/runtime/subagents/skill-fork.d.ts +3 -3
- package/dist/runtime/subagents/skill-fork.js +5 -5
- package/dist/runtime/subagents/subagents.service.js +1 -1
- package/dist/runtime/subagents/tree-jobs.d.ts +61 -0
- package/dist/runtime/subagents/tree-jobs.js +86 -0
- package/dist/runtime/usage-report-jobs.d.ts +23 -48
- package/dist/runtime/usage-report-jobs.js +67 -119
- package/dist/runtime/usage-report.d.ts +28 -77
- package/dist/runtime/usage-report.js +35 -213
- package/dist/runtime/waits.d.ts +48 -198
- package/dist/runtime/waits.js +103 -805
- package/dist/sandbox/sandbox.service.d.ts +17 -4
- package/dist/sandbox/sandbox.service.js +55 -14
- package/dist/schedules/calendar.d.ts +134 -0
- package/dist/schedules/calendar.js +410 -0
- package/dist/schedules/fire.d.ts +1 -3
- package/dist/schedules/fire.js +5 -10
- package/dist/schedules/run-tracker.d.ts +0 -5
- package/dist/schedules/run-tracker.js +0 -13
- package/dist/schedules/scan.job.js +15 -20
- package/dist/schedules/schedule-math.d.ts +42 -0
- package/dist/schedules/schedule-math.js +52 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +16 -0
- package/dist/schedules/scheduled-tasks.service.js +53 -0
- package/dist/schedules/schedules.controller.d.ts +5 -1
- package/dist/schedules/schedules.controller.js +15 -1
- package/dist/schedules/schedules.schemas.d.ts +35 -0
- package/dist/schedules/schedules.schemas.js +27 -0
- package/dist/schedules/store.d.ts +5 -0
- package/dist/schedules/store.js +20 -0
- package/dist/tools/approvals.service.d.ts +26 -7
- package/dist/tools/approvals.service.js +88 -10
- package/dist/tools/builtin-names.d.ts +5 -3
- package/dist/tools/builtin-names.js +12 -4
- package/dist/tools/builtins/builtin-tools.d.ts +1 -2
- package/dist/tools/builtins/builtin-tools.js +21 -17
- package/dist/tools/builtins/sandbox/bash.js +9 -3
- package/dist/tools/builtins/sandbox/common.d.ts +18 -1
- package/dist/tools/builtins/sandbox/common.js +14 -1
- package/dist/tools/builtins/sandbox/index.d.ts +2 -1
- package/dist/tools/builtins/sandbox/index.js +4 -1
- package/dist/tools/builtins/sandbox/task-list.d.ts +12 -0
- package/dist/tools/builtins/sandbox/task-list.js +130 -0
- package/dist/tools/builtins/sandbox/task-output.d.ts +8 -1
- package/dist/tools/builtins/sandbox/task-output.js +21 -3
- package/dist/tools/builtins/web/extract.d.ts +2 -4
- package/dist/tools/builtins/web/extract.js +2 -5
- package/dist/tools/pipeline.d.ts +1 -1
- package/dist/tools/pipeline.js +1 -1
- package/dist/tools/questions.service.d.ts +15 -6
- package/dist/tools/questions.service.js +20 -8
- package/dist/tools/registry.d.ts +4 -2
- package/dist/tools/registry.js +4 -3
- package/dist/tools/tools.module.js +4 -3
- package/package.json +3 -3
- package/src/migrations/0118_device_policy_ops.sql +6 -0
- package/src/migrations/0119_outbox.sql +22 -0
- package/src/migrations/0120_drop_usage_report_sweep.sql +7 -0
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
import { scanStep, tickerOf, ticksIn } from "./schedule-math.js";
|
|
2
|
+
/**
|
|
3
|
+
* The scheduled task calendar (spec 2026-11-12-scheduled-tasks-calendar-design.md §5): recorded
|
|
4
|
+
* runs and planned occurrences between two instants, dense task-days collapsed, cut at a whole
|
|
5
|
+
* day when a limit is reached. The assembly here is pure; `ScheduledTasksService.calendar` reads
|
|
6
|
+
* the rows and decides who sees what.
|
|
7
|
+
*/
|
|
8
|
+
const DAY_MS = 86_400_000;
|
|
9
|
+
/** Planned occurrences reach this far ahead of now (spec §5.2, "about six weeks"). */
|
|
10
|
+
export const CALENDAR_HORIZON_DAYS = 42;
|
|
11
|
+
/** The longest range one request may ask for: a 6-week month grid plus a DST hour (§5.1). */
|
|
12
|
+
export const CALENDAR_RANGE_DAYS = 43;
|
|
13
|
+
/** The longest range with `taskId` (the panel's one-day list, §5.4). */
|
|
14
|
+
export const CALENDAR_TASK_RANGE_DAYS = 2;
|
|
15
|
+
/** A task-day with more items than this is collapsed into one summary (§5.4). */
|
|
16
|
+
export const CALENDAR_COLLAPSE_OVER = 6;
|
|
17
|
+
/**
|
|
18
|
+
* Ticks taken per request after the first local day, across all tasks (§5.5). One tick costs
|
|
19
|
+
* about 150-340 µs (cron's `getNextDateFrom`, measured 2026-11-12 with a ticker built once per
|
|
20
|
+
* task), so 3,000 keeps a worst-case request near one second.
|
|
21
|
+
*/
|
|
22
|
+
export const CALENDAR_TICK_BUDGET = 3_000;
|
|
23
|
+
/** Entries (`items` + `collapsed`) per request after the first local day (§5.5). */
|
|
24
|
+
export const CALENDAR_ITEM_CAP = 3_000;
|
|
25
|
+
/** Run rows read per request after the first local day (§5.5). */
|
|
26
|
+
export const CALENDAR_RUN_ROW_CAP = 20_000;
|
|
27
|
+
/**
|
|
28
|
+
* The first requested local day is always returned whole, outside the limits above; only these
|
|
29
|
+
* safety caps stop it (then nothing is returned, `truncated`, `horizon` = `from`).
|
|
30
|
+
*/
|
|
31
|
+
export const CALENDAR_FIRST_DAY_TICK_CAP = 10_000;
|
|
32
|
+
export const CALENDAR_FIRST_DAY_RUN_CAP = 20_000;
|
|
33
|
+
export const CALENDAR_LIMITS = {
|
|
34
|
+
horizonDays: CALENDAR_HORIZON_DAYS,
|
|
35
|
+
collapseOver: CALENDAR_COLLAPSE_OVER,
|
|
36
|
+
tickBudget: CALENDAR_TICK_BUDGET,
|
|
37
|
+
itemCap: CALENDAR_ITEM_CAP,
|
|
38
|
+
runRowCap: CALENDAR_RUN_ROW_CAP,
|
|
39
|
+
firstDayTickCap: CALENDAR_FIRST_DAY_TICK_CAP,
|
|
40
|
+
firstDayRunCap: CALENDAR_FIRST_DAY_RUN_CAP,
|
|
41
|
+
};
|
|
42
|
+
/** How many run rows the service reads: enough to tell where either run cap cuts. */
|
|
43
|
+
export const calendarRunReadLimit = (limits) => limits.firstDayRunCap + limits.runRowCap + 1;
|
|
44
|
+
/** The calendar answer from the rows (spec §5.2–§5.5). */
|
|
45
|
+
export function assembleCalendar(input, limits = CALENDAR_LIMITS) {
|
|
46
|
+
const { timeZone, now } = input;
|
|
47
|
+
const horizonAt = now.getTime() + limits.horizonDays * DAY_MS;
|
|
48
|
+
const fromMs = input.from.getTime();
|
|
49
|
+
// `cut`: the exclusive end of what is returned; a limit moves it back to a local day start, and
|
|
50
|
+
// only a first-day safety cap moves it to `from` (nothing returned)
|
|
51
|
+
let cut = input.to.getTime();
|
|
52
|
+
let truncated = false;
|
|
53
|
+
// the first requested local day is returned whole, outside the budget and the caps
|
|
54
|
+
const firstDay = dayKey(fromMs, timeZone);
|
|
55
|
+
const firstDayEnd = Math.min(cut, startOfLocalDay(startOfLocalDay(fromMs, timeZone) + 26 * 3_600_000, timeZone));
|
|
56
|
+
// the run read is bounded too: past a cap, the range ends at the start of the local day of the
|
|
57
|
+
// first row over it
|
|
58
|
+
const firstDayRuns = input.runs.filter((r) => r.scheduledFor.getTime() < firstDayEnd).length;
|
|
59
|
+
const overRun = input.runs[firstDayRuns + limits.runRowCap];
|
|
60
|
+
if (firstDayRuns > limits.firstDayRunCap) {
|
|
61
|
+
cut = fromMs;
|
|
62
|
+
truncated = true;
|
|
63
|
+
}
|
|
64
|
+
else if (overRun !== undefined) {
|
|
65
|
+
cut = startOfLocalDay(overRun.scheduledFor.getTime(), timeZone);
|
|
66
|
+
truncated = true;
|
|
67
|
+
}
|
|
68
|
+
const planned = input.plan && cut > fromMs
|
|
69
|
+
? planOccurrences(input, horizonAt, limits, firstDayEnd, cut)
|
|
70
|
+
: { ticks: new Map(), cut };
|
|
71
|
+
if (planned.cut < cut) {
|
|
72
|
+
cut = planned.cut;
|
|
73
|
+
truncated = true;
|
|
74
|
+
}
|
|
75
|
+
// a tick the scan recorded is shown as its run, never twice
|
|
76
|
+
const recorded = new Set(input.runs.filter((r) => !r.trial).map((r) => `${r.taskId}|${r.scheduledFor.getTime()}`));
|
|
77
|
+
const byId = new Map(input.tasks.map((t) => [t.id, t]));
|
|
78
|
+
const raw = [];
|
|
79
|
+
for (const r of input.runs) {
|
|
80
|
+
const own = byId.get(r.taskId)?.accountId === input.viewerAccountId;
|
|
81
|
+
raw.push({ at: r.scheduledFor.getTime(), taskId: r.taskId, item: runItem(r, own) });
|
|
82
|
+
}
|
|
83
|
+
for (const [taskId, ticks] of planned.ticks) {
|
|
84
|
+
const jitterMs = (byId.get(taskId)?.jitterSeconds ?? 0) * 1000;
|
|
85
|
+
for (const tick of ticks) {
|
|
86
|
+
if (recorded.has(`${taskId}|${tick}`))
|
|
87
|
+
continue;
|
|
88
|
+
raw.push({
|
|
89
|
+
at: tick,
|
|
90
|
+
taskId,
|
|
91
|
+
item: {
|
|
92
|
+
kind: "occurrence",
|
|
93
|
+
taskId,
|
|
94
|
+
scheduledFor: new Date(tick).toISOString(),
|
|
95
|
+
at: new Date(tick + jitterMs).toISOString(),
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// group by local day, then by task: a task-day over the threshold becomes one summary
|
|
101
|
+
const days = new Map();
|
|
102
|
+
for (const e of raw) {
|
|
103
|
+
if (e.at >= cut)
|
|
104
|
+
continue;
|
|
105
|
+
const day = dayKey(e.at, timeZone);
|
|
106
|
+
let tasks = days.get(day);
|
|
107
|
+
if (!tasks) {
|
|
108
|
+
tasks = new Map();
|
|
109
|
+
days.set(day, tasks);
|
|
110
|
+
}
|
|
111
|
+
const list = tasks.get(e.taskId);
|
|
112
|
+
if (list)
|
|
113
|
+
list.push(e);
|
|
114
|
+
else
|
|
115
|
+
tasks.set(e.taskId, [e]);
|
|
116
|
+
}
|
|
117
|
+
// walk the days in order; a day is added whole or not at all
|
|
118
|
+
const entries = [];
|
|
119
|
+
// the first day's entries do not count against the cap (the day is always returned whole)
|
|
120
|
+
let counted = 0;
|
|
121
|
+
for (const day of [...days.keys()].sort()) {
|
|
122
|
+
const dayEntries = [];
|
|
123
|
+
for (const [taskId, list] of days.get(day) ?? []) {
|
|
124
|
+
if (input.collapse && list.length > limits.collapseOver) {
|
|
125
|
+
dayEntries.push({
|
|
126
|
+
kind: "collapsed",
|
|
127
|
+
at: firstAt(list),
|
|
128
|
+
taskId,
|
|
129
|
+
collapsed: summarize(taskId, day, list),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
for (const e of list)
|
|
134
|
+
dayEntries.push({ kind: "item", ...e });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (day !== firstDay && counted + dayEntries.length > limits.itemCap) {
|
|
138
|
+
const start = startOfLocalDay(dayEntries[0]?.at ?? cut, timeZone);
|
|
139
|
+
if (start < cut) {
|
|
140
|
+
cut = start;
|
|
141
|
+
truncated = true;
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
entries.push(...dayEntries);
|
|
146
|
+
if (day !== firstDay)
|
|
147
|
+
counted += dayEntries.length;
|
|
148
|
+
}
|
|
149
|
+
const kept = entries.filter((e) => e.at < cut);
|
|
150
|
+
kept.sort((a, b) => a.at - b.at || cmp(a.taskId, b.taskId) || cmp(entryKey(a), entryKey(b)));
|
|
151
|
+
const items = [];
|
|
152
|
+
const collapsed = [];
|
|
153
|
+
const named = new Set();
|
|
154
|
+
for (const e of kept) {
|
|
155
|
+
named.add(e.taskId);
|
|
156
|
+
if (e.kind === "item")
|
|
157
|
+
items.push(e.item);
|
|
158
|
+
else
|
|
159
|
+
collapsed.push(e.collapsed);
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
tasks: input.tasks.filter((t) => named.has(t.id)).map(calendarTask),
|
|
163
|
+
items,
|
|
164
|
+
collapsed,
|
|
165
|
+
horizon: new Date(Math.max(fromMs, truncated ? Math.min(cut, horizonAt) : horizonAt)).toISOString(),
|
|
166
|
+
truncated,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/** The stream's next tick, or null when it has no more (or its trigger cannot be evaluated). */
|
|
170
|
+
function pull(s) {
|
|
171
|
+
if (s.after >= s.until)
|
|
172
|
+
return null;
|
|
173
|
+
try {
|
|
174
|
+
const tick = ticksIn(s.ticker, new Date(s.after), new Date(s.until), 1).ticks[0];
|
|
175
|
+
s.after = tick === undefined ? Number.POSITIVE_INFINITY : tick.getTime();
|
|
176
|
+
return tick === undefined ? null : s.after;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// a trigger the math cannot evaluate: the scanner pauses the task; nothing to plan
|
|
180
|
+
s.after = Number.POSITIVE_INFINITY;
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Each active task's planned ticks in `[from, min(to, horizon, expires_at)]` (spec §5.2), from the
|
|
186
|
+
* scanner's own math: a task already due (`next_run_at <= now`, not scanned yet) keeps only the
|
|
187
|
+
* tick the scan will fire (the latest due one inside the catch-up window; the older ones become
|
|
188
|
+
* one `missed` run), then the ticks after it as the scan continues; a task not due starts at its
|
|
189
|
+
* due tick, `next_run_at - jitter`.
|
|
190
|
+
*
|
|
191
|
+
* The tasks' ticks are taken in time order across all tasks (a merge), each one tick evaluation
|
|
192
|
+
* on the task's ticker (built once). Ticks of the first local day count against the first day's
|
|
193
|
+
* safety cap only (past it, nothing is returned: the cut moves to `from`); the first later tick
|
|
194
|
+
* past the budget cuts the range at the start of its local day, and the ticks already taken on
|
|
195
|
+
* that day are dropped, so the days before the cut are whole (spec §5.5). The counts are ticks
|
|
196
|
+
* taken: each stream's lookahead past its `until` and `scanStep`'s doubling search for a task
|
|
197
|
+
* already due are not counted (at most about 2 × tasks evaluations more).
|
|
198
|
+
*/
|
|
199
|
+
function planOccurrences(input, horizonAt, limits, firstDayEnd, end) {
|
|
200
|
+
const fromMs = input.from.getTime();
|
|
201
|
+
const ticks = new Map();
|
|
202
|
+
const heap = new TickHeap();
|
|
203
|
+
for (const task of input.tasks) {
|
|
204
|
+
if (task.status !== "active" || task.nextRunAt === null)
|
|
205
|
+
continue;
|
|
206
|
+
const jitterMs = task.jitterSeconds * 1000;
|
|
207
|
+
const until = Math.min(end - 1, horizonAt, task.expiresAt?.getTime() ?? Number.POSITIVE_INFINITY);
|
|
208
|
+
if (until < fromMs)
|
|
209
|
+
continue;
|
|
210
|
+
let after = task.nextRunAt.getTime() - jitterMs - 1;
|
|
211
|
+
let first = null;
|
|
212
|
+
if (task.nextRunAt.getTime() <= input.now.getTime()) {
|
|
213
|
+
let step;
|
|
214
|
+
try {
|
|
215
|
+
step = scanStep({
|
|
216
|
+
trigger: task.trigger,
|
|
217
|
+
nextRunAt: task.nextRunAt,
|
|
218
|
+
jitterSeconds: task.jitterSeconds,
|
|
219
|
+
expiresAt: task.expiresAt,
|
|
220
|
+
}, input.now, input.catchUpWindowMinutes);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const fire = step.fire?.getTime();
|
|
226
|
+
if (fire !== undefined && fire >= fromMs && fire <= until)
|
|
227
|
+
first = fire;
|
|
228
|
+
after =
|
|
229
|
+
step.nextRunAt === null
|
|
230
|
+
? Number.POSITIVE_INFINITY
|
|
231
|
+
: step.nextRunAt.getTime() - jitterMs - 1;
|
|
232
|
+
}
|
|
233
|
+
let ticker;
|
|
234
|
+
try {
|
|
235
|
+
ticker = tickerOf(task.trigger);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const stream = { task, ticker, until, after: Math.max(after, fromMs - 1) };
|
|
241
|
+
const head = first ?? pull(stream);
|
|
242
|
+
if (head === null)
|
|
243
|
+
continue;
|
|
244
|
+
ticks.set(task.id, []);
|
|
245
|
+
heap.push({ at: head, stream });
|
|
246
|
+
}
|
|
247
|
+
let cut = end;
|
|
248
|
+
// the first local day has its own safety cap; the budget counts the days after it
|
|
249
|
+
let used = 0;
|
|
250
|
+
let firstDayUsed = 0;
|
|
251
|
+
for (let top = heap.pop(); top !== undefined; top = heap.pop()) {
|
|
252
|
+
if (top.at < firstDayEnd) {
|
|
253
|
+
if (firstDayUsed >= limits.firstDayTickCap) {
|
|
254
|
+
cut = fromMs;
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
firstDayUsed++;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
if (used >= limits.tickBudget) {
|
|
261
|
+
cut = startOfLocalDay(top.at, input.timeZone);
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
used++;
|
|
265
|
+
}
|
|
266
|
+
ticks.get(top.stream.task.id)?.push(top.at);
|
|
267
|
+
const next = pull(top.stream);
|
|
268
|
+
if (next !== null)
|
|
269
|
+
heap.push({ at: next, stream: top.stream });
|
|
270
|
+
}
|
|
271
|
+
if (cut < end) {
|
|
272
|
+
for (const [id, list] of ticks)
|
|
273
|
+
ticks.set(id, list.filter((t) => t < cut));
|
|
274
|
+
}
|
|
275
|
+
return { ticks, cut };
|
|
276
|
+
}
|
|
277
|
+
/** A binary min-heap of stream heads by tick, then task id (a stable order). */
|
|
278
|
+
class TickHeap {
|
|
279
|
+
a = [];
|
|
280
|
+
less(i, j) {
|
|
281
|
+
const x = this.a[i];
|
|
282
|
+
const y = this.a[j];
|
|
283
|
+
return x.at < y.at || (x.at === y.at && x.stream.task.id < y.stream.task.id);
|
|
284
|
+
}
|
|
285
|
+
swap(i, j) {
|
|
286
|
+
const t = this.a[i];
|
|
287
|
+
this.a[i] = this.a[j];
|
|
288
|
+
this.a[j] = t;
|
|
289
|
+
}
|
|
290
|
+
push(e) {
|
|
291
|
+
this.a.push(e);
|
|
292
|
+
for (let i = this.a.length - 1; i > 0;) {
|
|
293
|
+
const p = (i - 1) >> 1;
|
|
294
|
+
if (!this.less(i, p))
|
|
295
|
+
break;
|
|
296
|
+
this.swap(i, p);
|
|
297
|
+
i = p;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
pop() {
|
|
301
|
+
const top = this.a[0];
|
|
302
|
+
const last = this.a.pop();
|
|
303
|
+
if (top === undefined || last === undefined || this.a.length === 0)
|
|
304
|
+
return top;
|
|
305
|
+
this.a[0] = last;
|
|
306
|
+
for (let i = 0;;) {
|
|
307
|
+
const l = 2 * i + 1;
|
|
308
|
+
const r = l + 1;
|
|
309
|
+
let m = i;
|
|
310
|
+
if (l < this.a.length && this.less(l, m))
|
|
311
|
+
m = l;
|
|
312
|
+
if (r < this.a.length && this.less(r, m))
|
|
313
|
+
m = r;
|
|
314
|
+
if (m === i)
|
|
315
|
+
break;
|
|
316
|
+
this.swap(i, m);
|
|
317
|
+
i = m;
|
|
318
|
+
}
|
|
319
|
+
return top;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function runItem(r, own) {
|
|
323
|
+
return {
|
|
324
|
+
kind: "run",
|
|
325
|
+
taskId: r.taskId,
|
|
326
|
+
id: r.id,
|
|
327
|
+
scheduledFor: r.scheduledFor.toISOString(),
|
|
328
|
+
firedAt: r.firedAt?.toISOString() ?? null,
|
|
329
|
+
status: r.status,
|
|
330
|
+
skipReason: r.skipReason,
|
|
331
|
+
trial: r.trial,
|
|
332
|
+
manual: r.manual,
|
|
333
|
+
conversationId: own ? r.conversationId : null,
|
|
334
|
+
error: own ? r.error : null,
|
|
335
|
+
missingGrants: own ? r.missingGrants.length : null,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
function summarize(taskId, day, list) {
|
|
339
|
+
const c = {
|
|
340
|
+
taskId,
|
|
341
|
+
day,
|
|
342
|
+
count: list.length,
|
|
343
|
+
upcoming: 0,
|
|
344
|
+
running: 0,
|
|
345
|
+
completed: 0,
|
|
346
|
+
failed: 0,
|
|
347
|
+
skipped: 0,
|
|
348
|
+
first: new Date(firstAt(list)).toISOString(),
|
|
349
|
+
last: new Date(Math.max(...list.map((e) => e.at))).toISOString(),
|
|
350
|
+
};
|
|
351
|
+
for (const { item } of list) {
|
|
352
|
+
if (item.kind === "occurrence")
|
|
353
|
+
c.upcoming++;
|
|
354
|
+
else if (item.status === "pending" || item.status === "running")
|
|
355
|
+
c.running++;
|
|
356
|
+
else
|
|
357
|
+
c[item.status]++;
|
|
358
|
+
}
|
|
359
|
+
return c;
|
|
360
|
+
}
|
|
361
|
+
function calendarTask(t) {
|
|
362
|
+
return {
|
|
363
|
+
id: t.id,
|
|
364
|
+
name: t.name,
|
|
365
|
+
agentId: t.agentId,
|
|
366
|
+
accountId: t.accountId,
|
|
367
|
+
status: t.status,
|
|
368
|
+
pauseReason: t.pauseReason,
|
|
369
|
+
trigger: t.trigger,
|
|
370
|
+
mode: t.mode,
|
|
371
|
+
jitterSeconds: t.jitterSeconds,
|
|
372
|
+
nextRunAt: t.nextRunAt?.toISOString() ?? null,
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const firstAt = (list) => Math.min(...list.map((e) => e.at));
|
|
376
|
+
const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
377
|
+
const entryKey = (e) => e.kind === "collapsed" ? "~" : e.item.kind === "run" ? `r${e.item.id}` : "o";
|
|
378
|
+
const dayFormats = new Map();
|
|
379
|
+
/** YYYY-MM-DD of the instant (epoch ms) in `timeZone`. */
|
|
380
|
+
export function dayKey(ms, timeZone) {
|
|
381
|
+
let f = dayFormats.get(timeZone);
|
|
382
|
+
if (!f) {
|
|
383
|
+
f = new Intl.DateTimeFormat("en-CA", {
|
|
384
|
+
timeZone,
|
|
385
|
+
year: "numeric",
|
|
386
|
+
month: "2-digit",
|
|
387
|
+
day: "2-digit",
|
|
388
|
+
});
|
|
389
|
+
dayFormats.set(timeZone, f);
|
|
390
|
+
}
|
|
391
|
+
const p = Object.fromEntries(f.formatToParts(new Date(ms)).map((x) => [x.type, x.value]));
|
|
392
|
+
return `${p.year}-${p.month}-${p.day}`;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* The first instant (epoch ms) of the local day of `ms` in `timeZone`: midnight, or the end of a
|
|
396
|
+
* DST gap that swallows it. A day is at most 25 hours long, so 27 hours back is an earlier day.
|
|
397
|
+
*/
|
|
398
|
+
export function startOfLocalDay(ms, timeZone) {
|
|
399
|
+
const day = dayKey(ms, timeZone);
|
|
400
|
+
let lo = ms - 27 * 3_600_000;
|
|
401
|
+
let hi = ms;
|
|
402
|
+
while (lo < hi) {
|
|
403
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
404
|
+
if (dayKey(mid, timeZone) < day)
|
|
405
|
+
lo = mid + 1;
|
|
406
|
+
else
|
|
407
|
+
hi = mid;
|
|
408
|
+
}
|
|
409
|
+
return lo;
|
|
410
|
+
}
|
package/dist/schedules/fire.d.ts
CHANGED
|
@@ -9,7 +9,6 @@ import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
|
9
9
|
import { LockService } from "../infra/lock/lock.service.js";
|
|
10
10
|
import type { KernelLogger } from "../log.js";
|
|
11
11
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
12
|
-
import { RunControl } from "../runtime/run-control.js";
|
|
13
12
|
import { ScheduledTaskRunTracker } from "./run-tracker.js";
|
|
14
13
|
import { ScheduledTaskStore } from "./store.js";
|
|
15
14
|
/** A task run's message client id in a conversation's queue (never read back as "the task's"). */
|
|
@@ -49,11 +48,10 @@ export declare class ScheduledTaskFirer {
|
|
|
49
48
|
private readonly conversations;
|
|
50
49
|
private readonly queue;
|
|
51
50
|
private readonly connections;
|
|
52
|
-
private readonly runs;
|
|
53
51
|
/** Reads a local device's connection lease (online or not). */
|
|
54
52
|
private readonly locks;
|
|
55
53
|
private readonly log;
|
|
56
|
-
constructor(kdb: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, store: ScheduledTaskStore, tracker: Pick<ScheduledTaskRunTracker, "inFlightIn" | "failStartIn">, orgLock: Pick<OrgLock, "tryLock">, agents: Pick<AgentLookup, "get">, access: Pick<ConversationAccess, "isAccessible">, conversations: Pick<ConversationsService, "createWithMessage" | "create">, queue: Pick<ConversationQueue, "add">, connections: Pick<ChannelConnections, "row" | "channel">,
|
|
54
|
+
constructor(kdb: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, store: ScheduledTaskStore, tracker: Pick<ScheduledTaskRunTracker, "inFlightIn" | "failStartIn">, orgLock: Pick<OrgLock, "tryLock">, agents: Pick<AgentLookup, "get">, access: Pick<ConversationAccess, "isAccessible">, conversations: Pick<ConversationsService, "createWithMessage" | "create">, queue: Pick<ConversationQueue, "add">, connections: Pick<ChannelConnections, "row" | "channel">,
|
|
57
55
|
/** Reads a local device's connection lease (online or not). */
|
|
58
56
|
locks: Pick<LockService, "holderOf">, log: Pick<KernelLogger, "warn" | "info">);
|
|
59
57
|
private get db();
|
package/dist/schedules/fire.js
CHANGED
|
@@ -27,7 +27,6 @@ import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
|
27
27
|
import { deviceLeaseKey } from "../local-devices/keys.js";
|
|
28
28
|
import { OrgLock, OrgLockModule } from "../orgs/org-lock.js";
|
|
29
29
|
import { MAINTENANCE_QUEUE_NAME } from "../runtime/maintenance-queue.js";
|
|
30
|
-
import { RunControl } from "../runtime/run-control.js";
|
|
31
30
|
import { RuntimeModule } from "../runtime/runtime.module.js";
|
|
32
31
|
import { readScheduleSettings } from "./effective-settings.js";
|
|
33
32
|
import { pauseTaskIn } from "./pause.js";
|
|
@@ -97,10 +96,9 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
|
|
|
97
96
|
conversations;
|
|
98
97
|
queue;
|
|
99
98
|
connections;
|
|
100
|
-
runs;
|
|
101
99
|
locks;
|
|
102
100
|
log;
|
|
103
|
-
constructor(kdb, txHost, config, store, tracker, orgLock, agents, access, conversations, queue, connections,
|
|
101
|
+
constructor(kdb, txHost, config, store, tracker, orgLock, agents, access, conversations, queue, connections,
|
|
104
102
|
/** Reads a local device's connection lease (online or not). */
|
|
105
103
|
locks, log) {
|
|
106
104
|
this.kdb = kdb;
|
|
@@ -114,7 +112,6 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
|
|
|
114
112
|
this.conversations = conversations;
|
|
115
113
|
this.queue = queue;
|
|
116
114
|
this.connections = connections;
|
|
117
|
-
this.runs = runs;
|
|
118
115
|
this.locks = locks;
|
|
119
116
|
this.log = log;
|
|
120
117
|
}
|
|
@@ -286,10 +283,9 @@ let ScheduledTaskFirer = class ScheduledTaskFirer {
|
|
|
286
283
|
...(task.effort !== null && { effort: task.effort }),
|
|
287
284
|
};
|
|
288
285
|
const out = await this.conversations.createWithMessage(task.orgId, task.accountId, task.agentId, { text: task.prompt, clientMessageId: run.id, ...pick }, task.deviceId === null ? {} : { deviceId: task.deviceId }, internal);
|
|
286
|
+
// a retried start found the conversation made before: `createWithMessage` already asked
|
|
287
|
+
// for its run (and the first start's message owes the ensure-run backstop)
|
|
289
288
|
await this.setConversation(run.id, out.conversation.id);
|
|
290
|
-
// a retried start found the conversation made before: its run may be still to start
|
|
291
|
-
if (!out.created)
|
|
292
|
-
await this.runs.ensureRun(out.conversation.id);
|
|
293
289
|
return;
|
|
294
290
|
}
|
|
295
291
|
let conversationId;
|
|
@@ -593,9 +589,8 @@ ScheduledTaskFirer = __decorate([
|
|
|
593
589
|
__param(8, Inject(ConversationsService)),
|
|
594
590
|
__param(9, Inject(ConversationQueue)),
|
|
595
591
|
__param(10, Inject(ChannelConnections)),
|
|
596
|
-
__param(11, Inject(
|
|
597
|
-
__param(12, Inject(
|
|
598
|
-
__param(13, Inject(KERNEL_LOGGER))
|
|
592
|
+
__param(11, Inject(LockService)),
|
|
593
|
+
__param(12, Inject(KERNEL_LOGGER))
|
|
599
594
|
], ScheduledTaskFirer);
|
|
600
595
|
export { ScheduledTaskFirer };
|
|
601
596
|
/**
|
|
@@ -59,11 +59,6 @@ export declare class ScheduledTaskRunTracker {
|
|
|
59
59
|
* sub-agent or wake run inheriting the marker) is left alone.
|
|
60
60
|
*/
|
|
61
61
|
export declare function linkStartedRunIn(t: KernelSchemaDb, taskRunId: string, runId: string, conversationId: string): Promise<void>;
|
|
62
|
-
/**
|
|
63
|
-
* A queued run row was taken back before it ran (`RunQueue.deleteQueued`: its job could not be
|
|
64
|
-
* enqueued): the task run it had taken waits for the next run of its message again.
|
|
65
|
-
*/
|
|
66
|
-
export declare function unlinkRunIn(t: KernelSchemaDb, taskRunId: string, runId: string): Promise<void>;
|
|
67
62
|
/**
|
|
68
63
|
* A person removed, edited or sent the queued item that carried `schedule` (the item's stored
|
|
69
64
|
* marker, or null): its task run will never run as the task's, so it becomes `skipped`
|
|
@@ -281,19 +281,6 @@ export async function linkStartedRunIn(t, taskRunId, runId, conversationId) {
|
|
|
281
281
|
.where("run_id", "is", null)
|
|
282
282
|
.execute();
|
|
283
283
|
}
|
|
284
|
-
/**
|
|
285
|
-
* A queued run row was taken back before it ran (`RunQueue.deleteQueued`: its job could not be
|
|
286
|
-
* enqueued): the task run it had taken waits for the next run of its message again.
|
|
287
|
-
*/
|
|
288
|
-
export async function unlinkRunIn(t, taskRunId, runId) {
|
|
289
|
-
await t
|
|
290
|
-
.updateTable("scheduled_task_runs")
|
|
291
|
-
.set({ status: "pending", run_id: null })
|
|
292
|
-
.where("id", "=", taskRunId)
|
|
293
|
-
.where("run_id", "=", runId)
|
|
294
|
-
.where("status", "=", "running")
|
|
295
|
-
.execute();
|
|
296
|
-
}
|
|
297
284
|
/**
|
|
298
285
|
* A person removed, edited or sent the queued item that carried `schedule` (the item's stored
|
|
299
286
|
* marker, or null): its task run will never run as the task's, so it becomes `skipped`
|
|
@@ -24,7 +24,7 @@ import { readScheduleSettings } from "./effective-settings.js";
|
|
|
24
24
|
import { SCHED_SCAN_JOB, schedFireJob } from "./job-ids.js";
|
|
25
25
|
import { pauseTaskIn } from "./pause.js";
|
|
26
26
|
import { lockTaskRow, ScheduledTaskRunTracker } from "./run-tracker.js";
|
|
27
|
-
import {
|
|
27
|
+
import { scanStep } from "./schedule-math.js";
|
|
28
28
|
import { ScheduledTaskStore } from "./store.js";
|
|
29
29
|
/** Due tasks per scan; what is left waits for the next minute. */
|
|
30
30
|
const MAX_TASKS = 2000;
|
|
@@ -145,51 +145,46 @@ let ScheduledTaskScanJob = class ScheduledTaskScanJob {
|
|
|
145
145
|
}
|
|
146
146
|
/** One due task (see the class); returns the id of the run to fire, if any. */
|
|
147
147
|
async advance(t, task, now, catchUpWindowMinutes) {
|
|
148
|
-
const jitterMs = task.jitterSeconds * 1000;
|
|
149
|
-
// ticks are before jitter: the due tick is next_run_at - jitter, and a tick is due once
|
|
150
|
-
// tick + jitter <= now
|
|
151
|
-
const dueTick = new Date(task.nextRunAt.getTime() - jitterMs);
|
|
152
|
-
const until = new Date(now.getTime() - jitterMs);
|
|
153
148
|
// the schedule math alone may mark the task broken; database errors fail this pass only
|
|
154
|
-
let
|
|
155
|
-
let next;
|
|
149
|
+
let step;
|
|
156
150
|
try {
|
|
157
|
-
|
|
158
|
-
|
|
151
|
+
step = scanStep({
|
|
152
|
+
trigger: task.trigger,
|
|
153
|
+
nextRunAt: task.nextRunAt,
|
|
154
|
+
jitterSeconds: task.jitterSeconds,
|
|
155
|
+
expiresAt: task.expiresAt,
|
|
156
|
+
}, now, catchUpWindowMinutes);
|
|
159
157
|
}
|
|
160
158
|
catch (err) {
|
|
161
159
|
throw new TaskScanFailed(task.id, err);
|
|
162
160
|
}
|
|
163
|
-
|
|
164
|
-
const fireAt = ticks.fire !== null && ticks.fire.getTime() <= expires ? ticks.fire : null;
|
|
165
|
-
const first = ticks.firstMissed;
|
|
166
|
-
if (first !== null && first.getTime() <= expires) {
|
|
161
|
+
if (step.missed !== null) {
|
|
167
162
|
await this.store.insertRun(t, {
|
|
168
163
|
id: randomUUID(),
|
|
169
164
|
taskId: task.id,
|
|
170
165
|
orgId: task.orgId,
|
|
171
|
-
scheduledFor:
|
|
166
|
+
scheduledFor: step.missed,
|
|
172
167
|
trial: false,
|
|
173
168
|
status: "skipped",
|
|
174
169
|
skipReason: "missed",
|
|
175
170
|
});
|
|
176
171
|
}
|
|
177
172
|
let fired = null;
|
|
178
|
-
if (
|
|
173
|
+
if (step.fire !== null) {
|
|
179
174
|
const run = await this.store.insertRun(t, {
|
|
180
175
|
id: randomUUID(),
|
|
181
176
|
taskId: task.id,
|
|
182
177
|
orgId: task.orgId,
|
|
183
|
-
scheduledFor:
|
|
178
|
+
scheduledFor: step.fire,
|
|
184
179
|
trial: false,
|
|
185
180
|
});
|
|
186
181
|
fired = run?.id ?? null;
|
|
187
182
|
}
|
|
188
|
-
const patch =
|
|
183
|
+
const patch = step.end === "done"
|
|
189
184
|
? { status: "ended", nextRunAt: null }
|
|
190
|
-
:
|
|
185
|
+
: step.end === "expired"
|
|
191
186
|
? { status: "ended", pauseReason: "expired", nextRunAt: null }
|
|
192
|
-
: { nextRunAt:
|
|
187
|
+
: { nextRunAt: step.nextRunAt };
|
|
193
188
|
await this.store.updateTask(t, task.id, patch);
|
|
194
189
|
return fired;
|
|
195
190
|
}
|
|
@@ -8,6 +8,48 @@ export declare function validateTrigger(t: ScheduleTrigger, now: Date): {
|
|
|
8
8
|
};
|
|
9
9
|
/** The first tick strictly after `after` (before jitter); null for a past once task. */
|
|
10
10
|
export declare function nextTick(t: ScheduleTrigger, after: Date): Date | null;
|
|
11
|
+
/** The first tick strictly after `after` (before jitter) of one trigger; null when none. */
|
|
12
|
+
export type Ticker = (after: Date) => Date | null;
|
|
13
|
+
/**
|
|
14
|
+
* The scanner's ticker for one trigger ({@link nextTick} without rebuilding its `CronTime`s per
|
|
15
|
+
* call), for callers that walk the same trigger many times ({@link ticksIn}).
|
|
16
|
+
*/
|
|
17
|
+
export declare function tickerOf(t: ScheduleTrigger): Ticker;
|
|
18
|
+
/**
|
|
19
|
+
* The ticks in `(after, until]` (before jitter), at most `max`, from the same ticker
|
|
20
|
+
* {@link nextTick} and {@link dueTicks} use, so a list of planned runs equals what the scanner
|
|
21
|
+
* fires (calendar spec §5.2). `exhausted`: `max` ticks were taken before a tick past `until` was
|
|
22
|
+
* seen, so more may follow. A `once` trigger yields its one tick when it lies in the window.
|
|
23
|
+
* Costs one tick evaluation per tick returned, plus one when the walk reaches `until`. A caller
|
|
24
|
+
* that walks one trigger in many windows passes a {@link tickerOf} it built once.
|
|
25
|
+
*/
|
|
26
|
+
export declare function ticksIn(t: ScheduleTrigger | Ticker, after: Date, until: Date, max: number): {
|
|
27
|
+
ticks: Date[];
|
|
28
|
+
exhausted: boolean;
|
|
29
|
+
};
|
|
30
|
+
/** What one scan does to a due task (spec §6 step 1), as {@link scanStep} decides it. */
|
|
31
|
+
export interface ScanStep {
|
|
32
|
+
/** The first missed tick, recorded as one skipped run (`missed`); null when none. */
|
|
33
|
+
missed: Date | null;
|
|
34
|
+
/** The tick whose run is recorded and fired; null when none. */
|
|
35
|
+
fire: Date | null;
|
|
36
|
+
/** The task's new `next_run_at` (jitter included); null when it ends. */
|
|
37
|
+
nextRunAt: Date | null;
|
|
38
|
+
/** Why the task ends: no tick left (`done`) or the next one lies past `expires_at`. */
|
|
39
|
+
end: "done" | "expired" | null;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* One scan of a due task (`next_run_at <= now`) as of `now`, the schedule math alone: the ticks
|
|
43
|
+
* are before jitter, so the due tick is `nextRunAt - jitter` and a tick is due once
|
|
44
|
+
* `tick + jitter <= now`. The scanner applies the result; the calendar's parity test drives it.
|
|
45
|
+
* Throws when the trigger cannot be evaluated.
|
|
46
|
+
*/
|
|
47
|
+
export declare function scanStep(task: {
|
|
48
|
+
trigger: ScheduleTrigger;
|
|
49
|
+
nextRunAt: Date;
|
|
50
|
+
jitterSeconds: number;
|
|
51
|
+
expiresAt: Date | null;
|
|
52
|
+
}, now: Date, catchUpWindowMinutes: number): ScanStep;
|
|
11
53
|
/** The smallest gap, in minutes, among the next 50 fire times after `from`; null for once. */
|
|
12
54
|
export declare function minGapMinutes(t: ScheduleTrigger, from: Date): number | null;
|
|
13
55
|
/** A fixed per-task offset: `fnv1a32(id) mod min(floor(10 % of the interval), 300 s)`; once is 0. */
|