@capybara-hq/kernel 0.1.0-rc.21 → 0.1.0-rc.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/admin-agents.d.ts +16 -3
- package/dist/agents/admin-agents.js +65 -7
- package/dist/agents/agents.schemas.d.ts +24 -6
- package/dist/agents/agents.service.d.ts +4 -3
- package/dist/agents/agents.service.js +39 -26
- package/dist/agents/definition.d.ts +44 -6
- package/dist/agents/definition.js +86 -18
- package/dist/agents/from-template.d.ts +6 -3
- package/dist/agents/from-template.js +57 -44
- package/dist/agents/org-templates.js +97 -41
- package/dist/agents/templates.d.ts +6 -0
- package/dist/agents/templates.js +15 -1
- package/dist/catalog/admin-catalog.controller.d.ts +2 -4
- package/dist/catalog/admin-catalog.controller.js +2 -6
- package/dist/catalog/catalog.module.js +3 -0
- package/dist/catalog/claude/report.d.ts +9 -2
- package/dist/catalog/claude/report.js +1 -0
- package/dist/catalog/claude/translate.js +1 -1
- package/dist/catalog/component-promotion.js +1 -2
- package/dist/catalog/component-state.d.ts +13 -1
- package/dist/catalog/component-state.js +14 -5
- package/dist/catalog/marketplaces.service.d.ts +1 -1
- package/dist/catalog/marketplaces.service.js +2 -1
- package/dist/catalog/org-catalog.controller.d.ts +13 -3
- package/dist/catalog/org-catalog.controller.js +26 -4
- package/dist/catalog/org-catalog.schemas.d.ts +13 -0
- package/dist/catalog/org-catalog.schemas.js +34 -0
- package/dist/catalog/org-plugin-uploads.controller.js +0 -1
- package/dist/catalog/org-plugin-uploads.d.ts +0 -1
- package/dist/catalog/org-plugin-uploads.js +0 -3
- package/dist/catalog/org-plugin-uploads.schemas.d.ts +0 -1
- package/dist/catalog/org-plugin-uploads.schemas.js +0 -2
- package/dist/catalog/platform-components.d.ts +0 -1
- package/dist/catalog/platform-components.js +3 -8
- package/dist/catalog/plugin-import.d.ts +0 -8
- package/dist/catalog/plugin-import.js +4 -35
- package/dist/catalog/plugin-install.d.ts +2 -2
- package/dist/catalog/plugin-install.js +2 -2
- package/dist/catalog/plugin-package.d.ts +7 -0
- package/dist/catalog/plugin-package.js +27 -1
- package/dist/catalog/plugin-upgrade.d.ts +5 -6
- package/dist/catalog/plugin-upgrade.js +5 -23
- package/dist/catalog/requests.service.d.ts +66 -10
- package/dist/catalog/requests.service.js +196 -15
- package/dist/channels/actions.d.ts +2 -0
- package/dist/channels/actions.js +19 -4
- package/dist/channels/cards.d.ts +16 -2
- package/dist/channels/cards.js +40 -7
- package/dist/channels/channels.module.js +3 -0
- package/dist/channels/outbound.d.ts +22 -1
- package/dist/channels/outbound.js +116 -7
- package/dist/channels/phrases.d.ts +25 -0
- package/dist/channels/phrases.js +19 -0
- package/dist/channels/threads.d.ts +12 -0
- package/dist/channels/threads.js +34 -0
- package/dist/config/schema.d.ts +23 -1
- package/dist/config/schema.js +105 -5
- package/dist/conversations/conversations.service.d.ts +42 -9
- package/dist/conversations/conversations.service.js +57 -25
- package/dist/conversations/events.d.ts +106 -11
- package/dist/conversations/events.js +121 -18
- package/dist/conversations/message-context.d.ts +6 -3
- package/dist/conversations/message-context.js +14 -10
- package/dist/conversations/queue.d.ts +15 -2
- package/dist/conversations/queue.js +22 -3
- package/dist/conversations/queue.service.d.ts +12 -6
- package/dist/conversations/queue.service.js +36 -13
- package/dist/db/kernel-schema.d.ts +105 -3
- package/dist/devices/conversation-devices.d.ts +8 -1
- package/dist/devices/conversation-devices.js +8 -0
- package/dist/devices/devices.controller.d.ts +21 -4
- package/dist/devices/devices.controller.js +46 -8
- package/dist/devices/devices.schemas.d.ts +9 -1
- package/dist/devices/devices.schemas.js +11 -3
- package/dist/devices/devices.service.d.ts +176 -4
- package/dist/devices/devices.service.js +341 -6
- package/dist/devices/dto.d.ts +56 -3
- package/dist/devices/dto.js +51 -1
- package/dist/devices/public-suffix.d.ts +6 -0
- package/dist/devices/public-suffix.js +79 -0
- package/dist/errors.d.ts +46 -5
- package/dist/errors.js +46 -5
- package/dist/evals/reconstruct.d.ts +18 -13
- package/dist/evals/reconstruct.js +50 -36
- package/dist/evals/recorded-tools.d.ts +7 -7
- package/dist/evals/recorded-tools.js +4 -3
- package/dist/evals/trajectory.d.ts +6 -1
- package/dist/evals/trajectory.js +7 -0
- package/dist/extensions/package.js +2 -2
- package/dist/http/api.module.js +5 -0
- package/dist/inbox/inbox.module.js +2 -1
- package/dist/inbox/inbox.service.d.ts +19 -4
- package/dist/inbox/inbox.service.js +14 -3
- package/dist/infra/config/kernel-config.service.js +11 -0
- package/dist/infra/events/kernel-events.d.ts +2 -1
- package/dist/local-devices/device-policy.service.d.ts +65 -0
- package/dist/local-devices/device-policy.service.js +217 -0
- package/dist/local-devices/local-device-gateway.js +4 -1
- package/dist/local-devices/local-device-provider.d.ts +40 -3
- package/dist/local-devices/local-device-provider.js +109 -7
- package/dist/local-devices/local-devices.module.d.ts +2 -1
- package/dist/local-devices/local-devices.module.js +19 -3
- package/dist/local-devices/machines.d.ts +14 -3
- package/dist/local-devices/machines.js +41 -3
- package/dist/local-devices/network-grants.d.ts +104 -0
- package/dist/local-devices/network-grants.js +288 -0
- package/dist/local-devices/policy-summary.d.ts +20 -0
- package/dist/local-devices/policy-summary.js +63 -0
- package/dist/orgs/orgs.module.js +4 -10
- package/dist/orgs/roles.d.ts +1 -1
- package/dist/orgs/roles.js +4 -0
- package/dist/orgs/settings.controller.js +2 -1
- package/dist/orgs/settings.service.d.ts +164 -12
- package/dist/orgs/settings.service.js +212 -24
- package/dist/platform/exclusions.js +11 -1
- package/dist/platform/operation.js +4 -0
- package/dist/platform/ops/admin-items.ops.js +1 -1
- package/dist/platform/ops/agent-skills.ops.js +2 -2
- package/dist/platform/ops/agent-templates.ops.js +2 -2
- package/dist/platform/ops/agents.ops.js +11 -15
- package/dist/platform/ops/devices.ops.js +3 -1
- package/dist/platform/ops/inbox.ops.js +19 -0
- package/dist/platform/ops/org-settings.ops.js +14 -5
- package/dist/platform/ops/platform.ops.d.ts +9 -1
- package/dist/platform/ops/platform.ops.js +34 -12
- package/dist/platform/ops/requests.ops.js +3 -2
- package/dist/platform/ops/schedules.ops.d.ts +21 -0
- package/dist/platform/ops/schedules.ops.js +470 -0
- package/dist/platform/ops/skills.ops.js +3 -3
- package/dist/platform/platform-ops.module.d.ts +2 -1
- package/dist/platform/platform-ops.module.js +5 -0
- package/dist/platform/route-schemas.js +3 -0
- package/dist/roles/worker.module.js +3 -0
- package/dist/runtime/context/build.js +13 -4
- package/dist/runtime/context/compaction.js +6 -3
- package/dist/runtime/context/device-paths.d.ts +43 -21
- package/dist/runtime/context/device-paths.js +104 -21
- package/dist/runtime/context/run-context.d.ts +9 -0
- package/dist/runtime/context/run-context.js +8 -0
- package/dist/runtime/context/skills.d.ts +14 -1
- package/dist/runtime/context/skills.js +36 -5
- package/dist/runtime/maintenance.d.ts +4 -2
- package/dist/runtime/maintenance.js +16 -5
- package/dist/runtime/run-finish.d.ts +6 -1
- package/dist/runtime/run-finish.js +16 -2
- package/dist/runtime/run-queue.js +21 -2
- package/dist/runtime/run-worker.js +14 -33
- package/dist/runtime/runtime.module.js +3 -0
- package/dist/runtime/subagents/definition.d.ts +32 -14
- package/dist/runtime/subagents/definition.js +106 -109
- package/dist/runtime/subagents/dispatch.js +4 -3
- package/dist/runtime/subagents/preamble.d.ts +2 -1
- package/dist/runtime/subagents/preamble.js +2 -1
- package/dist/runtime/waits.js +14 -4
- package/dist/sandbox/sandbox.service.d.ts +120 -10
- package/dist/sandbox/sandbox.service.js +335 -28
- package/dist/sandbox/tiers.d.ts +86 -0
- package/dist/sandbox/tiers.js +96 -0
- package/dist/schedules/effective-settings.d.ts +16 -0
- package/dist/schedules/effective-settings.js +17 -0
- package/dist/schedules/errors.d.ts +8 -0
- package/dist/schedules/errors.js +8 -0
- package/dist/schedules/fire.d.ts +118 -0
- package/dist/schedules/fire.js +625 -0
- package/dist/schedules/grant-rules.d.ts +94 -0
- package/dist/schedules/grant-rules.js +126 -0
- package/dist/schedules/job-ids.d.ts +32 -0
- package/dist/schedules/job-ids.js +27 -0
- package/dist/schedules/pause.d.ts +9 -0
- package/dist/schedules/pause.js +33 -0
- package/dist/schedules/run-tracker.d.ts +81 -0
- package/dist/schedules/run-tracker.js +333 -0
- package/dist/schedules/scan.job.d.ts +64 -0
- package/dist/schedules/scan.job.js +303 -0
- package/dist/schedules/schedule-math.d.ts +31 -0
- package/dist/schedules/schedule-math.js +223 -0
- package/dist/schedules/scheduled-tasks.service.d.ts +351 -0
- package/dist/schedules/scheduled-tasks.service.js +1193 -0
- package/dist/schedules/schedules.controller.d.ts +68 -0
- package/dist/schedules/schedules.controller.js +242 -0
- package/dist/schedules/schedules.module.d.ts +16 -0
- package/dist/schedules/schedules.module.js +65 -0
- package/dist/schedules/schedules.schemas.d.ts +316 -0
- package/dist/schedules/schedules.schemas.js +186 -0
- package/dist/schedules/store.d.ts +81 -0
- package/dist/schedules/store.js +390 -0
- package/dist/schedules/store.module.d.ts +6 -0
- package/dist/schedules/store.module.js +21 -0
- package/dist/schedules/types.d.ts +108 -0
- package/dist/schedules/types.js +1 -0
- package/dist/schedules/unattended.d.ts +117 -0
- package/dist/schedules/unattended.js +228 -0
- package/dist/setup/oauth-token-probe.js +8 -4
- package/dist/skills/agent-skills.service.d.ts +4 -8
- package/dist/skills/agent-skills.service.js +18 -53
- package/dist/skills/catalog.d.ts +65 -62
- package/dist/skills/catalog.js +109 -206
- package/dist/skills/conversation-catalog.d.ts +3 -3
- package/dist/skills/conversation-catalog.js +5 -12
- package/dist/skills/invoke.d.ts +2 -2
- package/dist/skills/skills.controller.d.ts +3 -1
- package/dist/skills/skills.service.d.ts +38 -37
- package/dist/skills/skills.service.js +168 -107
- package/dist/skills/usable.d.ts +57 -8
- package/dist/skills/usable.js +172 -38
- package/dist/tools/approvals.controller.d.ts +4 -2
- package/dist/tools/approvals.controller.js +17 -2
- package/dist/tools/approvals.schemas.d.ts +1 -0
- package/dist/tools/approvals.schemas.js +3 -1
- package/dist/tools/approvals.service.d.ts +182 -7
- package/dist/tools/approvals.service.js +597 -13
- package/dist/tools/builtin-names.d.ts +25 -4
- package/dist/tools/builtin-names.js +45 -4
- package/dist/tools/builtins/builtin-tools.d.ts +8 -0
- package/dist/tools/builtins/builtin-tools.js +23 -2
- package/dist/tools/builtins/sandbox/bash.js +16 -7
- package/dist/tools/builtins/sandbox/common.d.ts +1 -1
- package/dist/tools/builtins/sandbox/common.js +33 -10
- package/dist/tools/builtins/sandbox/edit.js +7 -3
- package/dist/tools/builtins/sandbox/file-state.d.ts +31 -1
- package/dist/tools/builtins/sandbox/file-state.js +33 -3
- package/dist/tools/builtins/sandbox/write.js +8 -4
- package/dist/tools/builtins/schedules.d.ts +107 -0
- package/dist/tools/builtins/schedules.js +789 -0
- package/dist/tools/elicitation.d.ts +2 -0
- package/dist/tools/elicitation.js +1 -1
- package/dist/tools/fixed-ask.d.ts +7 -0
- package/dist/tools/fixed-ask.js +9 -0
- package/dist/tools/network-asks.d.ts +109 -0
- package/dist/tools/network-asks.js +221 -0
- package/dist/tools/pipeline.d.ts +53 -6
- package/dist/tools/pipeline.js +202 -13
- package/dist/tools/policy.d.ts +21 -8
- package/dist/tools/policy.js +63 -9
- package/dist/tools/registry.d.ts +10 -1
- package/dist/tools/tools.module.js +12 -0
- package/package.json +2 -2
- package/src/migrations/0113_scheduled_tasks.sql +108 -0
- package/src/migrations/0114_network_asks.sql +23 -0
- package/src/migrations/0115_network_requests.sql +28 -0
- package/src/migrations/0116_skills_default_on.sql +37 -0
- package/src/migrations/0117_sandbox_tiers.sql +11 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
8
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
9
|
+
};
|
|
10
|
+
import { Inject, Injectable, Module } from "@nestjs/common";
|
|
11
|
+
import { sql } from "kysely";
|
|
12
|
+
import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
13
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
14
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
15
|
+
import { Transactional } from "../infra/database/transactional.decorator.js";
|
|
16
|
+
import { readScheduleSettings } from "./effective-settings.js";
|
|
17
|
+
import { pauseTaskIn } from "./pause.js";
|
|
18
|
+
/** The kernel run statuses that are terminal (`runtime/run-finish.ts`, not imported: a cycle). */
|
|
19
|
+
const RUN_TERMINAL = new Set(["completed", "failed", "aborted"]);
|
|
20
|
+
/**
|
|
21
|
+
* How long a task run that was started (its message created or queued) may show no trace of its
|
|
22
|
+
* message before the overlap check releases it: the fire job writes `fired_at` before it creates
|
|
23
|
+
* the conversation or queues the item, in a transaction of its own.
|
|
24
|
+
*/
|
|
25
|
+
export const START_GRACE_MS = 5 * 60_000;
|
|
26
|
+
/**
|
|
27
|
+
* Follows a task run's kernel run to its end (scheduled tasks spec §6). {@link onRunFinished} runs
|
|
28
|
+
* in the transaction that writes a run's terminal state (`RunFinish.finishIn`, the one writer of
|
|
29
|
+
* terminal states), so a task run never misses its run's end: a run aborted by a person counts as
|
|
30
|
+
* completed; a failed one counts against the task (`consecutive_failures`, paused at the org's
|
|
31
|
+
* `pauseAfterFailures`), a completed one resets the count. Trial runs do not count.
|
|
32
|
+
* {@link inFlightIn} is the overlap check's view of the task's previous run: it settles a run whose
|
|
33
|
+
* end was missed and releases one whose queued message is gone.
|
|
34
|
+
*/
|
|
35
|
+
let ScheduledTaskRunTracker = class ScheduledTaskRunTracker {
|
|
36
|
+
kdb;
|
|
37
|
+
txHost;
|
|
38
|
+
config;
|
|
39
|
+
constructor(kdb, txHost, config) {
|
|
40
|
+
this.kdb = kdb;
|
|
41
|
+
this.txHost = txHost;
|
|
42
|
+
this.config = config;
|
|
43
|
+
}
|
|
44
|
+
get db() {
|
|
45
|
+
return this.kdb.db;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The kernel run `runId` ended with `outcome`: the task run it was started for (the run its
|
|
49
|
+
* message triggered, `scheduled_task_runs.run_id`) takes the outcome. A sub-agent or wake run
|
|
50
|
+
* that inherited the marker is not the task run's own and changes nothing.
|
|
51
|
+
*/
|
|
52
|
+
async onRunFinished(runId, outcome, error) {
|
|
53
|
+
const run = await this.db
|
|
54
|
+
.selectFrom("runs")
|
|
55
|
+
.select("scheduled_task_run_id")
|
|
56
|
+
.where("id", "=", runId)
|
|
57
|
+
.executeTakeFirst();
|
|
58
|
+
const taskRunId = run?.scheduled_task_run_id;
|
|
59
|
+
if (taskRunId == null)
|
|
60
|
+
return;
|
|
61
|
+
const owner = await this.db
|
|
62
|
+
.selectFrom("scheduled_task_runs")
|
|
63
|
+
.select("task_id")
|
|
64
|
+
.where("id", "=", taskRunId)
|
|
65
|
+
.executeTakeFirst();
|
|
66
|
+
if (!owner)
|
|
67
|
+
return;
|
|
68
|
+
// lock order task row → task run, as the fire job's claim takes them (no lock cycle)
|
|
69
|
+
await lockTaskRow(this.db, owner.task_id);
|
|
70
|
+
const tr = await this.db
|
|
71
|
+
.selectFrom("scheduled_task_runs")
|
|
72
|
+
.select(["id", "task_id", "org_id", "trial", "status", "run_id"])
|
|
73
|
+
.where("id", "=", taskRunId)
|
|
74
|
+
.forUpdate()
|
|
75
|
+
.executeTakeFirst();
|
|
76
|
+
if (!tr || tr.run_id !== runId || tr.status !== "running")
|
|
77
|
+
return;
|
|
78
|
+
await this.settle(tr, outcome, error);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Whether `prev`, a run of the task still `pending` or `running`, is really in flight (the
|
|
82
|
+
* overlap check, inside the fire's transaction). Settled on the way: a running one whose kernel
|
|
83
|
+
* run already ended takes its outcome; a started one linked to its run now when the run exists;
|
|
84
|
+
* a started one whose message another run consumed (`consumed_seq`, no run of its own) ends
|
|
85
|
+
* `completed`; a started one with no queued item, no message and no run after {@link START_GRACE_MS} (its
|
|
86
|
+
* queued item was removed or the queue cleared without the queue's own hook) becomes `skipped`
|
|
87
|
+
* (`removed`). The marker alone decides what is the task's message, never a client message id.
|
|
88
|
+
*/
|
|
89
|
+
async inFlightIn(prev, now = new Date()) {
|
|
90
|
+
const t = this.db;
|
|
91
|
+
await lockTaskRow(t, prev.taskId);
|
|
92
|
+
const tr = await t
|
|
93
|
+
.selectFrom("scheduled_task_runs")
|
|
94
|
+
.selectAll()
|
|
95
|
+
.where("id", "=", prev.id)
|
|
96
|
+
.forUpdate()
|
|
97
|
+
.executeTakeFirst();
|
|
98
|
+
if (!tr || (tr.status !== "pending" && tr.status !== "running"))
|
|
99
|
+
return false;
|
|
100
|
+
if (tr.status === "running") {
|
|
101
|
+
if (tr.run_id === null)
|
|
102
|
+
return true;
|
|
103
|
+
const run = await t
|
|
104
|
+
.selectFrom("runs")
|
|
105
|
+
.select("status")
|
|
106
|
+
.where("id", "=", tr.run_id)
|
|
107
|
+
.executeTakeFirst();
|
|
108
|
+
if (!run) {
|
|
109
|
+
await this.settle(tr, "failed", "The run was lost");
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (!RUN_TERMINAL.has(run.status))
|
|
113
|
+
return true;
|
|
114
|
+
await this.settle(tr, run.status);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
// not started yet: its fire job is still to come (the scanner repairs a lost one)
|
|
118
|
+
if (tr.fired_at === null)
|
|
119
|
+
return true;
|
|
120
|
+
// only a run with a target conversation is ever queued (`conversation`, `chat`): no scan of
|
|
121
|
+
// every queue's markers for the others
|
|
122
|
+
const queued = tr.conversation_id === null
|
|
123
|
+
? undefined
|
|
124
|
+
: await t
|
|
125
|
+
.selectFrom("conversation_queue")
|
|
126
|
+
.select("id")
|
|
127
|
+
.where("conversation_id", "=", tr.conversation_id)
|
|
128
|
+
.where(sql `schedule ->> 'taskRunId'`, "=", tr.id)
|
|
129
|
+
.limit(1)
|
|
130
|
+
.executeTakeFirst();
|
|
131
|
+
if (queued)
|
|
132
|
+
return true;
|
|
133
|
+
const conv = tr.conversation_id ??
|
|
134
|
+
(await t
|
|
135
|
+
.selectFrom("conversations")
|
|
136
|
+
.select("id")
|
|
137
|
+
.where("scheduled_task_id", "=", tr.task_id)
|
|
138
|
+
.where("create_request_id", "=", tr.id)
|
|
139
|
+
.executeTakeFirst())?.id;
|
|
140
|
+
if (conv !== undefined) {
|
|
141
|
+
const run = await t
|
|
142
|
+
.selectFrom("runs")
|
|
143
|
+
.select(["id", "status"])
|
|
144
|
+
.where("conversation_id", "=", conv)
|
|
145
|
+
.where("scheduled_task_run_id", "=", tr.id)
|
|
146
|
+
.orderBy("created_at")
|
|
147
|
+
.limit(1)
|
|
148
|
+
.executeTakeFirst();
|
|
149
|
+
if (run) {
|
|
150
|
+
await t
|
|
151
|
+
.updateTable("scheduled_task_runs")
|
|
152
|
+
.set({ status: "running", run_id: run.id, conversation_id: conv })
|
|
153
|
+
.where("id", "=", tr.id)
|
|
154
|
+
.execute();
|
|
155
|
+
if (!RUN_TERMINAL.has(run.status))
|
|
156
|
+
return true;
|
|
157
|
+
await this.settle(tr, run.status);
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
const message = await t
|
|
161
|
+
.selectFrom("conversation_events")
|
|
162
|
+
.select("seq")
|
|
163
|
+
.where("conversation_id", "=", conv)
|
|
164
|
+
.where("type", "=", "user_message")
|
|
165
|
+
.where(sql `payload -> 'schedule' ->> 'taskRunId'`, "=", tr.id)
|
|
166
|
+
.limit(1)
|
|
167
|
+
.executeTakeFirst();
|
|
168
|
+
if (message) {
|
|
169
|
+
const c = await t
|
|
170
|
+
.selectFrom("conversations")
|
|
171
|
+
.select("consumed_seq")
|
|
172
|
+
.where("id", "=", conv)
|
|
173
|
+
.executeTakeFirst();
|
|
174
|
+
// posted, its run still to be created (`ensureRun`)
|
|
175
|
+
if (c === undefined || Number(message.seq) > Number(c.consumed_seq))
|
|
176
|
+
return true;
|
|
177
|
+
// taken in by another run (a person's message posted with it): its content was handled
|
|
178
|
+
// and no run of its own will come, so it ends as completed rather than in flight forever
|
|
179
|
+
await this.settle(tr, "completed");
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const firedAt = tr.fired_at instanceof Date ? tr.fired_at : new Date(tr.fired_at);
|
|
184
|
+
if (now.getTime() - firedAt.getTime() < START_GRACE_MS)
|
|
185
|
+
return true;
|
|
186
|
+
await t
|
|
187
|
+
.updateTable("scheduled_task_runs")
|
|
188
|
+
.set({ status: "skipped", skip_reason: "removed", fired_at: null })
|
|
189
|
+
.where("id", "=", tr.id)
|
|
190
|
+
.where("status", "=", "pending")
|
|
191
|
+
.execute();
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* A pending task run whose start was refused (the fire job could not create or queue its
|
|
196
|
+
* message, for a reason that is not a lasting skip): it fails and counts against the task like
|
|
197
|
+
* a failed run (`consecutive_failures`, paused at the org's `pauseAfterFailures`), a trial run
|
|
198
|
+
* excepted. In the caller's transaction, which holds the task row.
|
|
199
|
+
*/
|
|
200
|
+
async failStartIn(taskRunId, error) {
|
|
201
|
+
const tr = await this.db
|
|
202
|
+
.selectFrom("scheduled_task_runs")
|
|
203
|
+
.select(["id", "task_id", "org_id", "trial", "status"])
|
|
204
|
+
.where("id", "=", taskRunId)
|
|
205
|
+
.forUpdate()
|
|
206
|
+
.executeTakeFirst();
|
|
207
|
+
if (tr?.status !== "pending")
|
|
208
|
+
return;
|
|
209
|
+
await this.settle(tr, "failed", error);
|
|
210
|
+
}
|
|
211
|
+
/** Writes the task run's end and the task's failure count (non-trial runs). */
|
|
212
|
+
async settle(tr, outcome, error) {
|
|
213
|
+
const t = this.db;
|
|
214
|
+
const failed = outcome === "failed";
|
|
215
|
+
await t
|
|
216
|
+
.updateTable("scheduled_task_runs")
|
|
217
|
+
.set({
|
|
218
|
+
status: failed ? "failed" : "completed",
|
|
219
|
+
...(failed && { error: error ?? "The run failed" }),
|
|
220
|
+
})
|
|
221
|
+
.where("id", "=", tr.id)
|
|
222
|
+
.execute();
|
|
223
|
+
if (tr.trial)
|
|
224
|
+
return;
|
|
225
|
+
if (!failed) {
|
|
226
|
+
await t
|
|
227
|
+
.updateTable("scheduled_tasks")
|
|
228
|
+
.set({ consecutive_failures: 0 })
|
|
229
|
+
.where("id", "=", tr.task_id)
|
|
230
|
+
.where("consecutive_failures", "!=", 0)
|
|
231
|
+
.execute();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const row = await t
|
|
235
|
+
.updateTable("scheduled_tasks")
|
|
236
|
+
.set({ consecutive_failures: sql `consecutive_failures + 1` })
|
|
237
|
+
.where("id", "=", tr.task_id)
|
|
238
|
+
.where("deleted_at", "is", null)
|
|
239
|
+
.returning("consecutive_failures")
|
|
240
|
+
.executeTakeFirst();
|
|
241
|
+
if (!row)
|
|
242
|
+
return;
|
|
243
|
+
const settings = await readScheduleSettings(t, this.config, tr.org_id);
|
|
244
|
+
if (row.consecutive_failures >= settings.pauseAfterFailures) {
|
|
245
|
+
await pauseTaskIn(t, tr.task_id, "failures", null);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
__decorate([
|
|
250
|
+
Transactional()
|
|
251
|
+
], ScheduledTaskRunTracker.prototype, "onRunFinished", null);
|
|
252
|
+
__decorate([
|
|
253
|
+
Transactional({ propagation: "mandatory" })
|
|
254
|
+
], ScheduledTaskRunTracker.prototype, "inFlightIn", null);
|
|
255
|
+
__decorate([
|
|
256
|
+
Transactional({ propagation: "mandatory" })
|
|
257
|
+
], ScheduledTaskRunTracker.prototype, "failStartIn", null);
|
|
258
|
+
ScheduledTaskRunTracker = __decorate([
|
|
259
|
+
Injectable(),
|
|
260
|
+
__param(0, Inject(KernelDb)),
|
|
261
|
+
__param(1, Inject(TransactionHost)),
|
|
262
|
+
__param(2, Inject(KernelConfigService))
|
|
263
|
+
], ScheduledTaskRunTracker);
|
|
264
|
+
export { ScheduledTaskRunTracker };
|
|
265
|
+
/**
|
|
266
|
+
* A kernel run was created for task run `taskRunId` (its message is the run's trigger, or it
|
|
267
|
+
* inherited the marker): a task run still `pending` takes the run (`running`), in the caller's
|
|
268
|
+
* transaction (`RunQueue.ensureRunIn`, which inserts the run). A task run already linked (a
|
|
269
|
+
* sub-agent or wake run inheriting the marker) is left alone.
|
|
270
|
+
*/
|
|
271
|
+
export async function linkStartedRunIn(t, taskRunId, runId, conversationId) {
|
|
272
|
+
await t
|
|
273
|
+
.updateTable("scheduled_task_runs")
|
|
274
|
+
.set({
|
|
275
|
+
status: "running",
|
|
276
|
+
run_id: runId,
|
|
277
|
+
conversation_id: sql `COALESCE(conversation_id, ${conversationId})`,
|
|
278
|
+
})
|
|
279
|
+
.where("id", "=", taskRunId)
|
|
280
|
+
.where("status", "=", "pending")
|
|
281
|
+
.where("run_id", "is", null)
|
|
282
|
+
.execute();
|
|
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
|
+
/**
|
|
298
|
+
* A person removed, edited or sent the queued item that carried `schedule` (the item's stored
|
|
299
|
+
* marker, or null): its task run will never run as the task's, so it becomes `skipped`
|
|
300
|
+
* (`removed`) in the caller's transaction, and the next tick is not held by the overlap check.
|
|
301
|
+
*/
|
|
302
|
+
export async function releaseQueuedTaskRunIn(t, schedule) {
|
|
303
|
+
const taskRunId = schedule?.taskRunId;
|
|
304
|
+
if (typeof taskRunId !== "string")
|
|
305
|
+
return;
|
|
306
|
+
await t
|
|
307
|
+
.updateTable("scheduled_task_runs")
|
|
308
|
+
.set({ status: "skipped", skip_reason: "removed", fired_at: null })
|
|
309
|
+
.where("id", "=", taskRunId)
|
|
310
|
+
.where("status", "=", "pending")
|
|
311
|
+
.where("run_id", "is", null)
|
|
312
|
+
.execute();
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Locks a task's row (deleted or not) in the caller's transaction. Every path that locks a task
|
|
316
|
+
* run and the task takes the task row first: the fire job's claim, the run tracker, the scan's
|
|
317
|
+
* repair (a run finishing while the next tick's claim checks overlap must not deadlock).
|
|
318
|
+
*/
|
|
319
|
+
export async function lockTaskRow(t, taskId) {
|
|
320
|
+
await t
|
|
321
|
+
.selectFrom("scheduled_tasks")
|
|
322
|
+
.select("id")
|
|
323
|
+
.where("id", "=", taskId)
|
|
324
|
+
.forUpdate()
|
|
325
|
+
.executeTakeFirst();
|
|
326
|
+
}
|
|
327
|
+
/** The run tracker alone: the runtime (`RunFinish`) imports it without the schedules service. */
|
|
328
|
+
let ScheduledTaskTrackerModule = class ScheduledTaskTrackerModule {
|
|
329
|
+
};
|
|
330
|
+
ScheduledTaskTrackerModule = __decorate([
|
|
331
|
+
Module({ providers: [ScheduledTaskRunTracker], exports: [ScheduledTaskRunTracker] })
|
|
332
|
+
], ScheduledTaskTrackerModule);
|
|
333
|
+
export { ScheduledTaskTrackerModule };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Queue } from "bullmq";
|
|
2
|
+
import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
3
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
4
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
5
|
+
import { LockService } from "../infra/lock/lock.service.js";
|
|
6
|
+
import type { KernelLogger } from "../log.js";
|
|
7
|
+
import { ScheduledTaskRunTracker } from "./run-tracker.js";
|
|
8
|
+
import { ScheduledTaskStore } from "./store.js";
|
|
9
|
+
/**
|
|
10
|
+
* The scheduled task scanner (spec §6), once a minute across replicas. Each due task in its own
|
|
11
|
+
* transaction, locked with `FOR UPDATE SKIP LOCKED` (replicas share the work and never take the
|
|
12
|
+
* same task): the ticks missed outside the org's `catchUpWindowMinutes` become one `skipped` run
|
|
13
|
+
* (`missed`, at the first missed tick), the latest tick in the window gets its run (the unique
|
|
14
|
+
* `(task_id, scheduled_for)` key makes a second insert a no-op), `next_run_at` moves to the next
|
|
15
|
+
* tick plus the task's jitter (or a `once` task ends, or one past its `expires_at`), and after the
|
|
16
|
+
* commit `sched-fire-<taskRunId>` is enqueued. A task whose stored trigger the schedule math cannot
|
|
17
|
+
* evaluate is paused (`invalid_schedule`) instead of failing the scan; a database error fails the
|
|
18
|
+
* pass (the next minute retries) and pauses nothing.
|
|
19
|
+
*
|
|
20
|
+
* Then the repair: a run still `pending` and unfired 10 minutes after its tick has its fire job
|
|
21
|
+
* enqueued again, and is given up (`skipped`, `missed`) after an hour; a run fired an hour ago and
|
|
22
|
+
* still `pending` (its start failed for good) goes through the run tracker's check, which links,
|
|
23
|
+
* settles or releases it, so a `once` task's last run never stays pending.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ScheduledTaskScanJob {
|
|
26
|
+
private readonly kdb;
|
|
27
|
+
readonly txHost: TransactionHost;
|
|
28
|
+
private readonly config;
|
|
29
|
+
private readonly store;
|
|
30
|
+
private readonly tracker;
|
|
31
|
+
private readonly maintenance;
|
|
32
|
+
readonly lockService: LockService;
|
|
33
|
+
readonly log: KernelLogger;
|
|
34
|
+
constructor(kdb: KernelDb, txHost: TransactionHost, config: Pick<KernelConfigService, "get">, store: ScheduledTaskStore, tracker: Pick<ScheduledTaskRunTracker, "inFlightIn">, maintenance: Pick<Queue, "add">, lockService: LockService, log: KernelLogger);
|
|
35
|
+
private get db();
|
|
36
|
+
scan(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* One scan as of `now` (tests pass their own); returns how many due tasks it handled. The repair
|
|
39
|
+
* runs even when the scan loop failed (its error is rethrown afterwards). `repairBatch`: how many
|
|
40
|
+
* rows each repair step takes (tests pass a small one).
|
|
41
|
+
*/
|
|
42
|
+
scanAt(now: Date, repairBatch?: number): Promise<number>;
|
|
43
|
+
/**
|
|
44
|
+
* One due task in its own transaction (a task whose trigger cannot be evaluated rolls back
|
|
45
|
+
* alone and is paused, so it cannot block the others): locked with `SKIP LOCKED`, so replicas take different tasks. False
|
|
46
|
+
* when no task is due.
|
|
47
|
+
*/
|
|
48
|
+
private scanOne;
|
|
49
|
+
/** A task the scan cannot advance stops being due: paused with `invalid_schedule`. */
|
|
50
|
+
private quarantine;
|
|
51
|
+
/** One due task (see the class); returns the id of the run to fire, if any. */
|
|
52
|
+
private advance;
|
|
53
|
+
/** Enqueues the fire jobs (after the commit; a failure is repaired by a later scan). */
|
|
54
|
+
private enqueue;
|
|
55
|
+
/** Lost fire jobs (see the class). */
|
|
56
|
+
private repair;
|
|
57
|
+
/** A run never fired an hour after its tick: given up, task row first (the one lock order). */
|
|
58
|
+
private giveUp;
|
|
59
|
+
/**
|
|
60
|
+
* A started run still pending long after: the tracker links, settles or releases it; one still
|
|
61
|
+
* in flight is stamped (`rechecked_at`) and goes to the back of the next rechecks.
|
|
62
|
+
*/
|
|
63
|
+
private recheck;
|
|
64
|
+
}
|