mu-agent 0.1.14 → 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.
@@ -1226,19 +1226,19 @@
1226
1226
  "group": "turn",
1227
1227
  "feature": "ttsr",
1228
1228
  "title": {
1229
- "zh": "写偏即停(实验)",
1230
- "en": "Mid-stream correction (experimental)",
1231
- "zh-TW": "寫偏即停(實驗)",
1232
- "ja-JP": "出力中の軌道修正(実験的)",
1233
- "ko-KR": "출력 중 교정(실험)",
1234
- "de-DE": "Korrektur während der Ausgabe (Experiment)",
1235
- "fr-FR": "Correction en cours d’écriture (expérimental)",
1236
- "es-ES": "Corrección sobre la marcha (experimental)",
1237
- "pt-BR": "Correção durante a escrita (experimental)",
1238
- "ru-RU": "Исправление на лету (эксперимент)",
1239
- "uk-UA": "Виправлення на льоту (експеримент)",
1240
- "tr-TR": "Akış içinde düzeltme (deneysel)",
1241
- "fa-IR": "اصلاح در میانهٔ خروجی (آزمایشی)"
1229
+ "zh": "写偏即停",
1230
+ "en": "Mid-stream correction",
1231
+ "zh-TW": "寫偏即停",
1232
+ "ja-JP": "出力中の軌道修正",
1233
+ "ko-KR": "출력 중 교정",
1234
+ "de-DE": "Korrektur während der Ausgabe",
1235
+ "fr-FR": "Correction en cours d’écriture",
1236
+ "es-ES": "Corrección sobre la marcha",
1237
+ "pt-BR": "Correção durante a escrita",
1238
+ "ru-RU": "Исправление на лету",
1239
+ "uk-UA": "Виправлення на льоту",
1240
+ "tr-TR": "Akış içinde düzeltme",
1241
+ "fa-IR": "اصلاح در میانهٔ خروجی"
1242
1242
  },
1243
1243
  "summary": {
1244
1244
  "zh": "模型边输出,判定器边每隔几百字对照一次你的硬约束和下面配置的规则;确信写偏了就立刻掐断输出,告诉模型是哪一条,让它从断点接着写。只有开启了「写偏即停」功能才会运行。",
@@ -5619,19 +5619,19 @@
5619
5619
  {
5620
5620
  "name": "ttsr",
5621
5621
  "title": {
5622
- "zh": "写偏即停(实验)",
5623
- "en": "Mid-stream correction (experimental)",
5624
- "zh-TW": "寫偏即停(實驗)",
5625
- "ja-JP": "出力中の軌道修正(実験的)",
5626
- "ko-KR": "출력 중 교정(실험)",
5627
- "de-DE": "Korrektur während der Ausgabe (Experiment)",
5628
- "fr-FR": "Correction en cours d’écriture (expérimental)",
5629
- "es-ES": "Corrección sobre la marcha (experimental)",
5630
- "pt-BR": "Correção durante a escrita (experimental)",
5631
- "ru-RU": "Исправление на лету (эксперимент)",
5632
- "uk-UA": "Виправлення на льоту (експеримент)",
5633
- "tr-TR": "Akış içinde düzeltme (deneysel)",
5634
- "fa-IR": "اصلاح در میانهٔ خروجی (آزمایشی)"
5622
+ "zh": "写偏即停",
5623
+ "en": "Mid-stream correction",
5624
+ "zh-TW": "寫偏即停",
5625
+ "ja-JP": "出力中の軌道修正",
5626
+ "ko-KR": "출력 중 교정",
5627
+ "de-DE": "Korrektur während der Ausgabe",
5628
+ "fr-FR": "Correction en cours d’écriture",
5629
+ "es-ES": "Corrección sobre la marcha",
5630
+ "pt-BR": "Correção durante a escrita",
5631
+ "ru-RU": "Исправление на лету",
5632
+ "uk-UA": "Виправлення на льоту",
5633
+ "tr-TR": "Akış içinde düzeltme",
5634
+ "fa-IR": "اصلاح در میانهٔ خروجی"
5635
5635
  },
5636
5636
  "summary": {
5637
5637
  "zh": "语义版 TTSR:不用正则,而由判定器发现模型写偏,中断输出、摆出规则、从原处继续。默认关闭;开启后每隔一段输出就会多一次判定调用。",
package/judge/mu/index.js CHANGED
@@ -27471,6 +27471,20 @@ var BUILT_IN_JUDGES = {
27471
27471
  type: "local",
27472
27472
  profile: { capabilities: { relate: false, rate: false, meta: false, screen: false, intent: false } }
27473
27473
  },
27474
+ // mude, the decision model trained for mu's judgment layer, on this Mac (`mu judge mude install`, Apple silicon):
27475
+ // the System One server that comes in its package, on 127.0.0.1:8790, with the key the install wrote to the .env.
27476
+ // Trusted on every capability: on mu's own dev scenarios (an internal benchmark, not public yet) it did as well as Jev
27477
+ // or better on relate, rate, meta, screening tool output (`tool.admission`) and promises (`turn.continue`), and on
27478
+ // classify but for one check: a tool's description screened for injected instructions (`tool.description`), where
27479
+ // Jev did better. Nothing measured it on injections in fetched content (`tool.injection`). So with a judge behind it,
27480
+ // `mu judge mude` routes those two checks to that judge (`routes` in mu.json). It answers in about 0.1 s here.
27481
+ mude: {
27482
+ type: "typesafe",
27483
+ baseUrl: "http://127.0.0.1:8790/v1/systemone",
27484
+ model: "mude",
27485
+ apiKeyEnv: "MU_JUDGE_MUDE_KEY",
27486
+ timeoutMs: 4e3
27487
+ },
27474
27488
  mock: { type: "mock" }
27475
27489
  };
27476
27490
  var DEFAULT_CONFIG = {
@@ -29713,7 +29727,8 @@ var DEFAULT_TIMEOUT_MS2 = {
29713
29727
  var KEYS_FOR_ELSEWHERE = /* @__PURE__ */ new Set([
29714
29728
  "MU_JUDGE_OPENROUTER_API_KEY",
29715
29729
  "MU_JUDGE_CUSTOM_API_KEY",
29716
- "MU_JUDGE_CLM_API_KEY"
29730
+ "MU_JUDGE_CLM_API_KEY",
29731
+ "MU_JUDGE_MUDE_KEY"
29717
29732
  ]);
29718
29733
  function resolveJudgeConfig(name, config) {
29719
29734
  if (name.startsWith("llm:")) return { type: "llm", model: name.slice("llm:".length) };
@@ -196,7 +196,10 @@ const CLASSIFIER_KEYS = {
196
196
  const fromFile = existsSync(envFile) ? parseEnvFile(readFileSync(envFile, "utf8")).entries : [];
197
197
  const has = (name) => Boolean(process.env[name]) || fromFile.some(([key, value]) => key === name && value !== "");
198
198
  const where = `to ${tilde(envFile)} or to your environment`;
199
- const jevTier = tiers.find((tier) => ["jev", "typesafe", "gateway"].includes((config?.judges?.[tier] ?? JEV_ROUTES[tier])?.type));
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
+ );
200
203
  if (jevTier && asksJudge) {
201
204
  const judge = config?.judges?.[jevTier] ?? JEV_ROUTES[jevTier];
202
205
  if (judge.type === "jev") {
@@ -237,6 +240,29 @@ if (jevTier && asksJudge) {
237
240
  }
238
241
  }
239
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
+
240
266
  // Classifier models of pi's catalog (Jev on OpenCode Zen or Cloudflare, Clef, …), for the tiers and routes that use them.
241
267
  const classifierOf = (tier) =>
242
268
  tier.startsWith("classifier:")
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` and the
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
 
@@ -440,6 +443,28 @@ export function wantsLaya({ env, agentDir, platform, readFile }) {
440
443
  }
441
444
 
442
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
+
443
468
  export function localJudgeSupport({ platform, env, wsl = false }) {
444
469
  if (platform === "darwin") return { runs: true };
445
470
  const url = muEnv("LOCAL_JUDGE_URL", env);
@@ -580,6 +605,14 @@ export function planLaunch({
580
605
  }
581
606
  }
582
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
+
583
616
  return {
584
617
  command: execPath,
585
618
  args: [...entry, ...judgeArgs({ argv, layout, root, platform, agentDir, readFile: fs.readFile }), ...argv],
@@ -590,6 +623,7 @@ export function planLaunch({
590
623
  appDir,
591
624
  agentDir,
592
625
  startJudge,
626
+ startMude,
593
627
  notes,
594
628
  preface: argv[0] === "-h" || argv[0] === "--help" ? `${usage(platform)}\n\npi's help (mu passes all of this through):\n` : undefined,
595
629
  };
@@ -606,10 +640,17 @@ export function planLaunch({
606
640
  *
607
641
  * A checkout runs there as it runs on the command line (sourceRuntime). Returns { error } when pi cannot run.
608
642
  */
609
- 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 = {} }) {
610
644
  const path = pathFor(platform);
611
645
  const launch = planLaunch({ platform, env, argv, root, home, execPath: "", fs, wsl, stripsTypes });
612
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
+ }
613
654
  const common = {
614
655
  env: launch.env,
615
656
  layout: launch.layout,
@@ -617,6 +658,7 @@ export function planHost({ platform, env, argv, root, home, fs, wsl = false, str
617
658
  appDir: launch.appDir,
618
659
  agentDir: launch.agentDir,
619
660
  startJudge: launch.startJudge,
661
+ startMude,
620
662
  notes: launch.notes,
621
663
  };
622
664
  if (launch.layout === "package") {
@@ -661,6 +703,7 @@ function prepareFiles({ plan, platform, root, err }) {
661
703
  */
662
704
  export function prepareLaunch({ plan, platform, root, bin, err }) {
663
705
  prepareFiles({ plan, platform, root, err });
706
+ startMudeServer(plan);
664
707
  if (plan.startJudge) {
665
708
  const started = spawnSync(pathFor(platform).join(bin, "kyrn-judge-local"), ["start"], {
666
709
  stdio: "ignore",
@@ -670,12 +713,32 @@ export function prepareLaunch({ plan, platform, root, bin, err }) {
670
713
  }
671
714
  }
672
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
+
673
735
  /**
674
736
  * prepareLaunch for a program that must not stop while the local judge starts, such as the desktop app's main process:
675
737
  * the same steps and notes, with the local judge started through a child process it waits for without blocking.
676
738
  */
677
739
  export async function prepareLaunchAsync({ plan, platform, root, bin, err }) {
678
740
  prepareFiles({ plan, platform, root, err });
741
+ startMudeServer(plan);
679
742
  if (!plan.startJudge) return;
680
743
  const status = await new Promise((resolve) => {
681
744
  const child = spawn(pathFor(platform).join(bin, "kyrn-judge-local"), ["start"], {
@@ -793,6 +856,42 @@ export function planImport({ platform, env, argv, root, home, execPath, fs, stri
793
856
  return { command: execPath, args: [...entry, ...argv], env: childEnv, agentDir };
794
857
  }
795
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
+
796
895
  // ---------------------------------------------------------------------------------------------------------
797
896
  // mu setup
798
897
  // ---------------------------------------------------------------------------------------------------------
@@ -1504,6 +1603,23 @@ export async function main(argv = process.argv.slice(2)) {
1504
1603
  },
1505
1604
  });
1506
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
+ }
1507
1623
  if (command === "judge") {
1508
1624
  const plan = planJudge({ platform, env, argv: rest, bin, wsl });
1509
1625
  if (plan.kind === "script") return handOver({ command: plan.command, args: plan.args, env, strategy });