@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
|
@@ -1,25 +1,34 @@
|
|
|
1
1
|
import { CronProgramRegistry } from "./cron_programs.js";
|
|
2
2
|
import { HeartbeatProgramRegistry } from "./heartbeat_programs.js";
|
|
3
|
-
|
|
3
|
+
function describeError(err) {
|
|
4
|
+
if (err instanceof Error) {
|
|
5
|
+
return err.message;
|
|
6
|
+
}
|
|
7
|
+
return String(err);
|
|
8
|
+
}
|
|
4
9
|
export function createServerProgramOrchestration(opts) {
|
|
5
|
-
const autoRunHeartbeatProgramByJobId = new Map();
|
|
6
10
|
const heartbeatPrograms = new HeartbeatProgramRegistry({
|
|
7
11
|
repoRoot: opts.repoRoot,
|
|
8
12
|
heartbeatScheduler: opts.heartbeatScheduler,
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
activityId: activityOpts.activityId ?? null,
|
|
21
|
-
reason: activityOpts.reason ?? null,
|
|
13
|
+
dispatchWake: async (wakeOpts) => {
|
|
14
|
+
const wakeResult = await opts.emitOperatorWake({
|
|
15
|
+
dedupeKey: `heartbeat-program:${wakeOpts.programId}`,
|
|
16
|
+
message: `Heartbeat wake: ${wakeOpts.title}`,
|
|
17
|
+
payload: {
|
|
18
|
+
wake_source: "heartbeat_program",
|
|
19
|
+
source_ts_ms: wakeOpts.triggeredAtMs,
|
|
20
|
+
program_id: wakeOpts.programId,
|
|
21
|
+
reason: wakeOpts.reason,
|
|
22
|
+
metadata: wakeOpts.metadata,
|
|
23
|
+
},
|
|
22
24
|
});
|
|
25
|
+
if (wakeResult.status === "coalesced") {
|
|
26
|
+
return { status: "coalesced", reason: wakeResult.reason };
|
|
27
|
+
}
|
|
28
|
+
if (wakeResult.status === "failed") {
|
|
29
|
+
return { status: "failed", reason: wakeResult.reason };
|
|
30
|
+
}
|
|
31
|
+
return { status: "ok" };
|
|
23
32
|
},
|
|
24
33
|
onTickEvent: async (event) => {
|
|
25
34
|
await opts.eventLog.emit("heartbeat_program.tick", {
|
|
@@ -32,44 +41,36 @@ export function createServerProgramOrchestration(opts) {
|
|
|
32
41
|
program: event.program,
|
|
33
42
|
},
|
|
34
43
|
});
|
|
35
|
-
await opts.emitOperatorWake({
|
|
36
|
-
dedupeKey: `heartbeat-program:${event.program_id}`,
|
|
37
|
-
message: event.message,
|
|
38
|
-
payload: {
|
|
39
|
-
wake_source: "heartbeat_program",
|
|
40
|
-
source_ts_ms: event.ts_ms,
|
|
41
|
-
program_id: event.program_id,
|
|
42
|
-
status: event.status,
|
|
43
|
-
reason: event.reason,
|
|
44
|
-
wake_mode: event.program.wake_mode,
|
|
45
|
-
target_kind: event.program.target.kind,
|
|
46
|
-
target: event.program.target.kind === "run"
|
|
47
|
-
? {
|
|
48
|
-
job_id: event.program.target.job_id,
|
|
49
|
-
root_issue_id: event.program.target.root_issue_id,
|
|
50
|
-
}
|
|
51
|
-
: { activity_id: event.program.target.activity_id },
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
44
|
},
|
|
55
45
|
});
|
|
56
46
|
const cronPrograms = new CronProgramRegistry({
|
|
57
47
|
repoRoot: opts.repoRoot,
|
|
58
48
|
heartbeatScheduler: opts.heartbeatScheduler,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
49
|
+
dispatchWake: async (wakeOpts) => {
|
|
50
|
+
try {
|
|
51
|
+
const wakeResult = await opts.emitOperatorWake({
|
|
52
|
+
dedupeKey: `cron-program:${wakeOpts.programId}`,
|
|
53
|
+
message: `Cron wake: ${wakeOpts.title}`,
|
|
54
|
+
payload: {
|
|
55
|
+
wake_source: "cron_program",
|
|
56
|
+
source_ts_ms: wakeOpts.triggeredAtMs,
|
|
57
|
+
program_id: wakeOpts.programId,
|
|
58
|
+
reason: wakeOpts.reason,
|
|
59
|
+
schedule: wakeOpts.schedule,
|
|
60
|
+
metadata: wakeOpts.metadata,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
if (wakeResult.status === "coalesced") {
|
|
64
|
+
return { status: "coalesced", reason: wakeResult.reason };
|
|
65
|
+
}
|
|
66
|
+
if (wakeResult.status === "failed") {
|
|
67
|
+
return { status: "failed", reason: wakeResult.reason };
|
|
68
|
+
}
|
|
69
|
+
return { status: "ok" };
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return { status: "failed", reason: describeError(error) };
|
|
73
|
+
}
|
|
73
74
|
},
|
|
74
75
|
onLifecycleEvent: async (event) => {
|
|
75
76
|
await opts.eventLog.emit("cron_program.lifecycle", {
|
|
@@ -93,162 +94,10 @@ export function createServerProgramOrchestration(opts) {
|
|
|
93
94
|
program: event.program,
|
|
94
95
|
},
|
|
95
96
|
});
|
|
96
|
-
await opts.emitOperatorWake({
|
|
97
|
-
dedupeKey: `cron-program:${event.program_id}`,
|
|
98
|
-
message: event.message,
|
|
99
|
-
payload: {
|
|
100
|
-
wake_source: "cron_program",
|
|
101
|
-
source_ts_ms: event.ts_ms,
|
|
102
|
-
program_id: event.program_id,
|
|
103
|
-
status: event.status,
|
|
104
|
-
reason: event.reason,
|
|
105
|
-
wake_mode: event.program.wake_mode,
|
|
106
|
-
target_kind: event.program.target.kind,
|
|
107
|
-
target: event.program.target.kind === "run"
|
|
108
|
-
? {
|
|
109
|
-
job_id: event.program.target.job_id,
|
|
110
|
-
root_issue_id: event.program.target.root_issue_id,
|
|
111
|
-
}
|
|
112
|
-
: { activity_id: event.program.target.activity_id },
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
97
|
},
|
|
116
98
|
});
|
|
117
|
-
const findAutoRunHeartbeatProgram = async (jobId) => {
|
|
118
|
-
const normalizedJobId = jobId.trim();
|
|
119
|
-
if (!normalizedJobId) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
const knownProgramId = autoRunHeartbeatProgramByJobId.get(normalizedJobId);
|
|
123
|
-
if (knownProgramId) {
|
|
124
|
-
const knownProgram = await heartbeatPrograms.get(knownProgramId);
|
|
125
|
-
if (knownProgram) {
|
|
126
|
-
return knownProgram;
|
|
127
|
-
}
|
|
128
|
-
autoRunHeartbeatProgramByJobId.delete(normalizedJobId);
|
|
129
|
-
}
|
|
130
|
-
const programs = await heartbeatPrograms.list({ targetKind: "run", limit: 500 });
|
|
131
|
-
for (const program of programs) {
|
|
132
|
-
if (program.metadata.auto_run_job_id !== normalizedJobId) {
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
autoRunHeartbeatProgramByJobId.set(normalizedJobId, program.program_id);
|
|
136
|
-
return program;
|
|
137
|
-
}
|
|
138
|
-
return null;
|
|
139
|
-
};
|
|
140
|
-
const registerAutoRunHeartbeatProgram = async (run) => {
|
|
141
|
-
if (run.source === "command") {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const jobId = run.job_id.trim();
|
|
145
|
-
if (!jobId || run.status !== "running") {
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
const rootIssueId = typeof run.root_issue_id === "string" ? run.root_issue_id.trim() : "";
|
|
149
|
-
const metadata = {
|
|
150
|
-
auto_run_heartbeat: true,
|
|
151
|
-
auto_run_job_id: jobId,
|
|
152
|
-
auto_run_root_issue_id: rootIssueId || null,
|
|
153
|
-
auto_disable_on_terminal: true,
|
|
154
|
-
run_mode: run.mode,
|
|
155
|
-
run_source: run.source,
|
|
156
|
-
};
|
|
157
|
-
const existing = await findAutoRunHeartbeatProgram(jobId);
|
|
158
|
-
if (existing) {
|
|
159
|
-
const result = await heartbeatPrograms.update({
|
|
160
|
-
programId: existing.program_id,
|
|
161
|
-
title: `Run heartbeat: ${rootIssueId || jobId}`,
|
|
162
|
-
target: {
|
|
163
|
-
kind: "run",
|
|
164
|
-
job_id: jobId,
|
|
165
|
-
root_issue_id: rootIssueId || null,
|
|
166
|
-
},
|
|
167
|
-
enabled: true,
|
|
168
|
-
everyMs: opts.autoRunHeartbeatEveryMs,
|
|
169
|
-
reason: AUTO_RUN_HEARTBEAT_REASON,
|
|
170
|
-
wakeMode: "next_heartbeat",
|
|
171
|
-
metadata,
|
|
172
|
-
});
|
|
173
|
-
if (result.ok && result.program) {
|
|
174
|
-
autoRunHeartbeatProgramByJobId.set(jobId, result.program.program_id);
|
|
175
|
-
await opts.eventLog.emit("run.auto_heartbeat.lifecycle", {
|
|
176
|
-
source: "mu-server.runs",
|
|
177
|
-
payload: {
|
|
178
|
-
action: "updated",
|
|
179
|
-
run_job_id: jobId,
|
|
180
|
-
run_root_issue_id: rootIssueId || null,
|
|
181
|
-
program_id: result.program.program_id,
|
|
182
|
-
program: result.program,
|
|
183
|
-
},
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
const created = await heartbeatPrograms.create({
|
|
189
|
-
title: `Run heartbeat: ${rootIssueId || jobId}`,
|
|
190
|
-
target: {
|
|
191
|
-
kind: "run",
|
|
192
|
-
job_id: jobId,
|
|
193
|
-
root_issue_id: rootIssueId || null,
|
|
194
|
-
},
|
|
195
|
-
everyMs: opts.autoRunHeartbeatEveryMs,
|
|
196
|
-
reason: AUTO_RUN_HEARTBEAT_REASON,
|
|
197
|
-
wakeMode: "next_heartbeat",
|
|
198
|
-
metadata,
|
|
199
|
-
enabled: true,
|
|
200
|
-
});
|
|
201
|
-
autoRunHeartbeatProgramByJobId.set(jobId, created.program_id);
|
|
202
|
-
await opts.eventLog.emit("run.auto_heartbeat.lifecycle", {
|
|
203
|
-
source: "mu-server.runs",
|
|
204
|
-
payload: {
|
|
205
|
-
action: "registered",
|
|
206
|
-
run_job_id: jobId,
|
|
207
|
-
run_root_issue_id: rootIssueId || null,
|
|
208
|
-
program_id: created.program_id,
|
|
209
|
-
program: created,
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
};
|
|
213
|
-
const disableAutoRunHeartbeatProgram = async (disableOpts) => {
|
|
214
|
-
const program = await findAutoRunHeartbeatProgram(disableOpts.jobId);
|
|
215
|
-
if (!program) {
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
const metadata = {
|
|
219
|
-
...program.metadata,
|
|
220
|
-
auto_disabled_from_status: disableOpts.status,
|
|
221
|
-
auto_disabled_reason: disableOpts.reason,
|
|
222
|
-
auto_disabled_at_ms: Date.now(),
|
|
223
|
-
};
|
|
224
|
-
const result = await heartbeatPrograms.update({
|
|
225
|
-
programId: program.program_id,
|
|
226
|
-
enabled: false,
|
|
227
|
-
everyMs: 0,
|
|
228
|
-
reason: AUTO_RUN_HEARTBEAT_REASON,
|
|
229
|
-
wakeMode: program.wake_mode,
|
|
230
|
-
metadata,
|
|
231
|
-
});
|
|
232
|
-
autoRunHeartbeatProgramByJobId.delete(disableOpts.jobId.trim());
|
|
233
|
-
if (!result.ok || !result.program) {
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
await opts.eventLog.emit("run.auto_heartbeat.lifecycle", {
|
|
237
|
-
source: "mu-server.runs",
|
|
238
|
-
payload: {
|
|
239
|
-
action: "disabled",
|
|
240
|
-
run_job_id: disableOpts.jobId,
|
|
241
|
-
status: disableOpts.status,
|
|
242
|
-
reason: disableOpts.reason,
|
|
243
|
-
program_id: result.program.program_id,
|
|
244
|
-
program: result.program,
|
|
245
|
-
},
|
|
246
|
-
});
|
|
247
|
-
};
|
|
248
99
|
return {
|
|
249
100
|
heartbeatPrograms,
|
|
250
101
|
cronPrograms,
|
|
251
|
-
registerAutoRunHeartbeatProgram,
|
|
252
|
-
disableAutoRunHeartbeatProgram,
|
|
253
102
|
};
|
|
254
103
|
}
|
package/dist/server_routing.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { MuConfig } from "./config.js";
|
|
|
3
3
|
import type { ControlPlaneHandle } from "./control_plane_contract.js";
|
|
4
4
|
import type { CronProgramRegistry } from "./cron_programs.js";
|
|
5
5
|
import type { HeartbeatProgramRegistry } from "./heartbeat_programs.js";
|
|
6
|
-
import type { AutoHeartbeatRunSnapshot } from "./server_program_orchestration.js";
|
|
7
6
|
import type { ServerContext } from "./server.js";
|
|
8
7
|
export type ServerRoutingDependencies = {
|
|
9
8
|
context: ServerContext;
|
|
@@ -17,15 +16,7 @@ export type ServerRoutingDependencies = {
|
|
|
17
16
|
ok: boolean;
|
|
18
17
|
}>;
|
|
19
18
|
getControlPlaneStatus: () => unknown;
|
|
20
|
-
registerAutoRunHeartbeatProgram: (run: AutoHeartbeatRunSnapshot) => Promise<void>;
|
|
21
|
-
disableAutoRunHeartbeatProgram: (opts: {
|
|
22
|
-
jobId: string;
|
|
23
|
-
status: string;
|
|
24
|
-
reason: string;
|
|
25
|
-
}) => Promise<void>;
|
|
26
19
|
describeError: (error: unknown) => string;
|
|
27
20
|
initiateShutdown?: () => Promise<void>;
|
|
28
|
-
publicDir?: string;
|
|
29
|
-
mimeTypes?: Record<string, string>;
|
|
30
21
|
};
|
|
31
22
|
export declare function createServerRequestHandler(deps: ServerRoutingDependencies): (request: Request) => Promise<Response>;
|
package/dist/server_routing.js
CHANGED
|
@@ -1,30 +1,14 @@
|
|
|
1
|
-
import { extname, join, resolve } from "node:path";
|
|
2
1
|
import { activityRoutes } from "./api/activities.js";
|
|
3
2
|
import { configRoutes } from "./api/config.js";
|
|
4
3
|
import { controlPlaneRoutes } from "./api/control_plane.js";
|
|
5
4
|
import { cronRoutes } from "./api/cron.js";
|
|
6
5
|
import { eventRoutes } from "./api/events.js";
|
|
7
|
-
import { forumRoutes } from "./api/forum.js";
|
|
8
6
|
import { heartbeatRoutes } from "./api/heartbeats.js";
|
|
9
7
|
import { identityRoutes } from "./api/identities.js";
|
|
10
|
-
import { issueRoutes } from "./api/issues.js";
|
|
11
8
|
import { runRoutes } from "./api/runs.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
".js": "text/javascript; charset=utf-8",
|
|
15
|
-
".css": "text/css; charset=utf-8",
|
|
16
|
-
".json": "application/json",
|
|
17
|
-
".png": "image/png",
|
|
18
|
-
".jpg": "image/jpeg",
|
|
19
|
-
".svg": "image/svg+xml",
|
|
20
|
-
".ico": "image/x-icon",
|
|
21
|
-
".woff": "font/woff",
|
|
22
|
-
".woff2": "font/woff2",
|
|
23
|
-
};
|
|
24
|
-
const DEFAULT_PUBLIC_DIR = join(new URL(".", import.meta.url).pathname, "..", "public");
|
|
9
|
+
import { sessionFlashRoutes } from "./api/session_flash.js";
|
|
10
|
+
import { sessionTurnRoutes } from "./api/session_turn.js";
|
|
25
11
|
export function createServerRequestHandler(deps) {
|
|
26
|
-
const publicDir = deps.publicDir ?? DEFAULT_PUBLIC_DIR;
|
|
27
|
-
const mimeTypes = deps.mimeTypes ?? DEFAULT_MIME_TYPES;
|
|
28
12
|
return async (request) => {
|
|
29
13
|
const url = new URL(request.url);
|
|
30
14
|
const path = url.pathname;
|
|
@@ -47,121 +31,32 @@ export function createServerRequestHandler(deps) {
|
|
|
47
31
|
return Response.json({ error: "shutdown not supported" }, { status: 501, headers });
|
|
48
32
|
}
|
|
49
33
|
const shutdown = deps.initiateShutdown;
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
setTimeout(() => {
|
|
35
|
+
void shutdown();
|
|
36
|
+
}, 100);
|
|
52
37
|
return Response.json({ ok: true, message: "shutdown initiated" }, { headers });
|
|
53
38
|
}
|
|
54
39
|
if (path === "/api/config") {
|
|
55
40
|
return configRoutes(request, url, deps, headers);
|
|
56
41
|
}
|
|
57
|
-
if (path === "/api/control-plane/reload" ||
|
|
42
|
+
if (path === "/api/control-plane/reload" ||
|
|
43
|
+
path === "/api/control-plane/rollback" ||
|
|
44
|
+
path === "/api/control-plane/channels") {
|
|
58
45
|
return controlPlaneRoutes(request, url, deps, headers);
|
|
59
46
|
}
|
|
60
47
|
if (path === "/api/status") {
|
|
61
|
-
const issues = await deps.context.issueStore.list();
|
|
62
|
-
const openIssues = issues.filter((i) => i.status === "open");
|
|
63
|
-
const readyIssues = await deps.context.issueStore.ready();
|
|
64
|
-
const controlPlane = deps.getControlPlaneStatus();
|
|
65
48
|
return Response.json({
|
|
66
49
|
repo_root: deps.context.repoRoot,
|
|
67
|
-
|
|
68
|
-
ready_count: readyIssues.length,
|
|
69
|
-
control_plane: controlPlane,
|
|
50
|
+
control_plane: deps.getControlPlaneStatus(),
|
|
70
51
|
}, { headers });
|
|
71
52
|
}
|
|
72
|
-
if (path === "/api/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
return Response.json({ error: "invalid json body" }, { status: 400, headers });
|
|
82
|
-
}
|
|
83
|
-
const kind = typeof body.kind === "string" ? body.kind.trim() : "";
|
|
84
|
-
if (!kind) {
|
|
85
|
-
return Response.json({ error: "kind is required" }, { status: 400, headers });
|
|
86
|
-
}
|
|
87
|
-
let commandText;
|
|
88
|
-
switch (kind) {
|
|
89
|
-
case "run_start": {
|
|
90
|
-
const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
|
|
91
|
-
if (!prompt) {
|
|
92
|
-
return Response.json({ error: "prompt is required for run_start" }, { status: 400, headers });
|
|
93
|
-
}
|
|
94
|
-
const maxStepsSuffix = typeof body.max_steps === "number" && Number.isFinite(body.max_steps)
|
|
95
|
-
? ` --max-steps ${Math.max(1, Math.trunc(body.max_steps))}`
|
|
96
|
-
: "";
|
|
97
|
-
commandText = `mu! run start ${prompt}${maxStepsSuffix}`;
|
|
98
|
-
break;
|
|
99
|
-
}
|
|
100
|
-
case "run_resume": {
|
|
101
|
-
const rootId = typeof body.root_issue_id === "string" ? body.root_issue_id.trim() : "";
|
|
102
|
-
const maxSteps = typeof body.max_steps === "number" && Number.isFinite(body.max_steps)
|
|
103
|
-
? ` ${Math.max(1, Math.trunc(body.max_steps))}`
|
|
104
|
-
: "";
|
|
105
|
-
commandText = `mu! run resume${rootId ? ` ${rootId}` : ""}${maxSteps}`;
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
case "run_interrupt": {
|
|
109
|
-
const rootId = typeof body.root_issue_id === "string" ? body.root_issue_id.trim() : "";
|
|
110
|
-
commandText = `mu! run interrupt${rootId ? ` ${rootId}` : ""}`;
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
case "reload":
|
|
114
|
-
commandText = "/mu reload";
|
|
115
|
-
break;
|
|
116
|
-
case "update":
|
|
117
|
-
commandText = "/mu update";
|
|
118
|
-
break;
|
|
119
|
-
case "status":
|
|
120
|
-
commandText = "/mu status";
|
|
121
|
-
break;
|
|
122
|
-
case "issue_list":
|
|
123
|
-
commandText = "/mu issue list";
|
|
124
|
-
break;
|
|
125
|
-
case "issue_get": {
|
|
126
|
-
const issueId = typeof body.issue_id === "string" ? body.issue_id.trim() : "";
|
|
127
|
-
commandText = `/mu issue get${issueId ? ` ${issueId}` : ""}`;
|
|
128
|
-
break;
|
|
129
|
-
}
|
|
130
|
-
case "forum_read": {
|
|
131
|
-
const topic = typeof body.topic === "string" ? body.topic.trim() : "";
|
|
132
|
-
const limit = typeof body.limit === "number" && Number.isFinite(body.limit)
|
|
133
|
-
? ` ${Math.max(1, Math.trunc(body.limit))}`
|
|
134
|
-
: "";
|
|
135
|
-
commandText = `/mu forum read${topic ? ` ${topic}` : ""}${limit}`;
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
case "run_list":
|
|
139
|
-
commandText = "/mu run list";
|
|
140
|
-
break;
|
|
141
|
-
case "run_status": {
|
|
142
|
-
const rootId = typeof body.root_issue_id === "string" ? body.root_issue_id.trim() : "";
|
|
143
|
-
commandText = `/mu run status${rootId ? ` ${rootId}` : ""}`;
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
case "ready":
|
|
147
|
-
commandText = "/mu ready";
|
|
148
|
-
break;
|
|
149
|
-
default:
|
|
150
|
-
return Response.json({ error: `unknown command kind: ${kind}` }, { status: 400, headers });
|
|
151
|
-
}
|
|
152
|
-
try {
|
|
153
|
-
if (!deps.controlPlaneProxy.submitTerminalCommand) {
|
|
154
|
-
return Response.json({ error: "control plane not available" }, { status: 503, headers });
|
|
155
|
-
}
|
|
156
|
-
const result = await deps.controlPlaneProxy.submitTerminalCommand({
|
|
157
|
-
commandText,
|
|
158
|
-
repoRoot: deps.context.repoRoot,
|
|
159
|
-
});
|
|
160
|
-
return Response.json({ ok: true, result }, { headers });
|
|
161
|
-
}
|
|
162
|
-
catch (err) {
|
|
163
|
-
return Response.json({ error: `command failed: ${deps.describeError(err)}` }, { status: 500, headers });
|
|
164
|
-
}
|
|
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);
|
|
165
60
|
}
|
|
166
61
|
if (path === "/api/runs" || path.startsWith("/api/runs/")) {
|
|
167
62
|
return runRoutes(request, url, deps, headers);
|
|
@@ -178,20 +73,6 @@ export function createServerRequestHandler(deps) {
|
|
|
178
73
|
if (path === "/api/identities" || path === "/api/identities/link" || path === "/api/identities/unlink") {
|
|
179
74
|
return identityRoutes(request, url, deps, headers);
|
|
180
75
|
}
|
|
181
|
-
if (path.startsWith("/api/issues")) {
|
|
182
|
-
const response = await issueRoutes(request, deps.context);
|
|
183
|
-
headers.forEach((value, key) => {
|
|
184
|
-
response.headers.set(key, value);
|
|
185
|
-
});
|
|
186
|
-
return response;
|
|
187
|
-
}
|
|
188
|
-
if (path.startsWith("/api/forum")) {
|
|
189
|
-
const response = await forumRoutes(request, deps.context);
|
|
190
|
-
headers.forEach((value, key) => {
|
|
191
|
-
response.headers.set(key, value);
|
|
192
|
-
});
|
|
193
|
-
return response;
|
|
194
|
-
}
|
|
195
76
|
if (path.startsWith("/api/events")) {
|
|
196
77
|
const response = await eventRoutes(request, deps.context);
|
|
197
78
|
headers.forEach((value, key) => {
|
|
@@ -207,23 +88,10 @@ export function createServerRequestHandler(deps) {
|
|
|
207
88
|
});
|
|
208
89
|
return response;
|
|
209
90
|
}
|
|
91
|
+
return new Response("Not Found", { status: 404, headers });
|
|
210
92
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return new Response("Forbidden", { status: 403, headers });
|
|
214
|
-
}
|
|
215
|
-
const file = Bun.file(filePath);
|
|
216
|
-
if (await file.exists()) {
|
|
217
|
-
const ext = extname(filePath);
|
|
218
|
-
const mime = mimeTypes[ext] ?? "application/octet-stream";
|
|
219
|
-
headers.set("Content-Type", mime);
|
|
220
|
-
return new Response(await file.arrayBuffer(), { status: 200, headers });
|
|
221
|
-
}
|
|
222
|
-
const indexPath = join(publicDir, "index.html");
|
|
223
|
-
const indexFile = Bun.file(indexPath);
|
|
224
|
-
if (await indexFile.exists()) {
|
|
225
|
-
headers.set("Content-Type", "text/html; charset=utf-8");
|
|
226
|
-
return new Response(await indexFile.arrayBuffer(), { status: 200, headers });
|
|
93
|
+
if (path.startsWith("/api/")) {
|
|
94
|
+
return Response.json({ error: "Not Found" }, { status: 404, headers });
|
|
227
95
|
}
|
|
228
96
|
return new Response("Not Found", { status: 404, headers });
|
|
229
97
|
};
|
package/dist/server_runtime.js
CHANGED
package/dist/server_types.d.ts
CHANGED
package/dist/server_types.js
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
export function normalizeWakeMode(value) {
|
|
2
|
-
if (typeof value !== "string") {
|
|
3
|
-
return "immediate";
|
|
4
|
-
}
|
|
5
|
-
const normalized = value.trim().toLowerCase().replaceAll("-", "_");
|
|
6
|
-
return normalized === "next_heartbeat" ? "next_heartbeat" : "immediate";
|
|
7
|
-
}
|
|
8
1
|
export function toNonNegativeInt(value, fallback) {
|
|
9
2
|
if (typeof value === "number" && Number.isFinite(value)) {
|
|
10
3
|
return Math.max(0, Math.trunc(value));
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@femtomc/mu-server",
|
|
3
|
-
"version": "26.2.
|
|
4
|
-
"description": "HTTP API server for mu
|
|
3
|
+
"version": "26.2.74",
|
|
4
|
+
"description": "HTTP API server for mu control-plane transport/session plus run/activity scheduling coordination.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mu",
|
|
7
7
|
"server",
|
|
@@ -22,8 +22,7 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
|
-
"dist/**"
|
|
26
|
-
"public/**"
|
|
25
|
+
"dist/**"
|
|
27
26
|
],
|
|
28
27
|
"scripts": {
|
|
29
28
|
"build": "tsc -p tsconfig.build.json",
|
|
@@ -31,11 +30,8 @@
|
|
|
31
30
|
"start": "bun run dist/cli.js"
|
|
32
31
|
},
|
|
33
32
|
"dependencies": {
|
|
34
|
-
"@femtomc/mu-agent": "26.2.
|
|
35
|
-
"@femtomc/mu-control-plane": "26.2.
|
|
36
|
-
"@femtomc/mu-core": "26.2.
|
|
37
|
-
"@femtomc/mu-forum": "26.2.72",
|
|
38
|
-
"@femtomc/mu-issue": "26.2.72",
|
|
39
|
-
"@femtomc/mu-orchestrator": "26.2.72"
|
|
33
|
+
"@femtomc/mu-agent": "26.2.74",
|
|
34
|
+
"@femtomc/mu-control-plane": "26.2.74",
|
|
35
|
+
"@femtomc/mu-core": "26.2.74"
|
|
40
36
|
}
|
|
41
37
|
}
|
package/dist/api/forum.d.ts
DELETED
package/dist/api/forum.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_FORUM_TOPICS_LIMIT, ForumStoreValidationError, normalizeForumPrefix, normalizeForumReadLimit, normalizeForumTopic, normalizeForumTopicsLimit, } from "@femtomc/mu-forum";
|
|
2
|
-
async function readJsonBody(request) {
|
|
3
|
-
let body;
|
|
4
|
-
try {
|
|
5
|
-
body = await request.json();
|
|
6
|
-
}
|
|
7
|
-
catch {
|
|
8
|
-
throw new ForumStoreValidationError("invalid json body");
|
|
9
|
-
}
|
|
10
|
-
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
11
|
-
throw new ForumStoreValidationError("json body must be an object");
|
|
12
|
-
}
|
|
13
|
-
return body;
|
|
14
|
-
}
|
|
15
|
-
function errorResponse(status, message) {
|
|
16
|
-
return new Response(JSON.stringify({ error: message }), {
|
|
17
|
-
status,
|
|
18
|
-
headers: { "Content-Type": "application/json" },
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
function mapForumRouteError(error) {
|
|
22
|
-
if (error instanceof ForumStoreValidationError) {
|
|
23
|
-
return errorResponse(400, error.message);
|
|
24
|
-
}
|
|
25
|
-
if (error instanceof Error && error.name === "ZodError") {
|
|
26
|
-
return errorResponse(400, error.message);
|
|
27
|
-
}
|
|
28
|
-
console.error("Forum API error:", error);
|
|
29
|
-
return errorResponse(500, error instanceof Error ? error.message : "Internal server error");
|
|
30
|
-
}
|
|
31
|
-
export async function forumRoutes(request, context) {
|
|
32
|
-
const url = new URL(request.url);
|
|
33
|
-
const path = url.pathname.replace("/api/forum", "") || "/";
|
|
34
|
-
const method = request.method;
|
|
35
|
-
try {
|
|
36
|
-
// List topics - GET /api/forum/topics
|
|
37
|
-
if (path === "/topics" && method === "GET") {
|
|
38
|
-
const prefix = normalizeForumPrefix(url.searchParams.get("prefix"));
|
|
39
|
-
const limit = normalizeForumTopicsLimit(url.searchParams.get("limit"), {
|
|
40
|
-
defaultLimit: DEFAULT_FORUM_TOPICS_LIMIT,
|
|
41
|
-
});
|
|
42
|
-
const topics = await context.forumStore.topics(prefix, { limit });
|
|
43
|
-
return Response.json(topics);
|
|
44
|
-
}
|
|
45
|
-
// Read messages - GET /api/forum/read
|
|
46
|
-
if (path === "/read" && method === "GET") {
|
|
47
|
-
const topic = normalizeForumTopic(url.searchParams.get("topic"));
|
|
48
|
-
const limit = normalizeForumReadLimit(url.searchParams.get("limit"));
|
|
49
|
-
const messages = await context.forumStore.read(topic, limit);
|
|
50
|
-
return Response.json(messages);
|
|
51
|
-
}
|
|
52
|
-
// Post message - POST /api/forum/post
|
|
53
|
-
if (path === "/post" && method === "POST") {
|
|
54
|
-
const body = await readJsonBody(request);
|
|
55
|
-
const topic = normalizeForumTopic(body.topic);
|
|
56
|
-
if (typeof body.body !== "string" || body.body.trim().length === 0) {
|
|
57
|
-
return errorResponse(400, "body is required");
|
|
58
|
-
}
|
|
59
|
-
const messageBody = body.body;
|
|
60
|
-
let author = "system";
|
|
61
|
-
if (body.author != null) {
|
|
62
|
-
if (typeof body.author !== "string" || body.author.trim().length === 0) {
|
|
63
|
-
return errorResponse(400, "author must be a non-empty string when provided");
|
|
64
|
-
}
|
|
65
|
-
author = body.author.trim();
|
|
66
|
-
}
|
|
67
|
-
const message = await context.forumStore.post(topic, messageBody, author);
|
|
68
|
-
return Response.json(message, { status: 201 });
|
|
69
|
-
}
|
|
70
|
-
return new Response("Not Found", { status: 404 });
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
return mapForumRouteError(error);
|
|
74
|
-
}
|
|
75
|
-
}
|
package/dist/api/issues.d.ts
DELETED