mu-agent 0.2.11 → 0.2.12

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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.12] - 2026-10-25
6
+
7
+ **0.2.12: in judge mode, `tool.approval` approves a check (`isCheckChain`) at needed 0.7; everything else keeps 0.8.**
8
+
9
+ ### Changed
10
+
11
+ - `tool.approval` (version 2) approves a check at `needed` ≥ 0.7 (`CHECK_APPROVE_AT`); everything else keeps `APPROVE_AT` 0.8. The permission gate passes `check` when the call is a shell call that `isCheckChain` accepts: every part is a check (`CHECK_RUN`: `npm|pnpm|yarn|bun [run] test|build|check|lint|typecheck`, `vitest`, `jest`, `pytest`, `tsc`, `eslint`, `biome`, `ruff`, `mypy` and others, directly, after `npx`/`bunx`/`pnpm exec`/`yarn` or `python3 -m`; `cargo|go|dotnet|swift|make|deno test|build|check|vet|clippy`; `node --test`; `test.sh` in the project, run as `./`, by path or with `sh`/`bash`), a `cd`, or a read-only command as `isReadOnlyChain` reads it, with at least one check. A check may be preceded by `env` and `PYTHONPATH`, `CI`, `NODE_ENV`, `FORCE_COLOR`, `NO_COLOR` or `RUST_BACKTRACE` (no `$` in the value), and may write its output to a file under `/tmp`. It must run in the conversation's folder: a `cd` elsewhere, or a `bg_start` `cwd` elsewhere, rules it out, as does PowerShell. `gradle` and `mvn` are left out (`mvn deploy`). The judge's state is unchanged; it is not told. `beyond` and `unrelated` still ask. Recorded with MiMo v2.6 Pro on 0.2.11: `cd <cwd> && npm run check 2>&1` (needed 0.78) and `cd <cwd> && ./test.sh >/tmp/pi-test.log 2>&1; echo "exit=$?"; grep … | tail -10` (0.79) asked the user.
12
+
5
13
  ## [0.2.11] - 2026-10-25
6
14
 
7
15
  **0.2.11: in judge mode, read-only git commands go ahead after a `cd` into the project, and more of git counts as read-only.**
package/judge/mu/index.js CHANGED
@@ -33205,7 +33205,16 @@ function harmlessRedirect(text9, at, both) {
33205
33205
  if (!text9.startsWith("/dev/null", index) || !/^(?:[\s;&|]|$)/.test(text9.slice(end, end + 1))) return void 0;
33206
33206
  return end - 1;
33207
33207
  }
33208
- function simpleCommands(text9) {
33208
+ function fileRedirect(text9, at, writes) {
33209
+ let index = at + 1;
33210
+ if (text9[index] === ">") index++;
33211
+ while (text9[index] === " " || text9[index] === " ") index++;
33212
+ const name = /^[\w./@%+,:=-]+/.exec(text9.slice(index))?.[0];
33213
+ if (!name || !/^(?:[\s;&|]|$)/.test(text9.slice(index + name.length, index + name.length + 1))) return void 0;
33214
+ writes.push(name);
33215
+ return index + name.length - 1;
33216
+ }
33217
+ function simpleCommands(text9, writes) {
33209
33218
  if (text9.includes("\r")) return void 0;
33210
33219
  const commands = [];
33211
33220
  let words2 = [];
@@ -33238,7 +33247,7 @@ function simpleCommands(text9) {
33238
33247
  endCommand();
33239
33248
  } else if (next === ">") {
33240
33249
  endWord();
33241
- const end = harmlessRedirect(text9, index + 1, true);
33250
+ const end = harmlessRedirect(text9, index + 1, true) ?? (writes ? fileRedirect(text9, index + 1, writes) : void 0);
33242
33251
  if (end === void 0) return void 0;
33243
33252
  index = end;
33244
33253
  } else return void 0;
@@ -33247,7 +33256,7 @@ function simpleCommands(text9) {
33247
33256
  word = "";
33248
33257
  started = false;
33249
33258
  } else endWord();
33250
- const end = harmlessRedirect(text9, index, false);
33259
+ const end = harmlessRedirect(text9, index, false) ?? (writes ? fileRedirect(text9, index, writes) : void 0);
33251
33260
  if (end === void 0) return void 0;
33252
33261
  index = end;
33253
33262
  } else if (char === "'") {
@@ -33292,17 +33301,21 @@ function isReadOnlyChain(command, powershell = false, cwd, start = cwd) {
33292
33301
  if (!commands?.some((words2) => words2.length > 0)) return false;
33293
33302
  let elsewhere = startsElsewhere(cwd, start);
33294
33303
  return commands.every((words2) => {
33295
- const [program] = words2;
33296
- if (program === void 0 || program === "true" || program === "false" || program === ":") return true;
33297
- if (program === "cd") {
33298
- const [, to, ...more] = words2;
33299
- elsewhere = cwd === void 0 || to === void 0 || more.length > 0 || !isAbsolute2(to) || /[$*?[~]/.test(to) || to.split(/[\\/]/).includes("..") || !samePlace(to, cwd);
33300
- return true;
33301
- }
33302
- if (program === "find") return !words2.some((word) => FIND_ACTS.test(word));
33303
- return readOnlyProgram(words2.join(" "), elsewhere);
33304
+ if (words2[0] !== "cd") return readOnlyPart(words2, elsewhere);
33305
+ elsewhere = !cdHome(words2, cwd);
33306
+ return true;
33304
33307
  });
33305
33308
  }
33309
+ function cdHome(words2, cwd) {
33310
+ const [, to, ...more] = words2;
33311
+ return cwd !== void 0 && to !== void 0 && more.length === 0 && isAbsolute2(to) && !/[$*?[~]/.test(to) && !to.split(/[\\/]/).includes("..") && samePlace(to, cwd);
33312
+ }
33313
+ function readOnlyPart(words2, elsewhere) {
33314
+ const [program] = words2;
33315
+ if (program === void 0 || program === "true" || program === "false" || program === ":") return true;
33316
+ if (program === "find") return !words2.some((word) => FIND_ACTS.test(word));
33317
+ return readOnlyProgram(words2.join(" "), elsewhere);
33318
+ }
33306
33319
  function isMutatingCall(toolName, input, cwd) {
33307
33320
  if (READ_ONLY_TOOLS.has(toolName)) return false;
33308
33321
  if (isShellTool(toolName)) return !isReadOnlyChain(String(input.command ?? ""), toolName === "powershell", cwd);
@@ -33315,6 +33328,33 @@ var CHECK_COMMAND = /\b(?:vitest|jest|pytest|mocha|ava|tsc|eslint|biome|ruff|myp
33315
33328
  function isCheckCommand(command) {
33316
33329
  return CHECK_COMMAND.test(command);
33317
33330
  }
33331
+ var CHECK_RUN = /^(?:(?:(?:npx|bunx)\s+|(?:pnpm|yarn)\s+(?:exec\s+)?|python3?\s+-m\s+)?(?:vitest|jest|pytest|mocha|ava|tsc|eslint|biome|ruff|mypy|rspec|phpunit|ctest|tox|nox)|(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:test|build|check|lint|typecheck)|(?:cargo|go|dotnet|swift|make|deno)\s+(?:test|build|check|vet|clippy)|node\s+--test|(?:(?:ba)?sh\s+(?:\.\/)?|\.\/)(?:[\w-][\w.-]*\/)*test\.sh|(?:[\w-][\w.-]*\/)+test\.sh)(?:\s|$)/;
33332
+ var CHECK_SETTING = /^(?:PYTHONPATH|CI|NODE_ENV|FORCE_COLOR|NO_COLOR|RUST_BACKTRACE)=[^$]*$/;
33333
+ function tempFile(name) {
33334
+ return /^\/(?:private\/)?tmp\//.test(name) && !name.split("/").includes("..");
33335
+ }
33336
+ function isCheckChain(command, powershell = false, cwd, start = cwd) {
33337
+ if (powershell) return false;
33338
+ const writes = [];
33339
+ const commands = simpleCommands(command.trim(), writes);
33340
+ if (!commands || !writes.every(tempFile)) return false;
33341
+ let elsewhere = startsElsewhere(cwd, start);
33342
+ let checks = 0;
33343
+ const parts = commands.every((words2) => {
33344
+ if (words2[0] === "cd") {
33345
+ elsewhere = !cdHome(words2, cwd);
33346
+ return true;
33347
+ }
33348
+ let at = words2[0] === "env" ? 1 : 0;
33349
+ while (at < words2.length && CHECK_SETTING.test(words2[at])) at++;
33350
+ if (CHECK_RUN.test(words2.slice(at).join(" "))) {
33351
+ checks++;
33352
+ return !elsewhere;
33353
+ }
33354
+ return at === 0 && readOnlyPart(words2, elsewhere);
33355
+ });
33356
+ return parts && checks > 0;
33357
+ }
33318
33358
 
33319
33359
  // packages/kyrn-judge/src/decisions/board-read.ts
33320
33360
  var MAX_EVENTS = 16;
@@ -57281,9 +57321,10 @@ import { join as join48, resolve as resolve14, sep as sep3 } from "node:path";
57281
57321
 
57282
57322
  // packages/kyrn-judge/src/decisions/tool-approval.ts
57283
57323
  var APPROVE_AT = 0.8;
57324
+ var CHECK_APPROVE_AT = 0.7;
57284
57325
  var toolApproval = defineDecision({
57285
57326
  id: "tool.approval",
57286
- version: 1,
57327
+ version: 2,
57287
57328
  cacheImpact: "none",
57288
57329
  latency: "inline",
57289
57330
  capabilities: "relate",
@@ -57303,11 +57344,12 @@ var toolApproval = defineDecision({
57303
57344
  buildState(input) {
57304
57345
  return { tool_call: input.call, where: input.where, task: input.task, user_message: input.userMessage };
57305
57346
  },
57306
- policy(answers) {
57347
+ policy(answers, input) {
57307
57348
  const { choice, probabilities } = answers.verdict;
57308
57349
  if (isEscapeOption(choice)) return "unsure";
57309
57350
  const p = probabilities?.[choice];
57310
- if (choice === "needed") return p === void 0 || p >= APPROVE_AT ? "approve" : "unsure";
57351
+ const line2 = input.check ? CHECK_APPROVE_AT : APPROVE_AT;
57352
+ if (choice === "needed") return p === void 0 || p >= line2 ? "approve" : "unsure";
57311
57353
  return choice === "beyond" || choice === "unrelated" ? choice : "unsure";
57312
57354
  },
57313
57355
  // Nobody could look at it: the user decides.
@@ -57707,6 +57749,9 @@ function registerPermissions(runtime, roots) {
57707
57749
  return { approved: asked2 && (decision2.judged ?? decision2.outcome) === "allow", reason: "flagged" };
57708
57750
  }
57709
57751
  const frame = runtime.frame;
57752
+ const input = event.input;
57753
+ const powershell = event.toolName === "powershell" || event.toolName === "bg_start" && process.platform === "win32";
57754
+ const check = need.kind === "shell" && isCheckChain(String(input.command ?? ""), powershell, ctx.cwd, shellStart(event.toolName, input, ctx.cwd));
57710
57755
  const decision = await runtime.engine.decide(
57711
57756
  toolApproval,
57712
57757
  {
@@ -57716,7 +57761,8 @@ function registerPermissions(runtime, roots) {
57716
57761
  ),
57717
57762
  userMessage,
57718
57763
  call,
57719
- where: need.kind === "outside" ? "outside the project folder" : need.kind === "shell" || need.kind === "run" ? "a program run in the project folder" : need.kind === "delegate" ? "subagents working on the project" : "an action outside this computer or program, such as a web page or a connected service"
57764
+ where: need.kind === "outside" ? "outside the project folder" : need.kind === "shell" || need.kind === "run" ? "a program run in the project folder" : need.kind === "delegate" ? "subagents working on the project" : "an action outside this computer or program, such as a web page or a connected service",
57765
+ check
57720
57766
  },
57721
57767
  { signal: ctx.signal }
57722
57768
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-agent",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "description": "mu: most of what a coding agent decides isn't code. mu hands 40 kinds of yes/no calls to a small judge model: fewer wasted tokens, fewer approvals on autopilot, fewer runs that stop halfway. 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": "3679ea8db"
45
+ "commit": "3285d1f30"
46
46
  }
47
47
  }