@femtomc/mu-server 26.2.72 → 26.2.74
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 +54 -49
- package/dist/api/control_plane.js +56 -0
- package/dist/api/cron.js +2 -23
- package/dist/api/heartbeats.js +1 -66
- package/dist/api/identities.js +3 -2
- package/dist/api/runs.js +0 -83
- package/dist/api/session_flash.d.ts +60 -0
- package/dist/api/session_flash.js +326 -0
- package/dist/api/session_turn.d.ts +38 -0
- package/dist/api/session_turn.js +423 -0
- package/dist/config.d.ts +9 -4
- package/dist/config.js +24 -24
- package/dist/control_plane.d.ts +2 -16
- package/dist/control_plane.js +57 -83
- package/dist/control_plane_adapter_registry.d.ts +19 -0
- package/dist/control_plane_adapter_registry.js +74 -0
- package/dist/control_plane_bootstrap_helpers.js +4 -1
- package/dist/control_plane_contract.d.ts +3 -12
- package/dist/control_plane_contract.js +1 -1
- package/dist/control_plane_run_queue_coordinator.d.ts +1 -7
- package/dist/control_plane_run_queue_coordinator.js +1 -62
- package/dist/control_plane_telegram_generation.js +1 -0
- package/dist/control_plane_wake_delivery.js +1 -0
- package/dist/cron_programs.d.ts +21 -35
- package/dist/cron_programs.js +32 -113
- package/dist/cron_request.d.ts +0 -6
- package/dist/cron_request.js +0 -41
- package/dist/heartbeat_programs.d.ts +20 -35
- package/dist/heartbeat_programs.js +26 -122
- package/dist/index.d.ts +2 -2
- package/dist/orchestration_queue.d.ts +44 -0
- package/dist/orchestration_queue.js +111 -0
- package/dist/outbound_delivery_router.d.ts +12 -0
- package/dist/outbound_delivery_router.js +29 -0
- package/dist/run_queue.d.ts +1 -1
- package/dist/run_queue.js +78 -79
- package/dist/run_supervisor.d.ts +2 -17
- package/dist/run_supervisor.js +1 -71
- package/dist/server.d.ts +0 -5
- package/dist/server.js +95 -127
- package/dist/server_program_orchestration.d.ts +4 -19
- package/dist/server_program_orchestration.js +49 -200
- package/dist/server_routing.d.ts +0 -9
- package/dist/server_routing.js +19 -151
- package/dist/server_runtime.js +0 -1
- package/dist/server_types.d.ts +0 -2
- package/dist/server_types.js +0 -7
- package/package.json +6 -10
- package/dist/api/forum.d.ts +0 -2
- package/dist/api/forum.js +0 -75
- package/dist/api/issues.d.ts +0 -2
- package/dist/api/issues.js +0 -173
- package/public/assets/index-CxkevQNh.js +0 -100
- package/public/assets/index-D_8anM-D.css +0 -1
- package/public/index.html +0 -14
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
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import type { CommandRecord } from "@femtomc/mu-control-plane";
|
|
2
|
-
import { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
3
2
|
export type ControlPlaneRunMode = "run_start" | "run_resume";
|
|
4
3
|
export type ControlPlaneRunStatus = "running" | "completed" | "failed" | "cancelled";
|
|
5
|
-
export type ControlPlaneRunWakeMode = "immediate" | "next_heartbeat";
|
|
6
4
|
export type ControlPlaneRunSnapshot = {
|
|
7
5
|
job_id: string;
|
|
8
6
|
mode: ControlPlaneRunMode;
|
|
@@ -33,12 +31,7 @@ export type ControlPlaneRunInterruptResult = {
|
|
|
33
31
|
reason: "not_found" | "not_running" | "missing_target" | null;
|
|
34
32
|
run: ControlPlaneRunSnapshot | null;
|
|
35
33
|
};
|
|
36
|
-
export type
|
|
37
|
-
ok: boolean;
|
|
38
|
-
reason: "not_found" | "not_running" | "missing_target" | null;
|
|
39
|
-
run: ControlPlaneRunSnapshot | null;
|
|
40
|
-
};
|
|
41
|
-
export type ControlPlaneRunEventKind = "run_started" | "run_root_discovered" | "run_progress" | "run_heartbeat" | "run_interrupt_requested" | "run_completed" | "run_failed" | "run_cancelled";
|
|
34
|
+
export type ControlPlaneRunEventKind = "run_started" | "run_root_discovered" | "run_progress" | "run_interrupt_requested" | "run_completed" | "run_failed" | "run_cancelled";
|
|
42
35
|
export type ControlPlaneRunEvent = {
|
|
43
36
|
seq: number;
|
|
44
37
|
ts_ms: number;
|
|
@@ -61,8 +54,6 @@ export type ControlPlaneRunSupervisorOpts = {
|
|
|
61
54
|
argv: string[];
|
|
62
55
|
cwd: string;
|
|
63
56
|
}) => ControlPlaneRunProcess;
|
|
64
|
-
heartbeatIntervalMs?: number;
|
|
65
|
-
heartbeatScheduler?: ActivityHeartbeatScheduler;
|
|
66
57
|
maxStoredLines?: number;
|
|
67
58
|
maxHistory?: number;
|
|
68
59
|
onEvent?: (event: ControlPlaneRunEvent) => void | Promise<void>;
|
|
@@ -73,7 +64,7 @@ export type ControlPlaneRunSupervisorOpts = {
|
|
|
73
64
|
* Contract with the durable queue/reconcile layer:
|
|
74
65
|
* - this supervisor executes already-selected work; it does not decide inter-root scheduling policy
|
|
75
66
|
* - sequential/parallel root policy is enforced by queue leasing before launch
|
|
76
|
-
* -
|
|
67
|
+
* - queue-first launch is the supported execution path
|
|
77
68
|
*/
|
|
78
69
|
export declare class ControlPlaneRunSupervisor {
|
|
79
70
|
#private;
|
|
@@ -112,12 +103,6 @@ export declare class ControlPlaneRunSupervisor {
|
|
|
112
103
|
jobId?: string | null;
|
|
113
104
|
rootIssueId?: string | null;
|
|
114
105
|
}): ControlPlaneRunInterruptResult;
|
|
115
|
-
heartbeat(opts: {
|
|
116
|
-
jobId?: string | null;
|
|
117
|
-
rootIssueId?: string | null;
|
|
118
|
-
reason?: string | null;
|
|
119
|
-
wakeMode?: string | null;
|
|
120
|
-
}): ControlPlaneRunHeartbeatResult;
|
|
121
106
|
startFromCommand(command: CommandRecord): Promise<ControlPlaneRunSnapshot | null>;
|
|
122
107
|
stop(): Promise<void>;
|
|
123
108
|
}
|
package/dist/run_supervisor.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readdir } from "node:fs/promises";
|
|
2
2
|
import { join, relative } from "node:path";
|
|
3
|
-
import { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
4
3
|
const DEFAULT_MAX_STEPS = 20;
|
|
5
4
|
const ROOT_RE = /\bRoot:\s*(mu-[a-z0-9][a-z0-9-]*)\b/i;
|
|
6
5
|
const STEP_RE = /^(Step|Done)\s+\d+\/\d+\s+/;
|
|
@@ -45,13 +44,6 @@ function normalizeIssueId(value) {
|
|
|
45
44
|
}
|
|
46
45
|
return trimmed.toLowerCase();
|
|
47
46
|
}
|
|
48
|
-
function normalizeWakeMode(value) {
|
|
49
|
-
if (typeof value !== "string") {
|
|
50
|
-
return "immediate";
|
|
51
|
-
}
|
|
52
|
-
const normalized = value.trim().toLowerCase().replaceAll("-", "_");
|
|
53
|
-
return normalized === "next_heartbeat" ? "next_heartbeat" : "immediate";
|
|
54
|
-
}
|
|
55
47
|
function pushBounded(lines, line, maxLines) {
|
|
56
48
|
lines.push(line);
|
|
57
49
|
if (lines.length <= maxLines) {
|
|
@@ -103,15 +95,12 @@ function describeRun(snapshot) {
|
|
|
103
95
|
* Contract with the durable queue/reconcile layer:
|
|
104
96
|
* - this supervisor executes already-selected work; it does not decide inter-root scheduling policy
|
|
105
97
|
* - sequential/parallel root policy is enforced by queue leasing before launch
|
|
106
|
-
* -
|
|
98
|
+
* - queue-first launch is the supported execution path
|
|
107
99
|
*/
|
|
108
100
|
export class ControlPlaneRunSupervisor {
|
|
109
101
|
#repoRoot;
|
|
110
102
|
#nowMs;
|
|
111
103
|
#spawnProcess;
|
|
112
|
-
#heartbeatIntervalMs;
|
|
113
|
-
#heartbeatScheduler;
|
|
114
|
-
#ownsHeartbeatScheduler;
|
|
115
104
|
#maxStoredLines;
|
|
116
105
|
#maxHistory;
|
|
117
106
|
#onEvent;
|
|
@@ -123,9 +112,6 @@ export class ControlPlaneRunSupervisor {
|
|
|
123
112
|
this.#repoRoot = opts.repoRoot;
|
|
124
113
|
this.#nowMs = opts.nowMs ?? defaultNowMs;
|
|
125
114
|
this.#spawnProcess = opts.spawnProcess ?? defaultSpawnProcess;
|
|
126
|
-
this.#heartbeatIntervalMs = Math.max(2_000, Math.trunc(opts.heartbeatIntervalMs ?? 15_000));
|
|
127
|
-
this.#heartbeatScheduler = opts.heartbeatScheduler ?? new ActivityHeartbeatScheduler();
|
|
128
|
-
this.#ownsHeartbeatScheduler = !opts.heartbeatScheduler;
|
|
129
115
|
this.#maxStoredLines = Math.max(50, Math.trunc(opts.maxStoredLines ?? 1_000));
|
|
130
116
|
this.#maxHistory = Math.max(20, Math.trunc(opts.maxHistory ?? 200));
|
|
131
117
|
this.#onEvent = opts.onEvent ?? null;
|
|
@@ -253,7 +239,6 @@ export class ControlPlaneRunSupervisor {
|
|
|
253
239
|
stderr_lines: [],
|
|
254
240
|
log_hints: new Set(),
|
|
255
241
|
interrupt_requested: false,
|
|
256
|
-
next_heartbeat_reason: null,
|
|
257
242
|
hard_kill_timer: null,
|
|
258
243
|
};
|
|
259
244
|
this.#jobsById.set(snapshot.job_id, job);
|
|
@@ -261,34 +246,11 @@ export class ControlPlaneRunSupervisor {
|
|
|
261
246
|
this.#jobIdByRootIssueId.set(snapshot.root_issue_id, snapshot.job_id);
|
|
262
247
|
}
|
|
263
248
|
this.#emit("run_started", job, `🚀 Started ${describeRun(snapshot)} (job ${snapshot.job_id}, pid ${snapshot.pid ?? "?"})`);
|
|
264
|
-
this.#heartbeatScheduler.register({
|
|
265
|
-
activityId: snapshot.job_id,
|
|
266
|
-
everyMs: this.#heartbeatIntervalMs,
|
|
267
|
-
handler: async ({ reason }) => {
|
|
268
|
-
if (job.snapshot.status !== "running") {
|
|
269
|
-
return { status: "skipped", reason: "not_running" };
|
|
270
|
-
}
|
|
271
|
-
const normalizedReason = reason?.trim();
|
|
272
|
-
const heartbeatReason = normalizedReason && normalizedReason.length > 0 && normalizedReason !== "interval"
|
|
273
|
-
? normalizedReason
|
|
274
|
-
: job.next_heartbeat_reason;
|
|
275
|
-
if (heartbeatReason) {
|
|
276
|
-
job.next_heartbeat_reason = null;
|
|
277
|
-
}
|
|
278
|
-
const elapsedSec = Math.max(0, Math.trunc((this.#nowMs() - job.snapshot.started_at_ms) / 1_000));
|
|
279
|
-
const root = job.snapshot.root_issue_id ?? job.snapshot.job_id;
|
|
280
|
-
const progress = job.snapshot.last_progress ? ` · ${job.snapshot.last_progress}` : "";
|
|
281
|
-
const reasonSuffix = heartbeatReason ? ` · wake=${heartbeatReason}` : "";
|
|
282
|
-
this.#emit("run_heartbeat", job, `⏱ ${root} running for ${elapsedSec}s${progress}${reasonSuffix}`);
|
|
283
|
-
return { status: "ran" };
|
|
284
|
-
},
|
|
285
|
-
});
|
|
286
249
|
void (async () => {
|
|
287
250
|
const stdoutTask = consumeStreamLines(process.stdout, (line) => this.#handleLine(job, "stdout", line));
|
|
288
251
|
const stderrTask = consumeStreamLines(process.stderr, (line) => this.#handleLine(job, "stderr", line));
|
|
289
252
|
const exitCode = await process.exited.catch(() => -1);
|
|
290
253
|
await Promise.allSettled([stdoutTask, stderrTask]);
|
|
291
|
-
this.#heartbeatScheduler.unregister(job.snapshot.job_id);
|
|
292
254
|
if (job.hard_kill_timer) {
|
|
293
255
|
clearTimeout(job.hard_kill_timer);
|
|
294
256
|
job.hard_kill_timer = null;
|
|
@@ -451,34 +413,6 @@ export class ControlPlaneRunSupervisor {
|
|
|
451
413
|
this.#emit("run_interrupt_requested", job, `⚠️ Interrupt requested for ${root}.`);
|
|
452
414
|
return { ok: true, reason: null, run: this.#snapshot(job) };
|
|
453
415
|
}
|
|
454
|
-
heartbeat(opts) {
|
|
455
|
-
const target = opts.jobId?.trim() || opts.rootIssueId?.trim() || "";
|
|
456
|
-
if (target.length === 0) {
|
|
457
|
-
return { ok: false, reason: "missing_target", run: null };
|
|
458
|
-
}
|
|
459
|
-
const job = this.#resolveJob(target);
|
|
460
|
-
if (!job) {
|
|
461
|
-
return { ok: false, reason: "not_found", run: null };
|
|
462
|
-
}
|
|
463
|
-
if (job.snapshot.status !== "running") {
|
|
464
|
-
return { ok: false, reason: "not_running", run: this.#snapshot(job) };
|
|
465
|
-
}
|
|
466
|
-
const reason = opts.reason?.trim() || "manual";
|
|
467
|
-
const wakeMode = normalizeWakeMode(opts.wakeMode);
|
|
468
|
-
if (wakeMode === "next_heartbeat") {
|
|
469
|
-
job.next_heartbeat_reason = reason;
|
|
470
|
-
this.#touch(job);
|
|
471
|
-
return { ok: true, reason: null, run: this.#snapshot(job) };
|
|
472
|
-
}
|
|
473
|
-
if (reason !== "interval") {
|
|
474
|
-
job.next_heartbeat_reason = null;
|
|
475
|
-
}
|
|
476
|
-
this.#heartbeatScheduler.requestNow(job.snapshot.job_id, {
|
|
477
|
-
reason,
|
|
478
|
-
coalesceMs: 0,
|
|
479
|
-
});
|
|
480
|
-
return { ok: true, reason: null, run: this.#snapshot(job) };
|
|
481
|
-
}
|
|
482
416
|
async startFromCommand(command) {
|
|
483
417
|
switch (command.target_type) {
|
|
484
418
|
case "run start": {
|
|
@@ -508,7 +442,6 @@ export class ControlPlaneRunSupervisor {
|
|
|
508
442
|
}
|
|
509
443
|
async stop() {
|
|
510
444
|
for (const job of this.#jobsById.values()) {
|
|
511
|
-
this.#heartbeatScheduler.unregister(job.snapshot.job_id);
|
|
512
445
|
if (job.hard_kill_timer) {
|
|
513
446
|
clearTimeout(job.hard_kill_timer);
|
|
514
447
|
job.hard_kill_timer = null;
|
|
@@ -522,8 +455,5 @@ export class ControlPlaneRunSupervisor {
|
|
|
522
455
|
}
|
|
523
456
|
}
|
|
524
457
|
}
|
|
525
|
-
if (this.#ownsHeartbeatScheduler) {
|
|
526
|
-
this.#heartbeatScheduler.stop();
|
|
527
|
-
}
|
|
528
458
|
}
|
|
529
459
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { GenerationTelemetryRecorder } from "@femtomc/mu-control-plane";
|
|
2
2
|
import type { EventEnvelope, JsonlStore } from "@femtomc/mu-core";
|
|
3
3
|
import { EventLog } from "@femtomc/mu-core/node";
|
|
4
|
-
import { ForumStore } from "@femtomc/mu-forum";
|
|
5
|
-
import { IssueStore } from "@femtomc/mu-issue";
|
|
6
4
|
import { ControlPlaneActivitySupervisor } from "./activity_supervisor.js";
|
|
7
5
|
import { type MuConfig } from "./config.js";
|
|
8
6
|
import type { ControlPlaneHandle, ControlPlaneSessionLifecycle } from "./control_plane_contract.js";
|
|
@@ -21,7 +19,6 @@ export type ServerOptions = {
|
|
|
21
19
|
controlPlaneReloader?: ControlPlaneReloader;
|
|
22
20
|
generationTelemetry?: GenerationTelemetryRecorder;
|
|
23
21
|
operatorWakeCoalesceMs?: number;
|
|
24
|
-
autoRunHeartbeatEveryMs?: number;
|
|
25
22
|
config?: MuConfig;
|
|
26
23
|
configReader?: ConfigReader;
|
|
27
24
|
configWriter?: ConfigWriter;
|
|
@@ -31,8 +28,6 @@ export type ServerOptions = {
|
|
|
31
28
|
export type ServerInstanceOptions = Omit<ServerOptions, "repoRoot" | "controlPlane" | "heartbeatScheduler" | "generationTelemetry" | "config" | "sessionLifecycle">;
|
|
32
29
|
export type ServerContext = {
|
|
33
30
|
repoRoot: string;
|
|
34
|
-
issueStore: IssueStore;
|
|
35
|
-
forumStore: ForumStore;
|
|
36
31
|
eventLog: EventLog;
|
|
37
32
|
eventsStore: JsonlStore<EventEnvelope>;
|
|
38
33
|
};
|