@femtomc/mu-server 26.2.73 → 26.2.75

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.
Files changed (51) hide show
  1. package/README.md +54 -66
  2. package/dist/api/control_plane.js +56 -0
  3. package/dist/api/cron.js +2 -23
  4. package/dist/api/heartbeats.js +1 -66
  5. package/dist/api/identities.js +3 -2
  6. package/dist/api/runs.js +0 -83
  7. package/dist/api/session_flash.d.ts +60 -0
  8. package/dist/api/session_flash.js +326 -0
  9. package/dist/api/session_turn.d.ts +38 -0
  10. package/dist/api/session_turn.js +423 -0
  11. package/dist/config.d.ts +9 -4
  12. package/dist/config.js +24 -24
  13. package/dist/control_plane.d.ts +2 -16
  14. package/dist/control_plane.js +57 -83
  15. package/dist/control_plane_adapter_registry.d.ts +19 -0
  16. package/dist/control_plane_adapter_registry.js +74 -0
  17. package/dist/control_plane_contract.d.ts +1 -7
  18. package/dist/control_plane_run_queue_coordinator.d.ts +1 -7
  19. package/dist/control_plane_run_queue_coordinator.js +1 -62
  20. package/dist/control_plane_telegram_generation.js +1 -0
  21. package/dist/control_plane_wake_delivery.js +1 -0
  22. package/dist/cron_programs.d.ts +21 -35
  23. package/dist/cron_programs.js +32 -113
  24. package/dist/cron_request.d.ts +0 -6
  25. package/dist/cron_request.js +0 -41
  26. package/dist/heartbeat_programs.d.ts +20 -35
  27. package/dist/heartbeat_programs.js +26 -122
  28. package/dist/index.d.ts +2 -2
  29. package/dist/outbound_delivery_router.d.ts +12 -0
  30. package/dist/outbound_delivery_router.js +29 -0
  31. package/dist/run_supervisor.d.ts +1 -16
  32. package/dist/run_supervisor.js +0 -70
  33. package/dist/server.d.ts +0 -5
  34. package/dist/server.js +95 -127
  35. package/dist/server_program_orchestration.d.ts +4 -19
  36. package/dist/server_program_orchestration.js +49 -200
  37. package/dist/server_routing.d.ts +0 -9
  38. package/dist/server_routing.js +19 -654
  39. package/dist/server_runtime.js +0 -1
  40. package/dist/server_types.d.ts +0 -2
  41. package/dist/server_types.js +0 -7
  42. package/package.json +6 -9
  43. package/dist/api/context.d.ts +0 -5
  44. package/dist/api/context.js +0 -1147
  45. package/dist/api/forum.d.ts +0 -2
  46. package/dist/api/forum.js +0 -75
  47. package/dist/api/issues.d.ts +0 -2
  48. package/dist/api/issues.js +0 -173
  49. package/public/assets/index-CxkevQNh.js +0 -100
  50. package/public/assets/index-D_8anM-D.css +0 -1
  51. package/public/index.html +0 -14
@@ -1,136 +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
- import { contextRoutes } from "./api/context.js";
5
3
  import { controlPlaneRoutes } from "./api/control_plane.js";
6
4
  import { cronRoutes } from "./api/cron.js";
7
5
  import { eventRoutes } from "./api/events.js";
8
- import { forumRoutes } from "./api/forum.js";
9
6
  import { heartbeatRoutes } from "./api/heartbeats.js";
10
7
  import { identityRoutes } from "./api/identities.js";
11
- import { issueRoutes } from "./api/issues.js";
12
8
  import { runRoutes } from "./api/runs.js";
13
- import { cronScheduleInputFromBody, hasCronScheduleInput, parseCronTarget } from "./cron_request.js";
14
- import { normalizeWakeMode } from "./server_types.js";
15
- const DEFAULT_MIME_TYPES = {
16
- ".html": "text/html; charset=utf-8",
17
- ".js": "text/javascript; charset=utf-8",
18
- ".css": "text/css; charset=utf-8",
19
- ".json": "application/json",
20
- ".png": "image/png",
21
- ".jpg": "image/jpeg",
22
- ".svg": "image/svg+xml",
23
- ".ico": "image/x-icon",
24
- ".woff": "font/woff",
25
- ".woff2": "font/woff2",
26
- };
27
- const DEFAULT_PUBLIC_DIR = join(new URL(".", import.meta.url).pathname, "..", "public");
28
- function readTrimmedString(value) {
29
- return typeof value === "string" ? value.trim() : "";
30
- }
31
- function readIntOrNull(value) {
32
- if (typeof value !== "number" || !Number.isFinite(value) || !Number.isInteger(value)) {
33
- return null;
34
- }
35
- return Math.trunc(value);
36
- }
37
- function readFiniteNumberOrNull(value) {
38
- if (typeof value !== "number" || !Number.isFinite(value)) {
39
- return null;
40
- }
41
- return value;
42
- }
43
- function parseOptionalBoolean(value) {
44
- if (value == null) {
45
- return { ok: true, value: null };
46
- }
47
- if (typeof value === "boolean") {
48
- return { ok: true, value };
49
- }
50
- return { ok: false, value: null };
51
- }
52
- function readCommaList(value) {
53
- if (Array.isArray(value)) {
54
- return value
55
- .map((item) => (typeof item === "string" ? item.trim() : ""))
56
- .filter((item) => item.length > 0);
57
- }
58
- if (typeof value === "string") {
59
- return value
60
- .split(",")
61
- .map((item) => item.trim())
62
- .filter((item) => item.length > 0);
63
- }
64
- return [];
65
- }
66
- function parseHeartbeatTarget(body) {
67
- const targetKind = readTrimmedString(body.target_kind).toLowerCase();
68
- if (targetKind === "run") {
69
- const jobId = readTrimmedString(body.run_job_id);
70
- const rootIssueId = readTrimmedString(body.run_root_issue_id);
71
- if (!jobId && !rootIssueId) {
72
- return {
73
- target: null,
74
- error: "run target requires run_job_id or run_root_issue_id",
75
- };
76
- }
77
- return {
78
- target: {
79
- kind: "run",
80
- job_id: jobId || null,
81
- root_issue_id: rootIssueId || null,
82
- },
83
- error: null,
84
- };
85
- }
86
- if (targetKind === "activity") {
87
- const activityId = readTrimmedString(body.activity_id);
88
- if (!activityId) {
89
- return {
90
- target: null,
91
- error: "activity target requires activity_id",
92
- };
93
- }
94
- return {
95
- target: {
96
- kind: "activity",
97
- activity_id: activityId,
98
- },
99
- error: null,
100
- };
101
- }
102
- return {
103
- target: null,
104
- error: "target_kind must be run or activity",
105
- };
106
- }
107
- function commandProgramFailureStatus(reason) {
108
- if (reason === "not_found") {
109
- return 404;
110
- }
111
- if (reason === "missing_target" || reason === "invalid_target" || reason === "invalid_schedule") {
112
- return 400;
113
- }
114
- if (reason === "not_running" || reason === "failed") {
115
- return 409;
116
- }
117
- return 400;
118
- }
119
- function commandCompletedResponse(headers, targetType, result) {
120
- return Response.json({
121
- ok: true,
122
- result: {
123
- kind: "completed",
124
- command: {
125
- target_type: targetType,
126
- result,
127
- },
128
- },
129
- }, { headers });
130
- }
9
+ import { sessionFlashRoutes } from "./api/session_flash.js";
10
+ import { sessionTurnRoutes } from "./api/session_turn.js";
131
11
  export function createServerRequestHandler(deps) {
132
- const publicDir = deps.publicDir ?? DEFAULT_PUBLIC_DIR;
133
- const mimeTypes = deps.mimeTypes ?? DEFAULT_MIME_TYPES;
134
12
  return async (request) => {
135
13
  const url = new URL(request.url);
136
14
  const path = url.pathname;
@@ -153,515 +31,32 @@ export function createServerRequestHandler(deps) {
153
31
  return Response.json({ error: "shutdown not supported" }, { status: 501, headers });
154
32
  }
155
33
  const shutdown = deps.initiateShutdown;
156
- // Respond before shutting down so the client receives the response.
157
- setTimeout(() => { void shutdown(); }, 100);
34
+ setTimeout(() => {
35
+ void shutdown();
36
+ }, 100);
158
37
  return Response.json({ ok: true, message: "shutdown initiated" }, { headers });
159
38
  }
160
39
  if (path === "/api/config") {
161
40
  return configRoutes(request, url, deps, headers);
162
41
  }
163
- if (path === "/api/control-plane/reload" || path === "/api/control-plane/rollback") {
42
+ if (path === "/api/control-plane/reload" ||
43
+ path === "/api/control-plane/rollback" ||
44
+ path === "/api/control-plane/channels") {
164
45
  return controlPlaneRoutes(request, url, deps, headers);
165
46
  }
166
47
  if (path === "/api/status") {
167
- const issues = await deps.context.issueStore.list();
168
- const openIssues = issues.filter((i) => i.status === "open");
169
- const readyIssues = await deps.context.issueStore.ready();
170
- const controlPlane = deps.getControlPlaneStatus();
171
48
  return Response.json({
172
49
  repo_root: deps.context.repoRoot,
173
- open_count: openIssues.length,
174
- ready_count: readyIssues.length,
175
- control_plane: controlPlane,
50
+ control_plane: deps.getControlPlaneStatus(),
176
51
  }, { headers });
177
52
  }
178
- if (path === "/api/commands/submit") {
179
- if (request.method !== "POST") {
180
- return Response.json({ error: "Method Not Allowed" }, { status: 405, headers });
181
- }
182
- let body;
183
- try {
184
- body = (await request.json());
185
- }
186
- catch {
187
- return Response.json({ error: "invalid json body" }, { status: 400, headers });
188
- }
189
- const kind = typeof body.kind === "string" ? body.kind.trim() : "";
190
- if (!kind) {
191
- return Response.json({ error: "kind is required" }, { status: 400, headers });
192
- }
193
- let commandText = null;
194
- switch (kind) {
195
- case "run_start": {
196
- const prompt = readTrimmedString(body.prompt);
197
- if (!prompt) {
198
- return Response.json({ error: "prompt is required for run_start" }, { status: 400, headers });
199
- }
200
- const maxStepsSuffix = typeof body.max_steps === "number" && Number.isFinite(body.max_steps)
201
- ? ` --max-steps ${Math.max(1, Math.trunc(body.max_steps))}`
202
- : "";
203
- commandText = `mu! run start ${prompt}${maxStepsSuffix}`;
204
- break;
205
- }
206
- case "run_resume": {
207
- const rootId = readTrimmedString(body.root_issue_id);
208
- const maxSteps = typeof body.max_steps === "number" && Number.isFinite(body.max_steps)
209
- ? ` ${Math.max(1, Math.trunc(body.max_steps))}`
210
- : "";
211
- commandText = `mu! run resume${rootId ? ` ${rootId}` : ""}${maxSteps}`;
212
- break;
213
- }
214
- case "run_interrupt": {
215
- const rootId = readTrimmedString(body.root_issue_id);
216
- commandText = `mu! run interrupt${rootId ? ` ${rootId}` : ""}`;
217
- break;
218
- }
219
- case "reload":
220
- commandText = "/mu reload";
221
- break;
222
- case "update":
223
- commandText = "/mu update";
224
- break;
225
- case "issue_create": {
226
- const title = readTrimmedString(body.title);
227
- if (!title) {
228
- return Response.json({ error: "title is required for issue_create" }, { status: 400, headers });
229
- }
230
- const issueBody = typeof body.body === "string" ? body.body : undefined;
231
- const tags = readCommaList(body.tags);
232
- const priority = readIntOrNull(body.priority);
233
- if (body.priority != null && priority == null) {
234
- return Response.json({ error: "priority must be an integer" }, { status: 400, headers });
235
- }
236
- const created = await deps.context.issueStore.create(title, {
237
- body: issueBody,
238
- tags: tags.length > 0 ? tags : undefined,
239
- priority: priority ?? undefined,
240
- });
241
- const parentId = readTrimmedString(body.parent_id);
242
- if (parentId) {
243
- await deps.context.issueStore.add_dep(created.id, "parent", parentId);
244
- }
245
- const issue = parentId ? ((await deps.context.issueStore.get(created.id)) ?? created) : created;
246
- return commandCompletedResponse(headers, "issue create", { issue });
247
- }
248
- case "issue_update": {
249
- const id = readTrimmedString(body.id);
250
- if (!id) {
251
- return Response.json({ error: "id is required for issue_update" }, { status: 400, headers });
252
- }
253
- const current = await deps.context.issueStore.get(id);
254
- if (!current) {
255
- return Response.json({ error: "issue not found" }, { status: 404, headers });
256
- }
257
- const patch = {};
258
- if (typeof body.title === "string")
259
- patch.title = body.title;
260
- if (typeof body.body === "string")
261
- patch.body = body.body;
262
- if (typeof body.status === "string")
263
- patch.status = body.status;
264
- if (typeof body.outcome === "string")
265
- patch.outcome = body.outcome;
266
- if (body.priority != null) {
267
- const priority = readIntOrNull(body.priority);
268
- if (priority == null) {
269
- return Response.json({ error: "priority must be an integer" }, { status: 400, headers });
270
- }
271
- patch.priority = priority;
272
- }
273
- if (body.tags != null) {
274
- patch.tags = readCommaList(body.tags);
275
- }
276
- const addTags = readCommaList(body.add_tags);
277
- const removeTags = readCommaList(body.remove_tags);
278
- if (addTags.length > 0 || removeTags.length > 0) {
279
- const baseTags = Array.isArray(patch.tags)
280
- ? (patch.tags ?? [])
281
- : Array.isArray(current.tags)
282
- ? [...current.tags]
283
- : [];
284
- const next = new Set(baseTags);
285
- for (const tag of addTags)
286
- next.add(tag);
287
- for (const tag of removeTags)
288
- next.delete(tag);
289
- patch.tags = [...next];
290
- }
291
- if (Object.keys(patch).length === 0) {
292
- return Response.json({ error: "issue_update requires at least one patch field" }, { status: 400, headers });
293
- }
294
- const issue = await deps.context.issueStore.update(id, patch);
295
- return commandCompletedResponse(headers, "issue update", { issue });
296
- }
297
- case "issue_claim": {
298
- const id = readTrimmedString(body.id);
299
- if (!id) {
300
- return Response.json({ error: "id is required for issue_claim" }, { status: 400, headers });
301
- }
302
- const claimed = await deps.context.issueStore.claim(id);
303
- if (!claimed) {
304
- return Response.json({ error: "failed to claim issue" }, { status: 409, headers });
305
- }
306
- const issue = await deps.context.issueStore.get(id);
307
- if (!issue) {
308
- return Response.json({ error: "issue not found" }, { status: 404, headers });
309
- }
310
- return commandCompletedResponse(headers, "issue claim", { issue });
311
- }
312
- case "issue_open": {
313
- const id = readTrimmedString(body.id);
314
- if (!id) {
315
- return Response.json({ error: "id is required for issue_open" }, { status: 400, headers });
316
- }
317
- const issue = await deps.context.issueStore.update(id, { status: "open", outcome: null });
318
- return commandCompletedResponse(headers, "issue open", { issue });
319
- }
320
- case "issue_close": {
321
- const id = readTrimmedString(body.id);
322
- if (!id) {
323
- return Response.json({ error: "id is required for issue_close" }, { status: 400, headers });
324
- }
325
- const outcome = readTrimmedString(body.outcome) || "success";
326
- const issue = await deps.context.issueStore.close(id, outcome);
327
- return commandCompletedResponse(headers, "issue close", { issue });
328
- }
329
- case "issue_dep": {
330
- const srcId = readTrimmedString(body.src_id);
331
- const dstId = readTrimmedString(body.dst_id);
332
- const depType = readTrimmedString(body.dep_type) || "blocks";
333
- if (!srcId || !dstId) {
334
- return Response.json({ error: "src_id and dst_id are required for issue_dep" }, { status: 400, headers });
335
- }
336
- if (depType !== "blocks" && depType !== "parent") {
337
- return Response.json({ error: "dep_type must be blocks or parent" }, { status: 400, headers });
338
- }
339
- await deps.context.issueStore.add_dep(srcId, depType, dstId);
340
- return commandCompletedResponse(headers, "issue dep", {
341
- src_id: srcId,
342
- dst_id: dstId,
343
- dep_type: depType,
344
- });
345
- }
346
- case "issue_undep": {
347
- const srcId = readTrimmedString(body.src_id);
348
- const dstId = readTrimmedString(body.dst_id);
349
- const depType = readTrimmedString(body.dep_type) || "blocks";
350
- if (!srcId || !dstId) {
351
- return Response.json({ error: "src_id and dst_id are required for issue_undep" }, { status: 400, headers });
352
- }
353
- if (depType !== "blocks" && depType !== "parent") {
354
- return Response.json({ error: "dep_type must be blocks or parent" }, { status: 400, headers });
355
- }
356
- const ok = await deps.context.issueStore.remove_dep(srcId, depType, dstId);
357
- return commandCompletedResponse(headers, "issue undep", {
358
- src_id: srcId,
359
- dst_id: dstId,
360
- dep_type: depType,
361
- ok,
362
- });
363
- }
364
- case "forum_post": {
365
- const topic = readTrimmedString(body.topic);
366
- const messageBody = readTrimmedString(body.body);
367
- if (!topic) {
368
- return Response.json({ error: "topic is required for forum_post" }, { status: 400, headers });
369
- }
370
- if (!messageBody) {
371
- return Response.json({ error: "body is required for forum_post" }, { status: 400, headers });
372
- }
373
- const author = readTrimmedString(body.author) || "operator";
374
- const message = await deps.context.forumStore.post(topic, messageBody, author);
375
- return commandCompletedResponse(headers, "forum post", { message });
376
- }
377
- case "heartbeat_create": {
378
- const title = readTrimmedString(body.title);
379
- if (!title) {
380
- return Response.json({ error: "title is required for heartbeat_create" }, { status: 400, headers });
381
- }
382
- const parsedTarget = parseHeartbeatTarget(body);
383
- if (!parsedTarget.target) {
384
- return Response.json({ error: parsedTarget.error ?? "invalid target" }, { status: 400, headers });
385
- }
386
- const everyMsRaw = readFiniteNumberOrNull(body.every_ms);
387
- if (body.every_ms != null && everyMsRaw == null) {
388
- return Response.json({ error: "every_ms must be a finite number" }, { status: 400, headers });
389
- }
390
- if (body.reason != null && typeof body.reason !== "string") {
391
- return Response.json({ error: "reason must be a string" }, { status: 400, headers });
392
- }
393
- if (body.wake_mode != null && typeof body.wake_mode !== "string") {
394
- return Response.json({ error: "wake_mode must be a string" }, { status: 400, headers });
395
- }
396
- const enabled = parseOptionalBoolean(body.enabled);
397
- if (!enabled.ok) {
398
- return Response.json({ error: "enabled must be boolean" }, { status: 400, headers });
399
- }
400
- if (body.metadata != null &&
401
- (typeof body.metadata !== "object" || Array.isArray(body.metadata))) {
402
- return Response.json({ error: "metadata must be an object" }, { status: 400, headers });
403
- }
404
- try {
405
- const program = await deps.heartbeatPrograms.create({
406
- title,
407
- target: parsedTarget.target,
408
- everyMs: everyMsRaw == null ? undefined : Math.max(0, Math.trunc(everyMsRaw)),
409
- reason: typeof body.reason === "string" ? body.reason : undefined,
410
- wakeMode: body.wake_mode == null ? undefined : normalizeWakeMode(body.wake_mode),
411
- enabled: enabled.value ?? undefined,
412
- metadata: body.metadata,
413
- });
414
- return commandCompletedResponse(headers, "heartbeat create", { program });
415
- }
416
- catch (err) {
417
- return Response.json({ error: deps.describeError(err) }, { status: 400, headers });
418
- }
419
- }
420
- case "heartbeat_update": {
421
- const programId = readTrimmedString(body.program_id);
422
- if (!programId) {
423
- return Response.json({ error: "program_id is required for heartbeat_update" }, { status: 400, headers });
424
- }
425
- let target;
426
- if (body.target_kind != null) {
427
- const parsedTarget = parseHeartbeatTarget(body);
428
- if (!parsedTarget.target) {
429
- return Response.json({ error: parsedTarget.error ?? "invalid target" }, { status: 400, headers });
430
- }
431
- target = parsedTarget.target;
432
- }
433
- const everyMsRaw = readFiniteNumberOrNull(body.every_ms);
434
- if (body.every_ms != null && everyMsRaw == null) {
435
- return Response.json({ error: "every_ms must be a finite number" }, { status: 400, headers });
436
- }
437
- if (body.reason != null && typeof body.reason !== "string") {
438
- return Response.json({ error: "reason must be a string" }, { status: 400, headers });
439
- }
440
- if (body.wake_mode != null && typeof body.wake_mode !== "string") {
441
- return Response.json({ error: "wake_mode must be a string" }, { status: 400, headers });
442
- }
443
- const enabled = parseOptionalBoolean(body.enabled);
444
- if (!enabled.ok) {
445
- return Response.json({ error: "enabled must be boolean" }, { status: 400, headers });
446
- }
447
- if (body.metadata != null &&
448
- (typeof body.metadata !== "object" || Array.isArray(body.metadata))) {
449
- return Response.json({ error: "metadata must be an object" }, { status: 400, headers });
450
- }
451
- try {
452
- const result = await deps.heartbeatPrograms.update({
453
- programId,
454
- title: typeof body.title === "string" ? body.title : undefined,
455
- target,
456
- everyMs: everyMsRaw == null ? undefined : Math.max(0, Math.trunc(everyMsRaw)),
457
- reason: typeof body.reason === "string" ? body.reason : undefined,
458
- wakeMode: body.wake_mode == null ? undefined : normalizeWakeMode(body.wake_mode),
459
- enabled: enabled.value ?? undefined,
460
- metadata: body.metadata,
461
- });
462
- if (!result.ok) {
463
- return Response.json({ error: `heartbeat update failed: ${result.reason ?? "unknown"}` }, { status: commandProgramFailureStatus(result.reason), headers });
464
- }
465
- return commandCompletedResponse(headers, "heartbeat update", { program: result.program });
466
- }
467
- catch (err) {
468
- return Response.json({ error: deps.describeError(err) }, { status: 400, headers });
469
- }
470
- }
471
- case "heartbeat_delete": {
472
- const programId = readTrimmedString(body.program_id);
473
- if (!programId) {
474
- return Response.json({ error: "program_id is required for heartbeat_delete" }, { status: 400, headers });
475
- }
476
- const result = await deps.heartbeatPrograms.remove(programId);
477
- if (!result.ok) {
478
- return Response.json({ error: `heartbeat delete failed: ${result.reason ?? "unknown"}` }, { status: commandProgramFailureStatus(result.reason), headers });
479
- }
480
- return commandCompletedResponse(headers, "heartbeat delete", { program: result.program });
481
- }
482
- case "heartbeat_trigger": {
483
- const programId = readTrimmedString(body.program_id);
484
- if (!programId) {
485
- return Response.json({ error: "program_id is required for heartbeat_trigger" }, { status: 400, headers });
486
- }
487
- if (body.reason != null && typeof body.reason !== "string") {
488
- return Response.json({ error: "reason must be a string" }, { status: 400, headers });
489
- }
490
- const result = await deps.heartbeatPrograms.trigger({
491
- programId,
492
- reason: typeof body.reason === "string" ? body.reason : null,
493
- });
494
- if (!result.ok) {
495
- return Response.json({ error: `heartbeat trigger failed: ${result.reason ?? "unknown"}` }, { status: commandProgramFailureStatus(result.reason), headers });
496
- }
497
- return commandCompletedResponse(headers, "heartbeat trigger", { program: result.program });
498
- }
499
- case "heartbeat_enable":
500
- case "heartbeat_disable": {
501
- const programId = readTrimmedString(body.program_id);
502
- if (!programId) {
503
- return Response.json({ error: `program_id is required for ${kind}` }, { status: 400, headers });
504
- }
505
- const result = await deps.heartbeatPrograms.update({
506
- programId,
507
- enabled: kind === "heartbeat_enable",
508
- });
509
- if (!result.ok) {
510
- return Response.json({ error: `${kind} failed: ${result.reason ?? "unknown"}` }, { status: commandProgramFailureStatus(result.reason), headers });
511
- }
512
- return commandCompletedResponse(headers, kind.replaceAll("_", " "), { program: result.program });
513
- }
514
- case "cron_create": {
515
- const title = readTrimmedString(body.title);
516
- if (!title) {
517
- return Response.json({ error: "title is required for cron_create" }, { status: 400, headers });
518
- }
519
- const parsedTarget = parseCronTarget(body);
520
- if (!parsedTarget.target) {
521
- return Response.json({ error: parsedTarget.error ?? "invalid target" }, { status: 400, headers });
522
- }
523
- if (!hasCronScheduleInput(body)) {
524
- return Response.json({ error: "schedule is required for cron_create" }, { status: 400, headers });
525
- }
526
- if (body.reason != null && typeof body.reason !== "string") {
527
- return Response.json({ error: "reason must be a string" }, { status: 400, headers });
528
- }
529
- if (body.wake_mode != null && typeof body.wake_mode !== "string") {
530
- return Response.json({ error: "wake_mode must be a string" }, { status: 400, headers });
531
- }
532
- const enabled = parseOptionalBoolean(body.enabled);
533
- if (!enabled.ok) {
534
- return Response.json({ error: "enabled must be boolean" }, { status: 400, headers });
535
- }
536
- if (body.metadata != null &&
537
- (typeof body.metadata !== "object" || Array.isArray(body.metadata))) {
538
- return Response.json({ error: "metadata must be an object" }, { status: 400, headers });
539
- }
540
- try {
541
- const program = await deps.cronPrograms.create({
542
- title,
543
- target: parsedTarget.target,
544
- schedule: cronScheduleInputFromBody(body),
545
- reason: typeof body.reason === "string" ? body.reason : undefined,
546
- wakeMode: body.wake_mode == null ? undefined : normalizeWakeMode(body.wake_mode),
547
- enabled: enabled.value ?? undefined,
548
- metadata: body.metadata,
549
- });
550
- return commandCompletedResponse(headers, "cron create", { program });
551
- }
552
- catch (err) {
553
- return Response.json({ error: deps.describeError(err) }, { status: 400, headers });
554
- }
555
- }
556
- case "cron_update": {
557
- const programId = readTrimmedString(body.program_id);
558
- if (!programId) {
559
- return Response.json({ error: "program_id is required for cron_update" }, { status: 400, headers });
560
- }
561
- let target;
562
- if (body.target_kind != null) {
563
- const parsedTarget = parseCronTarget(body);
564
- if (!parsedTarget.target) {
565
- return Response.json({ error: parsedTarget.error ?? "invalid target" }, { status: 400, headers });
566
- }
567
- target = parsedTarget.target;
568
- }
569
- if (body.reason != null && typeof body.reason !== "string") {
570
- return Response.json({ error: "reason must be a string" }, { status: 400, headers });
571
- }
572
- if (body.wake_mode != null && typeof body.wake_mode !== "string") {
573
- return Response.json({ error: "wake_mode must be a string" }, { status: 400, headers });
574
- }
575
- const enabled = parseOptionalBoolean(body.enabled);
576
- if (!enabled.ok) {
577
- return Response.json({ error: "enabled must be boolean" }, { status: 400, headers });
578
- }
579
- if (body.metadata != null &&
580
- (typeof body.metadata !== "object" || Array.isArray(body.metadata))) {
581
- return Response.json({ error: "metadata must be an object" }, { status: 400, headers });
582
- }
583
- try {
584
- const result = await deps.cronPrograms.update({
585
- programId,
586
- title: typeof body.title === "string" ? body.title : undefined,
587
- reason: typeof body.reason === "string" ? body.reason : undefined,
588
- wakeMode: body.wake_mode == null ? undefined : normalizeWakeMode(body.wake_mode),
589
- enabled: enabled.value ?? undefined,
590
- target,
591
- schedule: hasCronScheduleInput(body) ? cronScheduleInputFromBody(body) : undefined,
592
- metadata: body.metadata,
593
- });
594
- if (!result.ok) {
595
- return Response.json({ error: `cron update failed: ${result.reason ?? "unknown"}` }, { status: commandProgramFailureStatus(result.reason), headers });
596
- }
597
- return commandCompletedResponse(headers, "cron update", { program: result.program });
598
- }
599
- catch (err) {
600
- return Response.json({ error: deps.describeError(err) }, { status: 400, headers });
601
- }
602
- }
603
- case "cron_delete": {
604
- const programId = readTrimmedString(body.program_id);
605
- if (!programId) {
606
- return Response.json({ error: "program_id is required for cron_delete" }, { status: 400, headers });
607
- }
608
- const result = await deps.cronPrograms.remove(programId);
609
- if (!result.ok) {
610
- return Response.json({ error: `cron delete failed: ${result.reason ?? "unknown"}` }, { status: commandProgramFailureStatus(result.reason), headers });
611
- }
612
- return commandCompletedResponse(headers, "cron delete", { program: result.program });
613
- }
614
- case "cron_trigger": {
615
- const programId = readTrimmedString(body.program_id);
616
- if (!programId) {
617
- return Response.json({ error: "program_id is required for cron_trigger" }, { status: 400, headers });
618
- }
619
- if (body.reason != null && typeof body.reason !== "string") {
620
- return Response.json({ error: "reason must be a string" }, { status: 400, headers });
621
- }
622
- const result = await deps.cronPrograms.trigger({
623
- programId,
624
- reason: typeof body.reason === "string" ? body.reason : null,
625
- });
626
- if (!result.ok) {
627
- return Response.json({ error: `cron trigger failed: ${result.reason ?? "unknown"}` }, { status: commandProgramFailureStatus(result.reason), headers });
628
- }
629
- return commandCompletedResponse(headers, "cron trigger", { program: result.program });
630
- }
631
- case "cron_enable":
632
- case "cron_disable": {
633
- const programId = readTrimmedString(body.program_id);
634
- if (!programId) {
635
- return Response.json({ error: `program_id is required for ${kind}` }, { status: 400, headers });
636
- }
637
- const result = await deps.cronPrograms.update({
638
- programId,
639
- enabled: kind === "cron_enable",
640
- });
641
- if (!result.ok) {
642
- return Response.json({ error: `${kind} failed: ${result.reason ?? "unknown"}` }, { status: commandProgramFailureStatus(result.reason), headers });
643
- }
644
- return commandCompletedResponse(headers, kind.replaceAll("_", " "), { program: result.program });
645
- }
646
- default:
647
- return Response.json({ error: `unknown command kind: ${kind}` }, { status: 400, headers });
648
- }
649
- try {
650
- if (!commandText) {
651
- return Response.json({ error: `unknown command kind: ${kind}` }, { status: 400, headers });
652
- }
653
- if (!deps.controlPlaneProxy.submitTerminalCommand) {
654
- return Response.json({ error: "control plane not available" }, { status: 503, headers });
655
- }
656
- const result = await deps.controlPlaneProxy.submitTerminalCommand({
657
- commandText,
658
- repoRoot: deps.context.repoRoot,
659
- });
660
- return Response.json({ ok: true, result }, { headers });
661
- }
662
- catch (err) {
663
- return Response.json({ error: `command failed: ${deps.describeError(err)}` }, { status: 500, headers });
664
- }
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);
665
60
  }
666
61
  if (path === "/api/runs" || path.startsWith("/api/runs/")) {
667
62
  return runRoutes(request, url, deps, headers);
@@ -678,20 +73,6 @@ export function createServerRequestHandler(deps) {
678
73
  if (path === "/api/identities" || path === "/api/identities/link" || path === "/api/identities/unlink") {
679
74
  return identityRoutes(request, url, deps, headers);
680
75
  }
681
- if (path.startsWith("/api/issues")) {
682
- const response = await issueRoutes(request, deps.context);
683
- headers.forEach((value, key) => {
684
- response.headers.set(key, value);
685
- });
686
- return response;
687
- }
688
- if (path.startsWith("/api/forum")) {
689
- const response = await forumRoutes(request, deps.context);
690
- headers.forEach((value, key) => {
691
- response.headers.set(key, value);
692
- });
693
- return response;
694
- }
695
76
  if (path.startsWith("/api/events")) {
696
77
  const response = await eventRoutes(request, deps.context);
697
78
  headers.forEach((value, key) => {
@@ -699,9 +80,6 @@ export function createServerRequestHandler(deps) {
699
80
  });
700
81
  return response;
701
82
  }
702
- if (path === "/api/context" || path.startsWith("/api/context/")) {
703
- return contextRoutes(request, url, { context: deps.context, describeError: deps.describeError }, headers);
704
- }
705
83
  if (path.startsWith("/webhooks/")) {
706
84
  const response = await deps.controlPlaneProxy.handleWebhook(path, request);
707
85
  if (response) {
@@ -710,23 +88,10 @@ export function createServerRequestHandler(deps) {
710
88
  });
711
89
  return response;
712
90
  }
91
+ return new Response("Not Found", { status: 404, headers });
713
92
  }
714
- const filePath = resolve(publicDir, `.${path === "/" ? "/index.html" : path}`);
715
- if (!filePath.startsWith(publicDir)) {
716
- return new Response("Forbidden", { status: 403, headers });
717
- }
718
- const file = Bun.file(filePath);
719
- if (await file.exists()) {
720
- const ext = extname(filePath);
721
- const mime = mimeTypes[ext] ?? "application/octet-stream";
722
- headers.set("Content-Type", mime);
723
- return new Response(await file.arrayBuffer(), { status: 200, headers });
724
- }
725
- const indexPath = join(publicDir, "index.html");
726
- const indexFile = Bun.file(indexPath);
727
- if (await indexFile.exists()) {
728
- headers.set("Content-Type", "text/html; charset=utf-8");
729
- return new Response(await indexFile.arrayBuffer(), { status: 200, headers });
93
+ if (path.startsWith("/api/")) {
94
+ return Response.json({ error: "Not Found" }, { status: 404, headers });
730
95
  }
731
96
  return new Response("Not Found", { status: 404, headers });
732
97
  };