mini-coder 0.0.10 → 0.0.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/dist/mc.js +199 -29
- package/package.json +1 -1
- package/codex-lazy-fix.md +0 -76
- package/plan-code-health.md +0 -169
package/dist/mc.js
CHANGED
|
@@ -23,7 +23,10 @@ import * as c7 from "yoctocolors";
|
|
|
23
23
|
class TerminalIO {
|
|
24
24
|
cleanupHandlers = new Set;
|
|
25
25
|
rawModeEnabled = false;
|
|
26
|
-
|
|
26
|
+
interruptHandler = null;
|
|
27
|
+
setInterruptHandler(handler) {
|
|
28
|
+
this.interruptHandler = handler;
|
|
29
|
+
}
|
|
27
30
|
stdoutWrite(text) {
|
|
28
31
|
process.stdout.write(text);
|
|
29
32
|
}
|
|
@@ -58,8 +61,12 @@ class TerminalIO {
|
|
|
58
61
|
process.exit(143);
|
|
59
62
|
});
|
|
60
63
|
process.on("SIGINT", () => {
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
if (this.interruptHandler) {
|
|
65
|
+
this.interruptHandler();
|
|
66
|
+
} else {
|
|
67
|
+
cleanup();
|
|
68
|
+
process.exit(130);
|
|
69
|
+
}
|
|
63
70
|
});
|
|
64
71
|
process.on("uncaughtException", (err) => {
|
|
65
72
|
cleanup();
|
|
@@ -924,6 +931,8 @@ function fmtTokens(n) {
|
|
|
924
931
|
function renderStatusBar(opts) {
|
|
925
932
|
const cols = process.stdout.columns ?? 80;
|
|
926
933
|
const left = [c5.cyan(opts.model)];
|
|
934
|
+
if (opts.thinkingEffort)
|
|
935
|
+
left.push(c5.dim(`\u2726 ${opts.thinkingEffort}`));
|
|
927
936
|
if (opts.provider && opts.provider !== "zen")
|
|
928
937
|
left.push(c5.dim(opts.provider));
|
|
929
938
|
left.push(c5.dim(opts.sessionId.slice(0, 8)));
|
|
@@ -969,7 +978,7 @@ function renderError(err, context = "render") {
|
|
|
969
978
|
|
|
970
979
|
// src/cli/output.ts
|
|
971
980
|
var HOME2 = homedir3();
|
|
972
|
-
var PACKAGE_VERSION = "0.0.
|
|
981
|
+
var PACKAGE_VERSION = "0.0.12";
|
|
973
982
|
function tildePath(p) {
|
|
974
983
|
return p.startsWith(HOME2) ? `~${p.slice(HOME2.length)}` : p;
|
|
975
984
|
}
|
|
@@ -1340,6 +1349,87 @@ var CONTEXT_WINDOW_TABLE = [
|
|
|
1340
1349
|
[/^glm-/, 128000],
|
|
1341
1350
|
[/^qwen3-/, 131000]
|
|
1342
1351
|
];
|
|
1352
|
+
var REASONING_MODELS = [
|
|
1353
|
+
/^claude-3-5-sonnet/,
|
|
1354
|
+
/^claude-3-7/,
|
|
1355
|
+
/^claude-sonnet-4/,
|
|
1356
|
+
/^claude-opus-4/,
|
|
1357
|
+
/^o1/,
|
|
1358
|
+
/^o3/,
|
|
1359
|
+
/^o4/,
|
|
1360
|
+
/^gpt-5/,
|
|
1361
|
+
/^gemini-2\.5/,
|
|
1362
|
+
/^gemini-3/
|
|
1363
|
+
];
|
|
1364
|
+
function supportsThinking(modelString) {
|
|
1365
|
+
const { modelId } = parseModelString(modelString);
|
|
1366
|
+
return REASONING_MODELS.some((p) => p.test(modelId));
|
|
1367
|
+
}
|
|
1368
|
+
var ANTHROPIC_BUDGET = {
|
|
1369
|
+
low: 4096,
|
|
1370
|
+
medium: 8192,
|
|
1371
|
+
high: 16384,
|
|
1372
|
+
xhigh: 32768
|
|
1373
|
+
};
|
|
1374
|
+
function clampEffort(effort, max) {
|
|
1375
|
+
const ORDER = ["low", "medium", "high", "xhigh"];
|
|
1376
|
+
const i = ORDER.indexOf(effort);
|
|
1377
|
+
const m = ORDER.indexOf(max);
|
|
1378
|
+
return ORDER[Math.min(i, m)];
|
|
1379
|
+
}
|
|
1380
|
+
function getThinkingProviderOptions(modelString, effort) {
|
|
1381
|
+
if (!supportsThinking(modelString))
|
|
1382
|
+
return null;
|
|
1383
|
+
const { provider, modelId } = parseModelString(modelString);
|
|
1384
|
+
if (provider === "anthropic" || provider === "zen" && modelId.startsWith("claude-")) {
|
|
1385
|
+
const isAdaptive = /^claude-3-7/.test(modelId) || /^claude-sonnet-4/.test(modelId) || /^claude-opus-4/.test(modelId);
|
|
1386
|
+
if (isAdaptive) {
|
|
1387
|
+
const isOpus = /^claude-opus-4/.test(modelId);
|
|
1388
|
+
const mapped = effort === "xhigh" ? isOpus ? "max" : "high" : effort;
|
|
1389
|
+
return { anthropic: { thinking: { type: "adaptive" }, effort: mapped } };
|
|
1390
|
+
}
|
|
1391
|
+
const budget = ANTHROPIC_BUDGET[effort];
|
|
1392
|
+
return {
|
|
1393
|
+
anthropic: {
|
|
1394
|
+
thinking: { type: "enabled", budgetTokens: budget },
|
|
1395
|
+
betas: ["interleaved-thinking-2025-05-14"]
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
if (provider === "openai" || provider === "zen" && (modelId.startsWith("o") || modelId.startsWith("gpt-5"))) {
|
|
1400
|
+
const supportsXhigh = /^gpt-5\.[2-9]/.test(modelId) || /^o4/.test(modelId);
|
|
1401
|
+
const clamped = supportsXhigh ? effort : clampEffort(effort, "high");
|
|
1402
|
+
return { openai: { reasoningEffort: clamped } };
|
|
1403
|
+
}
|
|
1404
|
+
if (provider === "google" || provider === "zen" && modelId.startsWith("gemini-")) {
|
|
1405
|
+
if (/^gemini-3/.test(modelId)) {
|
|
1406
|
+
const level = clampEffort(effort, "high");
|
|
1407
|
+
return {
|
|
1408
|
+
google: {
|
|
1409
|
+
thinkingConfig: {
|
|
1410
|
+
includeThoughts: true,
|
|
1411
|
+
thinkingLevel: level
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
const GEMINI_BUDGET = {
|
|
1417
|
+
low: 4096,
|
|
1418
|
+
medium: 8192,
|
|
1419
|
+
high: 16384,
|
|
1420
|
+
xhigh: 24575
|
|
1421
|
+
};
|
|
1422
|
+
return {
|
|
1423
|
+
google: {
|
|
1424
|
+
thinkingConfig: {
|
|
1425
|
+
includeThoughts: true,
|
|
1426
|
+
thinkingBudget: GEMINI_BUDGET[effort]
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
};
|
|
1430
|
+
}
|
|
1431
|
+
return null;
|
|
1432
|
+
}
|
|
1343
1433
|
function getContextWindow(modelString) {
|
|
1344
1434
|
const { modelId } = parseModelString(modelString);
|
|
1345
1435
|
for (const [pattern, tokens] of CONTEXT_WINDOW_TABLE) {
|
|
@@ -1691,6 +1781,19 @@ function getPreferredModel() {
|
|
|
1691
1781
|
function setPreferredModel(model) {
|
|
1692
1782
|
setSetting("preferred_model", model);
|
|
1693
1783
|
}
|
|
1784
|
+
function getPreferredThinkingEffort() {
|
|
1785
|
+
const v = getSetting("preferred_thinking_effort");
|
|
1786
|
+
if (v === "low" || v === "medium" || v === "high" || v === "xhigh")
|
|
1787
|
+
return v;
|
|
1788
|
+
return null;
|
|
1789
|
+
}
|
|
1790
|
+
function setPreferredThinkingEffort(effort) {
|
|
1791
|
+
if (effort === null) {
|
|
1792
|
+
getDb().run("DELETE FROM settings WHERE key = 'preferred_thinking_effort'");
|
|
1793
|
+
} else {
|
|
1794
|
+
setSetting("preferred_thinking_effort", effort);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1694
1797
|
// src/session/db/mcp-repo.ts
|
|
1695
1798
|
function listMcpServers() {
|
|
1696
1799
|
return getDb().query("SELECT name, transport, url, command, args, env FROM mcp_servers ORDER BY name").all();
|
|
@@ -1779,7 +1882,15 @@ function isOpenAIGPT(modelString) {
|
|
|
1779
1882
|
return (provider === "openai" || provider === "zen") && modelId.startsWith("gpt-");
|
|
1780
1883
|
}
|
|
1781
1884
|
async function* runTurn(options) {
|
|
1782
|
-
const {
|
|
1885
|
+
const {
|
|
1886
|
+
model,
|
|
1887
|
+
modelString,
|
|
1888
|
+
messages,
|
|
1889
|
+
tools,
|
|
1890
|
+
systemPrompt,
|
|
1891
|
+
signal,
|
|
1892
|
+
thinkingEffort
|
|
1893
|
+
} = options;
|
|
1783
1894
|
let stepCount = 0;
|
|
1784
1895
|
let warningClaimed = false;
|
|
1785
1896
|
function claimWarning() {
|
|
@@ -1798,6 +1909,18 @@ async function* runTurn(options) {
|
|
|
1798
1909
|
try {
|
|
1799
1910
|
const useInstructions = systemPrompt !== undefined && isOpenAIGPT(modelString);
|
|
1800
1911
|
logApiEvent("turn start", { modelString, messageCount: messages.length });
|
|
1912
|
+
const thinkingOpts = thinkingEffort ? getThinkingProviderOptions(modelString, thinkingEffort) : null;
|
|
1913
|
+
const mergedProviderOptions = {
|
|
1914
|
+
...useInstructions ? { openai: { instructions: systemPrompt, store: false } } : {},
|
|
1915
|
+
...thinkingOpts ?? {},
|
|
1916
|
+
...useInstructions && thinkingOpts?.openai ? {
|
|
1917
|
+
openai: {
|
|
1918
|
+
instructions: systemPrompt,
|
|
1919
|
+
store: false,
|
|
1920
|
+
...thinkingOpts.openai
|
|
1921
|
+
}
|
|
1922
|
+
} : {}
|
|
1923
|
+
};
|
|
1801
1924
|
const streamOpts = {
|
|
1802
1925
|
model,
|
|
1803
1926
|
messages,
|
|
@@ -1822,14 +1945,7 @@ async function* runTurn(options) {
|
|
|
1822
1945
|
return;
|
|
1823
1946
|
},
|
|
1824
1947
|
...systemPrompt ? { system: systemPrompt } : {},
|
|
1825
|
-
...
|
|
1826
|
-
providerOptions: {
|
|
1827
|
-
openai: {
|
|
1828
|
-
instructions: systemPrompt,
|
|
1829
|
-
store: false
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
} : {},
|
|
1948
|
+
...Object.keys(mergedProviderOptions).length > 0 ? { providerOptions: mergedProviderOptions } : {},
|
|
1833
1949
|
...signal ? { abortSignal: signal } : {}
|
|
1834
1950
|
};
|
|
1835
1951
|
const result = streamText(streamOpts);
|
|
@@ -2792,7 +2908,7 @@ function createSubagentTool(runSubagent, availableAgents, parentLabel) {
|
|
|
2792
2908
|
When the user's message contains @<agent-name>, delegate to that agent by setting agentName to the exact agent name. Available custom agents: ${[...availableAgents.entries()].map(([name, cfg]) => `"${name}" (${cfg.description})`).join(", ")}.` : "";
|
|
2793
2909
|
return {
|
|
2794
2910
|
name: "subagent",
|
|
2795
|
-
description: `Spawn a sub-agent to handle a focused subtask. Use this for parallel exploration, specialised analysis, or tasks that benefit from a fresh context window.
|
|
2911
|
+
description: `Spawn a sub-agent to handle a focused subtask. Use this for parallel exploration, specialised analysis, or tasks that benefit from a fresh context window. ${agentSection}`,
|
|
2796
2912
|
schema: SubagentInput,
|
|
2797
2913
|
execute: async (input) => {
|
|
2798
2914
|
return runSubagent(input.prompt, input.agentName, parentLabel);
|
|
@@ -2878,7 +2994,7 @@ function buildReadOnlyToolSet(opts) {
|
|
|
2878
2994
|
}
|
|
2879
2995
|
|
|
2880
2996
|
// src/agent/subagent-runner.ts
|
|
2881
|
-
function createSubagentRunner(cwd, reporter, getCurrentModel) {
|
|
2997
|
+
function createSubagentRunner(cwd, reporter, getCurrentModel, getThinkingEffort) {
|
|
2882
2998
|
let nextLaneId = 1;
|
|
2883
2999
|
const activeLanes = new Set;
|
|
2884
3000
|
const runSubagent = async (prompt, depth = 0, agentName, modelOverride, parentLabel) => {
|
|
@@ -2901,17 +3017,19 @@ function createSubagentRunner(cwd, reporter, getCurrentModel) {
|
|
|
2901
3017
|
onHook: (tool, path, ok) => reporter.renderHook(tool, path, ok),
|
|
2902
3018
|
availableAgents: allAgents,
|
|
2903
3019
|
parentLabel: laneLabel
|
|
2904
|
-
});
|
|
3020
|
+
}).filter((tool) => tool.name !== "subagent");
|
|
2905
3021
|
const subLlm = resolveModel(model);
|
|
2906
3022
|
let result = "";
|
|
2907
3023
|
let inputTokens = 0;
|
|
2908
3024
|
let outputTokens = 0;
|
|
3025
|
+
const effort = getThinkingEffort();
|
|
2909
3026
|
const events = runTurn({
|
|
2910
3027
|
model: subLlm,
|
|
2911
3028
|
modelString: model,
|
|
2912
3029
|
messages: subMessages,
|
|
2913
3030
|
tools: subTools,
|
|
2914
|
-
systemPrompt
|
|
3031
|
+
systemPrompt,
|
|
3032
|
+
...effort ? { thinkingEffort: effort } : {}
|
|
2915
3033
|
});
|
|
2916
3034
|
for await (const event of events) {
|
|
2917
3035
|
reporter.stopSpinner();
|
|
@@ -3368,20 +3486,50 @@ async function expandTemplate(template, args, cwd) {
|
|
|
3368
3486
|
|
|
3369
3487
|
// src/cli/commands.ts
|
|
3370
3488
|
async function handleModel(ctx, args) {
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
if (
|
|
3489
|
+
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
3490
|
+
if (parts.length > 0) {
|
|
3491
|
+
if (parts[0] === "effort") {
|
|
3492
|
+
const effortArg2 = parts[1] ?? "";
|
|
3493
|
+
if (effortArg2 === "off") {
|
|
3494
|
+
ctx.setThinkingEffort(null);
|
|
3495
|
+
writeln(`${PREFIX.success} thinking effort disabled`);
|
|
3496
|
+
} else if (["low", "medium", "high", "xhigh"].includes(effortArg2)) {
|
|
3497
|
+
ctx.setThinkingEffort(effortArg2);
|
|
3498
|
+
writeln(`${PREFIX.success} thinking effort \u2192 ${c10.cyan(effortArg2)}`);
|
|
3499
|
+
} else {
|
|
3500
|
+
writeln(`${PREFIX.error} usage: /model effort <low|medium|high|xhigh|off>`);
|
|
3501
|
+
}
|
|
3502
|
+
return;
|
|
3503
|
+
}
|
|
3504
|
+
const idArg = parts[0] ?? "";
|
|
3505
|
+
let modelId = idArg;
|
|
3506
|
+
if (!idArg.includes("/")) {
|
|
3374
3507
|
const models2 = await fetchAvailableModels();
|
|
3375
|
-
const match = models2.find((m) => m.id.split("/").slice(1).join("/") ===
|
|
3508
|
+
const match = models2.find((m) => m.id.split("/").slice(1).join("/") === idArg || m.id === idArg);
|
|
3376
3509
|
if (match) {
|
|
3377
3510
|
modelId = match.id;
|
|
3378
3511
|
} else {
|
|
3379
|
-
writeln(`${PREFIX.error} unknown model ${c10.cyan(
|
|
3512
|
+
writeln(`${PREFIX.error} unknown model ${c10.cyan(idArg)} ${c10.dim("\u2014 run /models for the full list")}`);
|
|
3380
3513
|
return;
|
|
3381
3514
|
}
|
|
3382
3515
|
}
|
|
3383
3516
|
ctx.setModel(modelId);
|
|
3384
|
-
|
|
3517
|
+
const effortArg = parts[1];
|
|
3518
|
+
if (effortArg) {
|
|
3519
|
+
if (effortArg === "off") {
|
|
3520
|
+
ctx.setThinkingEffort(null);
|
|
3521
|
+
writeln(`${PREFIX.success} model \u2192 ${c10.cyan(modelId)} ${c10.dim("(thinking disabled)")}`);
|
|
3522
|
+
} else if (["low", "medium", "high", "xhigh"].includes(effortArg)) {
|
|
3523
|
+
ctx.setThinkingEffort(effortArg);
|
|
3524
|
+
writeln(`${PREFIX.success} model \u2192 ${c10.cyan(modelId)} ${c10.dim(`(\u2726 ${effortArg})`)}`);
|
|
3525
|
+
} else {
|
|
3526
|
+
writeln(`${PREFIX.success} model \u2192 ${c10.cyan(modelId)}`);
|
|
3527
|
+
writeln(`${PREFIX.error} unknown effort level ${c10.cyan(effortArg)} (use low, medium, high, xhigh, off)`);
|
|
3528
|
+
}
|
|
3529
|
+
} else {
|
|
3530
|
+
const e = ctx.thinkingEffort ? c10.dim(` (\u2726 ${ctx.thinkingEffort})`) : "";
|
|
3531
|
+
writeln(`${PREFIX.success} model \u2192 ${c10.cyan(modelId)}${e}`);
|
|
3532
|
+
}
|
|
3385
3533
|
return;
|
|
3386
3534
|
}
|
|
3387
3535
|
writeln(`${c10.dim(" fetching models\u2026")}`);
|
|
@@ -3408,13 +3556,15 @@ async function handleModel(ctx, args) {
|
|
|
3408
3556
|
const isCurrent = ctx.currentModel === m.id;
|
|
3409
3557
|
const freeTag = m.free ? c10.green(" free") : "";
|
|
3410
3558
|
const ctxTag = m.context ? c10.dim(` ${Math.round(m.context / 1000)}k`) : "";
|
|
3559
|
+
const effortTag = isCurrent && ctx.thinkingEffort ? c10.dim(` \u2726 ${ctx.thinkingEffort}`) : "";
|
|
3411
3560
|
const cur = isCurrent ? c10.cyan(" \u25C0") : "";
|
|
3412
|
-
writeln(` ${c10.dim("\xB7")} ${m.displayName}${freeTag}${ctxTag}${cur}`);
|
|
3561
|
+
writeln(` ${c10.dim("\xB7")} ${m.displayName}${freeTag}${ctxTag}${cur}${effortTag}`);
|
|
3413
3562
|
writeln(` ${c10.dim(m.id)}`);
|
|
3414
3563
|
}
|
|
3415
3564
|
}
|
|
3416
3565
|
writeln();
|
|
3417
3566
|
writeln(c10.dim(" /model <id> to switch \xB7 e.g. /model zen/claude-sonnet-4-6"));
|
|
3567
|
+
writeln(c10.dim(" /model effort <low|medium|high|xhigh|off> to set thinking effort"));
|
|
3418
3568
|
}
|
|
3419
3569
|
function handlePlan(ctx) {
|
|
3420
3570
|
ctx.setPlanMode(!ctx.planMode);
|
|
@@ -3803,20 +3953,25 @@ async function readKey(reader) {
|
|
|
3803
3953
|
function watchForInterrupt(abortController) {
|
|
3804
3954
|
if (!process.stdin.isTTY)
|
|
3805
3955
|
return () => {};
|
|
3956
|
+
const onInterrupt = () => {
|
|
3957
|
+
cleanup();
|
|
3958
|
+
abortController.abort();
|
|
3959
|
+
};
|
|
3806
3960
|
const onData = (chunk) => {
|
|
3807
3961
|
for (const byte of chunk) {
|
|
3808
3962
|
if (byte === 3) {
|
|
3809
|
-
|
|
3810
|
-
abortController.abort();
|
|
3963
|
+
onInterrupt();
|
|
3811
3964
|
return;
|
|
3812
3965
|
}
|
|
3813
3966
|
}
|
|
3814
3967
|
};
|
|
3815
3968
|
const cleanup = () => {
|
|
3816
3969
|
process.stdin.removeListener("data", onData);
|
|
3970
|
+
terminal.setInterruptHandler(null);
|
|
3817
3971
|
process.stdin.setRawMode(false);
|
|
3818
3972
|
process.stdin.pause();
|
|
3819
3973
|
};
|
|
3974
|
+
terminal.setInterruptHandler(onInterrupt);
|
|
3820
3975
|
process.stdin.setRawMode(true);
|
|
3821
3976
|
process.stdin.resume();
|
|
3822
3977
|
process.stdin.on("data", onData);
|
|
@@ -4276,6 +4431,7 @@ class SessionRunner {
|
|
|
4276
4431
|
tools;
|
|
4277
4432
|
mcpTools;
|
|
4278
4433
|
currentModel;
|
|
4434
|
+
currentThinkingEffort;
|
|
4279
4435
|
session;
|
|
4280
4436
|
coreHistory;
|
|
4281
4437
|
turnIndex = 1;
|
|
@@ -4291,6 +4447,7 @@ class SessionRunner {
|
|
|
4291
4447
|
this.tools = opts.tools;
|
|
4292
4448
|
this.mcpTools = opts.mcpTools;
|
|
4293
4449
|
this.currentModel = opts.initialModel;
|
|
4450
|
+
this.currentThinkingEffort = opts.initialThinkingEffort;
|
|
4294
4451
|
this.initSession(opts.sessionId);
|
|
4295
4452
|
}
|
|
4296
4453
|
initSession(sessionId) {
|
|
@@ -4373,7 +4530,8 @@ class SessionRunner {
|
|
|
4373
4530
|
messages: this.coreHistory,
|
|
4374
4531
|
tools: this.planMode ? [...buildReadOnlyToolSet({ cwd: this.cwd }), ...this.mcpTools] : this.tools,
|
|
4375
4532
|
systemPrompt,
|
|
4376
|
-
signal: abortController.signal
|
|
4533
|
+
signal: abortController.signal,
|
|
4534
|
+
...this.currentThinkingEffort ? { thinkingEffort: this.currentThinkingEffort } : {}
|
|
4377
4535
|
});
|
|
4378
4536
|
const { inputTokens, outputTokens, contextTokens, newMessages } = await this.reporter.renderTurn(events);
|
|
4379
4537
|
if (newMessages.length > 0) {
|
|
@@ -4419,7 +4577,8 @@ class SessionRunner {
|
|
|
4419
4577
|
async function runAgent(opts) {
|
|
4420
4578
|
const cwd = opts.cwd;
|
|
4421
4579
|
let currentModel = opts.model;
|
|
4422
|
-
|
|
4580
|
+
let currentThinkingEffort = opts.initialThinkingEffort;
|
|
4581
|
+
const runSubagent = createSubagentRunner(cwd, opts.reporter, () => currentModel, () => currentThinkingEffort);
|
|
4423
4582
|
const agents = loadAgents(cwd);
|
|
4424
4583
|
const tools = buildToolSet({
|
|
4425
4584
|
cwd,
|
|
@@ -4460,6 +4619,7 @@ async function runAgent(opts) {
|
|
|
4460
4619
|
tools,
|
|
4461
4620
|
mcpTools,
|
|
4462
4621
|
initialModel: currentModel,
|
|
4622
|
+
initialThinkingEffort: opts.initialThinkingEffort,
|
|
4463
4623
|
sessionId: opts.sessionId
|
|
4464
4624
|
});
|
|
4465
4625
|
const cmdCtx = {
|
|
@@ -4472,6 +4632,14 @@ async function runAgent(opts) {
|
|
|
4472
4632
|
setPreferredModel(m);
|
|
4473
4633
|
currentModel = m;
|
|
4474
4634
|
},
|
|
4635
|
+
get thinkingEffort() {
|
|
4636
|
+
return runner.currentThinkingEffort;
|
|
4637
|
+
},
|
|
4638
|
+
setThinkingEffort: (e) => {
|
|
4639
|
+
runner.currentThinkingEffort = e;
|
|
4640
|
+
setPreferredThinkingEffort(e);
|
|
4641
|
+
currentThinkingEffort = e;
|
|
4642
|
+
},
|
|
4475
4643
|
get planMode() {
|
|
4476
4644
|
return runner.planMode;
|
|
4477
4645
|
},
|
|
@@ -4515,7 +4683,8 @@ async function runAgent(opts) {
|
|
|
4515
4683
|
outputTokens: runner.totalOut,
|
|
4516
4684
|
contextTokens: runner.lastContextTokens,
|
|
4517
4685
|
contextWindow: getContextWindow(runner.currentModel) ?? 0,
|
|
4518
|
-
ralphMode: runner.ralphMode
|
|
4686
|
+
ralphMode: runner.ralphMode,
|
|
4687
|
+
thinkingEffort: runner.currentThinkingEffort
|
|
4519
4688
|
});
|
|
4520
4689
|
}
|
|
4521
4690
|
if (opts.initialPrompt) {
|
|
@@ -4691,6 +4860,7 @@ async function main() {
|
|
|
4691
4860
|
const agentOpts = {
|
|
4692
4861
|
model,
|
|
4693
4862
|
cwd: args.cwd,
|
|
4863
|
+
initialThinkingEffort: getPreferredThinkingEffort(),
|
|
4694
4864
|
reporter: new CliReporter
|
|
4695
4865
|
};
|
|
4696
4866
|
if (sessionId)
|
package/package.json
CHANGED
package/codex-lazy-fix.md
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
# Codex Autonomy Issues & Fix Analysis
|
|
2
|
-
|
|
3
|
-
## Behaviours
|
|
4
|
-
When using `zen/gpt-5.3-codex` as the agent, the model consistently exhibits "lazy" or permission-seeking behaviour. Specifically:
|
|
5
|
-
1. **Initial Compliance**: It starts by reading files or globbing the directory.
|
|
6
|
-
2. **Immediate Stall**: Instead of executing edits or implementing the plan, it outputs a multi-paragraph text explaining what it *plans* to do and ends the turn.
|
|
7
|
-
3. **Permission Seeking**: It explicitly asks the user for permission (e.g., "Reply **'proceed'** and I'll start implementing batch 1").
|
|
8
|
-
4. **Ralph Mode Incompatibility**: In `/ralph` mode, the agent loops continuously. Because it restarts with a fresh context on each loop and stalls after gathering context, it never actually writes any files. It just loops through the same read-and-plan phase until it hits the max iteration limit.
|
|
9
|
-
5. **Model Differences**: Both Claude and Gemini models do not exhibit this behaviour. They are not subjected to the same conversational RLHF that pushes the model to ask the user to double check its work.
|
|
10
|
-
|
|
11
|
-
## Root Cause Analysis
|
|
12
|
-
An analysis of both OpenAI's open-source `codex-rs` client and `opencode` source code reveals that Codex models (like `gpt-5.3-codex`) are highly RLHF-tuned for safety and collaborative pair-programming. By default, the model prefers to break tasks into chunks and explicitly ask for sign-off.
|
|
13
|
-
|
|
14
|
-
To override this, the model requires three things which `mini-coder` was failing to provide correctly:
|
|
15
|
-
|
|
16
|
-
### 1. Dual-Anchored System Prompts (`system` + `instructions`)
|
|
17
|
-
`mini-coder` implemented a check `useInstructions` that placed the system prompt into the `instructions` field of the `/v1/responses` API payload. However, doing so stripped the `system` role message from the conversation context (`input` array).
|
|
18
|
-
|
|
19
|
-
By looking at `opencode` and `codex-rs`, they both ensure that the context array *also* contains the system prompt:
|
|
20
|
-
- `opencode` maps its environment variables and system instructions to `role: "system"` (or `role: "developer"`) inside `input.messages`, **while also** passing behavioral instructions to the `instructions` field in the API payload.
|
|
21
|
-
- `codex-rs` directly injects `role: "developer"` into the message list (as seen in `codex-rs/core/src/compact.rs` and their memory tracing implementations).
|
|
22
|
-
|
|
23
|
-
Without the `system` / `developer` message anchored at the start of the `input` array, the AI SDK and the model deprioritized the standalone `instructions` field, allowing the model's base permission-seeking behaviors to take over.
|
|
24
|
-
|
|
25
|
-
### 2. Explicit "Do Not Ask" Directives
|
|
26
|
-
Both `opencode` and `codex-rs` employ heavy anti-permission prompts.
|
|
27
|
-
- **Opencode** (`session/prompt/codex_header.txt`):
|
|
28
|
-
> "- Default: do the work without asking questions... Never ask permission questions like 'Should I proceed?' or 'Do you want me to run tests?'; proceed with the most reasonable option and mention what you did."
|
|
29
|
-
- **Codex-RS** (`core/templates/model_instructions/gpt-5.2-codex_instructions_template.md`):
|
|
30
|
-
> "Persist until the task is fully handled end-to-end within the current turn whenever feasible: do not stop at analysis or partial fixes; carry changes through implementation, verification, and a clear explanation of outcomes unless the user explicitly pauses or redirects you."
|
|
31
|
-
|
|
32
|
-
`mini-coder` introduced `CODEX_AUTONOMY` in a previous commit, but because of Issue #1, it was never adequately anchored in the `input` array.
|
|
33
|
-
|
|
34
|
-
## Evidence & Tests
|
|
35
|
-
We introduced a fetch wrapper interceptor in `src/llm-api/providers.ts` that logs the full outbound API requests to `~/.config/mini-coder/api.log`.
|
|
36
|
-
|
|
37
|
-
A test script `test-turn.ts` running a dummy turn showed the exact payload generated by the AI SDK before our fix:
|
|
38
|
-
```json
|
|
39
|
-
"body": {
|
|
40
|
-
"model": "gpt-5.3-codex",
|
|
41
|
-
"input": [
|
|
42
|
-
{
|
|
43
|
-
"role": "user",
|
|
44
|
-
"content": [
|
|
45
|
-
{ "type": "input_text", "text": "hello" }
|
|
46
|
-
]
|
|
47
|
-
}
|
|
48
|
-
],
|
|
49
|
-
"store": false,
|
|
50
|
-
"instructions": "You are a test agent.",
|
|
51
|
-
...
|
|
52
|
-
```
|
|
53
|
-
```json
|
|
54
|
-
"body": {
|
|
55
|
-
"model": "gpt-5.3-codex",
|
|
56
|
-
"input": [
|
|
57
|
-
{
|
|
58
|
-
"role": "developer",
|
|
59
|
-
"content": "You are mini-coder, a small and fast CLI coding agent... [CODEX_AUTONOMY directives]"
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"role": "user",
|
|
63
|
-
"content": [
|
|
64
|
-
{ "type": "input_text", "text": "hello" }
|
|
65
|
-
]
|
|
66
|
-
}
|
|
67
|
-
],
|
|
68
|
-
"instructions": "You are mini-coder, a small and fast CLI coding agent... [CODEX_AUTONOMY directives]"
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
This perfectly mirrors the behavior seen in `opencode` and `codex-rs`.
|
|
72
|
-
|
|
73
|
-
## Actions Taken
|
|
74
|
-
1. Added an `api.log` request interceptor in `providers.ts` to capture and inspect the exact JSON payloads sent to the OpenAI/AI SDK endpoints.
|
|
75
|
-
2. Cloned and analyzed both `opencode` and `codex` repos to observe how they communicate with `gpt-5.*` codex endpoints.
|
|
76
|
-
3. Updated `src/llm-api/turn.ts` so `system: systemPrompt` is *always* passed to the AI SDK, guaranteeing a `developer` message anchors the `input` array, even when `instructions` is also used.
|
package/plan-code-health.md
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
# Code Health Remediation Plan
|
|
2
|
-
|
|
3
|
-
## Goal
|
|
4
|
-
Address maintainability and reliability issues identified in `code-health.md` with low-risk, incremental refactors that keep behavior stable.
|
|
5
|
-
|
|
6
|
-
## Constraints
|
|
7
|
-
- Keep `mini-coder-idea.md` and `README.md` unchanged.
|
|
8
|
-
- Prefer small PR-sized changes with passing tests after each step.
|
|
9
|
-
- Preserve current CLI behavior while improving structure.
|
|
10
|
-
|
|
11
|
-
## Workstreams
|
|
12
|
-
|
|
13
|
-
### 1) Decompose `src/agent/agent.ts` (High)
|
|
14
|
-
**Outcome:** `runAgent` remains orchestration entrypoint; responsibilities split into focused modules.
|
|
15
|
-
|
|
16
|
-
**Steps:**
|
|
17
|
-
1. Add `src/agent/reporter.ts` interface (narrow surface for output/status/tool events).
|
|
18
|
-
2. Extract session lifecycle + turn loop into `src/agent/session-runner.ts`.
|
|
19
|
-
3. Extract subagent execution into `src/agent/subagent-runner.ts`.
|
|
20
|
-
4. Extract snapshot/undo helpers into `src/agent/undo-snapshot.ts`.
|
|
21
|
-
5. Extract user input processing into `src/agent/input-loop.ts`.
|
|
22
|
-
6. Keep `agent.ts` as composition/wiring file only.
|
|
23
|
-
|
|
24
|
-
**Checks:**
|
|
25
|
-
- Add/adjust unit tests around orchestration boundaries.
|
|
26
|
-
- Ensure no behavior regressions in interrupts, resume, and tool-call flows.
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
### 2) Decompose `src/cli/output.ts` (High)
|
|
31
|
-
**Outcome:** Rendering responsibilities isolated and testable.
|
|
32
|
-
|
|
33
|
-
**Target modules:**
|
|
34
|
-
- `src/cli/spinner.ts`
|
|
35
|
-
- `src/cli/tool-render.ts`
|
|
36
|
-
- `src/cli/stream-render.ts`
|
|
37
|
-
- `src/cli/status-bar.ts`
|
|
38
|
-
- `src/cli/error-render.ts`
|
|
39
|
-
- `src/cli/output.ts` as facade
|
|
40
|
-
|
|
41
|
-
**Steps:**
|
|
42
|
-
1. Extract pure formatting helpers first (no IO).
|
|
43
|
-
2. Extract spinner lifecycle module.
|
|
44
|
-
3. Extract stream queue/tick/flush behavior.
|
|
45
|
-
4. Keep compatibility exports in `output.ts` to avoid broad callsite churn.
|
|
46
|
-
|
|
47
|
-
**Checks:**
|
|
48
|
-
- Add focused tests for formatting + stream behavior.
|
|
49
|
-
- Verify terminal rendering remains stable manually.
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
### 3) Introduce `TerminalIO` abstraction (Medium)
|
|
54
|
-
**Outcome:** Centralized process/TTY interactions and signal lifecycle.
|
|
55
|
-
|
|
56
|
-
**Steps:**
|
|
57
|
-
1. Create `src/cli/terminal-io.ts` with methods for stdout/stderr writes, raw mode, signal subscriptions.
|
|
58
|
-
2. Replace direct `process.*` use in output/input stack with injected `TerminalIO`.
|
|
59
|
-
3. Centralize signal registration/unregistration in one lifecycle owner.
|
|
60
|
-
|
|
61
|
-
**Checks:**
|
|
62
|
-
- Add unit tests for signal registration cleanup semantics.
|
|
63
|
-
- Confirm no stuck raw-mode edge cases.
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
### 4) Split DB layer by domain (Medium)
|
|
68
|
-
**Outcome:** Reduced blast radius and clearer data ownership.
|
|
69
|
-
|
|
70
|
-
**Target modules:**
|
|
71
|
-
- `src/session/db/connection.ts`
|
|
72
|
-
- `src/session/db/session-repo.ts`
|
|
73
|
-
- `src/session/db/message-repo.ts`
|
|
74
|
-
- `src/session/db/settings-repo.ts`
|
|
75
|
-
- `src/session/db/mcp-repo.ts`
|
|
76
|
-
- `src/session/db/snapshot-repo.ts`
|
|
77
|
-
- `src/session/db/index.ts` (facade exports)
|
|
78
|
-
|
|
79
|
-
**Steps:**
|
|
80
|
-
1. Move code without behavior changes.
|
|
81
|
-
2. Keep SQL and schema unchanged initially.
|
|
82
|
-
3. Replace direct `JSON.parse` in message loading with guarded parser:
|
|
83
|
-
- skip malformed rows
|
|
84
|
-
- emit diagnostic via logger/reporter
|
|
85
|
-
|
|
86
|
-
**Checks:**
|
|
87
|
-
- Add tests for malformed payload handling.
|
|
88
|
-
- Validate existing DB tests still pass.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
### 5) Shared markdown config loader (Medium)
|
|
93
|
-
**Outcome:** Remove duplication across agents/skills/custom-commands.
|
|
94
|
-
|
|
95
|
-
**Steps:**
|
|
96
|
-
1. Create `src/cli/load-markdown-configs.ts` with parameterized layout strategy.
|
|
97
|
-
2. Migrate:
|
|
98
|
-
- `src/cli/agents.ts`
|
|
99
|
-
- `src/cli/skills.ts`
|
|
100
|
-
- `src/cli/custom-commands.ts`
|
|
101
|
-
3. Keep precedence rules identical (built-in/user/project).
|
|
102
|
-
4. Preserve existing frontmatter semantics.
|
|
103
|
-
|
|
104
|
-
**Checks:**
|
|
105
|
-
- Reuse/expand existing loader tests to cover parity.
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
### 6) Runtime/UI decoupling via reporter boundary (Medium)
|
|
110
|
-
**Outcome:** Core runtime no longer depends directly on terminal rendering.
|
|
111
|
-
|
|
112
|
-
**Steps:**
|
|
113
|
-
1. Define domain events or reporter interface in `src/agent/reporter.ts`.
|
|
114
|
-
2. Implement CLI reporter adapter in `src/cli/output-reporter.ts`.
|
|
115
|
-
3. Replace direct output calls in agent runtime with reporter calls.
|
|
116
|
-
|
|
117
|
-
**Checks:**
|
|
118
|
-
- Add tests using test reporter to assert emitted events.
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
### 7) Error observability and silent catches (Medium)
|
|
123
|
-
**Outcome:** Non-fatal failures become diagnosable without crashing.
|
|
124
|
-
|
|
125
|
-
**Steps:**
|
|
126
|
-
1. Find empty/broad catches in agent/output/loaders.
|
|
127
|
-
2. Add debug-level diagnostics with contextual metadata.
|
|
128
|
-
3. Keep user-facing behavior unchanged unless critical.
|
|
129
|
-
|
|
130
|
-
**Checks:**
|
|
131
|
-
- Validate noisy paths are still quiet at normal verbosity.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
### 8) Startup FS sync usage (Low/Deferred)
|
|
136
|
-
**Outcome:** Optional responsiveness improvement if startup cost grows.
|
|
137
|
-
|
|
138
|
-
**Steps:**
|
|
139
|
-
1. Measure startup and config-loading time first.
|
|
140
|
-
2. If needed, move high-volume file scanning to async or cache results with invalidation.
|
|
141
|
-
|
|
142
|
-
---
|
|
143
|
-
|
|
144
|
-
### 9) Test hygiene cleanup (Low)
|
|
145
|
-
**Outcome:** Cleaner CI output.
|
|
146
|
-
|
|
147
|
-
**Steps:**
|
|
148
|
-
1. Remove `console.log` skip notices in `src/tools/shell.test.ts`.
|
|
149
|
-
2. Use test-framework-native skip annotations/helpers.
|
|
150
|
-
|
|
151
|
-
---
|
|
152
|
-
|
|
153
|
-
## Execution Order (recommended)
|
|
154
|
-
1. Reporter interface (foundation for later decoupling).
|
|
155
|
-
2. `agent.ts` decomposition.
|
|
156
|
-
3. `output.ts` decomposition.
|
|
157
|
-
4. Shared config loader extraction.
|
|
158
|
-
5. DB module split + safe JSON parsing.
|
|
159
|
-
6. TerminalIO + centralized signals.
|
|
160
|
-
7. Silent catch diagnostics.
|
|
161
|
-
8. Test hygiene and any deferred FS optimization.
|
|
162
|
-
|
|
163
|
-
## Definition of Done
|
|
164
|
-
- `bun run typecheck && bun run format && bun run lint && bun test` passes.
|
|
165
|
-
- No behavior regressions in interactive CLI flows.
|
|
166
|
-
- `agent.ts` and `output.ts` materially reduced in size/responsibility.
|
|
167
|
-
- Config loader duplication removed.
|
|
168
|
-
- Message loading resilient to malformed JSON rows.
|
|
169
|
-
- New abstractions documented in code comments where non-obvious.
|