@femtomc/mu-server 26.2.73 → 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 -66
- 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_contract.d.ts +1 -7
- 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/outbound_delivery_router.d.ts +12 -0
- package/dist/outbound_delivery_router.js +29 -0
- package/dist/run_supervisor.d.ts +1 -16
- package/dist/run_supervisor.js +0 -70
- 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 -654
- 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 -9
- package/dist/api/context.d.ts +0 -5
- package/dist/api/context.js +0 -1147
- 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/server.js
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { GenerationTelemetryRecorder } from "@femtomc/mu-control-plane";
|
|
1
|
+
import { GenerationTelemetryRecorder, presentPipelineResultMessage, } from "@femtomc/mu-control-plane";
|
|
2
2
|
import { currentRunId, EventLog, FsJsonlStore, getStorePaths, JsonlEventSink } from "@femtomc/mu-core/node";
|
|
3
|
-
import { ForumStore } from "@femtomc/mu-forum";
|
|
4
|
-
import { IssueStore } from "@femtomc/mu-issue";
|
|
5
3
|
import { ControlPlaneActivitySupervisor } from "./activity_supervisor.js";
|
|
6
|
-
import { DEFAULT_MU_CONFIG, readMuConfigFile, writeMuConfigFile
|
|
4
|
+
import { DEFAULT_MU_CONFIG, readMuConfigFile, writeMuConfigFile } from "./config.js";
|
|
7
5
|
import { bootstrapControlPlane } from "./control_plane.js";
|
|
8
6
|
import { createReloadManager, } from "./control_plane_reload.js";
|
|
9
7
|
import { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
@@ -12,7 +10,6 @@ import { createServerProgramOrchestration } from "./server_program_orchestration
|
|
|
12
10
|
import { createServerRequestHandler } from "./server_routing.js";
|
|
13
11
|
import { toNonNegativeInt } from "./server_types.js";
|
|
14
12
|
const DEFAULT_OPERATOR_WAKE_COALESCE_MS = 2_000;
|
|
15
|
-
const DEFAULT_AUTO_RUN_HEARTBEAT_EVERY_MS = 15_000;
|
|
16
13
|
export { createProcessSessionLifecycle };
|
|
17
14
|
function describeError(err) {
|
|
18
15
|
if (err instanceof Error)
|
|
@@ -27,19 +24,6 @@ function emptyNotifyOperatorsResult() {
|
|
|
27
24
|
decisions: [],
|
|
28
25
|
};
|
|
29
26
|
}
|
|
30
|
-
function normalizeWakeTurnMode(value) {
|
|
31
|
-
if (typeof value !== "string") {
|
|
32
|
-
return "off";
|
|
33
|
-
}
|
|
34
|
-
const normalized = value.trim().toLowerCase();
|
|
35
|
-
if (normalized === "shadow") {
|
|
36
|
-
return "shadow";
|
|
37
|
-
}
|
|
38
|
-
if (normalized === "active") {
|
|
39
|
-
return "active";
|
|
40
|
-
}
|
|
41
|
-
return "off";
|
|
42
|
-
}
|
|
43
27
|
function stringField(payload, key) {
|
|
44
28
|
const value = payload[key];
|
|
45
29
|
if (typeof value !== "string") {
|
|
@@ -55,47 +39,52 @@ function numberField(payload, key) {
|
|
|
55
39
|
}
|
|
56
40
|
return Math.trunc(value);
|
|
57
41
|
}
|
|
58
|
-
function
|
|
59
|
-
const source = stringField(opts.payload, "wake_source") ?? "unknown";
|
|
60
|
-
const programId = stringField(opts.payload, "program_id") ?? "unknown";
|
|
61
|
-
const sourceTsMs = numberField(opts.payload, "source_ts_ms");
|
|
62
|
-
const target = Object.hasOwn(opts.payload, "target") ? opts.payload.target : null;
|
|
63
|
-
let targetFingerprint = "null";
|
|
42
|
+
function stablePayloadSnapshot(payload) {
|
|
64
43
|
try {
|
|
65
|
-
|
|
44
|
+
return JSON.stringify(payload) ?? "{}";
|
|
66
45
|
}
|
|
67
46
|
catch {
|
|
68
|
-
|
|
47
|
+
return "[unserializable]";
|
|
69
48
|
}
|
|
49
|
+
}
|
|
50
|
+
function computeWakeId(opts) {
|
|
51
|
+
const source = stringField(opts.payload, "wake_source") ?? "unknown";
|
|
52
|
+
const programId = stringField(opts.payload, "program_id") ?? "unknown";
|
|
53
|
+
const sourceTsMs = numberField(opts.payload, "source_ts_ms");
|
|
54
|
+
const payloadSnapshot = stablePayloadSnapshot(opts.payload);
|
|
70
55
|
const hasher = new Bun.CryptoHasher("sha256");
|
|
71
|
-
hasher.update(`${source}|${programId}|${sourceTsMs ?? "na"}|${opts.dedupeKey}|${
|
|
56
|
+
hasher.update(`${source}|${programId}|${sourceTsMs ?? "na"}|${opts.dedupeKey}|${payloadSnapshot}`);
|
|
72
57
|
return hasher.digest("hex").slice(0, 16);
|
|
73
58
|
}
|
|
59
|
+
function extractWakeTurnReply(turnResult) {
|
|
60
|
+
if (turnResult.kind === "operator_response") {
|
|
61
|
+
const message = turnResult.message.trim();
|
|
62
|
+
return message.length > 0 ? message : null;
|
|
63
|
+
}
|
|
64
|
+
const presented = presentPipelineResultMessage(turnResult);
|
|
65
|
+
const payload = presented.message.payload;
|
|
66
|
+
const payloadMessage = typeof payload.message === "string" ? payload.message.trim() : "";
|
|
67
|
+
if (payloadMessage.length > 0) {
|
|
68
|
+
return payloadMessage;
|
|
69
|
+
}
|
|
70
|
+
const compact = presented.compact.trim();
|
|
71
|
+
return compact.length > 0 ? compact : null;
|
|
72
|
+
}
|
|
74
73
|
function buildWakeTurnCommandText(opts) {
|
|
75
74
|
const wakeSource = stringField(opts.payload, "wake_source") ?? "unknown";
|
|
76
75
|
const programId = stringField(opts.payload, "program_id") ?? "unknown";
|
|
77
|
-
const wakeMode = stringField(opts.payload, "wake_mode") ?? "immediate";
|
|
78
|
-
const targetKind = stringField(opts.payload, "target_kind") ?? "unknown";
|
|
79
76
|
const reason = stringField(opts.payload, "reason") ?? "scheduled";
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
target = JSON.stringify(Object.hasOwn(opts.payload, "target") ? opts.payload.target : null) ?? "null";
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
85
|
-
target = "[unserializable]";
|
|
86
|
-
}
|
|
77
|
+
const payloadSnapshot = stablePayloadSnapshot(opts.payload);
|
|
87
78
|
return [
|
|
88
79
|
"Autonomous wake turn triggered by heartbeat/cron scheduler.",
|
|
89
80
|
`wake_id=${opts.wakeId}`,
|
|
90
81
|
`wake_source=${wakeSource}`,
|
|
91
82
|
`program_id=${programId}`,
|
|
92
|
-
`wake_mode=${wakeMode}`,
|
|
93
|
-
`target_kind=${targetKind}`,
|
|
94
83
|
`reason=${reason}`,
|
|
95
|
-
`
|
|
96
|
-
`
|
|
84
|
+
`trigger_message=${opts.message}`,
|
|
85
|
+
`payload=${payloadSnapshot}`,
|
|
97
86
|
"",
|
|
98
|
-
"If
|
|
87
|
+
"If action is needed, produce exactly one `/mu ...` command. If no action is needed, return a short operator response that can be broadcast verbatim.",
|
|
99
88
|
].join("\n");
|
|
100
89
|
}
|
|
101
90
|
export function createContext(repoRoot) {
|
|
@@ -104,9 +93,7 @@ export function createContext(repoRoot) {
|
|
|
104
93
|
const eventLog = new EventLog(new JsonlEventSink(eventsStore), {
|
|
105
94
|
runIdProvider: currentRunId,
|
|
106
95
|
});
|
|
107
|
-
|
|
108
|
-
const forumStore = new ForumStore(new FsJsonlStore(paths.forumPath), { events: eventLog });
|
|
109
|
-
return { repoRoot, issueStore, forumStore, eventLog, eventsStore };
|
|
96
|
+
return { repoRoot, eventLog, eventsStore };
|
|
110
97
|
}
|
|
111
98
|
function createServer(options = {}) {
|
|
112
99
|
const repoRoot = options.repoRoot || process.cwd();
|
|
@@ -134,7 +121,6 @@ function createServer(options = {}) {
|
|
|
134
121
|
},
|
|
135
122
|
});
|
|
136
123
|
const operatorWakeCoalesceMs = toNonNegativeInt(options.operatorWakeCoalesceMs, DEFAULT_OPERATOR_WAKE_COALESCE_MS);
|
|
137
|
-
const autoRunHeartbeatEveryMs = Math.max(1_000, toNonNegativeInt(options.autoRunHeartbeatEveryMs, DEFAULT_AUTO_RUN_HEARTBEAT_EVERY_MS));
|
|
138
124
|
const operatorWakeLastByKey = new Map();
|
|
139
125
|
const sessionLifecycle = options.sessionLifecycle ?? createProcessSessionLifecycle({ repoRoot });
|
|
140
126
|
const emitWakeDeliveryEvent = async (payload) => {
|
|
@@ -146,61 +132,28 @@ function createServer(options = {}) {
|
|
|
146
132
|
const emitOperatorWake = async (opts) => {
|
|
147
133
|
const dedupeKey = opts.dedupeKey.trim();
|
|
148
134
|
if (!dedupeKey) {
|
|
149
|
-
return
|
|
135
|
+
return { status: "failed", reason: "missing_dedupe_key" };
|
|
150
136
|
}
|
|
151
137
|
const nowMs = Date.now();
|
|
152
138
|
const coalesceMs = Math.max(0, Math.trunc(opts.coalesceMs ?? operatorWakeCoalesceMs));
|
|
153
139
|
const previous = operatorWakeLastByKey.get(dedupeKey);
|
|
154
140
|
if (typeof previous === "number" && nowMs - previous < coalesceMs) {
|
|
155
|
-
return
|
|
141
|
+
return { status: "coalesced", reason: "coalesced_window" };
|
|
156
142
|
}
|
|
157
143
|
operatorWakeLastByKey.set(dedupeKey, nowMs);
|
|
158
144
|
const wakeId = computeWakeId({ dedupeKey, payload: opts.payload });
|
|
159
|
-
const selectedWakeMode = stringField(opts.payload, "wake_mode");
|
|
160
145
|
const wakeSource = stringField(opts.payload, "wake_source");
|
|
161
146
|
const programId = stringField(opts.payload, "program_id");
|
|
162
147
|
const sourceTsMs = numberField(opts.payload, "source_ts_ms");
|
|
163
|
-
let wakeTurnMode = normalizeWakeTurnMode(fallbackConfig.control_plane.operator.wake_turn_mode);
|
|
164
|
-
let configReadError = null;
|
|
165
|
-
try {
|
|
166
|
-
const config = await loadConfigFromDisk();
|
|
167
|
-
wakeTurnMode = normalizeWakeTurnMode(config.control_plane.operator.wake_turn_mode);
|
|
168
|
-
}
|
|
169
|
-
catch (err) {
|
|
170
|
-
configReadError = describeError(err);
|
|
171
|
-
}
|
|
172
148
|
let decision;
|
|
173
|
-
if (
|
|
174
|
-
decision = {
|
|
175
|
-
outcome: "skipped",
|
|
176
|
-
reason: "feature_disabled",
|
|
177
|
-
wakeTurnMode,
|
|
178
|
-
selectedWakeMode,
|
|
179
|
-
turnRequestId: null,
|
|
180
|
-
turnResultKind: null,
|
|
181
|
-
error: configReadError,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
184
|
-
else if (wakeTurnMode === "shadow") {
|
|
185
|
-
decision = {
|
|
186
|
-
outcome: "skipped",
|
|
187
|
-
reason: "shadow_mode",
|
|
188
|
-
wakeTurnMode,
|
|
189
|
-
selectedWakeMode,
|
|
190
|
-
turnRequestId: null,
|
|
191
|
-
turnResultKind: null,
|
|
192
|
-
error: configReadError,
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
else if (typeof controlPlaneProxy.submitTerminalCommand !== "function") {
|
|
149
|
+
if (typeof controlPlaneProxy.submitTerminalCommand !== "function") {
|
|
196
150
|
decision = {
|
|
197
151
|
outcome: "fallback",
|
|
198
152
|
reason: "control_plane_unavailable",
|
|
199
|
-
wakeTurnMode,
|
|
200
|
-
selectedWakeMode,
|
|
201
153
|
turnRequestId: null,
|
|
202
154
|
turnResultKind: null,
|
|
203
|
-
|
|
155
|
+
turnReply: null,
|
|
156
|
+
error: null,
|
|
204
157
|
};
|
|
205
158
|
}
|
|
206
159
|
else {
|
|
@@ -219,23 +172,34 @@ function createServer(options = {}) {
|
|
|
219
172
|
decision = {
|
|
220
173
|
outcome: "fallback",
|
|
221
174
|
reason: `turn_result_${turnResult.kind}`,
|
|
222
|
-
wakeTurnMode,
|
|
223
|
-
selectedWakeMode,
|
|
224
175
|
turnRequestId,
|
|
225
176
|
turnResultKind: turnResult.kind,
|
|
226
|
-
|
|
177
|
+
turnReply: null,
|
|
178
|
+
error: null,
|
|
227
179
|
};
|
|
228
180
|
}
|
|
229
181
|
else {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
182
|
+
const turnReply = extractWakeTurnReply(turnResult);
|
|
183
|
+
if (!turnReply) {
|
|
184
|
+
decision = {
|
|
185
|
+
outcome: "fallback",
|
|
186
|
+
reason: "turn_reply_empty",
|
|
187
|
+
turnRequestId,
|
|
188
|
+
turnResultKind: turnResult.kind,
|
|
189
|
+
turnReply: null,
|
|
190
|
+
error: null,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
decision = {
|
|
195
|
+
outcome: "triggered",
|
|
196
|
+
reason: "turn_invoked",
|
|
197
|
+
turnRequestId,
|
|
198
|
+
turnResultKind: turnResult.kind,
|
|
199
|
+
turnReply,
|
|
200
|
+
error: null,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
239
203
|
}
|
|
240
204
|
}
|
|
241
205
|
catch (err) {
|
|
@@ -243,10 +207,9 @@ function createServer(options = {}) {
|
|
|
243
207
|
decision = {
|
|
244
208
|
outcome: "fallback",
|
|
245
209
|
reason: error === "control_plane_unavailable" ? "control_plane_unavailable" : "turn_execution_failed",
|
|
246
|
-
wakeTurnMode,
|
|
247
|
-
selectedWakeMode,
|
|
248
210
|
turnRequestId,
|
|
249
211
|
turnResultKind: null,
|
|
212
|
+
turnReply: null,
|
|
250
213
|
error,
|
|
251
214
|
};
|
|
252
215
|
}
|
|
@@ -259,22 +222,27 @@ function createServer(options = {}) {
|
|
|
259
222
|
wake_source: wakeSource,
|
|
260
223
|
program_id: programId,
|
|
261
224
|
source_ts_ms: sourceTsMs,
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
wake_turn_feature_enabled: decision.wakeTurnMode === "active",
|
|
265
|
-
outcome: decision.outcome,
|
|
266
|
-
reason: decision.reason,
|
|
225
|
+
wake_turn_outcome: decision.outcome,
|
|
226
|
+
wake_turn_reason: decision.reason,
|
|
267
227
|
turn_request_id: decision.turnRequestId,
|
|
268
228
|
turn_result_kind: decision.turnResultKind,
|
|
269
|
-
|
|
229
|
+
turn_reply_present: decision.turnReply != null,
|
|
230
|
+
wake_turn_error: decision.error,
|
|
270
231
|
},
|
|
271
232
|
});
|
|
272
233
|
let notifyResult = emptyNotifyOperatorsResult();
|
|
273
234
|
let notifyError = null;
|
|
274
|
-
|
|
235
|
+
let deliverySkippedReason = null;
|
|
236
|
+
if (!decision.turnReply) {
|
|
237
|
+
deliverySkippedReason = "no_turn_reply";
|
|
238
|
+
}
|
|
239
|
+
else if (typeof controlPlaneProxy.notifyOperators !== "function") {
|
|
240
|
+
deliverySkippedReason = "notify_operators_unavailable";
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
275
243
|
try {
|
|
276
244
|
notifyResult = await controlPlaneProxy.notifyOperators({
|
|
277
|
-
message:
|
|
245
|
+
message: decision.turnReply,
|
|
278
246
|
dedupeKey,
|
|
279
247
|
wake: {
|
|
280
248
|
wakeId,
|
|
@@ -286,6 +254,7 @@ function createServer(options = {}) {
|
|
|
286
254
|
wake_delivery_reason: "heartbeat_cron_wake",
|
|
287
255
|
wake_turn_outcome: decision.outcome,
|
|
288
256
|
wake_turn_reason: decision.reason,
|
|
257
|
+
wake_turn_result_kind: decision.turnResultKind,
|
|
289
258
|
},
|
|
290
259
|
});
|
|
291
260
|
}
|
|
@@ -312,19 +281,18 @@ function createServer(options = {}) {
|
|
|
312
281
|
await context.eventLog.emit("operator.wake", {
|
|
313
282
|
source: "mu-server.operator-wake",
|
|
314
283
|
payload: {
|
|
315
|
-
|
|
284
|
+
trigger_message: opts.message,
|
|
285
|
+
broadcast_message: decision.turnReply,
|
|
286
|
+
broadcast_message_present: decision.turnReply != null,
|
|
316
287
|
dedupe_key: dedupeKey,
|
|
317
288
|
coalesce_ms: coalesceMs,
|
|
318
289
|
...opts.payload,
|
|
319
290
|
wake_id: wakeId,
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
wake_turn_mode: decision.wakeTurnMode,
|
|
323
|
-
selected_wake_mode: decision.selectedWakeMode,
|
|
324
|
-
wake_turn_feature_enabled: decision.wakeTurnMode === "active",
|
|
291
|
+
wake_turn_outcome: decision.outcome,
|
|
292
|
+
wake_turn_reason: decision.reason,
|
|
325
293
|
turn_request_id: decision.turnRequestId,
|
|
326
294
|
turn_result_kind: decision.turnResultKind,
|
|
327
|
-
|
|
295
|
+
wake_turn_error: decision.error,
|
|
328
296
|
delivery: {
|
|
329
297
|
queued: notifyResult.queued,
|
|
330
298
|
duplicate: notifyResult.duplicate,
|
|
@@ -336,10 +304,23 @@ function createServer(options = {}) {
|
|
|
336
304
|
skipped: notifyResult.skipped,
|
|
337
305
|
total: notifyResult.decisions.length,
|
|
338
306
|
},
|
|
307
|
+
delivery_skipped_reason: deliverySkippedReason,
|
|
339
308
|
delivery_error: notifyError,
|
|
340
309
|
},
|
|
341
310
|
});
|
|
342
|
-
|
|
311
|
+
if (decision.outcome !== "triggered") {
|
|
312
|
+
return { status: "failed", reason: decision.reason };
|
|
313
|
+
}
|
|
314
|
+
if (!decision.turnReply) {
|
|
315
|
+
return { status: "failed", reason: "no_turn_reply" };
|
|
316
|
+
}
|
|
317
|
+
if (notifyError) {
|
|
318
|
+
return { status: "failed", reason: "notify_failed" };
|
|
319
|
+
}
|
|
320
|
+
if (deliverySkippedReason === "notify_operators_unavailable") {
|
|
321
|
+
return { status: "failed", reason: deliverySkippedReason };
|
|
322
|
+
}
|
|
323
|
+
return { status: "dispatched", reason: "operator_reply_broadcast" };
|
|
343
324
|
};
|
|
344
325
|
const generationTelemetry = options.generationTelemetry ?? new GenerationTelemetryRecorder();
|
|
345
326
|
const loadConfigFromDisk = async () => {
|
|
@@ -358,7 +339,6 @@ function createServer(options = {}) {
|
|
|
358
339
|
return await bootstrapControlPlane({
|
|
359
340
|
repoRoot,
|
|
360
341
|
config,
|
|
361
|
-
heartbeatScheduler,
|
|
362
342
|
generation,
|
|
363
343
|
telemetry: generationTelemetry,
|
|
364
344
|
sessionLifecycle,
|
|
@@ -461,13 +441,6 @@ function createServer(options = {}) {
|
|
|
461
441
|
}
|
|
462
442
|
return await handle.interruptRun(opts);
|
|
463
443
|
},
|
|
464
|
-
async heartbeatRun(opts) {
|
|
465
|
-
const handle = reloadManager.getControlPlaneCurrent();
|
|
466
|
-
if (!handle?.heartbeatRun) {
|
|
467
|
-
return { ok: false, reason: "not_found", run: null };
|
|
468
|
-
}
|
|
469
|
-
return await handle.heartbeatRun(opts);
|
|
470
|
-
},
|
|
471
444
|
async traceRun(opts) {
|
|
472
445
|
const handle = reloadManager.getControlPlaneCurrent();
|
|
473
446
|
if (!handle?.traceRun)
|
|
@@ -488,13 +461,10 @@ function createServer(options = {}) {
|
|
|
488
461
|
await handle?.stop();
|
|
489
462
|
},
|
|
490
463
|
};
|
|
491
|
-
const { heartbeatPrograms, cronPrograms
|
|
464
|
+
const { heartbeatPrograms, cronPrograms } = createServerProgramOrchestration({
|
|
492
465
|
repoRoot,
|
|
493
466
|
heartbeatScheduler,
|
|
494
|
-
controlPlaneProxy,
|
|
495
|
-
activitySupervisor,
|
|
496
467
|
eventLog: context.eventLog,
|
|
497
|
-
autoRunHeartbeatEveryMs,
|
|
498
468
|
emitOperatorWake,
|
|
499
469
|
});
|
|
500
470
|
const handleRequest = createServerRequestHandler({
|
|
@@ -507,8 +477,6 @@ function createServer(options = {}) {
|
|
|
507
477
|
writeConfig,
|
|
508
478
|
reloadControlPlane,
|
|
509
479
|
getControlPlaneStatus: reloadManager.getControlPlaneStatus,
|
|
510
|
-
registerAutoRunHeartbeatProgram,
|
|
511
|
-
disableAutoRunHeartbeatProgram,
|
|
512
480
|
describeError,
|
|
513
481
|
initiateShutdown: options.initiateShutdown,
|
|
514
482
|
});
|
|
@@ -1,38 +1,23 @@
|
|
|
1
1
|
import type { EventLog } from "@femtomc/mu-core/node";
|
|
2
|
-
import type { ControlPlaneActivitySupervisor } from "./activity_supervisor.js";
|
|
3
|
-
import type { ControlPlaneHandle } from "./control_plane_contract.js";
|
|
4
2
|
import { CronProgramRegistry } from "./cron_programs.js";
|
|
5
3
|
import { HeartbeatProgramRegistry } from "./heartbeat_programs.js";
|
|
6
4
|
import type { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
7
|
-
export type AutoHeartbeatRunSnapshot = {
|
|
8
|
-
job_id: string;
|
|
9
|
-
root_issue_id: string | null;
|
|
10
|
-
status: string;
|
|
11
|
-
source: "command" | "api";
|
|
12
|
-
mode: string;
|
|
13
|
-
};
|
|
14
5
|
type OperatorWakeEmitter = (opts: {
|
|
15
6
|
dedupeKey: string;
|
|
16
7
|
message: string;
|
|
17
8
|
payload: Record<string, unknown>;
|
|
18
9
|
coalesceMs?: number;
|
|
19
|
-
}) => Promise<
|
|
10
|
+
}) => Promise<{
|
|
11
|
+
status: "dispatched" | "coalesced" | "failed";
|
|
12
|
+
reason: string;
|
|
13
|
+
}>;
|
|
20
14
|
export declare function createServerProgramOrchestration(opts: {
|
|
21
15
|
repoRoot: string;
|
|
22
16
|
heartbeatScheduler: ActivityHeartbeatScheduler;
|
|
23
|
-
controlPlaneProxy: ControlPlaneHandle;
|
|
24
|
-
activitySupervisor: ControlPlaneActivitySupervisor;
|
|
25
17
|
eventLog: EventLog;
|
|
26
|
-
autoRunHeartbeatEveryMs: number;
|
|
27
18
|
emitOperatorWake: OperatorWakeEmitter;
|
|
28
19
|
}): {
|
|
29
20
|
heartbeatPrograms: HeartbeatProgramRegistry;
|
|
30
21
|
cronPrograms: CronProgramRegistry;
|
|
31
|
-
registerAutoRunHeartbeatProgram: (run: AutoHeartbeatRunSnapshot) => Promise<void>;
|
|
32
|
-
disableAutoRunHeartbeatProgram: (opts: {
|
|
33
|
-
jobId: string;
|
|
34
|
-
status: string;
|
|
35
|
-
reason: string;
|
|
36
|
-
}) => Promise<void>;
|
|
37
22
|
};
|
|
38
23
|
export {};
|