@femtomc/mu-server 26.2.119 → 26.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/heartbeat_programs.js +15 -0
- package/dist/server.js +39 -9
- package/package.json +4 -4
|
@@ -75,6 +75,7 @@ export class HeartbeatProgramRegistry {
|
|
|
75
75
|
#onLifecycleEvent;
|
|
76
76
|
#nowMs;
|
|
77
77
|
#programs = new Map();
|
|
78
|
+
#inFlightTicks = new Map();
|
|
78
79
|
#loaded = null;
|
|
79
80
|
constructor(opts) {
|
|
80
81
|
this.#heartbeatScheduler = opts.heartbeatScheduler;
|
|
@@ -160,6 +161,19 @@ export class HeartbeatProgramRegistry {
|
|
|
160
161
|
await this.#onLifecycleEvent(event);
|
|
161
162
|
}
|
|
162
163
|
async #tickProgram(programId, reason) {
|
|
164
|
+
const inFlight = this.#inFlightTicks.get(programId);
|
|
165
|
+
if (inFlight) {
|
|
166
|
+
return { status: "skipped", reason: "coalesced" };
|
|
167
|
+
}
|
|
168
|
+
const run = this.#tickProgramUnlocked(programId, reason).finally(() => {
|
|
169
|
+
if (this.#inFlightTicks.get(programId) === run) {
|
|
170
|
+
this.#inFlightTicks.delete(programId);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
this.#inFlightTicks.set(programId, run);
|
|
174
|
+
return await run;
|
|
175
|
+
}
|
|
176
|
+
async #tickProgramUnlocked(programId, reason) {
|
|
163
177
|
const program = this.#programs.get(programId);
|
|
164
178
|
if (!program) {
|
|
165
179
|
return { status: "skipped", reason: "not_found" };
|
|
@@ -396,6 +410,7 @@ export class HeartbeatProgramRegistry {
|
|
|
396
410
|
for (const program of this.#programs.values()) {
|
|
397
411
|
this.#heartbeatScheduler.unregister(this.#scheduleId(program.program_id));
|
|
398
412
|
}
|
|
413
|
+
this.#inFlightTicks.clear();
|
|
399
414
|
this.#programs.clear();
|
|
400
415
|
}
|
|
401
416
|
}
|
package/dist/server.js
CHANGED
|
@@ -77,6 +77,20 @@ function extractWakeTurnReply(turnResult) {
|
|
|
77
77
|
const compact = presented.compact.trim();
|
|
78
78
|
return compact.length > 0 ? compact : null;
|
|
79
79
|
}
|
|
80
|
+
function extractWakeTurnFailureCode(turnReply) {
|
|
81
|
+
if (!turnReply) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
const normalized = turnReply.trim();
|
|
85
|
+
if (!normalized.toLowerCase().startsWith("i could not complete that turn safely.")) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const codeMatch = normalized.match(/(?:^|\n)\s*Code:\s*([a-z0-9_]+)/i);
|
|
89
|
+
if (!codeMatch || typeof codeMatch[1] !== "string") {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return codeMatch[1].toLowerCase();
|
|
93
|
+
}
|
|
80
94
|
function buildWakeTurnIngressText(opts) {
|
|
81
95
|
const wakeSource = stringField(opts.payload, "wake_source") ?? "unknown";
|
|
82
96
|
const programId = stringField(opts.payload, "program_id") ?? "unknown";
|
|
@@ -229,14 +243,27 @@ function createServer(options = {}) {
|
|
|
229
243
|
};
|
|
230
244
|
}
|
|
231
245
|
else {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
246
|
+
const failureCode = extractWakeTurnFailureCode(turnReply);
|
|
247
|
+
if (failureCode === "operator_busy") {
|
|
248
|
+
decision = {
|
|
249
|
+
outcome: "fallback",
|
|
250
|
+
reason: "operator_busy",
|
|
251
|
+
turnRequestId,
|
|
252
|
+
turnResultKind: turnResult.kind,
|
|
253
|
+
turnReply: null,
|
|
254
|
+
error: null,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
decision = {
|
|
259
|
+
outcome: "triggered",
|
|
260
|
+
reason: "turn_invoked",
|
|
261
|
+
turnRequestId,
|
|
262
|
+
turnResultKind: turnResult.kind,
|
|
263
|
+
turnReply,
|
|
264
|
+
error: null,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
240
267
|
}
|
|
241
268
|
}
|
|
242
269
|
}
|
|
@@ -272,7 +299,7 @@ function createServer(options = {}) {
|
|
|
272
299
|
let notifyError = null;
|
|
273
300
|
let deliverySkippedReason = null;
|
|
274
301
|
if (!decision.turnReply) {
|
|
275
|
-
deliverySkippedReason = "no_turn_reply";
|
|
302
|
+
deliverySkippedReason = decision.reason === "operator_busy" ? "operator_busy" : "no_turn_reply";
|
|
276
303
|
}
|
|
277
304
|
else if (typeof controlPlaneProxy.notifyOperators !== "function") {
|
|
278
305
|
deliverySkippedReason = "notify_operators_unavailable";
|
|
@@ -346,6 +373,9 @@ function createServer(options = {}) {
|
|
|
346
373
|
delivery_error: notifyError,
|
|
347
374
|
},
|
|
348
375
|
});
|
|
376
|
+
if (decision.reason === "operator_busy") {
|
|
377
|
+
return { status: "coalesced", reason: decision.reason };
|
|
378
|
+
}
|
|
349
379
|
if (decision.outcome !== "triggered") {
|
|
350
380
|
return { status: "failed", reason: decision.reason };
|
|
351
381
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-server",
|
|
3
|
-
"version": "26.
|
|
3
|
+
"version": "26.3.1",
|
|
4
4
|
"description": "HTTP API server for mu control-plane transport/session plus run/activity scheduling coordination.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mu",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"start": "bun run dist/cli.js"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@femtomc/mu-agent": "26.
|
|
34
|
-
"@femtomc/mu-control-plane": "26.
|
|
35
|
-
"@femtomc/mu-core": "26.
|
|
33
|
+
"@femtomc/mu-agent": "26.3.1",
|
|
34
|
+
"@femtomc/mu-control-plane": "26.3.1",
|
|
35
|
+
"@femtomc/mu-core": "26.3.1"
|
|
36
36
|
}
|
|
37
37
|
}
|