@femtomc/mu-server 26.2.72 → 26.2.73
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/README.md +17 -0
- package/dist/api/context.d.ts +5 -0
- package/dist/api/context.js +1147 -0
- package/dist/control_plane_bootstrap_helpers.js +4 -1
- package/dist/control_plane_contract.d.ts +2 -5
- package/dist/control_plane_contract.js +1 -1
- package/dist/orchestration_queue.d.ts +44 -0
- package/dist/orchestration_queue.js +111 -0
- package/dist/run_queue.d.ts +1 -1
- package/dist/run_queue.js +78 -79
- package/dist/run_supervisor.d.ts +1 -1
- package/dist/run_supervisor.js +1 -1
- package/dist/server_routing.js +534 -31
- package/package.json +6 -7
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ApprovedCommandBroker, CommandContextResolver, MessagingOperatorRuntime, operatorExtensionPaths, PiMessagingOperatorBackend, } from "@femtomc/mu-agent";
|
|
1
|
+
import { ApprovedCommandBroker, CommandContextResolver, JsonFileConversationSessionStore, MessagingOperatorRuntime, operatorExtensionPaths, PiMessagingOperatorBackend, } from "@femtomc/mu-agent";
|
|
2
|
+
import { join } from "node:path";
|
|
2
3
|
import { ControlPlaneOutboxDispatcher, } from "@femtomc/mu-control-plane";
|
|
3
4
|
const OUTBOX_DRAIN_INTERVAL_MS = 500;
|
|
4
5
|
export function buildMessagingOperatorRuntime(opts) {
|
|
@@ -11,6 +12,7 @@ export function buildMessagingOperatorRuntime(opts) {
|
|
|
11
12
|
model: opts.config.operator.model ?? undefined,
|
|
12
13
|
extensionPaths: operatorExtensionPaths,
|
|
13
14
|
});
|
|
15
|
+
const conversationSessionStore = new JsonFileConversationSessionStore(join(opts.repoRoot, ".mu", "control-plane", "operator_conversations.json"));
|
|
14
16
|
return new MessagingOperatorRuntime({
|
|
15
17
|
backend,
|
|
16
18
|
broker: new ApprovedCommandBroker({
|
|
@@ -18,6 +20,7 @@ export function buildMessagingOperatorRuntime(opts) {
|
|
|
18
20
|
contextResolver: new CommandContextResolver({ allowedRepoRoots: [opts.repoRoot] }),
|
|
19
21
|
}),
|
|
20
22
|
enabled: true,
|
|
23
|
+
conversationSessionStore,
|
|
21
24
|
});
|
|
22
25
|
}
|
|
23
26
|
export function createOutboxDrainLoop(opts) {
|
|
@@ -11,12 +11,9 @@ import type { MuConfig } from "./config.js";
|
|
|
11
11
|
export type ControlPlaneConfig = MuConfig["control_plane"];
|
|
12
12
|
/**
|
|
13
13
|
* Durable orchestration queue contract (default-on path).
|
|
14
|
-
*
|
|
15
|
-
* Scheduler policy/state-machine primitives are owned by `@femtomc/mu-orchestrator` and
|
|
16
|
-
* re-exported here so existing server/control-plane callers keep a stable import surface.
|
|
17
14
|
*/
|
|
18
|
-
export type { InterRootQueuePolicy, OrchestrationQueueState } from "
|
|
19
|
-
export { DEFAULT_INTER_ROOT_QUEUE_POLICY, normalizeInterRootQueuePolicy, ORCHESTRATION_QUEUE_ALLOWED_TRANSITIONS, ORCHESTRATION_QUEUE_INVARIANTS, } from "
|
|
15
|
+
export type { InterRootQueuePolicy, OrchestrationQueueState } from "./orchestration_queue.js";
|
|
16
|
+
export { DEFAULT_INTER_ROOT_QUEUE_POLICY, normalizeInterRootQueuePolicy, ORCHESTRATION_QUEUE_ALLOWED_TRANSITIONS, ORCHESTRATION_QUEUE_INVARIANTS, } from "./orchestration_queue.js";
|
|
20
17
|
export type ActiveAdapter = {
|
|
21
18
|
name: Channel;
|
|
22
19
|
route: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { DEFAULT_INTER_ROOT_QUEUE_POLICY, normalizeInterRootQueuePolicy, ORCHESTRATION_QUEUE_ALLOWED_TRANSITIONS, ORCHESTRATION_QUEUE_INVARIANTS, } from "
|
|
1
|
+
export { DEFAULT_INTER_ROOT_QUEUE_POLICY, normalizeInterRootQueuePolicy, ORCHESTRATION_QUEUE_ALLOWED_TRANSITIONS, ORCHESTRATION_QUEUE_INVARIANTS, } from "./orchestration_queue.js";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type OrchestrationQueueState = "queued" | "active" | "waiting_review" | "refining" | "done" | "failed" | "cancelled";
|
|
2
|
+
/**
|
|
3
|
+
* Inter-root scheduler policy knob for durable queue drain.
|
|
4
|
+
* - sequential: exactly one active root (`max_active_roots=1`)
|
|
5
|
+
* - parallel: bounded fanout (`max_active_roots>=1`)
|
|
6
|
+
*/
|
|
7
|
+
export type InterRootQueuePolicy = {
|
|
8
|
+
mode: "sequential";
|
|
9
|
+
max_active_roots: 1;
|
|
10
|
+
} | {
|
|
11
|
+
mode: "parallel";
|
|
12
|
+
max_active_roots: number;
|
|
13
|
+
};
|
|
14
|
+
export declare const DEFAULT_INTER_ROOT_QUEUE_POLICY: InterRootQueuePolicy;
|
|
15
|
+
export declare function normalizeInterRootQueuePolicy(policy: InterRootQueuePolicy | null | undefined): InterRootQueuePolicy;
|
|
16
|
+
/**
|
|
17
|
+
* Allowed queue transitions. Queue tests should enforce this table exactly.
|
|
18
|
+
*/
|
|
19
|
+
export declare const ORCHESTRATION_QUEUE_ALLOWED_TRANSITIONS: Record<OrchestrationQueueState, readonly OrchestrationQueueState[]>;
|
|
20
|
+
export declare const ORCHESTRATION_QUEUE_INVARIANTS: readonly ["ORCH-QUEUE-001: Queue writes are durable before acknowledging enqueue/start/resume requests.", "ORCH-QUEUE-002: Queue dispatch must claim exactly one active item at a time per root slot.", "ORCH-QUEUE-003: Terminal states (done|failed|cancelled) are immutable.", "ORCH-QUEUE-004: Review path is active -> waiting_review -> (done | refining).", "ORCH-QUEUE-005: Refinement re-enters execution only via refining -> queued.", "ORCH-QUEUE-006: sequential policy permits <=1 active root; parallel permits <=max_active_roots active roots."];
|
|
21
|
+
export type InterRootQueueSnapshot = {
|
|
22
|
+
queue_id: string;
|
|
23
|
+
root_issue_id: string | null;
|
|
24
|
+
state: OrchestrationQueueState;
|
|
25
|
+
job_id: string | null;
|
|
26
|
+
created_at_ms: number;
|
|
27
|
+
};
|
|
28
|
+
export type InterRootQueueReconcilePlan = {
|
|
29
|
+
policy: InterRootQueuePolicy;
|
|
30
|
+
max_active_roots: number;
|
|
31
|
+
active_root_count: number;
|
|
32
|
+
available_root_slots: number;
|
|
33
|
+
activate_queue_ids: string[];
|
|
34
|
+
launch_queue_ids: string[];
|
|
35
|
+
};
|
|
36
|
+
export declare const INTER_ROOT_QUEUE_RECONCILE_INVARIANTS: readonly ["ORCH-INTER-ROOT-RECON-001: one queue snapshot + policy yields one deterministic activation/launch plan.", "ORCH-INTER-ROOT-RECON-002: activation order is FIFO (`created_at_ms`, then `queue_id`) with per-root slot dedupe.", "ORCH-INTER-ROOT-RECON-003: sequential policy admits <=1 occupied root; parallel admits <=max_active_roots roots.", "ORCH-INTER-ROOT-RECON-004: launch candidates are active rows without bound job ids, one launch per root slot."];
|
|
37
|
+
/**
|
|
38
|
+
* Deterministic inter-root queue reconcile primitive.
|
|
39
|
+
*
|
|
40
|
+
* Computes queue activation/launch intentions from durable queue state and policy. The caller is
|
|
41
|
+
* responsible for performing side effects (claim, launch, bind, transition) and reconciling again
|
|
42
|
+
* against refreshed queue/runtime snapshots.
|
|
43
|
+
*/
|
|
44
|
+
export declare function reconcileInterRootQueue<Row extends InterRootQueueSnapshot>(rows: readonly Row[], policy: InterRootQueuePolicy): InterRootQueueReconcilePlan;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export const DEFAULT_INTER_ROOT_QUEUE_POLICY = {
|
|
2
|
+
mode: "sequential",
|
|
3
|
+
max_active_roots: 1,
|
|
4
|
+
};
|
|
5
|
+
export function normalizeInterRootQueuePolicy(policy) {
|
|
6
|
+
if (!policy) {
|
|
7
|
+
return DEFAULT_INTER_ROOT_QUEUE_POLICY;
|
|
8
|
+
}
|
|
9
|
+
if (policy.mode === "parallel") {
|
|
10
|
+
return {
|
|
11
|
+
mode: "parallel",
|
|
12
|
+
max_active_roots: Math.max(1, Math.trunc(policy.max_active_roots)),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
return DEFAULT_INTER_ROOT_QUEUE_POLICY;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Allowed queue transitions. Queue tests should enforce this table exactly.
|
|
19
|
+
*/
|
|
20
|
+
export const ORCHESTRATION_QUEUE_ALLOWED_TRANSITIONS = {
|
|
21
|
+
queued: ["active", "cancelled"],
|
|
22
|
+
active: ["waiting_review", "done", "failed", "cancelled"],
|
|
23
|
+
waiting_review: ["refining", "done", "failed", "cancelled"],
|
|
24
|
+
refining: ["queued", "failed", "cancelled"],
|
|
25
|
+
done: [],
|
|
26
|
+
failed: [],
|
|
27
|
+
cancelled: [],
|
|
28
|
+
};
|
|
29
|
+
export const ORCHESTRATION_QUEUE_INVARIANTS = [
|
|
30
|
+
"ORCH-QUEUE-001: Queue writes are durable before acknowledging enqueue/start/resume requests.",
|
|
31
|
+
"ORCH-QUEUE-002: Queue dispatch must claim exactly one active item at a time per root slot.",
|
|
32
|
+
"ORCH-QUEUE-003: Terminal states (done|failed|cancelled) are immutable.",
|
|
33
|
+
"ORCH-QUEUE-004: Review path is active -> waiting_review -> (done | refining).",
|
|
34
|
+
"ORCH-QUEUE-005: Refinement re-enters execution only via refining -> queued.",
|
|
35
|
+
"ORCH-QUEUE-006: sequential policy permits <=1 active root; parallel permits <=max_active_roots active roots.",
|
|
36
|
+
];
|
|
37
|
+
const INTER_ROOT_OCCUPIED_STATES = new Set(["active", "waiting_review", "refining"]);
|
|
38
|
+
export const INTER_ROOT_QUEUE_RECONCILE_INVARIANTS = [
|
|
39
|
+
"ORCH-INTER-ROOT-RECON-001: one queue snapshot + policy yields one deterministic activation/launch plan.",
|
|
40
|
+
"ORCH-INTER-ROOT-RECON-002: activation order is FIFO (`created_at_ms`, then `queue_id`) with per-root slot dedupe.",
|
|
41
|
+
"ORCH-INTER-ROOT-RECON-003: sequential policy admits <=1 occupied root; parallel admits <=max_active_roots roots.",
|
|
42
|
+
"ORCH-INTER-ROOT-RECON-004: launch candidates are active rows without bound job ids, one launch per root slot.",
|
|
43
|
+
];
|
|
44
|
+
function stableCompare(a, b) {
|
|
45
|
+
if (a.created_at_ms !== b.created_at_ms) {
|
|
46
|
+
return a.created_at_ms - b.created_at_ms;
|
|
47
|
+
}
|
|
48
|
+
return a.queue_id.localeCompare(b.queue_id);
|
|
49
|
+
}
|
|
50
|
+
function normalizeMaxActiveRoots(policy) {
|
|
51
|
+
if (policy.mode === "parallel") {
|
|
52
|
+
return Math.max(1, Math.trunc(policy.max_active_roots));
|
|
53
|
+
}
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
function queueRootSlotKey(row) {
|
|
57
|
+
return row.root_issue_id ? `root:${row.root_issue_id}` : `queue:${row.queue_id}`;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Deterministic inter-root queue reconcile primitive.
|
|
61
|
+
*
|
|
62
|
+
* Computes queue activation/launch intentions from durable queue state and policy. The caller is
|
|
63
|
+
* responsible for performing side effects (claim, launch, bind, transition) and reconciling again
|
|
64
|
+
* against refreshed queue/runtime snapshots.
|
|
65
|
+
*/
|
|
66
|
+
export function reconcileInterRootQueue(rows, policy) {
|
|
67
|
+
const sorted = [...rows].sort(stableCompare);
|
|
68
|
+
const maxActiveRoots = normalizeMaxActiveRoots(policy);
|
|
69
|
+
const occupiedRoots = new Set();
|
|
70
|
+
const launchRoots = new Set();
|
|
71
|
+
const launchQueueIds = [];
|
|
72
|
+
for (const row of sorted) {
|
|
73
|
+
if (!INTER_ROOT_OCCUPIED_STATES.has(row.state)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const slotKey = queueRootSlotKey(row);
|
|
77
|
+
occupiedRoots.add(slotKey);
|
|
78
|
+
if (row.state !== "active" || row.job_id != null || launchRoots.has(slotKey)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
launchRoots.add(slotKey);
|
|
82
|
+
launchQueueIds.push(row.queue_id);
|
|
83
|
+
}
|
|
84
|
+
const availableRootSlots = Math.max(0, maxActiveRoots - occupiedRoots.size);
|
|
85
|
+
const claimedRoots = new Set();
|
|
86
|
+
const activateQueueIds = [];
|
|
87
|
+
if (availableRootSlots > 0) {
|
|
88
|
+
for (const row of sorted) {
|
|
89
|
+
if (row.state !== "queued") {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const slotKey = queueRootSlotKey(row);
|
|
93
|
+
if (occupiedRoots.has(slotKey) || claimedRoots.has(slotKey)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
claimedRoots.add(slotKey);
|
|
97
|
+
activateQueueIds.push(row.queue_id);
|
|
98
|
+
if (activateQueueIds.length >= availableRootSlots) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
policy,
|
|
105
|
+
max_active_roots: maxActiveRoots,
|
|
106
|
+
active_root_count: occupiedRoots.size,
|
|
107
|
+
available_root_slots: availableRootSlots,
|
|
108
|
+
activate_queue_ids: activateQueueIds,
|
|
109
|
+
launch_queue_ids: launchQueueIds,
|
|
110
|
+
};
|
|
111
|
+
}
|
package/dist/run_queue.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { JsonlStore } from "@femtomc/mu-core";
|
|
2
|
-
import { type InterRootQueuePolicy, type InterRootQueueReconcilePlan, type OrchestrationQueueState } from "
|
|
2
|
+
import { type InterRootQueuePolicy, type InterRootQueueReconcilePlan, type OrchestrationQueueState } from "./orchestration_queue.js";
|
|
3
3
|
import type { ControlPlaneRunMode, ControlPlaneRunSnapshot, ControlPlaneRunStatus } from "./run_supervisor.js";
|
|
4
4
|
export type DurableRunQueueState = OrchestrationQueueState;
|
|
5
5
|
export type DurableRunQueueSnapshot = {
|
package/dist/run_queue.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { FsJsonlStore } from "@femtomc/mu-core/node";
|
|
3
|
-
import { INTER_ROOT_QUEUE_RECONCILE_INVARIANTS, ORCHESTRATION_QUEUE_ALLOWED_TRANSITIONS, reconcileInterRootQueue, } from "
|
|
3
|
+
import { INTER_ROOT_QUEUE_RECONCILE_INVARIANTS, ORCHESTRATION_QUEUE_ALLOWED_TRANSITIONS, reconcileInterRootQueue, } from "./orchestration_queue.js";
|
|
4
4
|
const RUN_QUEUE_FILENAME = "run_queue.jsonl";
|
|
5
5
|
const DEFAULT_MAX_STEPS = 20;
|
|
6
6
|
const DEFAULT_MAX_OPERATION_IDS = 128;
|
|
@@ -227,6 +227,16 @@ function snapshotClone(value) {
|
|
|
227
227
|
applied_operation_ids: [...value.applied_operation_ids],
|
|
228
228
|
};
|
|
229
229
|
}
|
|
230
|
+
function normalizeRunStatus(value) {
|
|
231
|
+
if (typeof value !== "string") {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const normalized = value.trim().toLowerCase();
|
|
235
|
+
if (normalized === "running" || normalized === "completed" || normalized === "failed" || normalized === "cancelled") {
|
|
236
|
+
return normalized;
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
230
240
|
function normalizeQueueRecordRow(row, nowMs, maxOperationIds) {
|
|
231
241
|
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
232
242
|
return null;
|
|
@@ -235,78 +245,73 @@ function normalizeQueueRecordRow(row, nowMs, maxOperationIds) {
|
|
|
235
245
|
const queueId = normalizeString(record.queue_id);
|
|
236
246
|
const mode = normalizeRunMode(record.mode);
|
|
237
247
|
const state = normalizeQueueState(record.state);
|
|
238
|
-
if (queueId
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const legacyJobId = normalizeString(record.job_id);
|
|
280
|
-
if (!legacyMode || !legacyStatus || !legacyJobId) {
|
|
248
|
+
if (!queueId || !mode || !state) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const createdAt = normalizeTimestamp(record.created_at_ms, nowMs);
|
|
252
|
+
const updatedAt = normalizeTimestamp(record.updated_at_ms, createdAt);
|
|
253
|
+
const revision = Math.max(1, normalizeMaxSteps(record.revision, 1));
|
|
254
|
+
const dedupeKey = normalizeString(record.dedupe_key) ?? `queue:${queueId}`;
|
|
255
|
+
const prompt = normalizePrompt(record.prompt);
|
|
256
|
+
const rootIssueId = normalizeIssueId(record.root_issue_id);
|
|
257
|
+
return {
|
|
258
|
+
v: 1,
|
|
259
|
+
queue_id: queueId,
|
|
260
|
+
dedupe_key: dedupeKey,
|
|
261
|
+
mode,
|
|
262
|
+
state,
|
|
263
|
+
prompt,
|
|
264
|
+
root_issue_id: rootIssueId,
|
|
265
|
+
max_steps: normalizeMaxSteps(record.max_steps, DEFAULT_MAX_STEPS),
|
|
266
|
+
command_id: normalizeString(record.command_id),
|
|
267
|
+
source: normalizeRunSource(record.source),
|
|
268
|
+
job_id: normalizeString(record.job_id),
|
|
269
|
+
started_at_ms: normalizeNullableTimestamp(record.started_at_ms),
|
|
270
|
+
updated_at_ms: updatedAt,
|
|
271
|
+
finished_at_ms: normalizeNullableTimestamp(record.finished_at_ms),
|
|
272
|
+
exit_code: normalizeNullableInt(record.exit_code),
|
|
273
|
+
pid: normalizeNullableInt(record.pid),
|
|
274
|
+
last_progress: normalizeString(record.last_progress),
|
|
275
|
+
created_at_ms: createdAt,
|
|
276
|
+
revision,
|
|
277
|
+
applied_operation_ids: normalizeAppliedOperationIds(record.applied_operation_ids, maxOperationIds),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
function queueSnapshotFromRunSnapshotRecord(row, nowMs) {
|
|
281
|
+
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const record = row;
|
|
285
|
+
const mode = normalizeRunMode(record.mode);
|
|
286
|
+
const status = normalizeRunStatus(record.status);
|
|
287
|
+
const jobId = normalizeString(record.job_id);
|
|
288
|
+
if (!mode || !status || !jobId) {
|
|
281
289
|
return null;
|
|
282
290
|
}
|
|
283
291
|
const createdAt = normalizeTimestamp(record.started_at_ms, nowMs);
|
|
284
292
|
const updatedAt = normalizeTimestamp(record.updated_at_ms, createdAt);
|
|
285
|
-
const
|
|
293
|
+
const queueId = normalizeString(record.queue_id) ?? `rq-sync-${jobId}`;
|
|
286
294
|
return {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
applied_operation_ids: [],
|
|
308
|
-
},
|
|
309
|
-
migrated: true,
|
|
295
|
+
v: 1,
|
|
296
|
+
queue_id: queueId,
|
|
297
|
+
dedupe_key: `runtime:${jobId}`,
|
|
298
|
+
mode,
|
|
299
|
+
state: queueStateFromRunStatus(status),
|
|
300
|
+
prompt: normalizePrompt(record.prompt),
|
|
301
|
+
root_issue_id: normalizeIssueId(record.root_issue_id),
|
|
302
|
+
max_steps: normalizeMaxSteps(record.max_steps, DEFAULT_MAX_STEPS),
|
|
303
|
+
command_id: normalizeString(record.command_id),
|
|
304
|
+
source: normalizeRunSource(record.source),
|
|
305
|
+
job_id: jobId,
|
|
306
|
+
started_at_ms: normalizeNullableTimestamp(record.started_at_ms),
|
|
307
|
+
updated_at_ms: updatedAt,
|
|
308
|
+
finished_at_ms: normalizeNullableTimestamp(record.finished_at_ms),
|
|
309
|
+
exit_code: normalizeNullableInt(record.exit_code),
|
|
310
|
+
pid: normalizeNullableInt(record.pid),
|
|
311
|
+
last_progress: normalizeString(record.last_progress),
|
|
312
|
+
created_at_ms: createdAt,
|
|
313
|
+
revision: 1,
|
|
314
|
+
applied_operation_ids: [],
|
|
310
315
|
};
|
|
311
316
|
}
|
|
312
317
|
function mergeQueueAndRunSnapshot(queue, run) {
|
|
@@ -444,7 +449,6 @@ export class DurableRunQueue {
|
|
|
444
449
|
}
|
|
445
450
|
async #load() {
|
|
446
451
|
const rows = await this.#store.read();
|
|
447
|
-
let migrated = false;
|
|
448
452
|
const byId = new Map();
|
|
449
453
|
const nowMs = Math.trunc(this.#nowMs());
|
|
450
454
|
for (const row of rows) {
|
|
@@ -452,22 +456,18 @@ export class DurableRunQueue {
|
|
|
452
456
|
if (!normalized) {
|
|
453
457
|
continue;
|
|
454
458
|
}
|
|
455
|
-
|
|
456
|
-
const existing = byId.get(normalized.snapshot.queue_id);
|
|
459
|
+
const existing = byId.get(normalized.queue_id);
|
|
457
460
|
if (!existing) {
|
|
458
|
-
byId.set(normalized.
|
|
461
|
+
byId.set(normalized.queue_id, normalized);
|
|
459
462
|
continue;
|
|
460
463
|
}
|
|
461
|
-
byId.set(normalized.
|
|
464
|
+
byId.set(normalized.queue_id, chooseLatest(existing, normalized));
|
|
462
465
|
}
|
|
463
466
|
this.#rowsById.clear();
|
|
464
467
|
for (const row of byId.values()) {
|
|
465
468
|
this.#rowsById.set(row.queue_id, row);
|
|
466
469
|
}
|
|
467
470
|
this.#rebuildIndexes();
|
|
468
|
-
if (migrated) {
|
|
469
|
-
await this.#persist();
|
|
470
|
-
}
|
|
471
471
|
}
|
|
472
472
|
async #persist() {
|
|
473
473
|
const rows = this.#allRowsSorted().map((row) => snapshotClone(row));
|
|
@@ -756,11 +756,10 @@ export class DurableRunQueue {
|
|
|
756
756
|
}
|
|
757
757
|
}
|
|
758
758
|
if (!row && opts.createIfMissing) {
|
|
759
|
-
const
|
|
760
|
-
if (!
|
|
759
|
+
const next = queueSnapshotFromRunSnapshotRecord(opts.run, nowMs);
|
|
760
|
+
if (!next) {
|
|
761
761
|
return null;
|
|
762
762
|
}
|
|
763
|
-
const next = migrated.snapshot;
|
|
764
763
|
if (operationId) {
|
|
765
764
|
this.#rememberOperation(next, operationId);
|
|
766
765
|
next.revision += 1;
|
package/dist/run_supervisor.d.ts
CHANGED
|
@@ -73,7 +73,7 @@ export type ControlPlaneRunSupervisorOpts = {
|
|
|
73
73
|
* Contract with the durable queue/reconcile layer:
|
|
74
74
|
* - this supervisor executes already-selected work; it does not decide inter-root scheduling policy
|
|
75
75
|
* - sequential/parallel root policy is enforced by queue leasing before launch
|
|
76
|
-
* -
|
|
76
|
+
* - queue-first launch is the supported execution path
|
|
77
77
|
*/
|
|
78
78
|
export declare class ControlPlaneRunSupervisor {
|
|
79
79
|
#private;
|
package/dist/run_supervisor.js
CHANGED
|
@@ -103,7 +103,7 @@ function describeRun(snapshot) {
|
|
|
103
103
|
* Contract with the durable queue/reconcile layer:
|
|
104
104
|
* - this supervisor executes already-selected work; it does not decide inter-root scheduling policy
|
|
105
105
|
* - sequential/parallel root policy is enforced by queue leasing before launch
|
|
106
|
-
* -
|
|
106
|
+
* - queue-first launch is the supported execution path
|
|
107
107
|
*/
|
|
108
108
|
export class ControlPlaneRunSupervisor {
|
|
109
109
|
#repoRoot;
|