mu-agent 0.1.13 → 0.1.14

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.
@@ -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-data`,
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 (tiers.some((tier) => tier.startsWith("laya")) && !judgeSupport.runs && !judgeSupport.url) {
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>");
@@ -175,7 +197,7 @@ const fromFile = existsSync(envFile) ? parseEnvFile(readFileSync(envFile, "utf8"
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
199
  const jevTier = tiers.find((tier) => ["jev", "typesafe", "gateway"].includes((config?.judges?.[tier] ?? JEV_ROUTES[tier])?.type));
178
- if (jevTier) {
200
+ if (jevTier && asksJudge) {
179
201
  const judge = config?.judges?.[jevTier] ?? JEV_ROUTES[jevTier];
180
202
  if (judge.type === "jev") {
181
203
  // The order the registry tries: TypeSafe directly, Jev on OpenRouter, the Vercel AI Gateway, OpenCode Zen,
@@ -220,10 +242,12 @@ const classifierOf = (tier) =>
220
242
  tier.startsWith("classifier:")
221
243
  ? { type: "classifier", model: tier.slice("classifier:".length) }
222
244
  : (config?.judges?.[tier] ?? JEV_ROUTES[tier]);
223
- const askedTiers = [
224
- ...tiers,
225
- ...Object.values(config?.routes ?? {}).flatMap((value) => (Array.isArray(value) ? value : String(value).split(","))),
226
- ].map((tier) => String(tier).trim());
245
+ const askedTiers = asksJudge
246
+ ? [
247
+ ...tiers,
248
+ ...Object.values(config?.routes ?? {}).flatMap((value) => (Array.isArray(value) ? value : String(value).split(","))),
249
+ ].map((tier) => String(tier).trim())
250
+ : [];
227
251
  for (const model of new Set(askedTiers.map(classifierOf).filter((judge) => judge?.type === "classifier").map((judge) => judge.model))) {
228
252
  const provider = String(model ?? "").split("/")[0];
229
253
  // OpenCode's free models answer without a key (the client sends "public", as OpenCode's own does).
@@ -325,12 +349,16 @@ const canRelate = (tier) =>
325
349
  const browseTiers = config?.routes?.["browser.step"] ?? tiers;
326
350
  const routed = Object.entries(config?.routes ?? {});
327
351
  if (routed.length > 0) row("--", "routes", routed.map(([id, value]) => `${id} -> ${[value].flat().join(" > ")}`).join(" · "));
352
+ // mu's browser runs on the judge: with browser.step off it is switched off (src/extension/features/browser.ts).
353
+ const browseMode = config?.modes?.["browser.step"] ?? mode;
328
354
  row(
329
- [browseTiers].flat().some(canRelate) ? "ok" : "--",
355
+ browseMode !== "off" && [browseTiers].flat().some(canRelate) ? "ok" : "--",
330
356
  "browse",
331
- [browseTiers].flat().some(canRelate)
332
- ? `driven by ${[browseTiers].flat().join(" > ")}`
333
- : 'needs a judge that can match a goal to a page. In mu.json: "routes": { "browser.step": ["llm:openai/gpt-5.6-luna"] }',
357
+ browseMode === "off"
358
+ ? "off: mu's browser runs on the judge, and browser.step is off"
359
+ : [browseTiers].flat().some(canRelate)
360
+ ? `driven by ${[browseTiers].flat().join(" > ")}`
361
+ : 'needs a judge that can match a goal to a page. In mu.json: "routes": { "browser.step": ["llm:openai/gpt-5.6-luna"] }',
334
362
  );
335
363
 
336
364
  const marks = { ok: "\x1b[32m ok\x1b[0m", fix: "\x1b[33mfix\x1b[0m", "--": "\x1b[2m ·\x1b[0m" };
package/kyrn/bin/mu.mjs CHANGED
@@ -354,12 +354,26 @@ export function dependenciesInstalled({ root, platform, exists }) {
354
354
  }
355
355
  }
356
356
 
357
- export function installHint({ root, platform }) {
358
- const steps = [`cd ${root}`, "npm ci --ignore-scripts", "npm run hydrate:model-data"];
359
- // Windows PowerShell 5 has no `&&`.
357
+ /**
358
+ * Writes the model list pi reads at its start (packages/ai/src/providers/data, not committed) from the pinned catalog in
359
+ * the repository: offline and the same every time, as CI builds it.
360
+ */
361
+ const HYDRATE = "npm run hydrate:model-catalog";
362
+
363
+ /** A problem and the commands that fix it, one line to paste; Windows PowerShell 5 has no `&&`, so a list there. */
364
+ function stepsHint(problem, steps, platform) {
360
365
  return platform === "win32"
361
- ? `mu's dependencies aren't installed. Run:\n${steps.map((step) => ` ${step}`).join("\n")}`
362
- : `mu's dependencies aren't installed. Run: ${steps.join(" && ")}`;
366
+ ? `${problem}. Run:\n${steps.map((step) => ` ${step}`).join("\n")}`
367
+ : `${problem}. Run: ${steps.join(" && ")}`;
368
+ }
369
+
370
+ export function installHint({ root, platform }) {
371
+ return stepsHint("mu's dependencies aren't installed", [`cd ${root}`, "npm ci --ignore-scripts", HYDRATE], platform);
372
+ }
373
+
374
+ /** A checkout whose dependencies are in but whose model list was never written: a clone right after npm install. */
375
+ export function modelDataHint({ root, platform }) {
376
+ return stepsHint("mu's list of models isn't generated in this checkout yet", [`cd ${root}`, HYDRATE], platform);
363
377
  }
364
378
 
365
379
  /** What mu says on a Node that does not strip TypeScript types, which a checkout cannot run on. */
@@ -372,12 +386,16 @@ const NO_TYPE_STRIPPING =
372
386
  * pi's own source resolver (packages/coding-agent/src/experimental/source-resolver.ts: each workspace package
373
387
  * resolves to its sources, as tsconfig.json's paths say) and Node's compile cache (compile-cache.mjs), the way pi
374
388
  * 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 import.
389
+ * installed or whose model list is not generated, and one without the resolver are each said, not left to a failed
390
+ * import.
376
391
  */
377
392
  export function sourceRuntime({ root, platform, stripsTypes, exists }) {
378
393
  const path = pathFor(platform);
379
394
  if (!stripsTypes) return { error: NO_TYPE_STRIPPING };
380
395
  if (!dependenciesInstalled({ root, platform, exists })) return { error: installHint({ root, platform }) };
396
+ // Without it pi fails at its first import with ERR_MODULE_NOT_FOUND for a provider's .json.
397
+ if (!exists(path.join(root, "packages", "ai", "src", "providers", "data", ".manifest.json")))
398
+ return { error: modelDataHint({ root, platform }) };
381
399
  const resolver = path.join(root, "packages", "coding-agent", "src", "experimental", "source-resolver.ts");
382
400
  if (!exists(resolver)) return { error: `pi's source resolver is missing from this checkout (${resolver})` };
383
401
  // As URLs: `--import` reads a Windows path such as C:\... as a URL whose scheme is c:.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-agent",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "description": "mu: a coding agent that hands its routine decisions to a small, fast judge model, so the main model can keep its attention on the work. Built on pi.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,6 +42,6 @@
42
42
  "muBuild": {
43
43
  "pi": "1.3.0",
44
44
  "judge": "0.1.0",
45
- "commit": "47bdfac19"
45
+ "commit": "36ffdd935"
46
46
  }
47
47
  }