@femtomc/mu-server 26.2.75 → 26.2.77
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 +24 -35
- package/dist/api/control_plane.js +35 -0
- package/dist/api/events.js +7 -3
- package/dist/api/heartbeats.js +19 -2
- package/dist/api/identities.js +3 -3
- package/dist/api/runs.js +6 -6
- package/dist/api/session_turn.d.ts +0 -36
- package/dist/api/session_turn.js +32 -372
- package/dist/cli.js +4 -4
- package/dist/config.d.ts +15 -0
- package/dist/config.js +70 -1
- package/dist/control_plane.js +1 -1
- package/dist/control_plane_bootstrap_helpers.js +3 -2
- package/dist/control_plane_contract.d.ts +1 -1
- package/dist/cron_programs.js +3 -2
- package/dist/heartbeat_programs.d.ts +4 -0
- package/dist/heartbeat_programs.js +17 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +1 -1
- package/dist/memory_index_maintainer.d.ts +15 -0
- package/dist/memory_index_maintainer.js +165 -0
- package/dist/run_queue.js +2 -2
- package/dist/run_supervisor.d.ts +4 -4
- package/dist/run_supervisor.js +6 -5
- package/dist/server.d.ts +0 -3
- package/dist/server.js +26 -23
- package/dist/server_program_orchestration.js +6 -1
- package/dist/server_routing.d.ts +0 -2
- package/dist/server_routing.js +1 -43
- package/package.json +4 -4
- package/dist/activity_supervisor.d.ts +0 -81
- package/dist/activity_supervisor.js +0 -306
- package/dist/api/activities.d.ts +0 -2
- package/dist/api/activities.js +0 -160
- package/dist/api/session_flash.d.ts +0 -60
- package/dist/api/session_flash.js +0 -326
|
@@ -4,6 +4,7 @@ export type HeartbeatProgramSnapshot = {
|
|
|
4
4
|
v: 1;
|
|
5
5
|
program_id: string;
|
|
6
6
|
title: string;
|
|
7
|
+
prompt: string | null;
|
|
7
8
|
enabled: boolean;
|
|
8
9
|
every_ms: number;
|
|
9
10
|
reason: string;
|
|
@@ -45,6 +46,7 @@ export type HeartbeatProgramRegistryOpts = {
|
|
|
45
46
|
dispatchWake: (opts: {
|
|
46
47
|
programId: string;
|
|
47
48
|
title: string;
|
|
49
|
+
prompt: string | null;
|
|
48
50
|
reason: string;
|
|
49
51
|
metadata: Record<string, unknown>;
|
|
50
52
|
triggeredAtMs: number;
|
|
@@ -61,6 +63,7 @@ export declare class HeartbeatProgramRegistry {
|
|
|
61
63
|
get(programId: string): Promise<HeartbeatProgramSnapshot | null>;
|
|
62
64
|
create(opts: {
|
|
63
65
|
title: string;
|
|
66
|
+
prompt?: string | null;
|
|
64
67
|
everyMs?: number;
|
|
65
68
|
reason?: string;
|
|
66
69
|
enabled?: boolean;
|
|
@@ -69,6 +72,7 @@ export declare class HeartbeatProgramRegistry {
|
|
|
69
72
|
update(opts: {
|
|
70
73
|
programId: string;
|
|
71
74
|
title?: string;
|
|
75
|
+
prompt?: string | null;
|
|
72
76
|
everyMs?: number;
|
|
73
77
|
reason?: string;
|
|
74
78
|
enabled?: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { FsJsonlStore } from "@femtomc/mu-core/node";
|
|
2
|
+
import { FsJsonlStore, getStorePaths } from "@femtomc/mu-core/node";
|
|
3
3
|
const HEARTBEAT_PROGRAMS_FILENAME = "heartbeats.jsonl";
|
|
4
4
|
function defaultNowMs() {
|
|
5
5
|
return Date.now();
|
|
@@ -10,6 +10,15 @@ function sanitizeMetadata(value) {
|
|
|
10
10
|
}
|
|
11
11
|
return { ...value };
|
|
12
12
|
}
|
|
13
|
+
function normalizePrompt(value) {
|
|
14
|
+
if (typeof value !== "string") {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
if (value.trim().length === 0) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
13
22
|
function normalizeProgram(row) {
|
|
14
23
|
if (!row || typeof row !== "object" || Array.isArray(row)) {
|
|
15
24
|
return null;
|
|
@@ -38,6 +47,7 @@ function normalizeProgram(row) {
|
|
|
38
47
|
v: 1,
|
|
39
48
|
program_id: programId,
|
|
40
49
|
title,
|
|
50
|
+
prompt: normalizePrompt(record.prompt),
|
|
41
51
|
enabled: record.enabled !== false,
|
|
42
52
|
every_ms: everyMs,
|
|
43
53
|
reason,
|
|
@@ -72,7 +82,7 @@ export class HeartbeatProgramRegistry {
|
|
|
72
82
|
this.#nowMs = opts.nowMs ?? defaultNowMs;
|
|
73
83
|
this.#store =
|
|
74
84
|
opts.store ??
|
|
75
|
-
new FsJsonlStore(join(opts.repoRoot
|
|
85
|
+
new FsJsonlStore(join(getStorePaths(opts.repoRoot).storeDir, HEARTBEAT_PROGRAMS_FILENAME));
|
|
76
86
|
}
|
|
77
87
|
#scheduleId(programId) {
|
|
78
88
|
return `heartbeat-program:${programId}`;
|
|
@@ -146,6 +156,7 @@ export class HeartbeatProgramRegistry {
|
|
|
146
156
|
const result = await this.#dispatchWake({
|
|
147
157
|
programId: program.program_id,
|
|
148
158
|
title: program.title,
|
|
159
|
+
prompt: program.prompt,
|
|
149
160
|
reason: heartbeatReason,
|
|
150
161
|
metadata: { ...program.metadata },
|
|
151
162
|
triggeredAtMs: nowMs,
|
|
@@ -222,6 +233,7 @@ export class HeartbeatProgramRegistry {
|
|
|
222
233
|
v: 1,
|
|
223
234
|
program_id: `hb-${crypto.randomUUID().slice(0, 12)}`,
|
|
224
235
|
title,
|
|
236
|
+
prompt: normalizePrompt(opts.prompt),
|
|
225
237
|
enabled: opts.enabled !== false,
|
|
226
238
|
every_ms: typeof opts.everyMs === "number" && Number.isFinite(opts.everyMs)
|
|
227
239
|
? Math.max(0, Math.trunc(opts.everyMs))
|
|
@@ -252,6 +264,9 @@ export class HeartbeatProgramRegistry {
|
|
|
252
264
|
}
|
|
253
265
|
program.title = title;
|
|
254
266
|
}
|
|
267
|
+
if ("prompt" in opts) {
|
|
268
|
+
program.prompt = normalizePrompt(opts.prompt);
|
|
269
|
+
}
|
|
255
270
|
if (typeof opts.everyMs === "number" && Number.isFinite(opts.everyMs)) {
|
|
256
271
|
program.every_ms = Math.max(0, Math.trunc(opts.everyMs));
|
|
257
272
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export type { ControlPlaneActivityEvent, ControlPlaneActivityEventKind, ControlPlaneActivityMutationResult, ControlPlaneActivitySnapshot, ControlPlaneActivityStatus, ControlPlaneActivitySupervisorOpts, } from "./activity_supervisor.js";
|
|
2
|
-
export { ControlPlaneActivitySupervisor } from "./activity_supervisor.js";
|
|
3
1
|
export type { MuConfig, MuConfigPatch, MuConfigPresence } from "./config.js";
|
|
4
2
|
export { applyMuConfigPatch, DEFAULT_MU_CONFIG, getMuConfigPath, muConfigPresence, normalizeMuConfig, readMuConfigFile, redactMuConfigSecrets, writeMuConfigFile, } from "./config.js";
|
|
5
3
|
export type { ActiveAdapter, ControlPlaneConfig, ControlPlaneHandle, ControlPlaneSessionLifecycle, ControlPlaneSessionMutationAction, ControlPlaneSessionMutationResult, InterRootQueuePolicy, NotifyOperatorsOpts, NotifyOperatorsResult, OrchestrationQueueState, WakeDeliveryEvent, WakeNotifyContext, WakeNotifyDecision, } from "./control_plane_contract.js";
|
|
@@ -17,6 +15,7 @@ export type { HeartbeatProgramDispatchResult, HeartbeatProgramOperationResult, H
|
|
|
17
15
|
export { HeartbeatProgramRegistry } from "./heartbeat_programs.js";
|
|
18
16
|
export type { ActivityHeartbeatSchedulerOpts, HeartbeatRunResult, HeartbeatTickHandler, } from "./heartbeat_scheduler.js";
|
|
19
17
|
export { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
18
|
+
export { MemoryIndexMaintainer } from "./memory_index_maintainer.js";
|
|
20
19
|
export type { ServerContext, ServerInstanceOptions, ServerOptions, ServerRuntime, ServerRuntimeCapabilities, ServerRuntimeOptions, } from "./server.js";
|
|
21
20
|
export { composeServerRuntime, createContext, createServerFromRuntime } from "./server.js";
|
|
22
21
|
export type { ShellCommandResult, ShellCommandRunner } from "./session_lifecycle.js";
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export { ControlPlaneActivitySupervisor } from "./activity_supervisor.js";
|
|
2
1
|
export { applyMuConfigPatch, DEFAULT_MU_CONFIG, getMuConfigPath, muConfigPresence, normalizeMuConfig, readMuConfigFile, redactMuConfigSecrets, writeMuConfigFile, } from "./config.js";
|
|
3
2
|
export { DEFAULT_INTER_ROOT_QUEUE_POLICY, normalizeInterRootQueuePolicy, ORCHESTRATION_QUEUE_ALLOWED_TRANSITIONS, ORCHESTRATION_QUEUE_INVARIANTS, } from "./control_plane_contract.js";
|
|
4
3
|
export { DurableRunQueue, queueStatesForRunStatusFilter, reconcileRunQueue, RUN_QUEUE_RECONCILE_INVARIANTS, runQueuePath, runSnapshotFromQueueSnapshot, runStatusFromQueueState, } from "./run_queue.js";
|
|
@@ -8,5 +7,6 @@ export { computeNextScheduleRunAtMs, normalizeCronSchedule } from "./cron_schedu
|
|
|
8
7
|
export { CronTimerRegistry } from "./cron_timer.js";
|
|
9
8
|
export { HeartbeatProgramRegistry } from "./heartbeat_programs.js";
|
|
10
9
|
export { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
10
|
+
export { MemoryIndexMaintainer } from "./memory_index_maintainer.js";
|
|
11
11
|
export { composeServerRuntime, createContext, createServerFromRuntime } from "./server.js";
|
|
12
12
|
export { createProcessSessionLifecycle } from "./session_lifecycle.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type EventLog } from "@femtomc/mu-core/node";
|
|
2
|
+
import type { MuConfig } from "./config.js";
|
|
3
|
+
import type { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
4
|
+
export declare class MemoryIndexMaintainer {
|
|
5
|
+
#private;
|
|
6
|
+
constructor(opts: {
|
|
7
|
+
repoRoot: string;
|
|
8
|
+
heartbeatScheduler: ActivityHeartbeatScheduler;
|
|
9
|
+
eventLog: EventLog;
|
|
10
|
+
loadConfigFromDisk: () => Promise<MuConfig>;
|
|
11
|
+
fallbackConfig: MuConfig;
|
|
12
|
+
});
|
|
13
|
+
start(): void;
|
|
14
|
+
stop(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { runContextIndexRebuild, runContextIndexStatus, } from "@femtomc/mu-core/node";
|
|
2
|
+
const MEMORY_INDEX_ACTIVITY_ID = "maintenance:memory-index";
|
|
3
|
+
function describeError(err) {
|
|
4
|
+
if (err instanceof Error) {
|
|
5
|
+
return err.message;
|
|
6
|
+
}
|
|
7
|
+
return String(err);
|
|
8
|
+
}
|
|
9
|
+
function normalizeEveryMs(value) {
|
|
10
|
+
if (!Number.isFinite(value)) {
|
|
11
|
+
return 300_000;
|
|
12
|
+
}
|
|
13
|
+
return Math.max(1_000, Math.min(86_400_000, Math.trunc(value)));
|
|
14
|
+
}
|
|
15
|
+
function maintainerConfigFromMuConfig(config) {
|
|
16
|
+
return {
|
|
17
|
+
enabled: config.control_plane.memory_index.enabled,
|
|
18
|
+
every_ms: normalizeEveryMs(config.control_plane.memory_index.every_ms),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export class MemoryIndexMaintainer {
|
|
22
|
+
#repoRoot;
|
|
23
|
+
#scheduler;
|
|
24
|
+
#eventLog;
|
|
25
|
+
#loadConfigFromDisk;
|
|
26
|
+
#fallbackConfig;
|
|
27
|
+
#currentEveryMs;
|
|
28
|
+
#started = false;
|
|
29
|
+
constructor(opts) {
|
|
30
|
+
this.#repoRoot = opts.repoRoot;
|
|
31
|
+
this.#scheduler = opts.heartbeatScheduler;
|
|
32
|
+
this.#eventLog = opts.eventLog;
|
|
33
|
+
this.#loadConfigFromDisk = opts.loadConfigFromDisk;
|
|
34
|
+
this.#fallbackConfig = maintainerConfigFromMuConfig(opts.fallbackConfig);
|
|
35
|
+
this.#currentEveryMs = this.#fallbackConfig.every_ms;
|
|
36
|
+
}
|
|
37
|
+
#register(everyMs) {
|
|
38
|
+
this.#currentEveryMs = everyMs;
|
|
39
|
+
this.#scheduler.register({
|
|
40
|
+
activityId: MEMORY_INDEX_ACTIVITY_ID,
|
|
41
|
+
everyMs,
|
|
42
|
+
coalesceMs: 0,
|
|
43
|
+
handler: async ({ reason }) => await this.#tick(reason),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
async #emitTickEvent(payload) {
|
|
47
|
+
await this.#eventLog.emit("memory_index.maintenance_tick", {
|
|
48
|
+
source: "mu-server.memory-index-maintainer",
|
|
49
|
+
payload,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async #emitRebuildEvent(payload) {
|
|
53
|
+
await this.#eventLog.emit("memory_index.rebuild", {
|
|
54
|
+
source: "mu-server.memory-index-maintainer",
|
|
55
|
+
payload,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async #emitRebuildFailureEvent(payload) {
|
|
59
|
+
await this.#eventLog.emit("memory_index.rebuild_failed", {
|
|
60
|
+
source: "mu-server.memory-index-maintainer",
|
|
61
|
+
payload,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async #readConfig() {
|
|
65
|
+
try {
|
|
66
|
+
const config = await this.#loadConfigFromDisk();
|
|
67
|
+
return maintainerConfigFromMuConfig(config);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return this.#fallbackConfig;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async #tick(reasonRaw) {
|
|
74
|
+
const reason = typeof reasonRaw === "string" && reasonRaw.trim().length > 0 ? reasonRaw.trim() : "scheduled";
|
|
75
|
+
const cfg = await this.#readConfig();
|
|
76
|
+
if (cfg.every_ms !== this.#currentEveryMs) {
|
|
77
|
+
this.#register(cfg.every_ms);
|
|
78
|
+
await this.#emitTickEvent({
|
|
79
|
+
reason,
|
|
80
|
+
action: "rescheduled",
|
|
81
|
+
every_ms: cfg.every_ms,
|
|
82
|
+
enabled: cfg.enabled,
|
|
83
|
+
});
|
|
84
|
+
return { status: "skipped", reason: "rescheduled" };
|
|
85
|
+
}
|
|
86
|
+
if (!cfg.enabled) {
|
|
87
|
+
await this.#emitTickEvent({
|
|
88
|
+
reason,
|
|
89
|
+
action: "disabled",
|
|
90
|
+
every_ms: cfg.every_ms,
|
|
91
|
+
enabled: cfg.enabled,
|
|
92
|
+
});
|
|
93
|
+
return { status: "skipped", reason: "disabled" };
|
|
94
|
+
}
|
|
95
|
+
let status;
|
|
96
|
+
try {
|
|
97
|
+
status = await runContextIndexStatus({ repoRoot: this.#repoRoot });
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const error = describeError(err);
|
|
101
|
+
await this.#emitRebuildFailureEvent({
|
|
102
|
+
reason,
|
|
103
|
+
action: "status_failed",
|
|
104
|
+
error,
|
|
105
|
+
});
|
|
106
|
+
return { status: "failed", reason: error };
|
|
107
|
+
}
|
|
108
|
+
const needsRebuild = !status.exists || status.stale_source_count > 0;
|
|
109
|
+
if (!needsRebuild) {
|
|
110
|
+
await this.#emitTickEvent({
|
|
111
|
+
reason,
|
|
112
|
+
action: "up_to_date",
|
|
113
|
+
enabled: cfg.enabled,
|
|
114
|
+
every_ms: cfg.every_ms,
|
|
115
|
+
total_count: status.total_count,
|
|
116
|
+
stale_source_count: status.stale_source_count,
|
|
117
|
+
});
|
|
118
|
+
return { status: "skipped", reason: "up_to_date" };
|
|
119
|
+
}
|
|
120
|
+
const startedAtMs = Date.now();
|
|
121
|
+
try {
|
|
122
|
+
const rebuild = await runContextIndexRebuild({
|
|
123
|
+
repoRoot: this.#repoRoot,
|
|
124
|
+
search: new URLSearchParams(),
|
|
125
|
+
});
|
|
126
|
+
await this.#emitRebuildEvent({
|
|
127
|
+
reason,
|
|
128
|
+
action: "rebuilt",
|
|
129
|
+
duration_ms: Math.max(0, Date.now() - startedAtMs),
|
|
130
|
+
indexed_count: rebuild.indexed_count,
|
|
131
|
+
total_count: rebuild.total_count,
|
|
132
|
+
stale_source_count: rebuild.stale_source_count,
|
|
133
|
+
source_count: rebuild.source_count,
|
|
134
|
+
});
|
|
135
|
+
return { status: "ran" };
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
const error = describeError(err);
|
|
139
|
+
await this.#emitRebuildFailureEvent({
|
|
140
|
+
reason,
|
|
141
|
+
action: "rebuild_failed",
|
|
142
|
+
duration_ms: Math.max(0, Date.now() - startedAtMs),
|
|
143
|
+
error,
|
|
144
|
+
index_exists: status.exists,
|
|
145
|
+
stale_source_count: status.stale_source_count,
|
|
146
|
+
});
|
|
147
|
+
return { status: "failed", reason: error };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
start() {
|
|
151
|
+
if (this.#started) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.#started = true;
|
|
155
|
+
this.#register(this.#currentEveryMs);
|
|
156
|
+
this.#scheduler.requestNow(MEMORY_INDEX_ACTIVITY_ID, { reason: "startup", coalesceMs: 0 });
|
|
157
|
+
}
|
|
158
|
+
stop() {
|
|
159
|
+
if (!this.#started) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.#started = false;
|
|
163
|
+
this.#scheduler.unregister(MEMORY_INDEX_ACTIVITY_ID);
|
|
164
|
+
}
|
|
165
|
+
}
|
package/dist/run_queue.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { FsJsonlStore } from "@femtomc/mu-core/node";
|
|
2
|
+
import { FsJsonlStore, getStorePaths } from "@femtomc/mu-core/node";
|
|
3
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;
|
|
@@ -204,7 +204,7 @@ export function reconcileRunQueue(rows, policy) {
|
|
|
204
204
|
return reconcileInterRootQueue(rows, policy);
|
|
205
205
|
}
|
|
206
206
|
export function runQueuePath(repoRoot) {
|
|
207
|
-
return join(repoRoot
|
|
207
|
+
return join(getStorePaths(repoRoot).storeDir, "control-plane", RUN_QUEUE_FILENAME);
|
|
208
208
|
}
|
|
209
209
|
function stableCompare(a, b) {
|
|
210
210
|
if (a.created_at_ms !== b.created_at_ms) {
|
package/dist/run_supervisor.d.ts
CHANGED
|
@@ -70,8 +70,8 @@ export declare class ControlPlaneRunSupervisor {
|
|
|
70
70
|
#private;
|
|
71
71
|
constructor(opts: ControlPlaneRunSupervisorOpts);
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
73
|
+
* Queue-launch entrypoint for run-start intent.
|
|
74
|
+
* Expected call path: enqueue/activate first, then invoke after lease acquisition.
|
|
75
75
|
*/
|
|
76
76
|
launchStart(opts: {
|
|
77
77
|
prompt: string;
|
|
@@ -81,8 +81,8 @@ export declare class ControlPlaneRunSupervisor {
|
|
|
81
81
|
source?: "command" | "api";
|
|
82
82
|
}): Promise<ControlPlaneRunSnapshot>;
|
|
83
83
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
84
|
+
* Queue-launch entrypoint for run-resume intent.
|
|
85
|
+
* Queue-first reconcile remains the canonical execution path.
|
|
86
86
|
*/
|
|
87
87
|
launchResume(opts: {
|
|
88
88
|
rootIssueId: string;
|
package/dist/run_supervisor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdir } from "node:fs/promises";
|
|
2
2
|
import { join, relative } from "node:path";
|
|
3
|
+
import { getStorePaths } from "@femtomc/mu-core/node";
|
|
3
4
|
const DEFAULT_MAX_STEPS = 20;
|
|
4
5
|
const ROOT_RE = /\bRoot:\s*(mu-[a-z0-9][a-z0-9-]*)\b/i;
|
|
5
6
|
const STEP_RE = /^(Step|Done)\s+\d+\/\d+\s+/;
|
|
@@ -275,8 +276,8 @@ export class ControlPlaneRunSupervisor {
|
|
|
275
276
|
return this.#snapshot(job);
|
|
276
277
|
}
|
|
277
278
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
279
|
+
* Queue-launch entrypoint for run-start intent.
|
|
280
|
+
* Expected call path: enqueue/activate first, then invoke after lease acquisition.
|
|
280
281
|
*/
|
|
281
282
|
async launchStart(opts) {
|
|
282
283
|
const prompt = opts.prompt.trim();
|
|
@@ -297,8 +298,8 @@ export class ControlPlaneRunSupervisor {
|
|
|
297
298
|
});
|
|
298
299
|
}
|
|
299
300
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
301
|
+
* Queue-launch entrypoint for run-resume intent.
|
|
302
|
+
* Queue-first reconcile remains the canonical execution path.
|
|
302
303
|
*/
|
|
303
304
|
async launchResume(opts) {
|
|
304
305
|
const rootIssueId = normalizeIssueId(opts.rootIssueId);
|
|
@@ -355,7 +356,7 @@ export class ControlPlaneRunSupervisor {
|
|
|
355
356
|
const rootIssueId = job.snapshot.root_issue_id;
|
|
356
357
|
const traceFiles = [];
|
|
357
358
|
if (rootIssueId) {
|
|
358
|
-
const rootLogsDir = join(this.#repoRoot
|
|
359
|
+
const rootLogsDir = join(getStorePaths(this.#repoRoot).logsDir, rootIssueId);
|
|
359
360
|
try {
|
|
360
361
|
const entries = await readdir(rootLogsDir, { withFileTypes: true });
|
|
361
362
|
for (const entry of entries) {
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +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 { ControlPlaneActivitySupervisor } from "./activity_supervisor.js";
|
|
5
4
|
import { type MuConfig } from "./config.js";
|
|
6
5
|
import type { ControlPlaneHandle, ControlPlaneSessionLifecycle } from "./control_plane_contract.js";
|
|
7
6
|
import { type ConfigReader, type ConfigWriter, type ControlPlaneReloader, type ControlPlaneReloadResult, type ControlPlaneSummary } from "./control_plane_reload.js";
|
|
@@ -15,7 +14,6 @@ export type ServerOptions = {
|
|
|
15
14
|
port?: number;
|
|
16
15
|
controlPlane?: ControlPlaneHandle | null;
|
|
17
16
|
heartbeatScheduler?: ActivityHeartbeatScheduler;
|
|
18
|
-
activitySupervisor?: ControlPlaneActivitySupervisor;
|
|
19
17
|
controlPlaneReloader?: ControlPlaneReloader;
|
|
20
18
|
generationTelemetry?: GenerationTelemetryRecorder;
|
|
21
19
|
operatorWakeCoalesceMs?: number;
|
|
@@ -39,7 +37,6 @@ export declare function createServerFromRuntime(runtime: ServerRuntime, options?
|
|
|
39
37
|
fetch: (request: Request) => Promise<Response>;
|
|
40
38
|
hostname: string;
|
|
41
39
|
controlPlane: ControlPlaneHandle;
|
|
42
|
-
activitySupervisor: ControlPlaneActivitySupervisor;
|
|
43
40
|
heartbeatPrograms: import("./heartbeat_programs.js").HeartbeatProgramRegistry;
|
|
44
41
|
cronPrograms: import("./cron_programs.js").CronProgramRegistry;
|
|
45
42
|
};
|
package/dist/server.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { GenerationTelemetryRecorder, presentPipelineResultMessage, } from "@femtomc/mu-control-plane";
|
|
2
2
|
import { currentRunId, EventLog, FsJsonlStore, getStorePaths, JsonlEventSink } from "@femtomc/mu-core/node";
|
|
3
|
-
import { ControlPlaneActivitySupervisor } from "./activity_supervisor.js";
|
|
4
3
|
import { DEFAULT_MU_CONFIG, readMuConfigFile, writeMuConfigFile } from "./config.js";
|
|
5
4
|
import { bootstrapControlPlane } from "./control_plane.js";
|
|
6
5
|
import { createReloadManager, } from "./control_plane_reload.js";
|
|
7
6
|
import { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
8
7
|
import { createProcessSessionLifecycle } from "./session_lifecycle.js";
|
|
8
|
+
import { MemoryIndexMaintainer } from "./memory_index_maintainer.js";
|
|
9
9
|
import { createServerProgramOrchestration } from "./server_program_orchestration.js";
|
|
10
10
|
import { createServerRequestHandler } from "./server_routing.js";
|
|
11
11
|
import { toNonNegativeInt } from "./server_types.js";
|
|
@@ -32,6 +32,10 @@ function stringField(payload, key) {
|
|
|
32
32
|
const trimmed = value.trim();
|
|
33
33
|
return trimmed.length > 0 ? trimmed : null;
|
|
34
34
|
}
|
|
35
|
+
function stringFieldRaw(payload, key) {
|
|
36
|
+
const value = payload[key];
|
|
37
|
+
return typeof value === "string" ? value : null;
|
|
38
|
+
}
|
|
35
39
|
function numberField(payload, key) {
|
|
36
40
|
const value = payload[key];
|
|
37
41
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
@@ -73,15 +77,25 @@ function extractWakeTurnReply(turnResult) {
|
|
|
73
77
|
function buildWakeTurnCommandText(opts) {
|
|
74
78
|
const wakeSource = stringField(opts.payload, "wake_source") ?? "unknown";
|
|
75
79
|
const programId = stringField(opts.payload, "program_id") ?? "unknown";
|
|
80
|
+
const title = stringField(opts.payload, "title") ?? "(untitled wake program)";
|
|
76
81
|
const reason = stringField(opts.payload, "reason") ?? "scheduled";
|
|
82
|
+
const prompt = stringFieldRaw(opts.payload, "prompt");
|
|
83
|
+
const wakeInstruction = prompt ?? opts.message;
|
|
77
84
|
const payloadSnapshot = stablePayloadSnapshot(opts.payload);
|
|
78
85
|
return [
|
|
79
86
|
"Autonomous wake turn triggered by heartbeat/cron scheduler.",
|
|
80
87
|
`wake_id=${opts.wakeId}`,
|
|
81
88
|
`wake_source=${wakeSource}`,
|
|
82
89
|
`program_id=${programId}`,
|
|
83
|
-
`
|
|
84
|
-
`
|
|
90
|
+
`wake_title=${title}`,
|
|
91
|
+
`wake_reason=${reason}`,
|
|
92
|
+
"",
|
|
93
|
+
"wake_prompt:",
|
|
94
|
+
prompt ?? "(none)",
|
|
95
|
+
"",
|
|
96
|
+
"wake_instruction:",
|
|
97
|
+
wakeInstruction,
|
|
98
|
+
"",
|
|
85
99
|
`payload=${payloadSnapshot}`,
|
|
86
100
|
"",
|
|
87
101
|
"If action is needed, produce exactly one `/mu ...` command. If no action is needed, return a short operator response that can be broadcast verbatim.",
|
|
@@ -102,24 +116,6 @@ function createServer(options = {}) {
|
|
|
102
116
|
const writeConfig = options.configWriter ?? writeMuConfigFile;
|
|
103
117
|
const fallbackConfig = options.config ?? DEFAULT_MU_CONFIG;
|
|
104
118
|
const heartbeatScheduler = options.heartbeatScheduler ?? new ActivityHeartbeatScheduler();
|
|
105
|
-
const activitySupervisor = options.activitySupervisor ??
|
|
106
|
-
new ControlPlaneActivitySupervisor({
|
|
107
|
-
heartbeatScheduler,
|
|
108
|
-
onEvent: async (event) => {
|
|
109
|
-
await context.eventLog.emit(`activity.${event.kind}`, {
|
|
110
|
-
source: "mu-server.activity-supervisor",
|
|
111
|
-
payload: {
|
|
112
|
-
seq: event.seq,
|
|
113
|
-
message: event.message,
|
|
114
|
-
activity_id: event.activity.activity_id,
|
|
115
|
-
kind: event.activity.kind,
|
|
116
|
-
status: event.activity.status,
|
|
117
|
-
heartbeat_count: event.activity.heartbeat_count,
|
|
118
|
-
last_progress: event.activity.last_progress,
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
},
|
|
122
|
-
});
|
|
123
119
|
const operatorWakeCoalesceMs = toNonNegativeInt(options.operatorWakeCoalesceMs, DEFAULT_OPERATOR_WAKE_COALESCE_MS);
|
|
124
120
|
const operatorWakeLastByKey = new Map();
|
|
125
121
|
const sessionLifecycle = options.sessionLifecycle ?? createProcessSessionLifecycle({ repoRoot });
|
|
@@ -458,6 +454,7 @@ function createServer(options = {}) {
|
|
|
458
454
|
const handle = reloadManager.getControlPlaneCurrent();
|
|
459
455
|
handle?.setWakeDeliveryObserver?.(null);
|
|
460
456
|
reloadManager.setControlPlaneCurrent(null);
|
|
457
|
+
memoryIndexMaintainer.stop();
|
|
461
458
|
await handle?.stop();
|
|
462
459
|
},
|
|
463
460
|
};
|
|
@@ -467,10 +464,17 @@ function createServer(options = {}) {
|
|
|
467
464
|
eventLog: context.eventLog,
|
|
468
465
|
emitOperatorWake,
|
|
469
466
|
});
|
|
467
|
+
const memoryIndexMaintainer = new MemoryIndexMaintainer({
|
|
468
|
+
repoRoot,
|
|
469
|
+
heartbeatScheduler,
|
|
470
|
+
eventLog: context.eventLog,
|
|
471
|
+
loadConfigFromDisk,
|
|
472
|
+
fallbackConfig,
|
|
473
|
+
});
|
|
474
|
+
memoryIndexMaintainer.start();
|
|
470
475
|
const handleRequest = createServerRequestHandler({
|
|
471
476
|
context,
|
|
472
477
|
controlPlaneProxy,
|
|
473
|
-
activitySupervisor,
|
|
474
478
|
heartbeatPrograms,
|
|
475
479
|
cronPrograms,
|
|
476
480
|
loadConfigFromDisk,
|
|
@@ -485,7 +489,6 @@ function createServer(options = {}) {
|
|
|
485
489
|
fetch: handleRequest,
|
|
486
490
|
hostname: "0.0.0.0",
|
|
487
491
|
controlPlane: controlPlaneProxy,
|
|
488
|
-
activitySupervisor,
|
|
489
492
|
heartbeatPrograms,
|
|
490
493
|
cronPrograms,
|
|
491
494
|
};
|
|
@@ -11,13 +11,16 @@ export function createServerProgramOrchestration(opts) {
|
|
|
11
11
|
repoRoot: opts.repoRoot,
|
|
12
12
|
heartbeatScheduler: opts.heartbeatScheduler,
|
|
13
13
|
dispatchWake: async (wakeOpts) => {
|
|
14
|
+
const prompt = wakeOpts.prompt && wakeOpts.prompt.trim().length > 0 ? wakeOpts.prompt : null;
|
|
14
15
|
const wakeResult = await opts.emitOperatorWake({
|
|
15
16
|
dedupeKey: `heartbeat-program:${wakeOpts.programId}`,
|
|
16
|
-
message: `Heartbeat wake: ${wakeOpts.title}`,
|
|
17
|
+
message: prompt ?? `Heartbeat wake: ${wakeOpts.title}`,
|
|
17
18
|
payload: {
|
|
18
19
|
wake_source: "heartbeat_program",
|
|
19
20
|
source_ts_ms: wakeOpts.triggeredAtMs,
|
|
20
21
|
program_id: wakeOpts.programId,
|
|
22
|
+
title: wakeOpts.title,
|
|
23
|
+
prompt,
|
|
21
24
|
reason: wakeOpts.reason,
|
|
22
25
|
metadata: wakeOpts.metadata,
|
|
23
26
|
},
|
|
@@ -55,6 +58,8 @@ export function createServerProgramOrchestration(opts) {
|
|
|
55
58
|
wake_source: "cron_program",
|
|
56
59
|
source_ts_ms: wakeOpts.triggeredAtMs,
|
|
57
60
|
program_id: wakeOpts.programId,
|
|
61
|
+
title: wakeOpts.title,
|
|
62
|
+
prompt: null,
|
|
58
63
|
reason: wakeOpts.reason,
|
|
59
64
|
schedule: wakeOpts.schedule,
|
|
60
65
|
metadata: wakeOpts.metadata,
|
package/dist/server_routing.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { ControlPlaneActivitySupervisor } from "./activity_supervisor.js";
|
|
2
1
|
import type { MuConfig } from "./config.js";
|
|
3
2
|
import type { ControlPlaneHandle } from "./control_plane_contract.js";
|
|
4
3
|
import type { CronProgramRegistry } from "./cron_programs.js";
|
|
@@ -7,7 +6,6 @@ import type { ServerContext } from "./server.js";
|
|
|
7
6
|
export type ServerRoutingDependencies = {
|
|
8
7
|
context: ServerContext;
|
|
9
8
|
controlPlaneProxy: ControlPlaneHandle;
|
|
10
|
-
activitySupervisor: ControlPlaneActivitySupervisor;
|
|
11
9
|
heartbeatPrograms: HeartbeatProgramRegistry;
|
|
12
10
|
cronPrograms: CronProgramRegistry;
|
|
13
11
|
loadConfigFromDisk: () => Promise<MuConfig>;
|
package/dist/server_routing.js
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
import { activityRoutes } from "./api/activities.js";
|
|
2
|
-
import { configRoutes } from "./api/config.js";
|
|
3
1
|
import { controlPlaneRoutes } from "./api/control_plane.js";
|
|
4
2
|
import { cronRoutes } from "./api/cron.js";
|
|
5
|
-
import { eventRoutes } from "./api/events.js";
|
|
6
3
|
import { heartbeatRoutes } from "./api/heartbeats.js";
|
|
7
|
-
import { identityRoutes } from "./api/identities.js";
|
|
8
|
-
import { runRoutes } from "./api/runs.js";
|
|
9
|
-
import { sessionFlashRoutes } from "./api/session_flash.js";
|
|
10
|
-
import { sessionTurnRoutes } from "./api/session_turn.js";
|
|
11
4
|
export function createServerRequestHandler(deps) {
|
|
12
5
|
return async (request) => {
|
|
13
6
|
const url = new URL(request.url);
|
|
@@ -36,50 +29,15 @@ export function createServerRequestHandler(deps) {
|
|
|
36
29
|
}, 100);
|
|
37
30
|
return Response.json({ ok: true, message: "shutdown initiated" }, { headers });
|
|
38
31
|
}
|
|
39
|
-
if (path === "/api/
|
|
40
|
-
return configRoutes(request, url, deps, headers);
|
|
41
|
-
}
|
|
42
|
-
if (path === "/api/control-plane/reload" ||
|
|
43
|
-
path === "/api/control-plane/rollback" ||
|
|
44
|
-
path === "/api/control-plane/channels") {
|
|
32
|
+
if (path === "/api/control-plane" || path.startsWith("/api/control-plane/")) {
|
|
45
33
|
return controlPlaneRoutes(request, url, deps, headers);
|
|
46
34
|
}
|
|
47
|
-
if (path === "/api/status") {
|
|
48
|
-
return Response.json({
|
|
49
|
-
repo_root: deps.context.repoRoot,
|
|
50
|
-
control_plane: deps.getControlPlaneStatus(),
|
|
51
|
-
}, { headers });
|
|
52
|
-
}
|
|
53
|
-
if (path === "/api/session-flash" ||
|
|
54
|
-
path === "/api/session-flash/ack" ||
|
|
55
|
-
path.startsWith("/api/session-flash/")) {
|
|
56
|
-
return sessionFlashRoutes(request, url, deps, headers);
|
|
57
|
-
}
|
|
58
|
-
if (path === "/api/session-turn") {
|
|
59
|
-
return sessionTurnRoutes(request, url, deps, headers);
|
|
60
|
-
}
|
|
61
|
-
if (path === "/api/runs" || path.startsWith("/api/runs/")) {
|
|
62
|
-
return runRoutes(request, url, deps, headers);
|
|
63
|
-
}
|
|
64
35
|
if (path === "/api/cron" || path.startsWith("/api/cron/")) {
|
|
65
36
|
return cronRoutes(request, url, deps, headers);
|
|
66
37
|
}
|
|
67
38
|
if (path === "/api/heartbeats" || path.startsWith("/api/heartbeats/")) {
|
|
68
39
|
return heartbeatRoutes(request, url, deps, headers);
|
|
69
40
|
}
|
|
70
|
-
if (path === "/api/activities" || path.startsWith("/api/activities/")) {
|
|
71
|
-
return activityRoutes(request, url, deps, headers);
|
|
72
|
-
}
|
|
73
|
-
if (path === "/api/identities" || path === "/api/identities/link" || path === "/api/identities/unlink") {
|
|
74
|
-
return identityRoutes(request, url, deps, headers);
|
|
75
|
-
}
|
|
76
|
-
if (path.startsWith("/api/events")) {
|
|
77
|
-
const response = await eventRoutes(request, deps.context);
|
|
78
|
-
headers.forEach((value, key) => {
|
|
79
|
-
response.headers.set(key, value);
|
|
80
|
-
});
|
|
81
|
-
return response;
|
|
82
|
-
}
|
|
83
41
|
if (path.startsWith("/webhooks/")) {
|
|
84
42
|
const response = await deps.controlPlaneProxy.handleWebhook(path, request);
|
|
85
43
|
if (response) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-server",
|
|
3
|
-
"version": "26.2.
|
|
3
|
+
"version": "26.2.77",
|
|
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.2.
|
|
34
|
-
"@femtomc/mu-control-plane": "26.2.
|
|
35
|
-
"@femtomc/mu-core": "26.2.
|
|
33
|
+
"@femtomc/mu-agent": "26.2.77",
|
|
34
|
+
"@femtomc/mu-control-plane": "26.2.77",
|
|
35
|
+
"@femtomc/mu-core": "26.2.77"
|
|
36
36
|
}
|
|
37
37
|
}
|