mu-agent 0.1.13 → 0.2.0
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/CHANGELOG.md +47 -0
- package/README.md +5 -3
- package/dist/bundle/chunks/{chunk-WDURT4ZM.js → chunk-AN7MSYCK.js} +1 -1
- package/dist/bundle/chunks/{chunk-3J3HSHLX.js → chunk-BK7M6BDT.js} +3 -3
- package/dist/bundle/chunks/{virtual-modules-TJPHLTRT.js → virtual-modules-U6MRV5BC.js} +1 -1
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/judge/dist/auth.js +3 -0
- package/judge/dist/mude.js +1338 -0
- package/judge/dist/setup.js +15 -4
- package/judge/manifest.json +139 -71
- package/judge/mu/{chunk-SKGYWHW6.js → chunk-6XRUK3PM.js} +1 -1
- package/judge/mu/{cloud-code-V7FOGB4J.js → cloud-code-XR7SZMJC.js} +1 -1
- package/judge/mu/index.js +501 -214
- package/kyrn/bin/kyrn-doctor +67 -13
- package/kyrn/bin/mu.mjs +143 -9
- package/kyrn/local-judge/mude-requirements.lock.txt +746 -0
- package/package.json +2 -2
package/kyrn/bin/kyrn-doctor
CHANGED
|
@@ -74,8 +74,12 @@ if (layout === "package") {
|
|
|
74
74
|
"dependencies",
|
|
75
75
|
installed
|
|
76
76
|
? "installed"
|
|
77
|
-
: `missing: cd ${root} && npm ci --ignore-scripts && npm run hydrate:model-
|
|
77
|
+
: `missing: cd ${root} && npm ci --ignore-scripts && npm run hydrate:model-catalog`,
|
|
78
78
|
);
|
|
79
|
+
// The model list pi reads at its start is generated, not committed (installHint in mu.mjs).
|
|
80
|
+
if (installed && !existsSync(join(root, "packages", "ai", "src", "providers", "data", ".manifest.json"))) {
|
|
81
|
+
row("fix", "model list", `not generated: cd ${root} && npm run hydrate:model-catalog`);
|
|
82
|
+
}
|
|
79
83
|
|
|
80
84
|
// A link on POSIX, a small mu.cmd on Windows, where a link to a script cannot be run.
|
|
81
85
|
const linked = linkState({
|
|
@@ -118,14 +122,32 @@ row(
|
|
|
118
122
|
const config = readJson(join(agentDir, "mu.json")) ?? readJson(join(agentDir, "kyrn.json"));
|
|
119
123
|
const tiers = (env("JUDGE") ? env("JUDGE").split(",") : (config?.tiers ?? ["jev"])).map((tier) => tier.trim());
|
|
120
124
|
const mode = env("JUDGE_MODE") ?? config?.modes?.default ?? "active";
|
|
121
|
-
row("--", "judges", `${tiers.join(" > ")} · default mode ${mode}${env("JUDGE") ? " (from MU_JUDGE)" : ""}`);
|
|
122
125
|
const overrides = Object.entries(config?.modes ?? {}).filter(([id]) => id !== "default");
|
|
126
|
+
// The modes mu sets out of the box besides the default, as packages/kyrn-judge/src/config.ts has them: one of these in
|
|
127
|
+
// mu.json gives way to a default of off (DecisionEngine.getMode).
|
|
128
|
+
const BUILT_IN_MODES = {
|
|
129
|
+
"swarm.patch": "off",
|
|
130
|
+
"capability.disclosure": "off",
|
|
131
|
+
"notify.routing": "shadow",
|
|
132
|
+
"input.interjection": "shadow",
|
|
133
|
+
"turn.drift": "shadow",
|
|
134
|
+
};
|
|
135
|
+
// Whether any decision point asks a judge (DecisionEngine.asksJudge). With none, nothing is sent to one, and the rows
|
|
136
|
+
// about keys and judges that answer would only mislead.
|
|
137
|
+
const asksJudge = mode !== "off" || overrides.some(([id, value]) => value !== "off" && BUILT_IN_MODES[id] !== value);
|
|
138
|
+
row(
|
|
139
|
+
"--",
|
|
140
|
+
"judges",
|
|
141
|
+
`${tiers.join(" > ")} · default mode ${mode}${env("JUDGE") ? " (from MU_JUDGE)" : ""}${asksJudge ? "" : " · no decision point asks a judge, so nothing is sent to one"}`,
|
|
142
|
+
);
|
|
123
143
|
if (overrides.length > 0) row("--", "modes", overrides.map(([id, value]) => `${id}=${value}`).join(" "));
|
|
124
144
|
const ladder = config?.features?.swarm?.models ?? [];
|
|
125
145
|
row("--", "subagents", ladder.length > 0 ? `model ladder ${ladder.join(" < ")}` : "use the session's model (no ladder configured)");
|
|
126
146
|
|
|
127
147
|
const judgeSupport = localJudgeSupport({ platform: process.platform, env: process.env, wsl });
|
|
128
|
-
if (
|
|
148
|
+
if (!asksJudge) {
|
|
149
|
+
// Nothing asks the local judge either: whether it is up does not matter.
|
|
150
|
+
} else if (tiers.some((tier) => tier.startsWith("laya")) && !judgeSupport.runs && !judgeSupport.url) {
|
|
129
151
|
// Not a failure of this installation: the sidecar is Core ML, and there is none for this platform.
|
|
130
152
|
row("--", "local judge", `not available on ${platformName({ platform: process.platform, wsl })}: the Laya sidecar needs Core ML (macOS on Apple silicon)`);
|
|
131
153
|
row("--", "", "Laya's decision points use the next judge or their fallbacks. Use Jev, an LLM judge or MU_LOCAL_JUDGE_URL=<a server with the same API>");
|
|
@@ -174,8 +196,11 @@ const CLASSIFIER_KEYS = {
|
|
|
174
196
|
const fromFile = existsSync(envFile) ? parseEnvFile(readFileSync(envFile, "utf8")).entries : [];
|
|
175
197
|
const has = (name) => Boolean(process.env[name]) || fromFile.some(([key, value]) => key === name && value !== "");
|
|
176
198
|
const where = `to ${tilde(envFile)} or to your environment`;
|
|
177
|
-
|
|
178
|
-
|
|
199
|
+
// mude speaks System One too, but on this machine and with a key of its own (its rows are below).
|
|
200
|
+
const jevTier = tiers.find(
|
|
201
|
+
(tier) => tier !== "mude" && ["jev", "typesafe", "gateway"].includes((config?.judges?.[tier] ?? JEV_ROUTES[tier])?.type),
|
|
202
|
+
);
|
|
203
|
+
if (jevTier && asksJudge) {
|
|
179
204
|
const judge = config?.judges?.[jevTier] ?? JEV_ROUTES[jevTier];
|
|
180
205
|
if (judge.type === "jev") {
|
|
181
206
|
// The order the registry tries: TypeSafe directly, Jev on OpenRouter, the Vercel AI Gateway, OpenCode Zen,
|
|
@@ -215,15 +240,40 @@ if (jevTier) {
|
|
|
215
240
|
}
|
|
216
241
|
}
|
|
217
242
|
|
|
243
|
+
// mude on this Mac (mu judge mude): its server's /health, and the key mu sends it.
|
|
244
|
+
if (asksJudge && tiers.includes("mude")) {
|
|
245
|
+
const fallback = tiers.filter((tier) => tier !== "mude");
|
|
246
|
+
const behind = fallback.length > 0 ? `until it answers, ${fallback.join(" > ")} decide${fallback.length === 1 ? "s" : ""} (what they read leaves this machine)` : "until it answers, decisions take their fallbacks";
|
|
247
|
+
if (process.platform !== "darwin") {
|
|
248
|
+
row("--", "mude 1.0", `not available on ${platformName({ platform: process.platform, wsl })}: mude 1.0 runs on a Mac with Apple silicon`);
|
|
249
|
+
} else {
|
|
250
|
+
const entry = config?.judges?.mude;
|
|
251
|
+
const address = (typeof entry?.baseUrl === "string" ? entry.baseUrl : "http://127.0.0.1:8790/v1/systemone").replace(/\/v1\/systemone\/?$/, "");
|
|
252
|
+
const startedAt = performance.now();
|
|
253
|
+
try {
|
|
254
|
+
const response = await fetch(`${address}/health`, { signal: AbortSignal.timeout(2000) });
|
|
255
|
+
const health = await response.json();
|
|
256
|
+
const ms = Math.round(performance.now() - startedAt);
|
|
257
|
+
row(health.status === "ready" ? "ok" : "--", "mude 1.0", `${address} · ${health.status} · ${ms} ms`);
|
|
258
|
+
} catch {
|
|
259
|
+
row("fix", "mude 1.0", `${address} isn't answering: run mu judge mude start (first time: mu judge mude install)`);
|
|
260
|
+
row("--", "", behind);
|
|
261
|
+
}
|
|
262
|
+
if (!has("MU_JUDGE_MUDE_KEY")) row("fix", "mude 1.0 key", "MU_JUDGE_MUDE_KEY is not set, so mu can't ask mude 1.0: run mu judge mude enable");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
218
266
|
// Classifier models of pi's catalog (Jev on OpenCode Zen or Cloudflare, Clef, …), for the tiers and routes that use them.
|
|
219
267
|
const classifierOf = (tier) =>
|
|
220
268
|
tier.startsWith("classifier:")
|
|
221
269
|
? { type: "classifier", model: tier.slice("classifier:".length) }
|
|
222
270
|
: (config?.judges?.[tier] ?? JEV_ROUTES[tier]);
|
|
223
|
-
const askedTiers =
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
271
|
+
const askedTiers = asksJudge
|
|
272
|
+
? [
|
|
273
|
+
...tiers,
|
|
274
|
+
...Object.values(config?.routes ?? {}).flatMap((value) => (Array.isArray(value) ? value : String(value).split(","))),
|
|
275
|
+
].map((tier) => String(tier).trim())
|
|
276
|
+
: [];
|
|
227
277
|
for (const model of new Set(askedTiers.map(classifierOf).filter((judge) => judge?.type === "classifier").map((judge) => judge.model))) {
|
|
228
278
|
const provider = String(model ?? "").split("/")[0];
|
|
229
279
|
// OpenCode's free models answer without a key (the client sends "public", as OpenCode's own does).
|
|
@@ -325,12 +375,16 @@ const canRelate = (tier) =>
|
|
|
325
375
|
const browseTiers = config?.routes?.["browser.step"] ?? tiers;
|
|
326
376
|
const routed = Object.entries(config?.routes ?? {});
|
|
327
377
|
if (routed.length > 0) row("--", "routes", routed.map(([id, value]) => `${id} -> ${[value].flat().join(" > ")}`).join(" · "));
|
|
378
|
+
// mu's browser runs on the judge: with browser.step off it is switched off (src/extension/features/browser.ts).
|
|
379
|
+
const browseMode = config?.modes?.["browser.step"] ?? mode;
|
|
328
380
|
row(
|
|
329
|
-
[browseTiers].flat().some(canRelate) ? "ok" : "--",
|
|
381
|
+
browseMode !== "off" && [browseTiers].flat().some(canRelate) ? "ok" : "--",
|
|
330
382
|
"browse",
|
|
331
|
-
|
|
332
|
-
?
|
|
333
|
-
:
|
|
383
|
+
browseMode === "off"
|
|
384
|
+
? "off: mu's browser runs on the judge, and browser.step is off"
|
|
385
|
+
: [browseTiers].flat().some(canRelate)
|
|
386
|
+
? `driven by ${[browseTiers].flat().join(" > ")}`
|
|
387
|
+
: 'needs a judge that can match a goal to a page. In mu.json: "routes": { "browser.step": ["llm:openai/gpt-5.6-luna"] }',
|
|
334
388
|
);
|
|
335
389
|
|
|
336
390
|
const marks = { ok: "\x1b[32m ok\x1b[0m", fix: "\x1b[33mfix\x1b[0m", "--": "\x1b[2m ·\x1b[0m" };
|
package/kyrn/bin/mu.mjs
CHANGED
|
@@ -102,6 +102,8 @@ export function usage(platform) {
|
|
|
102
102
|
" mu -c | -r continue the last session | pick a session to resume",
|
|
103
103
|
" mu setup connect a model (paste an API key or pick a service) and choose a judge",
|
|
104
104
|
" (see mu setup --help for scripting, reading a key from stdin and the supported services)",
|
|
105
|
+
" mu judge mude install run mude 1.0, mu's decision model optimized for coding, on this Mac as the judge (Apple silicon, 1.8 GB)",
|
|
106
|
+
" enable | disable | start | stop | status | uninstall (mu judge mude help)",
|
|
105
107
|
" mu judge <cmd> manage the local judge, Laya (macOS on Apple silicon): setup | start | stop | status | run",
|
|
106
108
|
" mu ledger [n] [--json] print the judge's verdicts from the last n sessions",
|
|
107
109
|
" mu import <file>... import Claude Code, Codex and ZCode conversations; --list shows them (mu import --help)",
|
|
@@ -321,8 +323,8 @@ export function layoutOf({ root, platform, exists }) {
|
|
|
321
323
|
}
|
|
322
324
|
|
|
323
325
|
/**
|
|
324
|
-
* The files the npm package runs: pi, the judgment layer, the sign-in of `mu auth`, the importer of `mu import
|
|
325
|
-
* wizard of `mu setup`.
|
|
326
|
+
* The files the npm package runs: pi, the judgment layer, the sign-in of `mu auth`, the importer of `mu import`, the
|
|
327
|
+
* wizard of `mu setup` and `mu judge mude`.
|
|
326
328
|
*/
|
|
327
329
|
export function packageEntries({ root, platform }) {
|
|
328
330
|
const path = pathFor(platform);
|
|
@@ -332,6 +334,7 @@ export function packageEntries({ root, platform }) {
|
|
|
332
334
|
auth: path.join(root, "judge", "dist", "auth.js"),
|
|
333
335
|
import: path.join(root, "judge", "dist", "import.js"),
|
|
334
336
|
setup: path.join(root, "judge", "dist", "setup.js"),
|
|
337
|
+
mude: path.join(root, "judge", "dist", "mude.js"),
|
|
335
338
|
};
|
|
336
339
|
}
|
|
337
340
|
|
|
@@ -354,12 +357,26 @@ export function dependenciesInstalled({ root, platform, exists }) {
|
|
|
354
357
|
}
|
|
355
358
|
}
|
|
356
359
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
+
/**
|
|
361
|
+
* Writes the model list pi reads at its start (packages/ai/src/providers/data, not committed) from the pinned catalog in
|
|
362
|
+
* the repository: offline and the same every time, as CI builds it.
|
|
363
|
+
*/
|
|
364
|
+
const HYDRATE = "npm run hydrate:model-catalog";
|
|
365
|
+
|
|
366
|
+
/** A problem and the commands that fix it, one line to paste; Windows PowerShell 5 has no `&&`, so a list there. */
|
|
367
|
+
function stepsHint(problem, steps, platform) {
|
|
360
368
|
return platform === "win32"
|
|
361
|
-
?
|
|
362
|
-
:
|
|
369
|
+
? `${problem}. Run:\n${steps.map((step) => ` ${step}`).join("\n")}`
|
|
370
|
+
: `${problem}. Run: ${steps.join(" && ")}`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export function installHint({ root, platform }) {
|
|
374
|
+
return stepsHint("mu's dependencies aren't installed", [`cd ${root}`, "npm ci --ignore-scripts", HYDRATE], platform);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** A checkout whose dependencies are in but whose model list was never written: a clone right after npm install. */
|
|
378
|
+
export function modelDataHint({ root, platform }) {
|
|
379
|
+
return stepsHint("mu's list of models isn't generated in this checkout yet", [`cd ${root}`, HYDRATE], platform);
|
|
363
380
|
}
|
|
364
381
|
|
|
365
382
|
/** What mu says on a Node that does not strip TypeScript types, which a checkout cannot run on. */
|
|
@@ -372,12 +389,16 @@ const NO_TYPE_STRIPPING =
|
|
|
372
389
|
* pi's own source resolver (packages/coding-agent/src/experimental/source-resolver.ts: each workspace package
|
|
373
390
|
* resolves to its sources, as tsconfig.json's paths say) and Node's compile cache (compile-cache.mjs), the way pi
|
|
374
391
|
* runs its own checkout (pi-test.sh). A runtime that does not strip types, a checkout whose dependencies are not
|
|
375
|
-
* installed and one without the resolver are each said, not left to a failed
|
|
392
|
+
* installed or whose model list is not generated, and one without the resolver are each said, not left to a failed
|
|
393
|
+
* import.
|
|
376
394
|
*/
|
|
377
395
|
export function sourceRuntime({ root, platform, stripsTypes, exists }) {
|
|
378
396
|
const path = pathFor(platform);
|
|
379
397
|
if (!stripsTypes) return { error: NO_TYPE_STRIPPING };
|
|
380
398
|
if (!dependenciesInstalled({ root, platform, exists })) return { error: installHint({ root, platform }) };
|
|
399
|
+
// Without it pi fails at its first import with ERR_MODULE_NOT_FOUND for a provider's .json.
|
|
400
|
+
if (!exists(path.join(root, "packages", "ai", "src", "providers", "data", ".manifest.json")))
|
|
401
|
+
return { error: modelDataHint({ root, platform }) };
|
|
381
402
|
const resolver = path.join(root, "packages", "coding-agent", "src", "experimental", "source-resolver.ts");
|
|
382
403
|
if (!exists(resolver)) return { error: `pi's source resolver is missing from this checkout (${resolver})` };
|
|
383
404
|
// As URLs: `--import` reads a Windows path such as C:\... as a URL whose scheme is c:.
|
|
@@ -422,6 +443,28 @@ export function wantsLaya({ env, agentDir, platform, readFile }) {
|
|
|
422
443
|
}
|
|
423
444
|
|
|
424
445
|
/** The Core ML sidecar exists for macOS only. Anything else needs a server of its own behind MU_LOCAL_JUDGE_URL. */
|
|
446
|
+
/**
|
|
447
|
+
* Whether the configuration asks for mude (mu judge mude): MU_JUDGE naming it, else `tiers` or a route in mu.json (or
|
|
448
|
+
* kyrn.json when there is no mu.json). Its server is then started when a session starts, as Laya's is.
|
|
449
|
+
*/
|
|
450
|
+
export function wantsMude({ env, agentDir, platform, readFile }) {
|
|
451
|
+
const judges = muEnv("JUDGE", env);
|
|
452
|
+
if (judges) return judges.split(",").some((judge) => judge.trim() === "mude");
|
|
453
|
+
const path = pathFor(platform);
|
|
454
|
+
for (const name of ["mu.json", "kyrn.json"]) {
|
|
455
|
+
let config;
|
|
456
|
+
try {
|
|
457
|
+
config = JSON.parse(readFile(path.join(agentDir, name)));
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (error instanceof SyntaxError) return false;
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
const named = (tiers) => Array.isArray(tiers) && tiers.includes("mude");
|
|
463
|
+
return named(config?.tiers) || Object.values(config?.routes ?? {}).some(named);
|
|
464
|
+
}
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
|
|
425
468
|
export function localJudgeSupport({ platform, env, wsl = false }) {
|
|
426
469
|
if (platform === "darwin") return { runs: true };
|
|
427
470
|
const url = muEnv("LOCAL_JUDGE_URL", env);
|
|
@@ -562,6 +605,14 @@ export function planLaunch({
|
|
|
562
605
|
}
|
|
563
606
|
}
|
|
564
607
|
|
|
608
|
+
// mude's server, when the configuration asks for it: started without waiting, so a session never waits for the
|
|
609
|
+
// model to load (what mude cannot answer yet goes to the judges behind it). Only a Mac runs it.
|
|
610
|
+
let startMude;
|
|
611
|
+
if (execPath && platform === "darwin" && wantsMude({ env: childEnv, agentDir, platform, readFile: fs.readFile })) {
|
|
612
|
+
const mude = planMude({ platform, env: childEnv, argv: ["start", "--no-wait"], root, home, execPath, fs, stripsTypes });
|
|
613
|
+
if (!mude.error) startMude = { command: mude.command, args: mude.args, env: mude.env };
|
|
614
|
+
}
|
|
615
|
+
|
|
565
616
|
return {
|
|
566
617
|
command: execPath,
|
|
567
618
|
args: [...entry, ...judgeArgs({ argv, layout, root, platform, agentDir, readFile: fs.readFile }), ...argv],
|
|
@@ -572,6 +623,7 @@ export function planLaunch({
|
|
|
572
623
|
appDir,
|
|
573
624
|
agentDir,
|
|
574
625
|
startJudge,
|
|
626
|
+
startMude,
|
|
575
627
|
notes,
|
|
576
628
|
preface: argv[0] === "-h" || argv[0] === "--help" ? `${usage(platform)}\n\npi's help (mu passes all of this through):\n` : undefined,
|
|
577
629
|
};
|
|
@@ -588,10 +640,17 @@ export function planLaunch({
|
|
|
588
640
|
*
|
|
589
641
|
* A checkout runs there as it runs on the command line (sourceRuntime). Returns { error } when pi cannot run.
|
|
590
642
|
*/
|
|
591
|
-
export function planHost({ platform, env, argv, root, home, fs, wsl = false, stripsTypes = true }) {
|
|
643
|
+
export function planHost({ platform, env, argv, root, home, fs, wsl = false, stripsTypes = true, nodePath, nodeEnv = {} }) {
|
|
592
644
|
const path = pathFor(platform);
|
|
593
645
|
const launch = planLaunch({ platform, env, argv, root, home, execPath: "", fs, wsl, stripsTypes });
|
|
594
646
|
if (launch.error !== undefined) return { error: launch.error };
|
|
647
|
+
// mude's server is started by a Node of the host's: `nodePath` with `nodeEnv` (Electron's own binary with
|
|
648
|
+
// ELECTRON_RUN_AS_NODE=1 in the desktop app). Without one, the host leaves it to the app.
|
|
649
|
+
let startMude;
|
|
650
|
+
if (nodePath && platform === "darwin" && wantsMude({ env: launch.env, agentDir: launch.agentDir, platform, readFile: fs.readFile })) {
|
|
651
|
+
const mude = planMude({ platform, env: { ...launch.env, ...nodeEnv }, argv: ["start", "--no-wait"], root, home, execPath: nodePath, fs, stripsTypes });
|
|
652
|
+
if (!mude.error) startMude = { command: mude.command, args: mude.args, env: mude.env };
|
|
653
|
+
}
|
|
595
654
|
const common = {
|
|
596
655
|
env: launch.env,
|
|
597
656
|
layout: launch.layout,
|
|
@@ -599,6 +658,7 @@ export function planHost({ platform, env, argv, root, home, fs, wsl = false, str
|
|
|
599
658
|
appDir: launch.appDir,
|
|
600
659
|
agentDir: launch.agentDir,
|
|
601
660
|
startJudge: launch.startJudge,
|
|
661
|
+
startMude,
|
|
602
662
|
notes: launch.notes,
|
|
603
663
|
};
|
|
604
664
|
if (launch.layout === "package") {
|
|
@@ -643,6 +703,7 @@ function prepareFiles({ plan, platform, root, err }) {
|
|
|
643
703
|
*/
|
|
644
704
|
export function prepareLaunch({ plan, platform, root, bin, err }) {
|
|
645
705
|
prepareFiles({ plan, platform, root, err });
|
|
706
|
+
startMudeServer(plan);
|
|
646
707
|
if (plan.startJudge) {
|
|
647
708
|
const started = spawnSync(pathFor(platform).join(bin, "kyrn-judge-local"), ["start"], {
|
|
648
709
|
stdio: "ignore",
|
|
@@ -652,12 +713,32 @@ export function prepareLaunch({ plan, platform, root, bin, err }) {
|
|
|
652
713
|
}
|
|
653
714
|
}
|
|
654
715
|
|
|
716
|
+
/**
|
|
717
|
+
* mude's server, started in the background (`mu judge mude start --no-wait`): nothing waits for it, and it outlives this
|
|
718
|
+
* process, shared by every mu session like Laya. When mude is not installed it does nothing (the doctor says so).
|
|
719
|
+
*/
|
|
720
|
+
function startMudeServer(plan) {
|
|
721
|
+
if (!plan.startMude) return;
|
|
722
|
+
try {
|
|
723
|
+
const child = spawn(plan.startMude.command, plan.startMude.args, {
|
|
724
|
+
env: plan.startMude.env,
|
|
725
|
+
stdio: "ignore",
|
|
726
|
+
detached: true,
|
|
727
|
+
});
|
|
728
|
+
child.once("error", () => {});
|
|
729
|
+
child.unref();
|
|
730
|
+
} catch {
|
|
731
|
+
// mu works without it: the judges behind mude answer.
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
655
735
|
/**
|
|
656
736
|
* prepareLaunch for a program that must not stop while the local judge starts, such as the desktop app's main process:
|
|
657
737
|
* the same steps and notes, with the local judge started through a child process it waits for without blocking.
|
|
658
738
|
*/
|
|
659
739
|
export async function prepareLaunchAsync({ plan, platform, root, bin, err }) {
|
|
660
740
|
prepareFiles({ plan, platform, root, err });
|
|
741
|
+
startMudeServer(plan);
|
|
661
742
|
if (!plan.startJudge) return;
|
|
662
743
|
const status = await new Promise((resolve) => {
|
|
663
744
|
const child = spawn(pathFor(platform).join(bin, "kyrn-judge-local"), ["start"], {
|
|
@@ -775,6 +856,42 @@ export function planImport({ platform, env, argv, root, home, execPath, fs, stri
|
|
|
775
856
|
return { command: execPath, args: [...entry, ...argv], env: childEnv, agentDir };
|
|
776
857
|
}
|
|
777
858
|
|
|
859
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
860
|
+
// mu judge mude
|
|
861
|
+
// ---------------------------------------------------------------------------------------------------------
|
|
862
|
+
|
|
863
|
+
/**
|
|
864
|
+
* `mu judge mude <command>` (packages/kyrn-judge/src/mude): mude on this Mac, from the checkout's TypeScript or the
|
|
865
|
+
* package's built judge/dist/mude.js. It is told where mu keeps things: the agent folder (mu.json), mu's home (the
|
|
866
|
+
* model and its environment go under local-judge/mude), the .env (its key) and the lock of its Python packages.
|
|
867
|
+
*/
|
|
868
|
+
export function planMude({ platform, env, argv, root, home, execPath, fs, stripsTypes = true }) {
|
|
869
|
+
const path = pathFor(platform);
|
|
870
|
+
const layout = layoutOf({ root, platform, exists: fs.exists });
|
|
871
|
+
let entry;
|
|
872
|
+
if (layout === "package") {
|
|
873
|
+
const built = packageEntries({ root, platform }).mude;
|
|
874
|
+
if (!fs.exists(built)) {
|
|
875
|
+
return { error: `This mu-agent package has no mu judge mude (${built} is missing). Update it: npm i -g mu-agent` };
|
|
876
|
+
}
|
|
877
|
+
entry = [built];
|
|
878
|
+
} else {
|
|
879
|
+
const source = path.join(root, "packages", "kyrn-judge", "src", "mude", "cli.ts");
|
|
880
|
+
if (!fs.exists(source)) return { error: `mu judge mude is missing from this checkout (${source})` };
|
|
881
|
+
if (!stripsTypes) return { error: NO_TYPE_STRIPPING };
|
|
882
|
+
entry = ["--disable-warning=ExperimentalWarning", source];
|
|
883
|
+
}
|
|
884
|
+
const muDir = muHome({ home, platform, isDir: fs.isDir });
|
|
885
|
+
const agentDir = agentDirFor({ env, muDir, platform });
|
|
886
|
+
const childEnv = {};
|
|
887
|
+
for (const [key, value] of Object.entries(env)) if (typeof value === "string") childEnv[key] = value;
|
|
888
|
+
childEnv.MU_AGENT_DIR = agentDir;
|
|
889
|
+
childEnv.MU_MUDE_HOME = muDir;
|
|
890
|
+
childEnv.MU_MUDE_ENV_FILE = envFilePath({ layout, root, muDir, platform });
|
|
891
|
+
childEnv.MU_MUDE_LOCK = path.join(root, "kyrn", "local-judge", "mude-requirements.lock.txt");
|
|
892
|
+
return { command: execPath, args: [...entry, ...argv], env: childEnv, agentDir };
|
|
893
|
+
}
|
|
894
|
+
|
|
778
895
|
// ---------------------------------------------------------------------------------------------------------
|
|
779
896
|
// mu setup
|
|
780
897
|
// ---------------------------------------------------------------------------------------------------------
|
|
@@ -1486,6 +1603,23 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
1486
1603
|
},
|
|
1487
1604
|
});
|
|
1488
1605
|
}
|
|
1606
|
+
if (command === "judge" && rest[0] === "mude") {
|
|
1607
|
+
const plan = planMude({
|
|
1608
|
+
platform,
|
|
1609
|
+
env,
|
|
1610
|
+
argv: rest.slice(1),
|
|
1611
|
+
root,
|
|
1612
|
+
home,
|
|
1613
|
+
execPath: process.execPath,
|
|
1614
|
+
stripsTypes: Boolean(process.features.typescript),
|
|
1615
|
+
fs: { exists: existsSync, isDir, readFile: readText },
|
|
1616
|
+
});
|
|
1617
|
+
if (plan.error) {
|
|
1618
|
+
err(plan.error);
|
|
1619
|
+
return 1;
|
|
1620
|
+
}
|
|
1621
|
+
return handOver({ command: plan.command, args: plan.args, env: plan.env, strategy: "spawn" });
|
|
1622
|
+
}
|
|
1489
1623
|
if (command === "judge") {
|
|
1490
1624
|
const plan = planJudge({ platform, env, argv: rest, bin, wsl });
|
|
1491
1625
|
if (plan.kind === "script") return handOver({ command: plan.command, args: plan.args, env, strategy });
|