@capybara-hq/kernel 0.1.0-rc.23 → 0.1.0-rc.25
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 +9 -3
- package/dist/admin/health.service.d.ts +2 -2
- package/dist/admin/health.service.js +6 -2
- 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/agents.module.js +3 -0
- package/dist/agents/agents.service.d.ts +6 -1
- package/dist/agents/agents.service.js +11 -2
- 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-import.module.js +0 -4
- package/dist/catalog/catalog-scope.service.d.ts +63 -26
- package/dist/catalog/catalog-scope.service.js +146 -71
- package/dist/catalog/catalog.module.d.ts +2 -1
- package/dist/catalog/catalog.module.js +9 -4
- package/dist/catalog/catalog.service.d.ts +11 -4
- package/dist/catalog/catalog.service.js +21 -5
- package/dist/catalog/cli.d.ts +4 -3
- package/dist/catalog/cli.js +8 -13
- package/dist/catalog/job-ids.d.ts +8 -20
- package/dist/catalog/job-ids.js +11 -16
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/org-plugin-uploads.d.ts +11 -4
- package/dist/catalog/org-plugin-uploads.js +21 -6
- package/dist/catalog/owned-items.d.ts +6 -13
- package/dist/catalog/owned-items.js +11 -29
- package/dist/catalog/owned-plugin-cleanup.d.ts +8 -17
- package/dist/catalog/owned-plugin-cleanup.js +12 -66
- package/dist/catalog/platform-components.d.ts +56 -34
- package/dist/catalog/platform-components.js +68 -78
- 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 +64 -26
- package/dist/catalog/plugin-import.js +113 -77
- package/dist/catalog/plugin-upgrade.d.ts +22 -4
- package/dist/catalog/plugin-upgrade.js +26 -19
- package/dist/catalog/scope-jobs.d.ts +59 -0
- package/dist/catalog/scope-jobs.js +65 -0
- package/dist/catalog/scope-sync-workers.d.ts +22 -0
- package/dist/catalog/scope-sync-workers.js +58 -2
- package/dist/catalog/scope-sync.d.ts +40 -65
- package/dist/catalog/scope-sync.js +114 -122
- 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 +4 -17
- package/dist/channels/jobs.d.ts +28 -18
- package/dist/channels/jobs.js +63 -26
- package/dist/channels/threads.d.ts +4 -3
- package/dist/channels/threads.js +4 -3
- 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.controller.js +4 -2
- package/dist/conversations/conversations.service.d.ts +61 -19
- package/dist/conversations/conversations.service.js +69 -43
- package/dist/conversations/event-log.d.ts +7 -10
- package/dist/conversations/event-log.js +24 -15
- package/dist/conversations/events.d.ts +21 -0
- package/dist/conversations/events.js +26 -2
- package/dist/conversations/queue.service.d.ts +8 -3
- package/dist/conversations/queue.service.js +37 -13
- package/dist/conversations/source.d.ts +10 -0
- package/dist/conversations/source.js +17 -0
- package/dist/conversations/titles.d.ts +21 -8
- package/dist/conversations/titles.js +42 -15
- package/dist/conversations/tree.d.ts +10 -0
- package/dist/conversations/tree.js +35 -0
- package/dist/db/kernel-schema.d.ts +35 -22
- package/dist/devices/devices.service.d.ts +23 -11
- package/dist/devices/devices.service.js +34 -26
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +11 -0
- package/dist/evals/run-replay.d.ts +13 -2
- package/dist/evals/run-replay.js +42 -9
- package/dist/evals/snapshot.d.ts +7 -1
- package/dist/evals/snapshot.js +2 -1
- package/dist/extensions/extensions.module.js +3 -0
- package/dist/extensions/hooks.d.ts +22 -3
- package/dist/extensions/hooks.js +15 -1
- package/dist/extensions/lifecycle-hooks.d.ts +99 -0
- package/dist/extensions/lifecycle-hooks.js +273 -0
- package/dist/extensions/lifecycle.d.ts +6 -1
- package/dist/extensions/lifecycle.js +9 -2
- package/dist/extensions/org-config.d.ts +13 -1
- package/dist/extensions/org-config.js +19 -2
- package/dist/extensions/permission-view.d.ts +9 -5
- package/dist/extensions/permission-view.js +9 -4
- package/dist/extensions/point-defaults.d.ts +28 -1
- package/dist/extensions/point-defaults.js +44 -13
- 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/inbox/inbox.service.d.ts +6 -20
- package/dist/inbox/inbox.service.js +0 -3
- package/dist/inbox/org-notices.d.ts +1 -1
- package/dist/inbox/platform-inbox.service.d.ts +3 -32
- package/dist/inbox/platform-inbox.service.js +5 -54
- 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 +244 -0
- package/dist/infra/outbox/registry.js +234 -0
- package/dist/infra/queue/queue-names.d.ts +8 -0
- package/dist/infra/queue/queue-names.js +17 -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/directory-sync.js +1 -1
- package/dist/orgs/members.service.js +1 -1
- package/dist/orgs/offboarding.d.ts +50 -57
- package/dist/orgs/offboarding.js +117 -162
- package/dist/orgs/orgs.module.js +3 -0
- package/dist/orgs/orgs.service.d.ts +11 -5
- package/dist/orgs/orgs.service.js +22 -6
- package/dist/overrides.d.ts +23 -3
- package/dist/overrides.js +12 -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/inbox.ops.js +4 -20
- package/dist/platform/ops/outbox.ops.d.ts +21 -0
- package/dist/platform/ops/outbox.ops.js +125 -0
- package/dist/platform/ops/platform.ops.d.ts +1 -0
- package/dist/platform/ops/platform.ops.js +6 -3
- 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-role.d.ts +10 -5
- package/dist/roles/worker-role.js +19 -9
- package/dist/roles/worker.module.js +11 -8
- package/dist/runtime/context/build.d.ts +3 -0
- package/dist/runtime/context/build.js +15 -0
- package/dist/runtime/context/plugin-context.d.ts +20 -0
- package/dist/runtime/context/plugin-context.js +30 -0
- package/dist/runtime/langgraph-loop.js +17 -5
- package/dist/runtime/limits.d.ts +15 -0
- package/dist/runtime/limits.js +25 -0
- package/dist/runtime/loop.d.ts +16 -3
- package/dist/runtime/loop.js +2 -1
- package/dist/runtime/maintenance.d.ts +7 -3
- package/dist/runtime/maintenance.js +52 -12
- package/dist/runtime/model-call.d.ts +20 -17
- package/dist/runtime/model-call.js +15 -24
- package/dist/runtime/plugin-context.service.d.ts +69 -0
- package/dist/runtime/plugin-context.service.js +208 -0
- 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 +47 -5
- package/dist/runtime/run-control.js +79 -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 +75 -53
- package/dist/runtime/run-queue.js +189 -263
- package/dist/runtime/run-steps.d.ts +1 -2
- package/dist/runtime/run-steps.js +8 -11
- package/dist/runtime/run-worker.d.ts +26 -16
- package/dist/runtime/run-worker.js +132 -37
- package/dist/runtime/runtime.module.d.ts +2 -3
- package/dist/runtime/runtime.module.js +7 -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 +14 -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 +8 -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/jobs.d.ts +20 -10
- package/dist/sandbox/jobs.js +23 -17
- 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 +26 -7
- package/dist/schedules/fire.js +91 -22
- package/dist/schedules/job-ids.d.ts +66 -17
- package/dist/schedules/job-ids.js +81 -13
- package/dist/schedules/run-tracker.d.ts +0 -5
- package/dist/schedules/run-tracker.js +2 -14
- package/dist/schedules/scan.job.d.ts +13 -32
- package/dist/schedules/scan.job.js +50 -164
- 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 +22 -6
- package/dist/schedules/scheduled-tasks.service.js +70 -14
- package/dist/schedules/schedules.controller.d.ts +5 -1
- package/dist/schedules/schedules.controller.js +15 -1
- package/dist/schedules/schedules.module.js +0 -4
- 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/skills/changes.controller.d.ts +1 -1
- package/dist/skills/changes.controller.js +1 -1
- package/dist/tools/approvals.service.d.ts +41 -11
- package/dist/tools/approvals.service.js +108 -14
- 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 +12 -3
- package/dist/tools/pipeline.js +6 -1
- package/dist/tools/questions.service.d.ts +30 -10
- package/dist/tools/questions.service.js +41 -27
- package/dist/tools/registry.d.ts +12 -3
- package/dist/tools/registry.js +13 -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
- package/src/migrations/0121_drop_platform_migration_runs.sql +12 -0
- package/src/migrations/0122_drop_scheduled_task_runs_rechecked_at.sql +5 -0
- package/src/migrations/0123_pgvector.sql +27 -0
- package/src/migrations/0124_conversation_source.sql +36 -0
- package/dist/catalog/platform-migration.d.ts +0 -115
- package/dist/catalog/platform-migration.js +0 -583
- package/dist/infra/queue/add-job-once.d.ts +0 -14
- package/dist/infra/queue/add-job-once.js +0 -16
|
@@ -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
|
@@ -7,9 +7,10 @@ import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
|
7
7
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
8
8
|
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
9
9
|
import { LockService } from "../infra/lock/lock.service.js";
|
|
10
|
+
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
10
11
|
import type { KernelLogger } from "../log.js";
|
|
11
12
|
import { OrgLock } from "../orgs/org-lock.js";
|
|
12
|
-
import {
|
|
13
|
+
import { type SchedSettleJobData } from "./job-ids.js";
|
|
13
14
|
import { ScheduledTaskRunTracker } from "./run-tracker.js";
|
|
14
15
|
import { ScheduledTaskStore } from "./store.js";
|
|
15
16
|
/** A task run's message client id in a conversation's queue (never read back as "the task's"). */
|
|
@@ -22,10 +23,12 @@ export declare function localTimeOf(at: Date, timeZone: string): string;
|
|
|
22
23
|
*
|
|
23
24
|
* 1. The claim, one transaction under the org row lock (which serializes the org's rate limit
|
|
24
25
|
* count), then the task run and the task rows: the checks in order (task paused since the
|
|
25
|
-
* scan, unless a person's "run now"; org
|
|
26
|
-
*
|
|
26
|
+
* scan, unless a person's "run now"; a tick fired more than 10 minutes past the org's catch-up
|
|
27
|
+
* window, `missed`, unless a "run now"; org disabled, creator inactive or not a member, agent
|
|
28
|
+
* gone or not visible, conversation gone, connection inactive,
|
|
27
29
|
* overlap, rate limit, device offline, queue paused), each a `skipped` run with its reason, the
|
|
28
|
-
* lasting ones also pausing the task; then `fired_at` is written, after the rate limit count
|
|
30
|
+
* lasting ones also pausing the task; then `fired_at` is written, after the rate limit count,
|
|
31
|
+
* with the run's delayed settle check owed (`sched-settle`, {@link settle}).
|
|
29
32
|
* A run whose claim committed before (a retried job) goes straight to the start.
|
|
30
33
|
* 2. The start, idempotent: `new` creates the conversation with its first message keyed by the
|
|
31
34
|
* task run id (`create_request_id`); `conversation` queues the prompt in the target
|
|
@@ -49,16 +52,32 @@ export declare class ScheduledTaskFirer {
|
|
|
49
52
|
private readonly conversations;
|
|
50
53
|
private readonly queue;
|
|
51
54
|
private readonly connections;
|
|
52
|
-
private readonly runs;
|
|
53
55
|
/** Reads a local device's connection lease (online or not). */
|
|
54
56
|
private readonly locks;
|
|
55
57
|
private readonly log;
|
|
56
|
-
|
|
58
|
+
/** The claim owes the run's settle check (ruling R26). */
|
|
59
|
+
private readonly outbox;
|
|
60
|
+
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
61
|
/** Reads a local device's connection lease (online or not). */
|
|
58
|
-
locks: Pick<LockService, "holderOf">, log: Pick<KernelLogger, "warn" | "info"
|
|
62
|
+
locks: Pick<LockService, "holderOf">, log: Pick<KernelLogger, "warn" | "info">,
|
|
63
|
+
/** The claim owes the run's settle check (ruling R26). */
|
|
64
|
+
outbox: Pick<OutboxService, "add">);
|
|
59
65
|
private get db();
|
|
60
66
|
/** Fires task run `taskRunId` (see the class); a run not `pending` is left as it is. */
|
|
61
67
|
fire(taskRunId: string): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* The `sched-settle` job (ruling R26; `job-ids.ts`). The claimed check: a task run claimed
|
|
70
|
+
* (`fired_at`) and still `pending` goes through the run tracker's check, which links it to its
|
|
71
|
+
* run, settles it, or releases a run that left no trace (`skipped`, `removed`), so a `once`
|
|
72
|
+
* task's last run never stays pending; one still in flight (its item queued, its message posted)
|
|
73
|
+
* owes the next check an hour on. The unfired check: a run still `pending` that no claim took
|
|
74
|
+
* (its fire job failed for good before the claim) is skipped, `missed` for a tick, `removed` for
|
|
75
|
+
* a "run now", so the overlap check never waits on it. A run claimed, started, ended or skipped
|
|
76
|
+
* since is left as it is (the claimed check follows a claimed run).
|
|
77
|
+
*/
|
|
78
|
+
settle(data: SchedSettleJobData): Promise<void>;
|
|
79
|
+
/** The unfired check (see {@link settle}), task row first (the one lock order). */
|
|
80
|
+
private settleUnfired;
|
|
62
81
|
/** Step 1 (see the class): a skip, the plan, or null when there is nothing to fire. */
|
|
63
82
|
private claim;
|
|
64
83
|
/** Step 2 (see the class). */
|