@femtomc/mu-server 26.2.65 → 26.2.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/control_plane.d.ts +19 -1
- package/dist/control_plane.js +117 -1
- package/dist/index.d.ts +1 -1
- package/dist/server.d.ts +2 -1
- package/dist/server.js +249 -0
- package/package.json +38 -38
package/dist/control_plane.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type MessagingOperatorBackend, MessagingOperatorRuntime } from "@femtomc/mu-agent";
|
|
2
|
-
import { type Channel, type GenerationTelemetryRecorder, type ReloadableGenerationIdentity } from "@femtomc/mu-control-plane";
|
|
2
|
+
import { type Channel, type CommandPipelineResult, type GenerationTelemetryRecorder, type ReloadableGenerationIdentity } from "@femtomc/mu-control-plane";
|
|
3
3
|
import { type MuConfig } from "./config.js";
|
|
4
4
|
import type { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
5
5
|
import { type ControlPlaneRunHeartbeatResult, type ControlPlaneRunInterruptResult, type ControlPlaneRunSnapshot, type ControlPlaneRunSupervisorOpts, type ControlPlaneRunTrace } from "./run_supervisor.js";
|
|
@@ -76,6 +76,11 @@ export type ControlPlaneHandle = {
|
|
|
76
76
|
idOrRoot: string;
|
|
77
77
|
limit?: number;
|
|
78
78
|
}): Promise<ControlPlaneRunTrace | null>;
|
|
79
|
+
submitTerminalCommand?(opts: {
|
|
80
|
+
commandText: string;
|
|
81
|
+
repoRoot: string;
|
|
82
|
+
requestId?: string;
|
|
83
|
+
}): Promise<CommandPipelineResult>;
|
|
79
84
|
stop(): Promise<void>;
|
|
80
85
|
};
|
|
81
86
|
export type ControlPlaneConfig = MuConfig["control_plane"];
|
|
@@ -96,6 +101,17 @@ export type TelegramGenerationSwapHooks = {
|
|
|
96
101
|
timeout_ms: number;
|
|
97
102
|
}) => void | Promise<void>;
|
|
98
103
|
};
|
|
104
|
+
export type ControlPlaneSessionMutationAction = "reload" | "update";
|
|
105
|
+
export type ControlPlaneSessionMutationResult = {
|
|
106
|
+
ok: boolean;
|
|
107
|
+
action: ControlPlaneSessionMutationAction;
|
|
108
|
+
message: string;
|
|
109
|
+
details?: Record<string, unknown>;
|
|
110
|
+
};
|
|
111
|
+
export type ControlPlaneSessionMutationHooks = {
|
|
112
|
+
reload?: () => Promise<ControlPlaneSessionMutationResult>;
|
|
113
|
+
update?: () => Promise<ControlPlaneSessionMutationResult>;
|
|
114
|
+
};
|
|
99
115
|
type DetectedAdapter = {
|
|
100
116
|
name: "slack";
|
|
101
117
|
signingSecret: string;
|
|
@@ -135,9 +151,11 @@ export type BootstrapControlPlaneOpts = {
|
|
|
135
151
|
heartbeatScheduler?: ActivityHeartbeatScheduler;
|
|
136
152
|
runSupervisorSpawnProcess?: ControlPlaneRunSupervisorOpts["spawnProcess"];
|
|
137
153
|
runSupervisorHeartbeatIntervalMs?: number;
|
|
154
|
+
sessionMutationHooks?: ControlPlaneSessionMutationHooks;
|
|
138
155
|
generation?: ControlPlaneGenerationContext;
|
|
139
156
|
telemetry?: GenerationTelemetryRecorder | null;
|
|
140
157
|
telegramGenerationHooks?: TelegramGenerationSwapHooks;
|
|
158
|
+
terminalEnabled?: boolean;
|
|
141
159
|
};
|
|
142
160
|
export declare function bootstrapControlPlane(opts: BootstrapControlPlaneOpts): Promise<ControlPlaneHandle | null>;
|
|
143
161
|
export {};
|
package/dist/control_plane.js
CHANGED
|
@@ -702,7 +702,7 @@ export async function bootstrapControlPlane(opts) {
|
|
|
702
702
|
},
|
|
703
703
|
}
|
|
704
704
|
: undefined;
|
|
705
|
-
if (detected.length === 0) {
|
|
705
|
+
if (detected.length === 0 && !opts.terminalEnabled) {
|
|
706
706
|
return null;
|
|
707
707
|
}
|
|
708
708
|
const paths = getControlPlanePaths(opts.repoRoot);
|
|
@@ -745,6 +745,116 @@ export async function bootstrapControlPlane(opts) {
|
|
|
745
745
|
runtime,
|
|
746
746
|
operator,
|
|
747
747
|
mutationExecutor: async (record) => {
|
|
748
|
+
if (record.target_type === "reload" || record.target_type === "update") {
|
|
749
|
+
if (record.command_args.length > 0) {
|
|
750
|
+
return {
|
|
751
|
+
terminalState: "failed",
|
|
752
|
+
errorCode: "cli_validation_failed",
|
|
753
|
+
trace: {
|
|
754
|
+
cliCommandKind: record.target_type,
|
|
755
|
+
runRootId: null,
|
|
756
|
+
},
|
|
757
|
+
mutatingEvents: [
|
|
758
|
+
{
|
|
759
|
+
eventType: "session.lifecycle.command.failed",
|
|
760
|
+
payload: {
|
|
761
|
+
action: record.target_type,
|
|
762
|
+
reason: "unexpected_args",
|
|
763
|
+
args: record.command_args,
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
],
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
const action = record.target_type;
|
|
770
|
+
const hook = action === "reload"
|
|
771
|
+
? opts.sessionMutationHooks?.reload
|
|
772
|
+
: opts.sessionMutationHooks?.update;
|
|
773
|
+
if (!hook) {
|
|
774
|
+
return {
|
|
775
|
+
terminalState: "failed",
|
|
776
|
+
errorCode: "session_lifecycle_unavailable",
|
|
777
|
+
trace: {
|
|
778
|
+
cliCommandKind: action,
|
|
779
|
+
runRootId: null,
|
|
780
|
+
},
|
|
781
|
+
mutatingEvents: [
|
|
782
|
+
{
|
|
783
|
+
eventType: "session.lifecycle.command.failed",
|
|
784
|
+
payload: {
|
|
785
|
+
action,
|
|
786
|
+
reason: "hook_missing",
|
|
787
|
+
},
|
|
788
|
+
},
|
|
789
|
+
],
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
try {
|
|
793
|
+
const lifecycle = await hook();
|
|
794
|
+
if (!lifecycle.ok) {
|
|
795
|
+
return {
|
|
796
|
+
terminalState: "failed",
|
|
797
|
+
errorCode: "session_lifecycle_failed",
|
|
798
|
+
trace: {
|
|
799
|
+
cliCommandKind: action,
|
|
800
|
+
runRootId: null,
|
|
801
|
+
},
|
|
802
|
+
mutatingEvents: [
|
|
803
|
+
{
|
|
804
|
+
eventType: "session.lifecycle.command.failed",
|
|
805
|
+
payload: {
|
|
806
|
+
action,
|
|
807
|
+
reason: lifecycle.message,
|
|
808
|
+
details: lifecycle.details ?? null,
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
],
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
return {
|
|
815
|
+
terminalState: "completed",
|
|
816
|
+
result: {
|
|
817
|
+
ok: true,
|
|
818
|
+
action,
|
|
819
|
+
message: lifecycle.message,
|
|
820
|
+
details: lifecycle.details ?? null,
|
|
821
|
+
},
|
|
822
|
+
trace: {
|
|
823
|
+
cliCommandKind: action,
|
|
824
|
+
runRootId: null,
|
|
825
|
+
},
|
|
826
|
+
mutatingEvents: [
|
|
827
|
+
{
|
|
828
|
+
eventType: `session.lifecycle.command.${action}`,
|
|
829
|
+
payload: {
|
|
830
|
+
action,
|
|
831
|
+
message: lifecycle.message,
|
|
832
|
+
details: lifecycle.details ?? null,
|
|
833
|
+
},
|
|
834
|
+
},
|
|
835
|
+
],
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
catch (err) {
|
|
839
|
+
return {
|
|
840
|
+
terminalState: "failed",
|
|
841
|
+
errorCode: err instanceof Error && err.message ? err.message : "session_lifecycle_failed",
|
|
842
|
+
trace: {
|
|
843
|
+
cliCommandKind: action,
|
|
844
|
+
runRootId: null,
|
|
845
|
+
},
|
|
846
|
+
mutatingEvents: [
|
|
847
|
+
{
|
|
848
|
+
eventType: "session.lifecycle.command.failed",
|
|
849
|
+
payload: {
|
|
850
|
+
action,
|
|
851
|
+
reason: err instanceof Error && err.message ? err.message : "session_lifecycle_failed",
|
|
852
|
+
},
|
|
853
|
+
},
|
|
854
|
+
],
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
}
|
|
748
858
|
if (record.target_type === "run start" || record.target_type === "run resume") {
|
|
749
859
|
try {
|
|
750
860
|
const launched = await runSupervisor?.startFromCommand(record);
|
|
@@ -1050,6 +1160,12 @@ export async function bootstrapControlPlane(opts) {
|
|
|
1050
1160
|
async traceRun(traceOpts) {
|
|
1051
1161
|
return (await runSupervisor?.trace(traceOpts.idOrRoot, { limit: traceOpts.limit })) ?? null;
|
|
1052
1162
|
},
|
|
1163
|
+
async submitTerminalCommand(terminalOpts) {
|
|
1164
|
+
if (!pipeline) {
|
|
1165
|
+
throw new Error("control_plane_pipeline_unavailable");
|
|
1166
|
+
}
|
|
1167
|
+
return await pipeline.handleTerminalInbound(terminalOpts);
|
|
1168
|
+
},
|
|
1053
1169
|
async stop() {
|
|
1054
1170
|
if (drainInterval) {
|
|
1055
1171
|
clearInterval(drainInterval);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export type { ControlPlaneActivityEvent, ControlPlaneActivityEventKind, ControlP
|
|
|
2
2
|
export { ControlPlaneActivitySupervisor } from "./activity_supervisor.js";
|
|
3
3
|
export type { MuConfig, MuConfigPatch, MuConfigPresence } from "./config.js";
|
|
4
4
|
export { applyMuConfigPatch, DEFAULT_MU_CONFIG, getMuConfigPath, muConfigPresence, normalizeMuConfig, readMuConfigFile, redactMuConfigSecrets, writeMuConfigFile, } from "./config.js";
|
|
5
|
-
export type { ActiveAdapter, ControlPlaneConfig, ControlPlaneHandle } from "./control_plane.js";
|
|
5
|
+
export type { ActiveAdapter, ControlPlaneConfig, ControlPlaneHandle, ControlPlaneSessionMutationAction, ControlPlaneSessionMutationHooks, ControlPlaneSessionMutationResult, } from "./control_plane.js";
|
|
6
6
|
export { bootstrapControlPlane, detectAdapters } from "./control_plane.js";
|
|
7
7
|
export type { CronProgramLifecycleAction, CronProgramLifecycleEvent, CronProgramOperationResult, CronProgramRegistryOpts, CronProgramSnapshot, CronProgramStatusSnapshot, CronProgramTarget, CronProgramTickEvent, CronProgramWakeMode, } from "./cron_programs.js";
|
|
8
8
|
export { CronProgramRegistry } from "./cron_programs.js";
|
package/dist/server.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { ForumStore } from "@femtomc/mu-forum";
|
|
|
5
5
|
import { IssueStore } from "@femtomc/mu-issue";
|
|
6
6
|
import { ControlPlaneActivitySupervisor } from "./activity_supervisor.js";
|
|
7
7
|
import { type MuConfig } from "./config.js";
|
|
8
|
-
import { type ControlPlaneConfig, type ControlPlaneHandle } from "./control_plane.js";
|
|
8
|
+
import { type ControlPlaneConfig, type ControlPlaneHandle, type ControlPlaneSessionMutationHooks } from "./control_plane.js";
|
|
9
9
|
import { CronProgramRegistry } from "./cron_programs.js";
|
|
10
10
|
import { HeartbeatProgramRegistry } from "./heartbeat_programs.js";
|
|
11
11
|
import { ActivityHeartbeatScheduler } from "./heartbeat_scheduler.js";
|
|
@@ -30,6 +30,7 @@ export type ServerOptions = {
|
|
|
30
30
|
config?: MuConfig;
|
|
31
31
|
configReader?: ConfigReader;
|
|
32
32
|
configWriter?: ConfigWriter;
|
|
33
|
+
sessionMutationHooks?: ControlPlaneSessionMutationHooks;
|
|
33
34
|
};
|
|
34
35
|
export type ServerContext = {
|
|
35
36
|
repoRoot: string;
|
package/dist/server.js
CHANGED
|
@@ -46,6 +46,12 @@ function toNonNegativeInt(value, fallback) {
|
|
|
46
46
|
}
|
|
47
47
|
return Math.max(0, Math.trunc(fallback));
|
|
48
48
|
}
|
|
49
|
+
function shellQuoteArg(value) {
|
|
50
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
51
|
+
}
|
|
52
|
+
function shellJoin(args) {
|
|
53
|
+
return args.map(shellQuoteArg).join(" ");
|
|
54
|
+
}
|
|
49
55
|
function describeError(err) {
|
|
50
56
|
if (err instanceof Error)
|
|
51
57
|
return err.message;
|
|
@@ -165,6 +171,144 @@ export function createServer(options = {}) {
|
|
|
165
171
|
const autoRunHeartbeatEveryMs = Math.max(1_000, toNonNegativeInt(options.autoRunHeartbeatEveryMs, DEFAULT_AUTO_RUN_HEARTBEAT_EVERY_MS));
|
|
166
172
|
const operatorWakeLastByKey = new Map();
|
|
167
173
|
const autoRunHeartbeatProgramByJobId = new Map();
|
|
174
|
+
let sessionMutationScheduled = null;
|
|
175
|
+
const runShellCommand = async (command) => {
|
|
176
|
+
const proc = Bun.spawn({
|
|
177
|
+
cmd: ["bash", "-lc", command],
|
|
178
|
+
cwd: repoRoot,
|
|
179
|
+
env: Bun.env,
|
|
180
|
+
stdin: "ignore",
|
|
181
|
+
stdout: "pipe",
|
|
182
|
+
stderr: "pipe",
|
|
183
|
+
});
|
|
184
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
185
|
+
proc.exited,
|
|
186
|
+
proc.stdout ? new Response(proc.stdout).text() : Promise.resolve(""),
|
|
187
|
+
proc.stderr ? new Response(proc.stderr).text() : Promise.resolve(""),
|
|
188
|
+
]);
|
|
189
|
+
return {
|
|
190
|
+
exitCode: Number.isFinite(exitCode) ? Number(exitCode) : 1,
|
|
191
|
+
stdout,
|
|
192
|
+
stderr,
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
const defaultSessionMutationHooks = {
|
|
196
|
+
reload: async () => {
|
|
197
|
+
if (sessionMutationScheduled) {
|
|
198
|
+
return {
|
|
199
|
+
ok: true,
|
|
200
|
+
action: sessionMutationScheduled.action,
|
|
201
|
+
message: `session ${sessionMutationScheduled.action} already scheduled`,
|
|
202
|
+
details: { scheduled_at_ms: sessionMutationScheduled.at_ms },
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
const nowMs = Date.now();
|
|
206
|
+
const restartCommand = Bun.env.MU_RESTART_COMMAND?.trim();
|
|
207
|
+
const inferredArgs = process.argv[0] === process.execPath
|
|
208
|
+
? [process.execPath, ...process.argv.slice(1)]
|
|
209
|
+
: [process.execPath, ...process.argv];
|
|
210
|
+
const restartShellCommand = restartCommand && restartCommand.length > 0 ? restartCommand : shellJoin(inferredArgs);
|
|
211
|
+
if (!restartShellCommand.trim()) {
|
|
212
|
+
return {
|
|
213
|
+
ok: false,
|
|
214
|
+
action: "reload",
|
|
215
|
+
message: "unable to determine restart command",
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const exitDelayMs = 1_000;
|
|
219
|
+
const launchDelayMs = exitDelayMs + 300;
|
|
220
|
+
const delayedShellCommand = `sleep ${(launchDelayMs / 1_000).toFixed(2)}; ${restartShellCommand}`;
|
|
221
|
+
let spawnedPid = null;
|
|
222
|
+
try {
|
|
223
|
+
const proc = Bun.spawn({
|
|
224
|
+
cmd: ["bash", "-lc", delayedShellCommand],
|
|
225
|
+
cwd: repoRoot,
|
|
226
|
+
env: Bun.env,
|
|
227
|
+
stdin: "ignore",
|
|
228
|
+
stdout: "inherit",
|
|
229
|
+
stderr: "inherit",
|
|
230
|
+
});
|
|
231
|
+
spawnedPid = proc.pid ?? null;
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
return {
|
|
235
|
+
ok: false,
|
|
236
|
+
action: "reload",
|
|
237
|
+
message: `failed to spawn replacement process: ${describeError(err)}`,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
sessionMutationScheduled = { action: "reload", at_ms: nowMs };
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
process.exit(0);
|
|
243
|
+
}, exitDelayMs);
|
|
244
|
+
return {
|
|
245
|
+
ok: true,
|
|
246
|
+
action: "reload",
|
|
247
|
+
message: "reload scheduled; restarting process",
|
|
248
|
+
details: {
|
|
249
|
+
restart_command: restartShellCommand,
|
|
250
|
+
restart_launch_command: delayedShellCommand,
|
|
251
|
+
spawned_pid: spawnedPid,
|
|
252
|
+
exit_delay_ms: exitDelayMs,
|
|
253
|
+
launch_delay_ms: launchDelayMs,
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
},
|
|
257
|
+
update: async () => {
|
|
258
|
+
if (sessionMutationScheduled) {
|
|
259
|
+
return {
|
|
260
|
+
ok: true,
|
|
261
|
+
action: sessionMutationScheduled.action,
|
|
262
|
+
message: `session ${sessionMutationScheduled.action} already scheduled`,
|
|
263
|
+
details: { scheduled_at_ms: sessionMutationScheduled.at_ms },
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const updateCommand = Bun.env.MU_UPDATE_COMMAND?.trim() || "npm install -g @femtomc/mu@latest";
|
|
267
|
+
const result = await runShellCommand(updateCommand);
|
|
268
|
+
if (result.exitCode !== 0) {
|
|
269
|
+
return {
|
|
270
|
+
ok: false,
|
|
271
|
+
action: "update",
|
|
272
|
+
message: `update command failed (exit ${result.exitCode})`,
|
|
273
|
+
details: {
|
|
274
|
+
update_command: updateCommand,
|
|
275
|
+
stdout: result.stdout.slice(-4_000),
|
|
276
|
+
stderr: result.stderr.slice(-4_000),
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const reloadResult = await defaultSessionMutationHooks.reload?.();
|
|
281
|
+
if (!reloadResult) {
|
|
282
|
+
return {
|
|
283
|
+
ok: false,
|
|
284
|
+
action: "update",
|
|
285
|
+
message: "reload hook unavailable after update",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (!reloadResult.ok) {
|
|
289
|
+
return {
|
|
290
|
+
ok: false,
|
|
291
|
+
action: "update",
|
|
292
|
+
message: reloadResult.message,
|
|
293
|
+
details: {
|
|
294
|
+
update_command: updateCommand,
|
|
295
|
+
reload: reloadResult.details ?? null,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
ok: true,
|
|
301
|
+
action: "update",
|
|
302
|
+
message: "update applied; reload scheduled",
|
|
303
|
+
details: {
|
|
304
|
+
update_command: updateCommand,
|
|
305
|
+
reload: reloadResult.details ?? null,
|
|
306
|
+
update_stdout_tail: result.stdout.slice(-1_000),
|
|
307
|
+
},
|
|
308
|
+
};
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
const sessionMutationHooks = options.sessionMutationHooks ?? defaultSessionMutationHooks;
|
|
168
312
|
const emitOperatorWake = async (opts) => {
|
|
169
313
|
const dedupeKey = opts.dedupeKey.trim();
|
|
170
314
|
if (!dedupeKey) {
|
|
@@ -214,6 +358,8 @@ export function createServer(options = {}) {
|
|
|
214
358
|
heartbeatScheduler,
|
|
215
359
|
generation,
|
|
216
360
|
telemetry: generationTelemetry,
|
|
361
|
+
sessionMutationHooks,
|
|
362
|
+
terminalEnabled: true,
|
|
217
363
|
});
|
|
218
364
|
});
|
|
219
365
|
const controlPlaneProxy = {
|
|
@@ -272,6 +418,13 @@ export function createServer(options = {}) {
|
|
|
272
418
|
return null;
|
|
273
419
|
return await handle.traceRun(opts);
|
|
274
420
|
},
|
|
421
|
+
async submitTerminalCommand(opts) {
|
|
422
|
+
const handle = controlPlaneCurrent;
|
|
423
|
+
if (!handle?.submitTerminalCommand) {
|
|
424
|
+
throw new Error("control_plane_unavailable");
|
|
425
|
+
}
|
|
426
|
+
return await handle.submitTerminalCommand(opts);
|
|
427
|
+
},
|
|
275
428
|
async stop() {
|
|
276
429
|
const handle = controlPlaneCurrent;
|
|
277
430
|
controlPlaneCurrent = null;
|
|
@@ -1102,6 +1255,100 @@ export function createServer(options = {}) {
|
|
|
1102
1255
|
control_plane: controlPlane,
|
|
1103
1256
|
}, { headers });
|
|
1104
1257
|
}
|
|
1258
|
+
if (path === "/api/commands/submit") {
|
|
1259
|
+
if (request.method !== "POST") {
|
|
1260
|
+
return Response.json({ error: "Method Not Allowed" }, { status: 405, headers });
|
|
1261
|
+
}
|
|
1262
|
+
let body;
|
|
1263
|
+
try {
|
|
1264
|
+
body = (await request.json());
|
|
1265
|
+
}
|
|
1266
|
+
catch {
|
|
1267
|
+
return Response.json({ error: "invalid json body" }, { status: 400, headers });
|
|
1268
|
+
}
|
|
1269
|
+
const kind = typeof body.kind === "string" ? body.kind.trim() : "";
|
|
1270
|
+
if (!kind) {
|
|
1271
|
+
return Response.json({ error: "kind is required" }, { status: 400, headers });
|
|
1272
|
+
}
|
|
1273
|
+
let commandText;
|
|
1274
|
+
switch (kind) {
|
|
1275
|
+
case "run_start": {
|
|
1276
|
+
const prompt = typeof body.prompt === "string" ? body.prompt.trim() : "";
|
|
1277
|
+
if (!prompt) {
|
|
1278
|
+
return Response.json({ error: "prompt is required for run_start" }, { status: 400, headers });
|
|
1279
|
+
}
|
|
1280
|
+
const maxStepsSuffix = typeof body.max_steps === "number" && Number.isFinite(body.max_steps)
|
|
1281
|
+
? ` --max-steps ${Math.max(1, Math.trunc(body.max_steps))}`
|
|
1282
|
+
: "";
|
|
1283
|
+
commandText = `mu! run start ${prompt}${maxStepsSuffix}`;
|
|
1284
|
+
break;
|
|
1285
|
+
}
|
|
1286
|
+
case "run_resume": {
|
|
1287
|
+
const rootId = typeof body.root_issue_id === "string" ? body.root_issue_id.trim() : "";
|
|
1288
|
+
const maxSteps = typeof body.max_steps === "number" && Number.isFinite(body.max_steps)
|
|
1289
|
+
? ` ${Math.max(1, Math.trunc(body.max_steps))}`
|
|
1290
|
+
: "";
|
|
1291
|
+
commandText = `mu! run resume${rootId ? ` ${rootId}` : ""}${maxSteps}`;
|
|
1292
|
+
break;
|
|
1293
|
+
}
|
|
1294
|
+
case "run_interrupt": {
|
|
1295
|
+
const rootId = typeof body.root_issue_id === "string" ? body.root_issue_id.trim() : "";
|
|
1296
|
+
commandText = `mu! run interrupt${rootId ? ` ${rootId}` : ""}`;
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
case "reload":
|
|
1300
|
+
commandText = "/mu reload";
|
|
1301
|
+
break;
|
|
1302
|
+
case "update":
|
|
1303
|
+
commandText = "/mu update";
|
|
1304
|
+
break;
|
|
1305
|
+
case "status":
|
|
1306
|
+
commandText = "/mu status";
|
|
1307
|
+
break;
|
|
1308
|
+
case "issue_list":
|
|
1309
|
+
commandText = "/mu issue list";
|
|
1310
|
+
break;
|
|
1311
|
+
case "issue_get": {
|
|
1312
|
+
const issueId = typeof body.issue_id === "string" ? body.issue_id.trim() : "";
|
|
1313
|
+
commandText = `/mu issue get${issueId ? ` ${issueId}` : ""}`;
|
|
1314
|
+
break;
|
|
1315
|
+
}
|
|
1316
|
+
case "forum_read": {
|
|
1317
|
+
const topic = typeof body.topic === "string" ? body.topic.trim() : "";
|
|
1318
|
+
const limit = typeof body.limit === "number" && Number.isFinite(body.limit)
|
|
1319
|
+
? ` ${Math.max(1, Math.trunc(body.limit))}`
|
|
1320
|
+
: "";
|
|
1321
|
+
commandText = `/mu forum read${topic ? ` ${topic}` : ""}${limit}`;
|
|
1322
|
+
break;
|
|
1323
|
+
}
|
|
1324
|
+
case "run_list":
|
|
1325
|
+
commandText = "/mu run list";
|
|
1326
|
+
break;
|
|
1327
|
+
case "run_status": {
|
|
1328
|
+
const rootId = typeof body.root_issue_id === "string" ? body.root_issue_id.trim() : "";
|
|
1329
|
+
commandText = `/mu run status${rootId ? ` ${rootId}` : ""}`;
|
|
1330
|
+
break;
|
|
1331
|
+
}
|
|
1332
|
+
case "ready":
|
|
1333
|
+
commandText = "/mu ready";
|
|
1334
|
+
break;
|
|
1335
|
+
default:
|
|
1336
|
+
return Response.json({ error: `unknown command kind: ${kind}` }, { status: 400, headers });
|
|
1337
|
+
}
|
|
1338
|
+
try {
|
|
1339
|
+
if (!controlPlaneProxy.submitTerminalCommand) {
|
|
1340
|
+
return Response.json({ error: "control plane not available" }, { status: 503, headers });
|
|
1341
|
+
}
|
|
1342
|
+
const result = await controlPlaneProxy.submitTerminalCommand({
|
|
1343
|
+
commandText,
|
|
1344
|
+
repoRoot: context.repoRoot,
|
|
1345
|
+
});
|
|
1346
|
+
return Response.json({ ok: true, result }, { headers });
|
|
1347
|
+
}
|
|
1348
|
+
catch (err) {
|
|
1349
|
+
return Response.json({ error: `command failed: ${describeError(err)}` }, { status: 500, headers });
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1105
1352
|
if (path === "/api/runs") {
|
|
1106
1353
|
if (request.method !== "GET") {
|
|
1107
1354
|
return Response.json({ error: "Method Not Allowed" }, { status: 405, headers });
|
|
@@ -2016,6 +2263,8 @@ export async function createServerAsync(options = {}) {
|
|
|
2016
2263
|
generation_seq: 0,
|
|
2017
2264
|
},
|
|
2018
2265
|
telemetry: generationTelemetry,
|
|
2266
|
+
sessionMutationHooks: options.sessionMutationHooks,
|
|
2267
|
+
terminalEnabled: true,
|
|
2019
2268
|
});
|
|
2020
2269
|
const serverConfig = createServer({
|
|
2021
2270
|
...options,
|
package/package.json
CHANGED
|
@@ -1,40 +1,40 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
2
|
+
"name": "@femtomc/mu-server",
|
|
3
|
+
"version": "26.2.67",
|
|
4
|
+
"description": "HTTP API server for mu status, work items, messaging setup, and web UI.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mu",
|
|
7
|
+
"server",
|
|
8
|
+
"api",
|
|
9
|
+
"web",
|
|
10
|
+
"automation"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"bin": {
|
|
16
|
+
"mu-server": "./dist/cli.js"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"dist/**",
|
|
26
|
+
"public/**"
|
|
27
|
+
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc -p tsconfig.build.json",
|
|
30
|
+
"test": "bun test",
|
|
31
|
+
"start": "bun run dist/cli.js"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@femtomc/mu-agent": "26.2.67",
|
|
35
|
+
"@femtomc/mu-control-plane": "26.2.67",
|
|
36
|
+
"@femtomc/mu-core": "26.2.67",
|
|
37
|
+
"@femtomc/mu-forum": "26.2.67",
|
|
38
|
+
"@femtomc/mu-issue": "26.2.67"
|
|
39
|
+
}
|
|
40
40
|
}
|