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 +8 -0
- package/judge/mu/index.js +62 -16
- package/package.json +2 -2
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
|
|
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
|
-
|
|
33296
|
-
|
|
33297
|
-
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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": "
|
|
45
|
+
"commit": "3285d1f30"
|
|
46
46
|
}
|
|
47
47
|
}
|