mu-agent 0.2.4 → 0.2.5
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 +14 -0
- package/README.md +3 -3
- package/judge/dist/auth.js +56 -8
- package/judge/dist/mude.js +38 -21
- package/judge/dist/setup.js +44 -27
- package/judge/manifest.json +15 -28
- package/judge/mu/index.js +953 -540
- package/kyrn/bin/kyrn-doctor +11 -25
- package/kyrn/bin/mu.mjs +84 -106
- package/package.json +2 -2
- package/kyrn/bin/kyrn-judge-local +0 -160
- package/kyrn/local-judge/requirements.lock.txt +0 -26
- package/kyrn/local-judge/server.py +0 -344
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.5] - 2026-10-24
|
|
6
|
+
|
|
7
|
+
**0.2.5 removes Laya from mu's built-in judges. A configuration that still names it switches to mude 1.0 or Jev on its own.**
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- A configuration that still names Laya keeps working. `laya` in `tiers` or a route, and a judge of type `local`, are read without it: mude 1.0 takes its place when it is installed on this Mac and its key (`MU_JUDGE_MUDE_KEY`) is set, otherwise the judges left do, or Jev when none is left. A route left with no judge goes back to `tiers`. The first session with a terminal or the desktop app (not `mu --print` or json mode) writes that to mu.json once. It changes only those values, keeps a copy in `~/.mu/backups`, and says so once: `judge.notice` with the code `laya_removed` and the `judge`, then the line itself. A mu.json that isn't plain JSON is left alone. `MU_JUDGE=laya` is skipped in every session that has it. For such a configuration the launcher starts mude's server in the background, as it does for `mude`.
|
|
12
|
+
- `/mu judge laya` and `/mu route <point> laya` say that Laya is no longer built in. An `http` judge needs a `baseUrl`; its client is `HttpJudgeProvider` (it was `LocalJudgeProvider`).
|
|
13
|
+
|
|
14
|
+
### Removed
|
|
15
|
+
|
|
16
|
+
- Laya, the third-party local judge, is no longer built in: the `laya` judge, the `local` judge type, its sidecar (`kyrn/bin/kyrn-judge-local`, `kyrn/local-judge/server.py` and its lock, no longer in the package) and `mu judge setup | start | stop | status | run`. Those commands now say so and point to `mu judge mude install` and `mu judge model add <link>`; `mu judge` alone prints the judge commands. The launcher no longer starts a sidecar, and `mu doctor` drops its rows.
|
|
17
|
+
- `continuation.localPromised`, which only Laya used, and the one-question-at-a-time path of a `local` judge: `tool.admission` and `tool.injection` always ask in batches, and the questions about a message start together.
|
|
18
|
+
|
|
5
19
|
## [0.2.4] - 2026-10-24
|
|
6
20
|
|
|
7
21
|
**0.2.4 brings Gemini 4 Argon through Google Antigravity and fixes third-party OpenAI-compatible providers that turned away the `developer` role.**
|
package/README.md
CHANGED
|
@@ -27,13 +27,13 @@ mu is built on [pi](https://github.com/earendil-works/pi). It hands the small qu
|
|
|
27
27
|
|
|
28
28
|
## Choose a judge
|
|
29
29
|
|
|
30
|
-
Out of the box, the judge is Jev 1.13, TypeSafe's hosted judge model, free on OpenCode Zen for a limited time and with no key needed. What it judges goes to OpenCode, which doesn't train on it, and mu reminds you of that once a day. To send nothing anywhere, run `mu setup --judge off` (or choose "Not now" when `mu setup` asks): no decision point asks a judge, and mu's features keep working on their rules. Or
|
|
30
|
+
Out of the box, the judge is Jev 1.13, TypeSafe's hosted judge model, free on OpenCode Zen for a limited time and with no key needed. What it judges goes to OpenCode, which doesn't train on it, and mu reminds you of that once a day. To send nothing anywhere, run `mu setup --judge off` (or choose "Not now" when `mu setup` asks): no decision point asks a judge, and mu's features keep working on their rules. Or, on a Mac with Apple silicon, use mude 1.0 with `--local-only`.
|
|
31
31
|
|
|
32
32
|
- **Jev** (hosted): set `TYPESAFE_API_KEY` (TypeSafe), `MU_JUDGE_OPENROUTER_API_KEY` (Jev on OpenRouter), `AI_GATEWAY_API_KEY` (Vercel AI Gateway), `OPENCODE_API_KEY` (OpenCode Zen) or `CLOUDFLARE_API_KEY` with `CLOUDFLARE_ACCOUNT_ID` (Cloudflare Workers AI) in your environment, or add it to `~/.mu/.env` as `KEY=value`. `mu setup` asks for a key and writes it there. mu reads that file as data, never prints it and starts using the key automatically.
|
|
33
33
|
- **Another classifier** from pi's catalog: `/mu judge classifier:<provider>/<model>`, for example Cloudflare's Clef (`clef`, `clef-flash`) or a System One model on OpenRouter, with that provider's key.
|
|
34
34
|
- **CLM** (self-hosted; its encoder needs a GPU): start `clm-serve` from [Contrastive-LM/CLM](https://github.com/Contrastive-LM/CLM), then run `/mu judge clm`. For a server on another machine, add `"judges": { "clm": { "type": "clm", "baseUrl": "http://<host>:8700" } }` to `~/.mu/agent/mu.json`. If the server was started with `CLM_API_KEY`, set the same value in `MU_JUDGE_CLM_API_KEY`. `mu doctor` checks the server.
|
|
35
35
|
- **mude 1.0** (local, a Mac with Apple silicon, macOS 14+), mu's own decision model, optimized for coding: `mu judge mude install` downloads it (1.8 GB), sets it up in `~/.mu/local-judge/mude`, starts its server on 127.0.0.1 and puts it first. What it can't answer goes to the judges you had before, which are hosted; `mu judge mude enable --local-only` keeps everything on your Mac. Its server uses about 4 GB of memory until `mu judge mude stop`.
|
|
36
|
-
- **
|
|
36
|
+
- **Other decision models from Hugging Face** (local, a Mac with Apple silicon): `mu judge model add <link>` adds one that is packaged like mude 1.0 and makes it the judge. Laya is no longer included: a `mu.json` that still names it gets mude 1.0 in its place when mude is set up, otherwise the remaining judges or Jev, and mu says so once.
|
|
37
37
|
- **Any model you already use:** `/mu judge llm:<provider>/<model>`.
|
|
38
38
|
|
|
39
39
|
34 of the 40 decision points act on their verdicts from the start (`active`). To only record what the judge would do, run `/mu mode default shadow`, or put `"modes": { "default": "shadow" }` in `~/.mu/agent/mu.json`.
|
|
@@ -47,7 +47,7 @@ mu -p "prompt" run one prompt, print the answer and exit
|
|
|
47
47
|
mu -c | -r continue the last session | pick a session to resume
|
|
48
48
|
mu setup connect a model and choose a judge (see mu setup --help for scripting and services)
|
|
49
49
|
mu judge mude <cmd> mude 1.0 on this Mac: install | enable | disable | start | stop | status | uninstall
|
|
50
|
-
mu judge <cmd>
|
|
50
|
+
mu judge model <cmd> other decision models from Hugging Face: add <link> | list | use | remove
|
|
51
51
|
mu ledger [n] [--json] print the judge's verdicts from the last n sessions
|
|
52
52
|
mu import <file>... import Claude Code, Codex and ZCode conversations; --list shows them (mu import --help)
|
|
53
53
|
mu doctor check the installation, the judges and the connections
|
package/judge/dist/auth.js
CHANGED
|
@@ -73,12 +73,13 @@ var DEFAULT_CONFIG = {
|
|
|
73
73
|
recordState: false
|
|
74
74
|
};
|
|
75
75
|
var MODES = ["off", "shadow", "active"];
|
|
76
|
+
var LAYA = "laya";
|
|
77
|
+
var RETIRED_TYPE = "local";
|
|
76
78
|
var JUDGE_TYPES = [
|
|
77
79
|
"jev",
|
|
78
80
|
"typesafe",
|
|
79
81
|
"clm",
|
|
80
82
|
"gateway",
|
|
81
|
-
"local",
|
|
82
83
|
"http",
|
|
83
84
|
"llm",
|
|
84
85
|
"classifier",
|
|
@@ -118,14 +119,48 @@ function readJudges(value) {
|
|
|
118
119
|
}
|
|
119
120
|
return judges;
|
|
120
121
|
}
|
|
121
|
-
function
|
|
122
|
+
function retiredJudges(judges) {
|
|
123
|
+
const names = /* @__PURE__ */ new Set();
|
|
124
|
+
const entries = isRecord(judges) ? judges : {};
|
|
125
|
+
const own = entries[LAYA];
|
|
126
|
+
if (!(isRecord(own) && typeof own.type === "string" && JUDGE_TYPES.includes(own.type))) names.add(LAYA);
|
|
127
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
128
|
+
if (isRecord(entry) && entry.type === RETIRED_TYPE) names.add(name);
|
|
129
|
+
}
|
|
130
|
+
return names;
|
|
131
|
+
}
|
|
132
|
+
function withoutRetired(tiers, retired, replacement) {
|
|
133
|
+
if (!tiers.some((tier) => retired.has(tier.trim()))) return void 0;
|
|
134
|
+
const mapped = tiers.flatMap((tier) => retired.has(tier.trim()) ? replacement ? [replacement] : [] : [tier]);
|
|
135
|
+
return mapped.filter((tier, index) => tier !== replacement || mapped.indexOf(tier) === index);
|
|
136
|
+
}
|
|
137
|
+
function namesRetired(value) {
|
|
138
|
+
if (!isRecord(value)) return false;
|
|
139
|
+
const retired = retiredJudges(value.judges);
|
|
140
|
+
const lists = [
|
|
141
|
+
Array.isArray(value.tiers) ? value.tiers : [],
|
|
142
|
+
...isRecord(value.routes) ? Object.values(value.routes) : []
|
|
143
|
+
];
|
|
144
|
+
return lists.some((tiers) => readTiers(tiers).some((tier) => retired.has(tier)));
|
|
145
|
+
}
|
|
146
|
+
function parseConfig(value, options = {}) {
|
|
122
147
|
if (!isRecord(value)) return DEFAULT_CONFIG;
|
|
123
|
-
const
|
|
148
|
+
const named = Array.isArray(value.tiers) ? value.tiers.filter((tier) => typeof tier === "string") : [];
|
|
149
|
+
const retired = retiredJudges(value.judges);
|
|
150
|
+
const replacement = namesRetired(value) ? options.replaceLaya?.() : void 0;
|
|
151
|
+
const tiers = withoutRetired(named, retired, replacement) ?? named;
|
|
152
|
+
const routes = readRoutes(value.routes);
|
|
153
|
+
for (const [specId, routed] of Object.entries(routes)) {
|
|
154
|
+
const kept = withoutRetired(routed, retired, replacement);
|
|
155
|
+
if (kept === void 0) continue;
|
|
156
|
+
if (kept.length > 0) routes[specId] = kept;
|
|
157
|
+
else delete routes[specId];
|
|
158
|
+
}
|
|
124
159
|
return {
|
|
125
160
|
tiers: tiers.length > 0 ? tiers : DEFAULT_CONFIG.tiers,
|
|
126
161
|
judges: readJudges(value.judges),
|
|
127
162
|
modes: { ...DEFAULT_CONFIG.modes, ...readModes(value.modes) },
|
|
128
|
-
routes
|
|
163
|
+
routes,
|
|
129
164
|
features: isRecord(value.features) ? value.features : {},
|
|
130
165
|
mcp: isRecord(value.mcp) ? value.mcp : void 0,
|
|
131
166
|
writer: typeof value.writer === "string" ? value.writer : void 0,
|
|
@@ -136,9 +171,18 @@ function loadConfig(source = {}) {
|
|
|
136
171
|
const env = source.env ?? process.env;
|
|
137
172
|
let config = DEFAULT_CONFIG;
|
|
138
173
|
let problem;
|
|
174
|
+
let inFile = false;
|
|
175
|
+
let replacement;
|
|
139
176
|
for (const file of source.dir ? [CONFIG_FILE, LEGACY_CONFIG_FILE] : []) {
|
|
140
177
|
try {
|
|
141
|
-
|
|
178
|
+
const value = JSON.parse(readFileSync(join2(source.dir, file), "utf8"));
|
|
179
|
+
inFile = namesRetired(value);
|
|
180
|
+
config = parseConfig(value, {
|
|
181
|
+
replaceLaya: () => {
|
|
182
|
+
replacement = source.replaceLaya?.();
|
|
183
|
+
return replacement;
|
|
184
|
+
}
|
|
185
|
+
});
|
|
142
186
|
break;
|
|
143
187
|
} catch (error) {
|
|
144
188
|
if (error.code === "ENOENT") continue;
|
|
@@ -146,17 +190,21 @@ function loadConfig(source = {}) {
|
|
|
146
190
|
break;
|
|
147
191
|
}
|
|
148
192
|
}
|
|
193
|
+
let inEnv = false;
|
|
149
194
|
const judge = muEnv("JUDGE", env)?.trim();
|
|
150
195
|
if (judge === "off") return { config, disabled: true, problem };
|
|
151
196
|
if (judge) {
|
|
152
|
-
const tiers = judge.split(",").map((tier) => tier.trim() === "gateway" ? "jev" : tier.trim());
|
|
153
|
-
|
|
197
|
+
const tiers = judge.split(",").map((tier) => tier.trim() === "gateway" ? "jev" : tier.trim()).filter(Boolean);
|
|
198
|
+
const kept = config.judges[LAYA] ? tiers : tiers.filter((tier) => tier !== LAYA);
|
|
199
|
+
inEnv = kept.length < tiers.length;
|
|
200
|
+
if (kept.length > 0) config = { ...config, tiers: kept };
|
|
154
201
|
}
|
|
155
202
|
const mode = muEnv("JUDGE_MODE", env);
|
|
156
203
|
if (mode && MODES.includes(mode)) config = { ...config, modes: { ...config.modes, default: mode } };
|
|
157
204
|
const writer = muEnv("WRITER", env);
|
|
158
205
|
if (writer) config = { ...config, writer };
|
|
159
|
-
|
|
206
|
+
const laya = inFile || inEnv ? { file: inFile, env: inEnv, replacement } : void 0;
|
|
207
|
+
return { config, disabled: false, problem, ...laya ? { laya } : {} };
|
|
160
208
|
}
|
|
161
209
|
function featureOptions(config, name, defaults) {
|
|
162
210
|
const value = config.features[name];
|
package/judge/dist/mude.js
CHANGED
|
@@ -38,18 +38,6 @@ var BUILT_IN_JUDGES = {
|
|
|
38
38
|
"clef-flash": { type: "classifier", model: "cloudflare-workers-ai/@cf/cloudflare/clef-flash" },
|
|
39
39
|
// CLM-8B on this machine, where `clm-serve` listens by default. Its profile is not measured on mu's questions yet.
|
|
40
40
|
clm: { type: "clm" },
|
|
41
|
-
// Measured in kyrn/docs/03-local-judge.md: the base checkpoint classifies one text well, says "yes" to
|
|
42
|
-
// nearly every relational question, and cannot use a rubric or judge the request itself. Measured on
|
|
43
|
-
// Terminal-Bench 2.1 (Oct 2026): on tool output it is wrong or late. All 15 passages it withheld as injections were
|
|
44
|
-
// clean (directory listings, href lists, headers, JSON), its one admission drop was a makefile, and most of its
|
|
45
|
-
// calls on both failed or came too late. So screening tool output (`screen`) goes to the next tier, or to the
|
|
46
|
-
// rules. It also read a finished "Done. Created /app/meeting_scheduled.ics …" as a promise of more work, twice in one
|
|
47
|
-
// run (constraints-scheduling): 4 extra requests and the model searching / for test files. So whether a closing
|
|
48
|
-
// message promised more (`intent`, turn.continue) goes to the next tier too; `continuation.localPromised` asks it.
|
|
49
|
-
laya: {
|
|
50
|
-
type: "local",
|
|
51
|
-
profile: { capabilities: { relate: false, rate: false, meta: false, screen: false, intent: false } }
|
|
52
|
-
},
|
|
53
41
|
// mude, the decision model trained for mu's judgment layer, on this Mac (`mu judge mude install`, Apple silicon):
|
|
54
42
|
// the System One server that comes in its package, on 127.0.0.1:8790, with the key the install wrote to the .env.
|
|
55
43
|
// Trusted on every capability: on mu's own dev scenarios (an internal benchmark, not public yet) it did as well as Jev
|
|
@@ -66,6 +54,31 @@ var BUILT_IN_JUDGES = {
|
|
|
66
54
|
},
|
|
67
55
|
mock: { type: "mock" }
|
|
68
56
|
};
|
|
57
|
+
var LAYA = "laya";
|
|
58
|
+
var RETIRED_TYPE = "local";
|
|
59
|
+
var JUDGE_TYPES = [
|
|
60
|
+
"jev",
|
|
61
|
+
"typesafe",
|
|
62
|
+
"clm",
|
|
63
|
+
"gateway",
|
|
64
|
+
"http",
|
|
65
|
+
"llm",
|
|
66
|
+
"classifier",
|
|
67
|
+
"mock"
|
|
68
|
+
];
|
|
69
|
+
function isRecord(value) {
|
|
70
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
71
|
+
}
|
|
72
|
+
function retiredJudges(judges) {
|
|
73
|
+
const names = /* @__PURE__ */ new Set();
|
|
74
|
+
const entries = isRecord(judges) ? judges : {};
|
|
75
|
+
const own = entries[LAYA];
|
|
76
|
+
if (!(isRecord(own) && typeof own.type === "string" && JUDGE_TYPES.includes(own.type))) names.add(LAYA);
|
|
77
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
78
|
+
if (isRecord(entry) && entry.type === RETIRED_TYPE) names.add(name);
|
|
79
|
+
}
|
|
80
|
+
return names;
|
|
81
|
+
}
|
|
69
82
|
|
|
70
83
|
// packages/kyrn-judge/src/setup/env-file.ts
|
|
71
84
|
var DEFINITION = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
|
|
@@ -255,7 +268,7 @@ function writeJsonFile(path, value) {
|
|
|
255
268
|
writeFileAtomic(path, `${JSON.stringify(value, null, 2)}
|
|
256
269
|
`);
|
|
257
270
|
}
|
|
258
|
-
function
|
|
271
|
+
function isRecord2(value) {
|
|
259
272
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
260
273
|
}
|
|
261
274
|
function judgeConfigPath(agentDir, exists = existsSync) {
|
|
@@ -272,10 +285,10 @@ function withTiers(config, tiers) {
|
|
|
272
285
|
return { ...config, tiers: [...tiers] };
|
|
273
286
|
}
|
|
274
287
|
function judgeOff(config) {
|
|
275
|
-
return
|
|
288
|
+
return isRecord2(config?.modes) && config.modes.default === "off";
|
|
276
289
|
}
|
|
277
290
|
function withJudge(config, on) {
|
|
278
|
-
const { default: _default, ...modes } =
|
|
291
|
+
const { default: _default, ...modes } = isRecord2(config.modes) ? config.modes : {};
|
|
279
292
|
if (!on) return { ...config, modes: { default: "off", ...modes } };
|
|
280
293
|
if (!judgeOff(config)) return config;
|
|
281
294
|
const { modes: _modes, ...rest } = config;
|
|
@@ -596,8 +609,10 @@ function enableLocal(judge, paths, files, options) {
|
|
|
596
609
|
const name = judge.name;
|
|
597
610
|
const { path, config } = readConfig(files);
|
|
598
611
|
const state = readState(paths);
|
|
599
|
-
const
|
|
600
|
-
const
|
|
612
|
+
const retired = retiredJudges(config.judges);
|
|
613
|
+
const current = tiersOf(config).filter((tier) => tier !== name && !retired.has(tier));
|
|
614
|
+
const kept = (state?.previousTiers ?? []).filter((tier) => !retired.has(tier));
|
|
615
|
+
const before = current.length > 0 ? current : kept.length > 0 ? kept : ["jev"];
|
|
601
616
|
const tiers = options.localOnly ? [name] : [name, ...before.filter((tier) => tier !== name)];
|
|
602
617
|
const judgeWasOff = state?.judgeWasOff ?? judgeOff(config);
|
|
603
618
|
let next = withJudge(withTiers(withoutOwnRoutes(config, state), tiers), true);
|
|
@@ -640,8 +655,10 @@ function disableMude(paths, files, options = {}) {
|
|
|
640
655
|
function disableLocal(judge, paths, files, options = {}) {
|
|
641
656
|
const { path, config } = readConfig(files);
|
|
642
657
|
const state = readState(paths);
|
|
643
|
-
const
|
|
644
|
-
const
|
|
658
|
+
const retired = retiredJudges(config.judges);
|
|
659
|
+
const rest = tiersOf(config).filter((tier) => tier !== judge.name && !retired.has(tier));
|
|
660
|
+
const previous = (state?.previousTiers ?? []).filter((tier) => !retired.has(tier));
|
|
661
|
+
const tiers = rest.length > 0 ? rest : previous.length > 0 ? previous : ["jev"];
|
|
645
662
|
let next = withTiers(withoutOwnRoutes(config, state), tiers);
|
|
646
663
|
if (state?.judgeWasOff) next = withJudge(next, false);
|
|
647
664
|
if (options.removeEntry && state?.wroteEntry) {
|
|
@@ -1131,7 +1148,7 @@ function writeRecord(muHome, record) {
|
|
|
1131
1148
|
writeFileSync5(join5(root, "model.json"), `${JSON.stringify(record, null, 1)}
|
|
1132
1149
|
`);
|
|
1133
1150
|
}
|
|
1134
|
-
function
|
|
1151
|
+
function isRecord3(value) {
|
|
1135
1152
|
if (typeof value !== "object" || value === null) return false;
|
|
1136
1153
|
const record = value;
|
|
1137
1154
|
return typeof record.id === "string" && typeof record.repo === "string" && typeof record.revision === "string" && typeof record.bytes === "number" && Array.isArray(record.files);
|
|
@@ -1147,7 +1164,7 @@ function listRecords(muHome) {
|
|
|
1147
1164
|
for (const name of names.sort()) {
|
|
1148
1165
|
try {
|
|
1149
1166
|
const value = JSON.parse(readFileSync5(join5(modelsDir(muHome), name, "model.json"), "utf8"));
|
|
1150
|
-
if (
|
|
1167
|
+
if (isRecord3(value) && value.id === name) records.push(value);
|
|
1151
1168
|
} catch {
|
|
1152
1169
|
}
|
|
1153
1170
|
}
|
package/judge/dist/setup.js
CHANGED
|
@@ -100,18 +100,6 @@ var BUILT_IN_JUDGES = {
|
|
|
100
100
|
"clef-flash": { type: "classifier", model: "cloudflare-workers-ai/@cf/cloudflare/clef-flash" },
|
|
101
101
|
// CLM-8B on this machine, where `clm-serve` listens by default. Its profile is not measured on mu's questions yet.
|
|
102
102
|
clm: { type: "clm" },
|
|
103
|
-
// Measured in kyrn/docs/03-local-judge.md: the base checkpoint classifies one text well, says "yes" to
|
|
104
|
-
// nearly every relational question, and cannot use a rubric or judge the request itself. Measured on
|
|
105
|
-
// Terminal-Bench 2.1 (Oct 2026): on tool output it is wrong or late. All 15 passages it withheld as injections were
|
|
106
|
-
// clean (directory listings, href lists, headers, JSON), its one admission drop was a makefile, and most of its
|
|
107
|
-
// calls on both failed or came too late. So screening tool output (`screen`) goes to the next tier, or to the
|
|
108
|
-
// rules. It also read a finished "Done. Created /app/meeting_scheduled.ics …" as a promise of more work, twice in one
|
|
109
|
-
// run (constraints-scheduling): 4 extra requests and the model searching / for test files. So whether a closing
|
|
110
|
-
// message promised more (`intent`, turn.continue) goes to the next tier too; `continuation.localPromised` asks it.
|
|
111
|
-
laya: {
|
|
112
|
-
type: "local",
|
|
113
|
-
profile: { capabilities: { relate: false, rate: false, meta: false, screen: false, intent: false } }
|
|
114
|
-
},
|
|
115
103
|
// mude, the decision model trained for mu's judgment layer, on this Mac (`mu judge mude install`, Apple silicon):
|
|
116
104
|
// the System One server that comes in its package, on 127.0.0.1:8790, with the key the install wrote to the .env.
|
|
117
105
|
// Trusted on every capability: on mu's own dev scenarios (an internal benchmark, not public yet) it did as well as Jev
|
|
@@ -128,6 +116,31 @@ var BUILT_IN_JUDGES = {
|
|
|
128
116
|
},
|
|
129
117
|
mock: { type: "mock" }
|
|
130
118
|
};
|
|
119
|
+
var LAYA = "laya";
|
|
120
|
+
var RETIRED_TYPE = "local";
|
|
121
|
+
var JUDGE_TYPES = [
|
|
122
|
+
"jev",
|
|
123
|
+
"typesafe",
|
|
124
|
+
"clm",
|
|
125
|
+
"gateway",
|
|
126
|
+
"http",
|
|
127
|
+
"llm",
|
|
128
|
+
"classifier",
|
|
129
|
+
"mock"
|
|
130
|
+
];
|
|
131
|
+
function isRecord(value) {
|
|
132
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
133
|
+
}
|
|
134
|
+
function retiredJudges(judges) {
|
|
135
|
+
const names = /* @__PURE__ */ new Set();
|
|
136
|
+
const entries = isRecord(judges) ? judges : {};
|
|
137
|
+
const own = entries[LAYA];
|
|
138
|
+
if (!(isRecord(own) && typeof own.type === "string" && JUDGE_TYPES.includes(own.type))) names.add(LAYA);
|
|
139
|
+
for (const [name, entry] of Object.entries(entries)) {
|
|
140
|
+
if (isRecord(entry) && entry.type === RETIRED_TYPE) names.add(name);
|
|
141
|
+
}
|
|
142
|
+
return names;
|
|
143
|
+
}
|
|
131
144
|
|
|
132
145
|
// packages/kyrn-judge/src/setup/env-file.ts
|
|
133
146
|
var DEFINITION = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
|
|
@@ -317,21 +330,21 @@ function writeJsonFile(path, value) {
|
|
|
317
330
|
writeFileAtomic(path, `${JSON.stringify(value, null, 2)}
|
|
318
331
|
`);
|
|
319
332
|
}
|
|
320
|
-
function
|
|
333
|
+
function isRecord2(value) {
|
|
321
334
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
322
335
|
}
|
|
323
336
|
function existingProvider(models, id) {
|
|
324
337
|
const providers = models.providers;
|
|
325
|
-
return
|
|
338
|
+
return isRecord2(providers) && isRecord2(providers[id]) ? providers[id] : void 0;
|
|
326
339
|
}
|
|
327
340
|
function mergeModels(existing, added) {
|
|
328
341
|
const kept = Array.isArray(existing) ? [...existing] : [];
|
|
329
|
-
const have = new Set(kept.flatMap((model) =>
|
|
342
|
+
const have = new Set(kept.flatMap((model) => isRecord2(model) && typeof model.id === "string" ? [model.id] : []));
|
|
330
343
|
return [...kept, ...added.filter((model) => !have.has(model.id)).map((model) => ({ ...model }))];
|
|
331
344
|
}
|
|
332
345
|
function withProvider(models, id, entry) {
|
|
333
|
-
const providers =
|
|
334
|
-
const current =
|
|
346
|
+
const providers = isRecord2(models.providers) ? models.providers : {};
|
|
347
|
+
const current = isRecord2(providers[id]) ? providers[id] : {};
|
|
335
348
|
const next = {
|
|
336
349
|
...current,
|
|
337
350
|
name: typeof current.name === "string" ? current.name : entry.name,
|
|
@@ -345,8 +358,8 @@ function withProvider(models, id, entry) {
|
|
|
345
358
|
return { ...models, providers: { ...providers, [id]: next } };
|
|
346
359
|
}
|
|
347
360
|
function withModel(models, providerId, modelId) {
|
|
348
|
-
const providers =
|
|
349
|
-
const current =
|
|
361
|
+
const providers = isRecord2(models.providers) ? models.providers : {};
|
|
362
|
+
const current = isRecord2(providers[providerId]) ? providers[providerId] : {};
|
|
350
363
|
return {
|
|
351
364
|
...models,
|
|
352
365
|
providers: { ...providers, [providerId]: { ...current, models: mergeModels(current.models, [{ id: modelId }]) } }
|
|
@@ -366,10 +379,10 @@ function withTiers(config, tiers) {
|
|
|
366
379
|
return { ...config, tiers: [...tiers] };
|
|
367
380
|
}
|
|
368
381
|
function judgeOff(config) {
|
|
369
|
-
return
|
|
382
|
+
return isRecord2(config?.modes) && config.modes.default === "off";
|
|
370
383
|
}
|
|
371
384
|
function withJudge(config, on) {
|
|
372
|
-
const { default: _default, ...modes } =
|
|
385
|
+
const { default: _default, ...modes } = isRecord2(config.modes) ? config.modes : {};
|
|
373
386
|
if (!on) return { ...config, modes: { default: "off", ...modes } };
|
|
374
387
|
if (!judgeOff(config)) return config;
|
|
375
388
|
const { modes: _modes, ...rest } = config;
|
|
@@ -690,8 +703,10 @@ function enableLocal(judge, paths, files, options) {
|
|
|
690
703
|
const name = judge.name;
|
|
691
704
|
const { path, config } = readConfig(files);
|
|
692
705
|
const state = readState(paths);
|
|
693
|
-
const
|
|
694
|
-
const
|
|
706
|
+
const retired = retiredJudges(config.judges);
|
|
707
|
+
const current = tiersOf(config).filter((tier) => tier !== name && !retired.has(tier));
|
|
708
|
+
const kept = (state?.previousTiers ?? []).filter((tier) => !retired.has(tier));
|
|
709
|
+
const before = current.length > 0 ? current : kept.length > 0 ? kept : ["jev"];
|
|
695
710
|
const tiers = options.localOnly ? [name] : [name, ...before.filter((tier) => tier !== name)];
|
|
696
711
|
const judgeWasOff = state?.judgeWasOff ?? judgeOff(config);
|
|
697
712
|
let next = withJudge(withTiers(withoutOwnRoutes(config, state), tiers), true);
|
|
@@ -734,8 +749,10 @@ function disableMude(paths, files, options = {}) {
|
|
|
734
749
|
function disableLocal(judge, paths, files, options = {}) {
|
|
735
750
|
const { path, config } = readConfig(files);
|
|
736
751
|
const state = readState(paths);
|
|
737
|
-
const
|
|
738
|
-
const
|
|
752
|
+
const retired = retiredJudges(config.judges);
|
|
753
|
+
const rest = tiersOf(config).filter((tier) => tier !== judge.name && !retired.has(tier));
|
|
754
|
+
const previous = (state?.previousTiers ?? []).filter((tier) => !retired.has(tier));
|
|
755
|
+
const tiers = rest.length > 0 ? rest : previous.length > 0 ? previous : ["jev"];
|
|
739
756
|
let next = withTiers(withoutOwnRoutes(config, state), tiers);
|
|
740
757
|
if (state?.judgeWasOff) next = withJudge(next, false);
|
|
741
758
|
if (options.removeEntry && state?.wroteEntry) {
|
|
@@ -1098,7 +1115,7 @@ function modelPaths(muHome2, record) {
|
|
|
1098
1115
|
installLock: join6(root, "install.lock")
|
|
1099
1116
|
};
|
|
1100
1117
|
}
|
|
1101
|
-
function
|
|
1118
|
+
function isRecord3(value) {
|
|
1102
1119
|
if (typeof value !== "object" || value === null) return false;
|
|
1103
1120
|
const record = value;
|
|
1104
1121
|
return typeof record.id === "string" && typeof record.repo === "string" && typeof record.revision === "string" && typeof record.bytes === "number" && Array.isArray(record.files);
|
|
@@ -1114,7 +1131,7 @@ function listRecords(muHome2) {
|
|
|
1114
1131
|
for (const name of names.sort()) {
|
|
1115
1132
|
try {
|
|
1116
1133
|
const value = JSON.parse(readFileSync5(join6(modelsDir(muHome2), name, "model.json"), "utf8"));
|
|
1117
|
-
if (
|
|
1134
|
+
if (isRecord3(value) && value.id === name) records.push(value);
|
|
1118
1135
|
} catch {
|
|
1119
1136
|
}
|
|
1120
1137
|
}
|
package/judge/manifest.json
CHANGED
|
@@ -1181,8 +1181,8 @@
|
|
|
1181
1181
|
"fa-IR": "نیمهکاره ماند"
|
|
1182
1182
|
},
|
|
1183
1183
|
"summary": {
|
|
1184
|
-
"zh": "
|
|
1185
|
-
"en": "When a run ends on \"next I'll run the tests\" without doing it, or asks permission for work you already asked for, sends the agent back to it. Never nudges a step that is hard to undo or reaches beyond this machine (push, publish, delete, pay). At most twice per message; in an unattended run, at most once for work it said it would do, and not after it answered a nudge without a tool call.
|
|
1184
|
+
"zh": "一轮结束在「接下来我去跑测试」却什么也没做,或者你已经让它做了它还在问「要我改吗」:让它接着做。难以撤销、或要出这台电脑的一步(推送、发布、删除、付款)不催。每条消息最多催两次;无人值守的运行里「说了没做」最多催一次,它回应上一次催促时没调工具就不再催。",
|
|
1185
|
+
"en": "When a run ends on \"next I'll run the tests\" without doing it, or asks permission for work you already asked for, sends the agent back to it. Never nudges a step that is hard to undo or reaches beyond this machine (push, publish, delete, pay). At most twice per message; in an unattended run, at most once for work it said it would do, and not after it answered a nudge without a tool call."
|
|
1186
1186
|
}
|
|
1187
1187
|
},
|
|
1188
1188
|
{
|
|
@@ -3700,19 +3700,19 @@
|
|
|
3700
3700
|
"fa-IR": "تعداد تکه در هر درخواست"
|
|
3701
3701
|
},
|
|
3702
3702
|
"help": {
|
|
3703
|
-
"zh": "
|
|
3704
|
-
"en": "
|
|
3705
|
-
"zh-TW": "
|
|
3706
|
-
"ja-JP": "
|
|
3707
|
-
"ko-KR": "
|
|
3708
|
-
"de-DE": "
|
|
3709
|
-
"fr-FR": "
|
|
3710
|
-
"es-ES": "
|
|
3711
|
-
"pt-BR": "
|
|
3712
|
-
"ru-RU": "
|
|
3713
|
-
"uk-UA": "
|
|
3714
|
-
"tr-TR": "
|
|
3715
|
-
"fa-IR": "داور
|
|
3703
|
+
"zh": "判定器把多块放进一个请求,状态只计费一次。",
|
|
3704
|
+
"en": "The judge takes many chunks in one request, with the shared context billed once.",
|
|
3705
|
+
"zh-TW": "判定器把多塊放進一個請求,狀態只計費一次。",
|
|
3706
|
+
"ja-JP": "判定器は多くのチャンクを 1 回のリクエストで受け取り、状態の課金は 1 回だけです。",
|
|
3707
|
+
"ko-KR": "판정기는 여러 청크를 한 번의 요청으로 받으며 상태는 한 번만 과금됩니다.",
|
|
3708
|
+
"de-DE": "Der Judge nimmt viele Abschnitte in einer Anfrage, der Zustand wird nur einmal berechnet.",
|
|
3709
|
+
"fr-FR": "Le juge reçoit de nombreux fragments en une seule requête, l’état n’étant facturé qu’une fois.",
|
|
3710
|
+
"es-ES": "El juez recibe muchos fragmentos en una sola solicitud y el estado se factura una vez.",
|
|
3711
|
+
"pt-BR": "O juiz recebe muitos blocos numa só solicitação, com o estado cobrado uma vez.",
|
|
3712
|
+
"ru-RU": "Судья принимает много частей одним запросом, и состояние оплачивается один раз.",
|
|
3713
|
+
"uk-UA": "Суддя приймає багато частин одним запитом, і стан оплачується один раз.",
|
|
3714
|
+
"tr-TR": "Hakem birçok parçayı tek istekte alır ve durum bir kez ücretlendirilir.",
|
|
3715
|
+
"fa-IR": "داور چندین تکه را در یک درخواست میگیرد و هزینهٔ وضعیت فقط یک بار حساب میشود."
|
|
3716
3716
|
}
|
|
3717
3717
|
},
|
|
3718
3718
|
{
|
|
@@ -7092,19 +7092,6 @@
|
|
|
7092
7092
|
"zh": "默认不催:它回应上一次催促时一个工具也没调(比如「工作已经做完了」),就是用停下来作了答,不再说它没做完。什么也没说就停下的一轮照样会被催。",
|
|
7093
7093
|
"en": "Off by default: when the agent answered the last nudge without calling a tool (\"The work is already complete\"), it answered by stopping, and isn't told again that it said it would do more. A run that stops without a message is still sent back."
|
|
7094
7094
|
}
|
|
7095
|
-
},
|
|
7096
|
-
{
|
|
7097
|
-
"key": "localPromised",
|
|
7098
|
-
"kind": "boolean",
|
|
7099
|
-
"default": false,
|
|
7100
|
-
"label": {
|
|
7101
|
-
"zh": "让本地判定器判断「说了没做」",
|
|
7102
|
-
"en": "Let the local judge decide whether work was left undone"
|
|
7103
|
-
},
|
|
7104
|
-
"help": {
|
|
7105
|
-
"zh": "默认不让:这一问交给判定器列表里的下一个;只有本地判定器(Laya)时就不因此催。基准测试里 Laya 两次把做完了的「已完成,已创建 …」当成还要接着做。",
|
|
7106
|
-
"en": "Off by default: this question goes to the next judge in your list, and with the local judge (Laya) alone no such nudge is sent. In a benchmark run Laya twice read a finished \"Done. Created …\" as a promise of more work."
|
|
7107
|
-
}
|
|
7108
7095
|
}
|
|
7109
7096
|
]
|
|
7110
7097
|
},
|