@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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Job, Queue } from "bullmq";
|
|
2
|
-
import type { ConversationKind } from "../db/kernel-schema.js";
|
|
2
|
+
import type { ConversationKind, KernelSchemaDb } from "../db/kernel-schema.js";
|
|
3
3
|
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
4
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
5
|
+
import { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
4
6
|
import type { KernelLogger } from "../log.js";
|
|
5
7
|
/** 内核运行队列的 BullMQ 队列名(不含 `:`;与插件队列 `cap.<插件>.<名>` 不会重名)。 */
|
|
6
8
|
export declare const RUN_QUEUE_NAME = "cap-kernel-runs";
|
|
@@ -10,9 +12,23 @@ export declare const RUN_QUEUE_NAME = "cap-kernel-runs";
|
|
|
10
12
|
*/
|
|
11
13
|
export declare const SUBAGENT_RUN_QUEUE_NAME = "cap-kernel-subagent-runs";
|
|
12
14
|
/** The run queue of a conversation kind: sub-agent conversations have their own. */
|
|
13
|
-
export declare const runQueueNameFor: (kind: ConversationKind) =>
|
|
15
|
+
export declare const runQueueNameFor: (kind: ConversationKind) => typeof RUN_QUEUE_NAME | typeof SUBAGENT_RUN_QUEUE_NAME;
|
|
14
16
|
/** 未结束的运行状态(waiting:等人处理审批 / 输入请求,没有任务、不占会话锁与配额)。 */
|
|
15
17
|
export declare const ACTIVE_RUN_STATUSES: readonly ["queued", "running", "waiting"];
|
|
18
|
+
/** 运行的终态。 */
|
|
19
|
+
export type TerminalStatus = "completed" | "failed" | "aborted";
|
|
20
|
+
/** The terminal run statuses (here, not in `run-finish.ts`, which the sub-agent modules import). */
|
|
21
|
+
export declare const TERMINAL_STATUSES: ReadonlySet<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Locks a run's approval and input request rows (`FOR UPDATE`, in id order) in the caller's
|
|
24
|
+
* transaction, after its conversation row and before its run row: the one lock order of every path
|
|
25
|
+
* that writes them (conversation, decided rows, run; ruling R19), so a stop, a run's end, a decision
|
|
26
|
+
* and a timeout never deadlock. Returns whether any of them is still pending.
|
|
27
|
+
*/
|
|
28
|
+
export declare function lockWaitRowsIn(t: KernelSchemaDb, runId: string): Promise<{
|
|
29
|
+
approvals: boolean;
|
|
30
|
+
questions: boolean;
|
|
31
|
+
}>;
|
|
16
32
|
/** 运行任务的数据。 */
|
|
17
33
|
export interface RunJobData {
|
|
18
34
|
runId: string;
|
|
@@ -21,26 +37,92 @@ export interface RunJobData {
|
|
|
21
37
|
/** 同一触发点(会话 + 最新用户消息 seq)只投递一次。 */
|
|
22
38
|
export declare const runJobId: (conversationId: string, maxUserSeq: number) => string;
|
|
23
39
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
* A resumed run's job id: apart from the per-message `run-<conversationId>-<seq>`, and one per
|
|
41
|
+
* wait, so the same wait resumes its run once.
|
|
42
|
+
*/
|
|
43
|
+
export declare const resumeJobId: (conversationId: string, waitId: string) => string;
|
|
44
|
+
/** The BullMQ job name of every run job, on both run queues (an outbox registry name). */
|
|
45
|
+
export declare const RUN_JOB = "run";
|
|
46
|
+
/**
|
|
47
|
+
* The ensure-run backstop (transactional outbox spec §6 "Sending a message"): a maintenance job
|
|
48
|
+
* that calls `RunQueue.ensureRun` for a conversation. Every transaction that may start a run
|
|
49
|
+
* after its commit (a message, a queue change, a background task's finish, a sub-agent's delivery,
|
|
50
|
+
* a run's end) owes one, {@link ENSURE_RUN_BACKSTOP_MS} later, and its caller still calls
|
|
51
|
+
* `ensureRun` right after the commit: the job only matters when that call never happened (the
|
|
52
|
+
* process died) or failed (Redis was down).
|
|
53
|
+
*/
|
|
54
|
+
export declare const ENSURE_RUN_JOB = "ensure-run";
|
|
55
|
+
/** How long after its transaction the ensure-run backstop fires (a backstop, not a second call). */
|
|
56
|
+
export declare const ENSURE_RUN_BACKSTOP_MS = 30000;
|
|
57
|
+
/** The ensure-run job's data. */
|
|
58
|
+
export interface EnsureRunJobData {
|
|
59
|
+
conversationId: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The ensure-run job id: one per conversation and the seq of the event the transaction wrote (a
|
|
63
|
+
* write without an event of its own uses the conversation's newest seq; no `:`).
|
|
64
|
+
*/
|
|
65
|
+
export declare const ensureRunJobId: (conversationId: string, seq: number | string) => string;
|
|
66
|
+
/**
|
|
67
|
+
* Owes the ensure-run backstop of `conversationId` in the caller's transaction (throws outside
|
|
68
|
+
* one). False when the same job id is already owed (the owed job does the same).
|
|
69
|
+
*/
|
|
70
|
+
export declare function oweEnsureRun(outbox: Pick<OutboxService, "add">, conversationId: string, seq: number | string, orgId: string): Promise<boolean>;
|
|
71
|
+
/**
|
|
72
|
+
* The producer side of the two run queues (`cap-kernel-runs`, `cap-kernel-subagent-runs`): owes
|
|
73
|
+
* run jobs in the caller's transaction ({@link owe}: `ensureRun`, the sub-agent dispatch, the
|
|
74
|
+
* `run-orphan-check`) and looks them up; the run worker consumes both queues.
|
|
28
75
|
*/
|
|
29
76
|
export declare class RunJobs {
|
|
30
|
-
private readonly
|
|
77
|
+
private readonly outbox;
|
|
31
78
|
private readonly queues;
|
|
32
|
-
constructor(
|
|
79
|
+
constructor(runs: Queue<RunJobData>, subagentRuns: Queue<RunJobData>, log: KernelLogger, outbox: OutboxService);
|
|
33
80
|
/** The BullMQ queue `name` (default: conversation runs). */
|
|
34
81
|
queue(name?: string): Queue<RunJobData>;
|
|
35
82
|
/** A run job by id on the queue `name`, or undefined. */
|
|
36
83
|
getJob(jobId: string, name?: string): Promise<Job<RunJobData> | undefined>;
|
|
37
84
|
/**
|
|
38
|
-
*
|
|
39
|
-
* queue
|
|
40
|
-
*
|
|
85
|
+
* Owes the run job in the caller's transaction (transactional outbox spec §6): the outbox row
|
|
86
|
+
* commits or rolls back with the run row, on the queue of the conversation's `kind`. Returns
|
|
87
|
+
* false when the same job id is already owed (nothing written; the caller creates no run).
|
|
41
88
|
*/
|
|
42
|
-
|
|
89
|
+
owe(data: RunJobData, jobId: string, kind: ConversationKind, orgId: string): Promise<boolean>;
|
|
90
|
+
/** {@link oweEnsureRun} in the caller's transaction. */
|
|
91
|
+
oweEnsureRun(conversationId: string, seq: number | string, orgId: string): Promise<boolean>;
|
|
43
92
|
}
|
|
44
|
-
/**
|
|
93
|
+
/**
|
|
94
|
+
* What {@link RunResume.resumeIn} found: `queued`, the run was waiting on nothing else and now is
|
|
95
|
+
* queued with its resume job owed; `still-running`, the run has not reached `waiting` yet (its
|
|
96
|
+
* suspend's re-check resumes it); `other-wait-pending`, the run still waits on another approval,
|
|
97
|
+
* input request or fork sub-agent; `not-waiting`, the run is queued or finished (or gone).
|
|
98
|
+
*/
|
|
99
|
+
export type ResumeOutcome = "queued" | "still-running" | "other-wait-pending" | "not-waiting";
|
|
100
|
+
/**
|
|
101
|
+
* The resume of a waiting run (transactional outbox spec §6 "Resumes and timeouts", "Decisions
|
|
102
|
+
* resume in their own transaction"), in the caller's transaction. A decision (approvals, questions,
|
|
103
|
+
* a reply's skips) calls it after writing the decided row, so the lock order is the decided row,
|
|
104
|
+
* then the `runs` row. It lives beside the run jobs, not in `Waits`, so the tools domain (which the
|
|
105
|
+
* runtime imports) can call it; `Waits.resume` wraps it in a transaction of its own.
|
|
106
|
+
*/
|
|
107
|
+
export declare class RunResume {
|
|
108
|
+
private readonly kdb;
|
|
109
|
+
private readonly outbox;
|
|
110
|
+
readonly txHost: TransactionHost;
|
|
111
|
+
constructor(kdb: KernelDb, outbox: Pick<OutboxService, "add">, txHost: TransactionHost);
|
|
112
|
+
/**
|
|
113
|
+
* Locks the run row (`FOR UPDATE`); when it is `waiting` and waits on nothing else (no pending
|
|
114
|
+
* approval or input request, no active fork sub-agent), sets it `queued` (adding this wait's
|
|
115
|
+
* time to `waited_ms`, recording the resume job id) and owes the resume job
|
|
116
|
+
* {@link resumeJobId}`(conversation, waitId)` on its conversation kind's run queue. A `running`
|
|
117
|
+
* run is left alone: its suspend has not set `waiting` yet and re-checks the wait after.
|
|
118
|
+
*/
|
|
119
|
+
resumeIn(runId: string, waitId: string): Promise<ResumeOutcome>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The run queues' producer (the sub-agent dispatch, the run queue and the waits use it), the
|
|
123
|
+
* resume of a waiting run (the waits and the decisions of the tools domain use it) and the
|
|
124
|
+
* lost-jobs canary of the run queues (the `run-orphan-check` reports it, the admin overview reads
|
|
125
|
+
* it).
|
|
126
|
+
*/
|
|
45
127
|
export declare class RunJobsModule {
|
|
46
128
|
}
|
package/dist/runtime/run-jobs.js
CHANGED
|
@@ -9,9 +9,15 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
9
9
|
};
|
|
10
10
|
import { getQueueToken } from "@nestjs/bullmq";
|
|
11
11
|
import { Inject, Injectable, Module } from "@nestjs/common";
|
|
12
|
+
import { sql } from "kysely";
|
|
12
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 { OutboxService } from "../infra/outbox/outbox.service.js";
|
|
13
17
|
import { QueueModule } from "../infra/queue/queue.module.js";
|
|
14
18
|
import { KERNEL_LOGGER } from "../infra/tokens.js";
|
|
19
|
+
import { MAINTENANCE_QUEUE_NAME } from "./maintenance-queue.js";
|
|
20
|
+
import { QueueLostJobs } from "./queue-lost-jobs.js";
|
|
15
21
|
/** 内核运行队列的 BullMQ 队列名(不含 `:`;与插件队列 `cap.<插件>.<名>` 不会重名)。 */
|
|
16
22
|
export const RUN_QUEUE_NAME = "cap-kernel-runs";
|
|
17
23
|
/**
|
|
@@ -23,21 +29,87 @@ export const SUBAGENT_RUN_QUEUE_NAME = "cap-kernel-subagent-runs";
|
|
|
23
29
|
export const runQueueNameFor = (kind) => kind === "subagent" ? SUBAGENT_RUN_QUEUE_NAME : RUN_QUEUE_NAME;
|
|
24
30
|
/** 未结束的运行状态(waiting:等人处理审批 / 输入请求,没有任务、不占会话锁与配额)。 */
|
|
25
31
|
export const ACTIVE_RUN_STATUSES = ["queued", "running", "waiting"];
|
|
32
|
+
/** The terminal run statuses (here, not in `run-finish.ts`, which the sub-agent modules import). */
|
|
33
|
+
export const TERMINAL_STATUSES = new Set([
|
|
34
|
+
"completed",
|
|
35
|
+
"failed",
|
|
36
|
+
"aborted",
|
|
37
|
+
]);
|
|
38
|
+
/**
|
|
39
|
+
* Locks a run's approval and input request rows (`FOR UPDATE`, in id order) in the caller's
|
|
40
|
+
* transaction, after its conversation row and before its run row: the one lock order of every path
|
|
41
|
+
* that writes them (conversation, decided rows, run; ruling R19), so a stop, a run's end, a decision
|
|
42
|
+
* and a timeout never deadlock. Returns whether any of them is still pending.
|
|
43
|
+
*/
|
|
44
|
+
export async function lockWaitRowsIn(t, runId) {
|
|
45
|
+
const approvals = await t
|
|
46
|
+
.selectFrom("approvals")
|
|
47
|
+
.select("status")
|
|
48
|
+
.where("run_id", "=", runId)
|
|
49
|
+
.orderBy("id")
|
|
50
|
+
.forUpdate()
|
|
51
|
+
.execute();
|
|
52
|
+
const questions = await t
|
|
53
|
+
.selectFrom("questions")
|
|
54
|
+
.select("status")
|
|
55
|
+
.where("run_id", "=", runId)
|
|
56
|
+
.orderBy("id")
|
|
57
|
+
.forUpdate()
|
|
58
|
+
.execute();
|
|
59
|
+
return {
|
|
60
|
+
approvals: approvals.some((r) => r.status === "pending"),
|
|
61
|
+
questions: questions.some((r) => r.status === "pending"),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
26
64
|
/** 同一触发点(会话 + 最新用户消息 seq)只投递一次。 */
|
|
27
65
|
export const runJobId = (conversationId, maxUserSeq) => `run-${conversationId}-${maxUserSeq}`;
|
|
28
|
-
/** The options of every run job: one attempt (the run's own paths handle failures), not kept. */
|
|
29
|
-
const RUN_JOB_OPTIONS = { attempts: 1, removeOnComplete: true, removeOnFail: true };
|
|
30
66
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
|
|
34
|
-
|
|
67
|
+
* A resumed run's job id: apart from the per-message `run-<conversationId>-<seq>`, and one per
|
|
68
|
+
* wait, so the same wait resumes its run once.
|
|
69
|
+
*/
|
|
70
|
+
export const resumeJobId = (conversationId, waitId) => `run-${conversationId}-resume-${waitId}`;
|
|
71
|
+
/** The BullMQ job name of every run job, on both run queues (an outbox registry name). */
|
|
72
|
+
export const RUN_JOB = "run";
|
|
73
|
+
/**
|
|
74
|
+
* The ensure-run backstop (transactional outbox spec §6 "Sending a message"): a maintenance job
|
|
75
|
+
* that calls `RunQueue.ensureRun` for a conversation. Every transaction that may start a run
|
|
76
|
+
* after its commit (a message, a queue change, a background task's finish, a sub-agent's delivery,
|
|
77
|
+
* a run's end) owes one, {@link ENSURE_RUN_BACKSTOP_MS} later, and its caller still calls
|
|
78
|
+
* `ensureRun` right after the commit: the job only matters when that call never happened (the
|
|
79
|
+
* process died) or failed (Redis was down).
|
|
80
|
+
*/
|
|
81
|
+
export const ENSURE_RUN_JOB = "ensure-run";
|
|
82
|
+
/** How long after its transaction the ensure-run backstop fires (a backstop, not a second call). */
|
|
83
|
+
export const ENSURE_RUN_BACKSTOP_MS = 30_000;
|
|
84
|
+
/**
|
|
85
|
+
* The ensure-run job id: one per conversation and the seq of the event the transaction wrote (a
|
|
86
|
+
* write without an event of its own uses the conversation's newest seq; no `:`).
|
|
87
|
+
*/
|
|
88
|
+
export const ensureRunJobId = (conversationId, seq) => `ensure-run-${conversationId}-${seq}`;
|
|
89
|
+
/**
|
|
90
|
+
* Owes the ensure-run backstop of `conversationId` in the caller's transaction (throws outside
|
|
91
|
+
* one). False when the same job id is already owed (the owed job does the same).
|
|
92
|
+
*/
|
|
93
|
+
export function oweEnsureRun(outbox, conversationId, seq, orgId) {
|
|
94
|
+
return outbox.add({
|
|
95
|
+
queue: MAINTENANCE_QUEUE_NAME,
|
|
96
|
+
name: ENSURE_RUN_JOB,
|
|
97
|
+
jobId: ensureRunJobId(conversationId, seq),
|
|
98
|
+
data: { conversationId },
|
|
99
|
+
runAt: new Date(Date.now() + ENSURE_RUN_BACKSTOP_MS),
|
|
100
|
+
orgId,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The producer side of the two run queues (`cap-kernel-runs`, `cap-kernel-subagent-runs`): owes
|
|
105
|
+
* run jobs in the caller's transaction ({@link owe}: `ensureRun`, the sub-agent dispatch, the
|
|
106
|
+
* `run-orphan-check`) and looks them up; the run worker consumes both queues.
|
|
35
107
|
*/
|
|
36
108
|
let RunJobs = class RunJobs {
|
|
37
|
-
|
|
109
|
+
outbox;
|
|
38
110
|
queues;
|
|
39
|
-
constructor(
|
|
40
|
-
this.
|
|
111
|
+
constructor(runs, subagentRuns, log, outbox) {
|
|
112
|
+
this.outbox = outbox;
|
|
41
113
|
this.queues = { [RUN_QUEUE_NAME]: runs, [SUBAGENT_RUN_QUEUE_NAME]: subagentRuns };
|
|
42
114
|
for (const [name, q] of Object.entries(this.queues)) {
|
|
43
115
|
q.on("error", (err) => log.error("run queue error", { err, queue: name }));
|
|
@@ -55,39 +127,138 @@ let RunJobs = class RunJobs {
|
|
|
55
127
|
return this.queue(name).getJob(jobId);
|
|
56
128
|
}
|
|
57
129
|
/**
|
|
58
|
-
*
|
|
59
|
-
* queue
|
|
60
|
-
*
|
|
130
|
+
* Owes the run job in the caller's transaction (transactional outbox spec §6): the outbox row
|
|
131
|
+
* commits or rolls back with the run row, on the queue of the conversation's `kind`. Returns
|
|
132
|
+
* false when the same job id is already owed (nothing written; the caller creates no run).
|
|
61
133
|
*/
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
134
|
+
owe(data, jobId, kind, orgId) {
|
|
135
|
+
return this.outbox.add({
|
|
136
|
+
queue: runQueueNameFor(kind),
|
|
137
|
+
name: RUN_JOB,
|
|
138
|
+
jobId,
|
|
139
|
+
data,
|
|
140
|
+
orgId,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/** {@link oweEnsureRun} in the caller's transaction. */
|
|
144
|
+
oweEnsureRun(conversationId, seq, orgId) {
|
|
145
|
+
return oweEnsureRun(this.outbox, conversationId, seq, orgId);
|
|
71
146
|
}
|
|
72
147
|
};
|
|
73
148
|
RunJobs = __decorate([
|
|
74
149
|
Injectable(),
|
|
75
|
-
__param(0, Inject(
|
|
76
|
-
__param(1, Inject(getQueueToken(
|
|
77
|
-
__param(2, Inject(
|
|
78
|
-
__param(3, Inject(
|
|
150
|
+
__param(0, Inject(getQueueToken(RUN_QUEUE_NAME))),
|
|
151
|
+
__param(1, Inject(getQueueToken(SUBAGENT_RUN_QUEUE_NAME))),
|
|
152
|
+
__param(2, Inject(KERNEL_LOGGER)),
|
|
153
|
+
__param(3, Inject(OutboxService))
|
|
79
154
|
], RunJobs);
|
|
80
155
|
export { RunJobs };
|
|
156
|
+
/**
|
|
157
|
+
* The resume of a waiting run (transactional outbox spec §6 "Resumes and timeouts", "Decisions
|
|
158
|
+
* resume in their own transaction"), in the caller's transaction. A decision (approvals, questions,
|
|
159
|
+
* a reply's skips) calls it after writing the decided row, so the lock order is the decided row,
|
|
160
|
+
* then the `runs` row. It lives beside the run jobs, not in `Waits`, so the tools domain (which the
|
|
161
|
+
* runtime imports) can call it; `Waits.resume` wraps it in a transaction of its own.
|
|
162
|
+
*/
|
|
163
|
+
let RunResume = class RunResume {
|
|
164
|
+
kdb;
|
|
165
|
+
outbox;
|
|
166
|
+
txHost;
|
|
167
|
+
constructor(kdb, outbox, txHost) {
|
|
168
|
+
this.kdb = kdb;
|
|
169
|
+
this.outbox = outbox;
|
|
170
|
+
this.txHost = txHost;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Locks the run row (`FOR UPDATE`); when it is `waiting` and waits on nothing else (no pending
|
|
174
|
+
* approval or input request, no active fork sub-agent), sets it `queued` (adding this wait's
|
|
175
|
+
* time to `waited_ms`, recording the resume job id) and owes the resume job
|
|
176
|
+
* {@link resumeJobId}`(conversation, waitId)` on its conversation kind's run queue. A `running`
|
|
177
|
+
* run is left alone: its suspend has not set `waiting` yet and re-checks the wait after.
|
|
178
|
+
*/
|
|
179
|
+
async resumeIn(runId, waitId) {
|
|
180
|
+
const t = this.kdb.db;
|
|
181
|
+
const run = await t
|
|
182
|
+
.selectFrom("runs")
|
|
183
|
+
.select("status")
|
|
184
|
+
.where("id", "=", runId)
|
|
185
|
+
.forUpdate()
|
|
186
|
+
.executeTakeFirst();
|
|
187
|
+
if (run?.status === "running")
|
|
188
|
+
return "still-running";
|
|
189
|
+
if (run?.status !== "waiting")
|
|
190
|
+
return "not-waiting";
|
|
191
|
+
const row = await t
|
|
192
|
+
.updateTable("runs")
|
|
193
|
+
.set({
|
|
194
|
+
status: "queued",
|
|
195
|
+
queued_at: sql `now()`,
|
|
196
|
+
job_id: sql `'run-' || conversation_id || '-resume-' || ${waitId}::text`,
|
|
197
|
+
waited_ms: sql `waited_ms + COALESCE(floor(extract(epoch FROM now() - waiting_since) * 1000), 0)::bigint`,
|
|
198
|
+
waiting_since: null,
|
|
199
|
+
wait_expires_at: null,
|
|
200
|
+
})
|
|
201
|
+
.where("id", "=", runId)
|
|
202
|
+
.where(({ not, exists, selectFrom }) => not(exists(selectFrom("approvals")
|
|
203
|
+
.select(sql `1`.as("one"))
|
|
204
|
+
.where("approvals.run_id", "=", runId)
|
|
205
|
+
.where("approvals.status", "=", "pending"))))
|
|
206
|
+
.where(({ not, exists, selectFrom }) => not(exists(selectFrom("questions")
|
|
207
|
+
.select(sql `1`.as("one"))
|
|
208
|
+
.where("questions.run_id", "=", runId)
|
|
209
|
+
.where("questions.status", "=", "pending"))))
|
|
210
|
+
// a fork invocation's sub-agent (agent map spec §5.4: background `Agent` children never
|
|
211
|
+
// hold their parent run) is no longer active
|
|
212
|
+
.where(({ not, exists, selectFrom }) => not(exists(selectFrom("subagent_dispatches as sd")
|
|
213
|
+
.innerJoin("runs as cr", "cr.id", "sd.child_run_id")
|
|
214
|
+
.select(sql `1`.as("one"))
|
|
215
|
+
.where("sd.parent_run_id", "=", runId)
|
|
216
|
+
.where("sd.for_skill", "=", true)
|
|
217
|
+
.where("cr.status", "in", ACTIVE_RUN_STATUSES))))
|
|
218
|
+
.returning("conversation_id")
|
|
219
|
+
.executeTakeFirst();
|
|
220
|
+
if (!row)
|
|
221
|
+
return "other-wait-pending";
|
|
222
|
+
const conv = await t
|
|
223
|
+
.selectFrom("conversations")
|
|
224
|
+
.select(["kind", "org_id"])
|
|
225
|
+
.where("id", "=", row.conversation_id)
|
|
226
|
+
.executeTakeFirstOrThrow();
|
|
227
|
+
await this.outbox.add({
|
|
228
|
+
queue: runQueueNameFor(conv.kind),
|
|
229
|
+
name: RUN_JOB,
|
|
230
|
+
jobId: resumeJobId(row.conversation_id, waitId),
|
|
231
|
+
data: { runId, conversationId: row.conversation_id },
|
|
232
|
+
orgId: conv.org_id,
|
|
233
|
+
});
|
|
234
|
+
return "queued";
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
__decorate([
|
|
238
|
+
Transactional({ propagation: "mandatory" })
|
|
239
|
+
], RunResume.prototype, "resumeIn", null);
|
|
240
|
+
RunResume = __decorate([
|
|
241
|
+
Injectable(),
|
|
242
|
+
__param(0, Inject(KernelDb)),
|
|
243
|
+
__param(1, Inject(OutboxService)),
|
|
244
|
+
__param(2, Inject(TransactionHost))
|
|
245
|
+
], RunResume);
|
|
246
|
+
export { RunResume };
|
|
81
247
|
/** The run queues' registration (re-exported: the admin health reads their counts). */
|
|
82
248
|
const RUN_QUEUES_MODULE = QueueModule.registerQueue(RUN_QUEUE_NAME, SUBAGENT_RUN_QUEUE_NAME);
|
|
83
|
-
/**
|
|
249
|
+
/**
|
|
250
|
+
* The run queues' producer (the sub-agent dispatch, the run queue and the waits use it), the
|
|
251
|
+
* resume of a waiting run (the waits and the decisions of the tools domain use it) and the
|
|
252
|
+
* lost-jobs canary of the run queues (the `run-orphan-check` reports it, the admin overview reads
|
|
253
|
+
* it).
|
|
254
|
+
*/
|
|
84
255
|
let RunJobsModule = class RunJobsModule {
|
|
85
256
|
};
|
|
86
257
|
RunJobsModule = __decorate([
|
|
87
258
|
Module({
|
|
88
259
|
imports: [RUN_QUEUES_MODULE],
|
|
89
|
-
providers: [RunJobs],
|
|
90
|
-
exports: [RunJobs, RUN_QUEUES_MODULE],
|
|
260
|
+
providers: [RunJobs, RunResume, QueueLostJobs],
|
|
261
|
+
exports: [RunJobs, RunResume, QueueLostJobs, RUN_QUEUES_MODULE],
|
|
91
262
|
})
|
|
92
263
|
], RunJobsModule);
|
|
93
264
|
export { RunJobsModule };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { EventLog } from "../conversations/event-log.js";
|
|
2
|
+
import type { ConversationKind, KernelSchemaDb } from "../db/kernel-schema.js";
|
|
3
|
+
import { KernelConfigService } from "../infra/config/kernel-config.service.js";
|
|
4
|
+
import { KernelDb } from "../infra/database/kernel-db.js";
|
|
5
|
+
import { TransactionHost } from "../infra/database/transaction-host.js";
|
|
6
|
+
import { KernelEvents } from "../infra/events/kernel-events.js";
|
|
7
|
+
import { LockService } from "../infra/lock/lock.service.js";
|
|
8
|
+
import type { KernelLogger } from "../log.js";
|
|
9
|
+
import { KyselyCheckpointSaver } from "./checkpointer.js";
|
|
10
|
+
import { ConversationLock } from "./conversation-lock.js";
|
|
11
|
+
import { QueueLostJobs } from "./queue-lost-jobs.js";
|
|
12
|
+
import { RunFinish } from "./run-finish.js";
|
|
13
|
+
import { RunJobs } from "./run-jobs.js";
|
|
14
|
+
import { RunQueue } from "./run-queue.js";
|
|
15
|
+
import { Waits } from "./waits.js";
|
|
16
|
+
/** The check's `@LockedInterval` job name (its run lock key is the default `cron:run-orphan-check`). */
|
|
17
|
+
export declare const RUN_ORPHAN_CHECK_JOB = "run-orphan-check";
|
|
18
|
+
/** The check's period: a fixed constant, not a config field. */
|
|
19
|
+
export declare const RUN_ORPHAN_CHECK_PERIOD_MS = 60000;
|
|
20
|
+
/**
|
|
21
|
+
* How long a run must have been in its state (queued, or running since its last start) before it
|
|
22
|
+
* can count as an orphan: a transition in flight is never repaired.
|
|
23
|
+
*/
|
|
24
|
+
export declare const RUN_ORPHAN_MIN_AGE_MS = 60000;
|
|
25
|
+
/** The candidates one page of a pass reads (a keyset page over the state's start time and id). */
|
|
26
|
+
export declare const RUN_ORPHAN_CHECK_BATCH = 500;
|
|
27
|
+
/**
|
|
28
|
+
* The most pages one pass reads: a bound on its work. Healthy long runs and queued runs waiting
|
|
29
|
+
* for a quota slot are passed over page by page, so a real orphan behind them is still reached;
|
|
30
|
+
* beyond this many candidates the rest wait for the next minute.
|
|
31
|
+
*/
|
|
32
|
+
export declare const RUN_ORPHAN_CHECK_MAX_PAGES = 10;
|
|
33
|
+
/** The error the canary logs (transactional outbox spec §8.3). */
|
|
34
|
+
export declare const QUEUE_LOST_JOBS_MESSAGE = "queued run without a job: the queue lost jobs";
|
|
35
|
+
/** What one pass of {@link RunOrphanCheck.check} repaired. */
|
|
36
|
+
export interface RunOrphanCheckResult {
|
|
37
|
+
/** `running` runs nobody runs, failed `run.orphaned`. */
|
|
38
|
+
runningRepaired: number;
|
|
39
|
+
/** `queued` runs whose job was lost, owed again (each one is the canary). */
|
|
40
|
+
queuedReadded: number;
|
|
41
|
+
}
|
|
42
|
+
/** An active run that may be an orphan, with where its job lives. */
|
|
43
|
+
export interface OrphanCandidate {
|
|
44
|
+
runId: string;
|
|
45
|
+
conversationId: string;
|
|
46
|
+
orgId: string;
|
|
47
|
+
kind: ConversationKind;
|
|
48
|
+
status: "queued" | "running";
|
|
49
|
+
/** The run's job id (a resume's `runs.job_id`, else its trigger's) and queue. */
|
|
50
|
+
jobId: string;
|
|
51
|
+
queue: string;
|
|
52
|
+
/** When the run entered its state, as PG prints it (compared again in the repair). */
|
|
53
|
+
since: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* One page of the active runs of chat and sub-agent conversations in `statuses` that entered their
|
|
57
|
+
* state more than `minAgeMs` ago, by (state start, id), after `after` (an eval's hidden
|
|
58
|
+
* conversation drives its runs itself).
|
|
59
|
+
*/
|
|
60
|
+
export declare function orphanCandidates(db: KernelSchemaDb, statuses: readonly ("queued" | "running")[], minAgeMs: number, limit: number, after?: Pick<OrphanCandidate, "since" | "runId">): Promise<OrphanCandidate[]>;
|
|
61
|
+
/**
|
|
62
|
+
* Whether the outbox holds the job `jobId` on `queue`: a pending row (it is still owed) or a dead
|
|
63
|
+
* one (the outbox gave up on it; the super admin sees it under `outbox_dead` and retries or
|
|
64
|
+
* discards it, so it is not a job the queue lost).
|
|
65
|
+
*/
|
|
66
|
+
export declare function jobInOutbox(db: KernelSchemaDb, queue: string, jobId: string): Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* The `queued` runs (in their state for over `minAgeMs`) whose job is neither in the outbox
|
|
69
|
+
* (owed, or dead) nor in BullMQ (`jobExists`), and that are still queued after both looks: the run
|
|
70
|
+
* queue lost their jobs. The outbox is read before BullMQ (a row is deleted only after its job was added) and the
|
|
71
|
+
* run again after both (a job that finished took its run out of `queued` first), so a run in
|
|
72
|
+
* flight is never reported. The `run-orphan-check` uses it; so does the dual-replica test harness,
|
|
73
|
+
* which asserts at its teardown that the kernel left none (`minAgeMs` 0).
|
|
74
|
+
*/
|
|
75
|
+
export declare function queuedRunsWithoutJob(db: KernelSchemaDb, jobExists: (queue: string, jobId: string) => Promise<boolean>, opts: {
|
|
76
|
+
minAgeMs: number;
|
|
77
|
+
}): Promise<OrphanCandidate[]>;
|
|
78
|
+
/**
|
|
79
|
+
* The `run-orphan-check` (transactional outbox spec §6 "Replica death during a run", §8.3): once a
|
|
80
|
+
* minute across replicas (worker role), the one repair of a run nobody runs. A run counts when it
|
|
81
|
+
* has been in its state for over {@link RUN_ORPHAN_MIN_AGE_MS} with neither its BullMQ job nor a
|
|
82
|
+
* conversation lock holder:
|
|
83
|
+
* - `running`: its replica died while running it (BullMQ's stalled re-deliveries ran out, or the
|
|
84
|
+
* process died inside `onFailed`; run jobs are `attempts: 1, removeOnFail`). It is failed
|
|
85
|
+
* `run.orphaned` (its terminal transaction cancels its waits and owes what every run end owes),
|
|
86
|
+
* then, after the commit, its checkpoints are dropped, its fork child stopped (`run.orphaned`), a
|
|
87
|
+
* sub-agent's failure delivered to its parent at once and the conversation's next run started.
|
|
88
|
+
* - `queued`, with no outbox row either (pending or dead): a canary. Every queued run has its job owed in its
|
|
89
|
+
* own transaction, so this means Redis lost data. It is logged as an error, counted for the admin
|
|
90
|
+
* overview ({@link QueueLostJobs}) and its job owed again so the run is not stuck.
|
|
91
|
+
* Redis is read before each repair's transaction, which then re-reads the run under its
|
|
92
|
+
* conversation row lock and acts only if it is still in the same state since the same time. Read
|
|
93
|
+
* paths never repair runs.
|
|
94
|
+
*/
|
|
95
|
+
export declare class RunOrphanCheck {
|
|
96
|
+
private readonly kdb;
|
|
97
|
+
private readonly jobs;
|
|
98
|
+
private readonly lock;
|
|
99
|
+
private readonly finisher;
|
|
100
|
+
private readonly eventLog;
|
|
101
|
+
private readonly checkpoints;
|
|
102
|
+
/** A failed sub-agent run reports to its parent at once ({@link Waits.childFinished}). */
|
|
103
|
+
private readonly waits;
|
|
104
|
+
/** The conversation's next run, after a repaired run's end. */
|
|
105
|
+
private readonly queue;
|
|
106
|
+
private readonly lostJobs;
|
|
107
|
+
private readonly events;
|
|
108
|
+
readonly txHost: TransactionHost;
|
|
109
|
+
readonly config: KernelConfigService;
|
|
110
|
+
readonly lockService: LockService;
|
|
111
|
+
readonly log: KernelLogger;
|
|
112
|
+
constructor(kdb: KernelDb, jobs: Pick<RunJobs, "getJob" | "owe">, lock: Pick<ConversationLock, "holder">, finisher: Pick<RunFinish, "finishIn">, eventLog: Pick<EventLog, "notify" | "publishChanged">, checkpoints: Pick<KyselyCheckpointSaver, "deleteThread">,
|
|
113
|
+
/** A failed sub-agent run reports to its parent at once ({@link Waits.childFinished}). */
|
|
114
|
+
waits: Pick<Waits, "childFinished">,
|
|
115
|
+
/** The conversation's next run, after a repaired run's end. */
|
|
116
|
+
queue: Pick<RunQueue, "ensureRun">, lostJobs: Pick<QueueLostJobs, "report">, events: KernelEvents, txHost: TransactionHost, config: KernelConfigService, lockService: LockService, log: KernelLogger);
|
|
117
|
+
private get db();
|
|
118
|
+
run(): Promise<void>;
|
|
119
|
+
/** One pass: repairs the orphans it finds (each on its own; a failure is logged). */
|
|
120
|
+
check(): Promise<RunOrphanCheckResult>;
|
|
121
|
+
/**
|
|
122
|
+
* Neither a conversation lock holder nor the run's job in BullMQ (nor, queued, in the outbox: an
|
|
123
|
+
* owed row is delivered by the relay, a dead one waits for the super admin).
|
|
124
|
+
*/
|
|
125
|
+
private withoutJobOrHolder;
|
|
126
|
+
/**
|
|
127
|
+
* Fails a `running` orphan (`run.orphaned`), then does what its end causes after the commit:
|
|
128
|
+
* the open pages hear it, its checkpoints go, its fork child stops, a sub-agent reports to its
|
|
129
|
+
* parent and the conversation's next run starts (each also owed by the terminal transaction).
|
|
130
|
+
*/
|
|
131
|
+
private failRunning;
|
|
132
|
+
/** {@link failRunning}'s transaction: the terminal state, if the run is still the same orphan. */
|
|
133
|
+
private failIn;
|
|
134
|
+
/** Owes a lost queued run's job again under its own id, if it is still the same orphan. */
|
|
135
|
+
private oweAgainIn;
|
|
136
|
+
/**
|
|
137
|
+
* Under the conversation row lock (the first lock of every path that changes a run's state): the
|
|
138
|
+
* run is still in the state and since the time the pass read. Not `FOR UPDATE` on the run: the
|
|
139
|
+
* terminal transaction locks it after the run's wait rows.
|
|
140
|
+
*/
|
|
141
|
+
private stillIn;
|
|
142
|
+
}
|