mini-coder 0.0.7 → 0.0.9
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/codex-lazy-fix.md +76 -0
- package/dist/mc.js +500 -212
- package/hanging-bug.md +78 -0
- package/package.json +1 -1
- package/plan-code-health.md +169 -0
- package/better-errors.md +0 -96
package/dist/mc.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as c8 from "yoctocolors";
|
|
|
6
6
|
|
|
7
7
|
// src/agent/agent.ts
|
|
8
8
|
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
|
|
9
|
-
import { join as
|
|
9
|
+
import { join as join16 } from "path";
|
|
10
10
|
import * as c7 from "yoctocolors";
|
|
11
11
|
|
|
12
12
|
// src/cli/agents.ts
|
|
@@ -94,7 +94,7 @@ function zenEndpointFor(modelId) {
|
|
|
94
94
|
if (modelId.startsWith("claude-"))
|
|
95
95
|
return zenAnthropic()(modelId);
|
|
96
96
|
if (modelId.startsWith("gpt-"))
|
|
97
|
-
return zenOpenAI()(modelId);
|
|
97
|
+
return zenOpenAI().responses(modelId);
|
|
98
98
|
if (modelId.startsWith("gemini-"))
|
|
99
99
|
return zenGoogle()(modelId);
|
|
100
100
|
return zenCompat()(modelId);
|
|
@@ -176,6 +176,15 @@ function directGoogle() {
|
|
|
176
176
|
}
|
|
177
177
|
return _directGoogle;
|
|
178
178
|
}
|
|
179
|
+
function parseModelString(modelString) {
|
|
180
|
+
const slashIdx = modelString.indexOf("/");
|
|
181
|
+
if (slashIdx === -1)
|
|
182
|
+
return { provider: modelString, modelId: "" };
|
|
183
|
+
return {
|
|
184
|
+
provider: modelString.slice(0, slashIdx),
|
|
185
|
+
modelId: modelString.slice(slashIdx + 1)
|
|
186
|
+
};
|
|
187
|
+
}
|
|
179
188
|
var CONTEXT_WINDOW_TABLE = [
|
|
180
189
|
[/^claude-/, 200000],
|
|
181
190
|
[/^gemini-/, 1e6],
|
|
@@ -187,7 +196,7 @@ var CONTEXT_WINDOW_TABLE = [
|
|
|
187
196
|
[/^qwen3-/, 131000]
|
|
188
197
|
];
|
|
189
198
|
function getContextWindow(modelString) {
|
|
190
|
-
const modelId =
|
|
199
|
+
const { modelId } = parseModelString(modelString);
|
|
191
200
|
for (const [pattern, tokens] of CONTEXT_WINDOW_TABLE) {
|
|
192
201
|
if (pattern.test(modelId))
|
|
193
202
|
return tokens;
|
|
@@ -208,7 +217,7 @@ function resolveModel(modelString) {
|
|
|
208
217
|
case "anthropic":
|
|
209
218
|
return directAnthropic()(modelId);
|
|
210
219
|
case "openai":
|
|
211
|
-
return directOpenAI()(modelId);
|
|
220
|
+
return modelId.startsWith("gpt-") ? directOpenAI().responses(modelId) : directOpenAI()(modelId);
|
|
212
221
|
case "google":
|
|
213
222
|
return directGoogle()(modelId);
|
|
214
223
|
case "ollama": {
|
|
@@ -521,16 +530,146 @@ function generateSessionId() {
|
|
|
521
530
|
|
|
522
531
|
// src/cli/custom-commands.ts
|
|
523
532
|
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
524
|
-
import { homedir as
|
|
525
|
-
import { basename as basename2, join as
|
|
533
|
+
import { homedir as homedir5 } from "os";
|
|
534
|
+
import { basename as basename2, join as join4 } from "path";
|
|
526
535
|
|
|
527
536
|
// src/cli/config-conflicts.ts
|
|
528
537
|
import * as c3 from "yoctocolors";
|
|
529
538
|
|
|
530
539
|
// src/cli/output.ts
|
|
531
|
-
import { homedir as
|
|
540
|
+
import { homedir as homedir4 } from "os";
|
|
532
541
|
import * as c2 from "yoctocolors";
|
|
533
542
|
|
|
543
|
+
// src/cli/error-log.ts
|
|
544
|
+
import { mkdirSync as mkdirSync2 } from "fs";
|
|
545
|
+
import { homedir as homedir3 } from "os";
|
|
546
|
+
import { join as join3 } from "path";
|
|
547
|
+
var writer = null;
|
|
548
|
+
function initErrorLog() {
|
|
549
|
+
if (writer)
|
|
550
|
+
return;
|
|
551
|
+
const dirPath = join3(homedir3(), ".config", "mini-coder");
|
|
552
|
+
const logPath = join3(dirPath, "errors.log");
|
|
553
|
+
mkdirSync2(dirPath, { recursive: true });
|
|
554
|
+
writer = Bun.file(logPath).writer();
|
|
555
|
+
process.on("uncaughtException", (err) => {
|
|
556
|
+
logError(err, "uncaught");
|
|
557
|
+
process.exit(1);
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
function isObject(v) {
|
|
561
|
+
return typeof v === "object" && v !== null;
|
|
562
|
+
}
|
|
563
|
+
function logError(err, context) {
|
|
564
|
+
if (!writer)
|
|
565
|
+
return;
|
|
566
|
+
let entry = `[${new Date().toISOString()}]`;
|
|
567
|
+
if (context)
|
|
568
|
+
entry += ` context=${context}`;
|
|
569
|
+
entry += `
|
|
570
|
+
`;
|
|
571
|
+
if (isObject(err)) {
|
|
572
|
+
if (typeof err.name === "string")
|
|
573
|
+
entry += ` name: ${err.name}
|
|
574
|
+
`;
|
|
575
|
+
if (typeof err.message === "string")
|
|
576
|
+
entry += ` message: ${err.message}
|
|
577
|
+
`;
|
|
578
|
+
if ("statusCode" in err)
|
|
579
|
+
entry += ` statusCode: ${err.statusCode}
|
|
580
|
+
`;
|
|
581
|
+
if ("url" in err)
|
|
582
|
+
entry += ` url: ${err.url}
|
|
583
|
+
`;
|
|
584
|
+
if ("isRetryable" in err)
|
|
585
|
+
entry += ` isRetryable: ${err.isRetryable}
|
|
586
|
+
`;
|
|
587
|
+
if (typeof err.stack === "string") {
|
|
588
|
+
const indentedStack = err.stack.split(`
|
|
589
|
+
`).map((line, i) => i === 0 ? line : ` ${line}`).join(`
|
|
590
|
+
`);
|
|
591
|
+
entry += ` stack: ${indentedStack}
|
|
592
|
+
`;
|
|
593
|
+
}
|
|
594
|
+
} else {
|
|
595
|
+
entry += ` value: ${String(err)}
|
|
596
|
+
`;
|
|
597
|
+
}
|
|
598
|
+
entry += `---
|
|
599
|
+
`;
|
|
600
|
+
writer.write(entry);
|
|
601
|
+
writer.flush();
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/cli/error-parse.ts
|
|
605
|
+
import {
|
|
606
|
+
APICallError,
|
|
607
|
+
LoadAPIKeyError,
|
|
608
|
+
NoContentGeneratedError,
|
|
609
|
+
NoSuchModelError,
|
|
610
|
+
RetryError
|
|
611
|
+
} from "ai";
|
|
612
|
+
function parseAppError(err) {
|
|
613
|
+
if (typeof err === "string") {
|
|
614
|
+
return { headline: err };
|
|
615
|
+
}
|
|
616
|
+
if (err instanceof RetryError) {
|
|
617
|
+
const inner = parseAppError(err.lastError);
|
|
618
|
+
return {
|
|
619
|
+
headline: `Retries exhausted: ${inner.headline}`,
|
|
620
|
+
...inner.hint ? { hint: inner.hint } : {}
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
if (err instanceof APICallError) {
|
|
624
|
+
if (err.statusCode === 429) {
|
|
625
|
+
return {
|
|
626
|
+
headline: "Rate limit hit",
|
|
627
|
+
hint: "Wait a moment and retry, or switch model with /model"
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
if (err.statusCode === 401 || err.statusCode === 403) {
|
|
631
|
+
return {
|
|
632
|
+
headline: "Auth failed",
|
|
633
|
+
hint: "Check the relevant provider API key env var"
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
return {
|
|
637
|
+
headline: `API error ${err.statusCode ?? "unknown"}`,
|
|
638
|
+
...err.url ? { hint: err.url } : {}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
if (err instanceof NoContentGeneratedError) {
|
|
642
|
+
return {
|
|
643
|
+
headline: "Model returned empty response",
|
|
644
|
+
hint: "Try rephrasing or switching model with /model"
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (err instanceof LoadAPIKeyError) {
|
|
648
|
+
return {
|
|
649
|
+
headline: "API key not found",
|
|
650
|
+
hint: "Set the relevant provider env var"
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
if (err instanceof NoSuchModelError) {
|
|
654
|
+
return {
|
|
655
|
+
headline: "Model not found",
|
|
656
|
+
hint: "Use /model to pick a valid model"
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
const isObj = typeof err === "object" && err !== null;
|
|
660
|
+
const code = isObj && "code" in err ? String(err.code) : undefined;
|
|
661
|
+
const message = isObj && "message" in err ? String(err.message) : String(err);
|
|
662
|
+
if (code === "ECONNREFUSED" || message.includes("ECONNREFUSED")) {
|
|
663
|
+
return {
|
|
664
|
+
headline: "Connection failed",
|
|
665
|
+
hint: "Check network or local server"
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
const firstLine = message.split(`
|
|
669
|
+
`)[0]?.trim() || "Unknown error";
|
|
670
|
+
return { headline: firstLine };
|
|
671
|
+
}
|
|
672
|
+
|
|
534
673
|
// src/cli/markdown.ts
|
|
535
674
|
import * as c from "yoctocolors";
|
|
536
675
|
function renderInline(text) {
|
|
@@ -644,8 +783,8 @@ function renderChunk(text, inFence) {
|
|
|
644
783
|
}
|
|
645
784
|
|
|
646
785
|
// src/cli/output.ts
|
|
647
|
-
var HOME =
|
|
648
|
-
var PACKAGE_VERSION = "0.0.
|
|
786
|
+
var HOME = homedir4();
|
|
787
|
+
var PACKAGE_VERSION = "0.0.8";
|
|
649
788
|
function tildePath(p) {
|
|
650
789
|
return p.startsWith(HOME) ? `~${p.slice(HOME.length)}` : p;
|
|
651
790
|
}
|
|
@@ -668,8 +807,6 @@ function registerTerminalCleanup() {
|
|
|
668
807
|
process.exit(143);
|
|
669
808
|
});
|
|
670
809
|
process.on("SIGINT", () => {
|
|
671
|
-
if (process.listenerCount("SIGINT") > 1)
|
|
672
|
-
return;
|
|
673
810
|
cleanup();
|
|
674
811
|
process.exit(130);
|
|
675
812
|
});
|
|
@@ -856,10 +993,6 @@ function renderToolResultInline(toolName, result, isError, indent) {
|
|
|
856
993
|
return;
|
|
857
994
|
}
|
|
858
995
|
if (toolName === "subagent") {
|
|
859
|
-
const r = result;
|
|
860
|
-
if (r.inputTokens || r.outputTokens) {
|
|
861
|
-
writeln(`${indent}${G.ok} ${c2.dim(`\u2191${r.inputTokens ?? 0} \u2193${r.outputTokens ?? 0}`)}`);
|
|
862
|
-
}
|
|
863
996
|
return;
|
|
864
997
|
}
|
|
865
998
|
if (toolName.startsWith("mcp_")) {
|
|
@@ -876,16 +1009,48 @@ function renderToolResultInline(toolName, result, isError, indent) {
|
|
|
876
1009
|
const text = JSON.stringify(result);
|
|
877
1010
|
writeln(`${indent}${G.info} ${c2.dim(text.length > 80 ? `${text.slice(0, 77)}\u2026` : text)}`);
|
|
878
1011
|
}
|
|
879
|
-
function
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1012
|
+
function formatSubagentLabel(laneId, parentLabel) {
|
|
1013
|
+
const numStr = parentLabel ? `${parentLabel.replace(/[\[\]]/g, "")}.${laneId}` : `${laneId}`;
|
|
1014
|
+
return c2.dim(c2.cyan(`[${numStr}]`));
|
|
1015
|
+
}
|
|
1016
|
+
var laneBuffers = new Map;
|
|
1017
|
+
function renderSubagentEvent(event, opts) {
|
|
1018
|
+
const { laneId, parentLabel, activeLanes } = opts;
|
|
1019
|
+
const labelStr = formatSubagentLabel(laneId, parentLabel);
|
|
1020
|
+
const prefix = activeLanes.size > 1 ? `${labelStr} ` : "";
|
|
1021
|
+
if (event.type === "text-delta") {
|
|
1022
|
+
const buf = (laneBuffers.get(laneId) ?? "") + event.delta;
|
|
1023
|
+
const lines = buf.split(`
|
|
1024
|
+
`);
|
|
1025
|
+
if (lines.length > 1) {
|
|
1026
|
+
for (let i = 0;i < lines.length - 1; i++) {
|
|
1027
|
+
writeln(`${prefix}${lines[i]}`);
|
|
886
1028
|
}
|
|
1029
|
+
laneBuffers.set(laneId, lines[lines.length - 1] ?? "");
|
|
1030
|
+
} else {
|
|
1031
|
+
laneBuffers.set(laneId, buf);
|
|
1032
|
+
}
|
|
1033
|
+
} else if (event.type === "tool-call-start") {
|
|
1034
|
+
writeln(`${prefix}${toolCallLine(event.toolName, event.args)}`);
|
|
1035
|
+
} else if (event.type === "tool-result") {
|
|
1036
|
+
renderToolResultInline(event.toolName, event.result, event.isError, `${prefix} `);
|
|
1037
|
+
} else if (event.type === "turn-complete") {
|
|
1038
|
+
const buf = laneBuffers.get(laneId);
|
|
1039
|
+
if (buf) {
|
|
1040
|
+
writeln(`${prefix}${buf}`);
|
|
1041
|
+
laneBuffers.delete(laneId);
|
|
1042
|
+
}
|
|
1043
|
+
if (event.inputTokens > 0 || event.outputTokens > 0) {
|
|
1044
|
+
writeln(`${prefix}${c2.dim(`\u2191${event.inputTokens} \u2193${event.outputTokens}`)}`);
|
|
1045
|
+
}
|
|
1046
|
+
} else if (event.type === "turn-error") {
|
|
1047
|
+
laneBuffers.delete(laneId);
|
|
1048
|
+
logError(event.error, "turn");
|
|
1049
|
+
const parsed = parseAppError(event.error);
|
|
1050
|
+
writeln(`${prefix}${G.err} ${c2.red(parsed.headline)}`);
|
|
1051
|
+
if (parsed.hint) {
|
|
1052
|
+
writeln(`${prefix} ${c2.dim(parsed.hint)}`);
|
|
887
1053
|
}
|
|
888
|
-
renderToolResultInline(entry.toolName, entry.result, entry.isError, `${indent} `);
|
|
889
1054
|
}
|
|
890
1055
|
}
|
|
891
1056
|
function renderToolResult(toolName, result, isError) {
|
|
@@ -985,22 +1150,6 @@ function renderToolResult(toolName, result, isError) {
|
|
|
985
1150
|
return;
|
|
986
1151
|
}
|
|
987
1152
|
if (toolName === "subagent") {
|
|
988
|
-
const r = result;
|
|
989
|
-
if (r.activity?.length) {
|
|
990
|
-
renderSubagentActivity(r.activity, " ", 1);
|
|
991
|
-
}
|
|
992
|
-
if (r.result) {
|
|
993
|
-
const lines = r.result.split(`
|
|
994
|
-
`);
|
|
995
|
-
const preview = lines.slice(0, 8);
|
|
996
|
-
for (const line of preview)
|
|
997
|
-
writeln(` ${c2.dim("\u2502")} ${line}`);
|
|
998
|
-
if (lines.length > 8)
|
|
999
|
-
writeln(` ${c2.dim(`\u2502 \u2026 +${lines.length - 8} lines`)}`);
|
|
1000
|
-
}
|
|
1001
|
-
if (r.inputTokens || r.outputTokens) {
|
|
1002
|
-
writeln(` ${c2.dim(`\u2191${r.inputTokens ?? 0} \u2193${r.outputTokens ?? 0}`)}`);
|
|
1003
|
-
}
|
|
1004
1153
|
return;
|
|
1005
1154
|
}
|
|
1006
1155
|
if (toolName.startsWith("mcp_")) {
|
|
@@ -1159,9 +1308,12 @@ async function renderTurn(events, spinner) {
|
|
|
1159
1308
|
if (isAbort) {
|
|
1160
1309
|
writeln(`${G.warn} ${c2.dim("interrupted")}`);
|
|
1161
1310
|
} else {
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
writeln(`${G.err} ${c2.red(
|
|
1311
|
+
logError(event.error, "turn");
|
|
1312
|
+
const parsed = parseAppError(event.error);
|
|
1313
|
+
writeln(`${G.err} ${c2.red(parsed.headline)}`);
|
|
1314
|
+
if (parsed.hint) {
|
|
1315
|
+
writeln(` ${c2.dim(parsed.hint)}`);
|
|
1316
|
+
}
|
|
1165
1317
|
}
|
|
1166
1318
|
break;
|
|
1167
1319
|
}
|
|
@@ -1211,9 +1363,13 @@ function renderBanner(model, cwd) {
|
|
|
1211
1363
|
writeln(` ${c2.dim("/help for commands \xB7 ctrl+d to exit")}`);
|
|
1212
1364
|
writeln();
|
|
1213
1365
|
}
|
|
1214
|
-
function renderError(err) {
|
|
1215
|
-
|
|
1216
|
-
|
|
1366
|
+
function renderError(err, context = "render") {
|
|
1367
|
+
logError(err, context);
|
|
1368
|
+
const parsed = parseAppError(err);
|
|
1369
|
+
writeln(`${G.err} ${c2.red(parsed.headline)}`);
|
|
1370
|
+
if (parsed.hint) {
|
|
1371
|
+
writeln(` ${c2.dim(parsed.hint)}`);
|
|
1372
|
+
}
|
|
1217
1373
|
}
|
|
1218
1374
|
function renderInfo(msg) {
|
|
1219
1375
|
writeln(`${G.info} ${c2.dim(msg)}`);
|
|
@@ -1301,7 +1457,7 @@ function loadFromDir2(dir, source) {
|
|
|
1301
1457
|
if (!entry.endsWith(".md"))
|
|
1302
1458
|
continue;
|
|
1303
1459
|
const name = basename2(entry, ".md");
|
|
1304
|
-
const filePath =
|
|
1460
|
+
const filePath = join4(dir, entry);
|
|
1305
1461
|
let raw;
|
|
1306
1462
|
try {
|
|
1307
1463
|
raw = readFileSync2(filePath, "utf-8");
|
|
@@ -1320,10 +1476,10 @@ function loadFromDir2(dir, source) {
|
|
|
1320
1476
|
return commands;
|
|
1321
1477
|
}
|
|
1322
1478
|
function loadCustomCommands(cwd) {
|
|
1323
|
-
const globalAgentsDir =
|
|
1324
|
-
const globalClaudeDir =
|
|
1325
|
-
const localAgentsDir =
|
|
1326
|
-
const localClaudeDir =
|
|
1479
|
+
const globalAgentsDir = join4(homedir5(), ".agents", "commands");
|
|
1480
|
+
const globalClaudeDir = join4(homedir5(), ".claude", "commands");
|
|
1481
|
+
const localAgentsDir = join4(cwd, ".agents", "commands");
|
|
1482
|
+
const localClaudeDir = join4(cwd, ".claude", "commands");
|
|
1327
1483
|
const globalAgents = loadFromDir2(globalAgentsDir, "global");
|
|
1328
1484
|
const globalClaude = loadFromDir2(globalClaudeDir, "global");
|
|
1329
1485
|
const localAgents = loadFromDir2(localAgentsDir, "local");
|
|
@@ -1378,8 +1534,8 @@ async function expandTemplate(template, args, cwd) {
|
|
|
1378
1534
|
|
|
1379
1535
|
// src/cli/skills.ts
|
|
1380
1536
|
import { existsSync as existsSync4, readFileSync as readFileSync3, readdirSync as readdirSync3, statSync } from "fs";
|
|
1381
|
-
import { homedir as
|
|
1382
|
-
import { join as
|
|
1537
|
+
import { homedir as homedir6 } from "os";
|
|
1538
|
+
import { join as join5 } from "path";
|
|
1383
1539
|
function loadFromDir3(dir, source) {
|
|
1384
1540
|
const skills = new Map;
|
|
1385
1541
|
if (!existsSync4(dir))
|
|
@@ -1391,9 +1547,9 @@ function loadFromDir3(dir, source) {
|
|
|
1391
1547
|
return skills;
|
|
1392
1548
|
}
|
|
1393
1549
|
for (const entry of entries) {
|
|
1394
|
-
const skillFile =
|
|
1550
|
+
const skillFile = join5(dir, entry, "SKILL.md");
|
|
1395
1551
|
try {
|
|
1396
|
-
if (!statSync(
|
|
1552
|
+
if (!statSync(join5(dir, entry)).isDirectory())
|
|
1397
1553
|
continue;
|
|
1398
1554
|
if (!existsSync4(skillFile))
|
|
1399
1555
|
continue;
|
|
@@ -1411,10 +1567,10 @@ function loadFromDir3(dir, source) {
|
|
|
1411
1567
|
return skills;
|
|
1412
1568
|
}
|
|
1413
1569
|
function loadSkills(cwd) {
|
|
1414
|
-
const globalAgentsDir =
|
|
1415
|
-
const globalClaudeDir =
|
|
1416
|
-
const localAgentsDir =
|
|
1417
|
-
const localClaudeDir =
|
|
1570
|
+
const globalAgentsDir = join5(homedir6(), ".agents", "skills");
|
|
1571
|
+
const globalClaudeDir = join5(homedir6(), ".claude", "skills");
|
|
1572
|
+
const localAgentsDir = join5(cwd, ".agents", "skills");
|
|
1573
|
+
const localClaudeDir = join5(cwd, ".claude", "skills");
|
|
1418
1574
|
const globalAgents = loadFromDir3(globalAgentsDir, "global");
|
|
1419
1575
|
const globalClaude = loadFromDir3(globalClaudeDir, "global");
|
|
1420
1576
|
const localAgents = loadFromDir3(localAgentsDir, "local");
|
|
@@ -1609,10 +1765,6 @@ async function handleReview(ctx, args) {
|
|
|
1609
1765
|
writeln();
|
|
1610
1766
|
try {
|
|
1611
1767
|
const output = await ctx.runSubagent(REVIEW_PROMPT(ctx.cwd, focus));
|
|
1612
|
-
if (output.activity.length) {
|
|
1613
|
-
renderSubagentActivity(output.activity, " ", 1);
|
|
1614
|
-
writeln();
|
|
1615
|
-
}
|
|
1616
1768
|
write(renderMarkdown(output.result));
|
|
1617
1769
|
writeln();
|
|
1618
1770
|
return {
|
|
@@ -1641,10 +1793,6 @@ async function handleCustomCommand(cmd, args, ctx) {
|
|
|
1641
1793
|
writeln();
|
|
1642
1794
|
try {
|
|
1643
1795
|
const output = await ctx.runSubagent(prompt, cmd.model);
|
|
1644
|
-
if (output.activity.length) {
|
|
1645
|
-
renderSubagentActivity(output.activity, " ", 1);
|
|
1646
|
-
writeln();
|
|
1647
|
-
}
|
|
1648
1796
|
write(renderMarkdown(output.result));
|
|
1649
1797
|
writeln();
|
|
1650
1798
|
return {
|
|
@@ -1795,7 +1943,7 @@ async function loadImageFile(filePath) {
|
|
|
1795
1943
|
}
|
|
1796
1944
|
|
|
1797
1945
|
// src/cli/input.ts
|
|
1798
|
-
import { join as
|
|
1946
|
+
import { join as join6, relative } from "path";
|
|
1799
1947
|
import * as c5 from "yoctocolors";
|
|
1800
1948
|
var ESC = "\x1B";
|
|
1801
1949
|
var CSI = `${ESC}[`;
|
|
@@ -1847,7 +1995,7 @@ async function getAtCompletions(prefix, cwd) {
|
|
|
1847
1995
|
for await (const file of glob.scan({ cwd, onlyFiles: true })) {
|
|
1848
1996
|
if (file.includes("node_modules") || file.includes(".git"))
|
|
1849
1997
|
continue;
|
|
1850
|
-
results.push(`@${relative(cwd,
|
|
1998
|
+
results.push(`@${relative(cwd, join6(cwd, file))}`);
|
|
1851
1999
|
if (results.length >= MAX)
|
|
1852
2000
|
break;
|
|
1853
2001
|
}
|
|
@@ -1869,7 +2017,7 @@ async function tryExtractImageFromPaste(pasted, cwd) {
|
|
|
1869
2017
|
}
|
|
1870
2018
|
}
|
|
1871
2019
|
if (!trimmed.includes(" ") && isImageFilename(trimmed)) {
|
|
1872
|
-
const filePath = trimmed.startsWith("/") ? trimmed :
|
|
2020
|
+
const filePath = trimmed.startsWith("/") ? trimmed : join6(cwd, trimmed);
|
|
1873
2021
|
const attachment = await loadImageFile(filePath);
|
|
1874
2022
|
if (attachment) {
|
|
1875
2023
|
const name = filePath.split("/").pop() ?? trimmed;
|
|
@@ -1906,6 +2054,28 @@ async function readKey(reader) {
|
|
|
1906
2054
|
return "";
|
|
1907
2055
|
return new TextDecoder().decode(value);
|
|
1908
2056
|
}
|
|
2057
|
+
function watchForInterrupt(abortController) {
|
|
2058
|
+
if (!process.stdin.isTTY)
|
|
2059
|
+
return () => {};
|
|
2060
|
+
const onData = (chunk) => {
|
|
2061
|
+
for (const byte of chunk) {
|
|
2062
|
+
if (byte === 3) {
|
|
2063
|
+
cleanup();
|
|
2064
|
+
abortController.abort();
|
|
2065
|
+
return;
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
};
|
|
2069
|
+
const cleanup = () => {
|
|
2070
|
+
process.stdin.removeListener("data", onData);
|
|
2071
|
+
process.stdin.setRawMode(false);
|
|
2072
|
+
process.stdin.pause();
|
|
2073
|
+
};
|
|
2074
|
+
process.stdin.setRawMode(true);
|
|
2075
|
+
process.stdin.resume();
|
|
2076
|
+
process.stdin.on("data", onData);
|
|
2077
|
+
return cleanup;
|
|
2078
|
+
}
|
|
1909
2079
|
var PASTE_SENTINEL = "\x00PASTE\x00";
|
|
1910
2080
|
var PASTE_SENTINEL_LEN = PASTE_SENTINEL.length;
|
|
1911
2081
|
function pasteLabel(text) {
|
|
@@ -2240,30 +2410,51 @@ var MAX_STEPS = 50;
|
|
|
2240
2410
|
function isZodSchema(s) {
|
|
2241
2411
|
return s !== null && typeof s === "object" && "_def" in s;
|
|
2242
2412
|
}
|
|
2243
|
-
function toCoreTool(def) {
|
|
2413
|
+
function toCoreTool(def, claimWarning) {
|
|
2244
2414
|
const schema = isZodSchema(def.schema) ? def.schema : jsonSchema(def.schema);
|
|
2245
2415
|
return dynamicTool({
|
|
2246
2416
|
description: def.description,
|
|
2247
2417
|
inputSchema: schema,
|
|
2248
2418
|
execute: async (input) => {
|
|
2249
2419
|
try {
|
|
2250
|
-
|
|
2420
|
+
const result = await def.execute(input);
|
|
2421
|
+
if (claimWarning()) {
|
|
2422
|
+
const warning = `
|
|
2423
|
+
|
|
2424
|
+
<system-message>You have reached the maximum number of tool calls. ` + "No more tools will be available after this result. " + "Respond with a status update and list what still needs to be done.</system-message>";
|
|
2425
|
+
const str = typeof result === "string" ? result : JSON.stringify(result);
|
|
2426
|
+
return str + warning;
|
|
2427
|
+
}
|
|
2428
|
+
return result;
|
|
2251
2429
|
} catch (err) {
|
|
2252
2430
|
throw err instanceof Error ? err : new Error(String(err));
|
|
2253
2431
|
}
|
|
2254
2432
|
}
|
|
2255
2433
|
});
|
|
2256
2434
|
}
|
|
2435
|
+
function isOpenAIGPT(modelString) {
|
|
2436
|
+
const { provider, modelId } = parseModelString(modelString);
|
|
2437
|
+
return (provider === "openai" || provider === "zen") && modelId.startsWith("gpt-");
|
|
2438
|
+
}
|
|
2257
2439
|
async function* runTurn(options) {
|
|
2258
|
-
const { model, messages, tools, systemPrompt, signal } = options;
|
|
2440
|
+
const { model, modelString, messages, tools, systemPrompt, signal } = options;
|
|
2441
|
+
let stepCount = 0;
|
|
2442
|
+
let warningClaimed = false;
|
|
2443
|
+
function claimWarning() {
|
|
2444
|
+
if (stepCount !== MAX_STEPS - 2 || warningClaimed)
|
|
2445
|
+
return false;
|
|
2446
|
+
warningClaimed = true;
|
|
2447
|
+
return true;
|
|
2448
|
+
}
|
|
2259
2449
|
const toolSet = {};
|
|
2260
2450
|
for (const def of tools) {
|
|
2261
|
-
toolSet[def.name] = toCoreTool(def);
|
|
2451
|
+
toolSet[def.name] = toCoreTool(def, claimWarning);
|
|
2262
2452
|
}
|
|
2263
2453
|
let inputTokens = 0;
|
|
2264
2454
|
let outputTokens = 0;
|
|
2265
2455
|
let contextTokens = 0;
|
|
2266
2456
|
try {
|
|
2457
|
+
const useInstructions = systemPrompt !== undefined && isOpenAIGPT(modelString);
|
|
2267
2458
|
const streamOpts = {
|
|
2268
2459
|
model,
|
|
2269
2460
|
messages,
|
|
@@ -2273,11 +2464,28 @@ async function* runTurn(options) {
|
|
|
2273
2464
|
inputTokens += step.usage?.inputTokens ?? 0;
|
|
2274
2465
|
outputTokens += step.usage?.outputTokens ?? 0;
|
|
2275
2466
|
contextTokens = step.usage?.inputTokens ?? contextTokens;
|
|
2467
|
+
stepCount++;
|
|
2468
|
+
warningClaimed = false;
|
|
2276
2469
|
},
|
|
2277
|
-
|
|
2470
|
+
prepareStep: ({ stepNumber }) => {
|
|
2471
|
+
if (stepNumber >= MAX_STEPS - 1) {
|
|
2472
|
+
return { activeTools: [] };
|
|
2473
|
+
}
|
|
2474
|
+
return;
|
|
2475
|
+
},
|
|
2476
|
+
...systemPrompt && !useInstructions ? { system: systemPrompt } : {},
|
|
2477
|
+
...useInstructions ? {
|
|
2478
|
+
providerOptions: {
|
|
2479
|
+
openai: {
|
|
2480
|
+
instructions: systemPrompt,
|
|
2481
|
+
store: false
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
} : {},
|
|
2278
2485
|
...signal ? { abortSignal: signal } : {}
|
|
2279
2486
|
};
|
|
2280
2487
|
const result = streamText(streamOpts);
|
|
2488
|
+
result.response.catch(() => {});
|
|
2281
2489
|
for await (const chunk of result.fullStream) {
|
|
2282
2490
|
if (signal?.aborted)
|
|
2283
2491
|
break;
|
|
@@ -2402,7 +2610,7 @@ async function connectMcpServer(config) {
|
|
|
2402
2610
|
|
|
2403
2611
|
// src/tools/snapshot.ts
|
|
2404
2612
|
import { readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
2405
|
-
import { join as
|
|
2613
|
+
import { join as join7 } from "path";
|
|
2406
2614
|
async function gitBytes(args, cwd) {
|
|
2407
2615
|
try {
|
|
2408
2616
|
const proc = Bun.spawn(["git", ...args], {
|
|
@@ -2493,7 +2701,7 @@ async function takeSnapshot(cwd, sessionId, turnIndex) {
|
|
|
2493
2701
|
return false;
|
|
2494
2702
|
const files = [];
|
|
2495
2703
|
for (const entry of entries) {
|
|
2496
|
-
const absPath =
|
|
2704
|
+
const absPath = join7(repoRoot, entry.path);
|
|
2497
2705
|
if (!entry.existsOnDisk) {
|
|
2498
2706
|
const { bytes, code } = await gitBytes(["show", `HEAD:${entry.path}`], repoRoot);
|
|
2499
2707
|
if (code === 0) {
|
|
@@ -2542,7 +2750,7 @@ async function restoreSnapshot(cwd, sessionId, turnIndex) {
|
|
|
2542
2750
|
const root = repoRoot ?? cwd;
|
|
2543
2751
|
let anyFailed = false;
|
|
2544
2752
|
for (const file of files) {
|
|
2545
|
-
const absPath =
|
|
2753
|
+
const absPath = join7(root, file.path);
|
|
2546
2754
|
if (!file.existed) {
|
|
2547
2755
|
try {
|
|
2548
2756
|
if (await Bun.file(absPath).exists()) {
|
|
@@ -2609,10 +2817,75 @@ function getMostRecentSession() {
|
|
|
2609
2817
|
return sessions[0] ?? null;
|
|
2610
2818
|
}
|
|
2611
2819
|
|
|
2612
|
-
// src/tools/
|
|
2613
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
2614
|
-
import { dirname, join as join7, relative as relative2 } from "path";
|
|
2820
|
+
// src/tools/exa.ts
|
|
2615
2821
|
import { z as z2 } from "zod";
|
|
2822
|
+
var ExaSearchSchema = z2.object({
|
|
2823
|
+
query: z2.string().describe("The search query")
|
|
2824
|
+
});
|
|
2825
|
+
var webSearchTool = {
|
|
2826
|
+
name: "webSearch",
|
|
2827
|
+
description: "Search the web for a query using Exa.",
|
|
2828
|
+
schema: ExaSearchSchema,
|
|
2829
|
+
execute: async (input) => {
|
|
2830
|
+
const apiKey = process.env.EXA_API_KEY;
|
|
2831
|
+
if (!apiKey) {
|
|
2832
|
+
throw new Error("EXA_API_KEY is not set.");
|
|
2833
|
+
}
|
|
2834
|
+
const response = await fetch("https://api.exa.ai/search", {
|
|
2835
|
+
method: "POST",
|
|
2836
|
+
headers: {
|
|
2837
|
+
"Content-Type": "application/json",
|
|
2838
|
+
"x-api-key": apiKey
|
|
2839
|
+
},
|
|
2840
|
+
body: JSON.stringify({
|
|
2841
|
+
query: input.query,
|
|
2842
|
+
type: "auto",
|
|
2843
|
+
numResults: 10,
|
|
2844
|
+
contents: { text: { maxCharacters: 4000 } }
|
|
2845
|
+
})
|
|
2846
|
+
});
|
|
2847
|
+
if (!response.ok) {
|
|
2848
|
+
const errorBody = await response.text();
|
|
2849
|
+
throw new Error(`Exa API error: ${response.status} ${response.statusText} - ${errorBody}`);
|
|
2850
|
+
}
|
|
2851
|
+
return await response.json();
|
|
2852
|
+
}
|
|
2853
|
+
};
|
|
2854
|
+
var ExaContentSchema = z2.object({
|
|
2855
|
+
urls: z2.array(z2.string()).max(3).describe("Array of URLs to retrieve content for (max 3)")
|
|
2856
|
+
});
|
|
2857
|
+
var webContentTool = {
|
|
2858
|
+
name: "webContent",
|
|
2859
|
+
description: "Get the full content of specific URLs using Exa.",
|
|
2860
|
+
schema: ExaContentSchema,
|
|
2861
|
+
execute: async (input) => {
|
|
2862
|
+
const apiKey = process.env.EXA_API_KEY;
|
|
2863
|
+
if (!apiKey) {
|
|
2864
|
+
throw new Error("EXA_API_KEY is not set.");
|
|
2865
|
+
}
|
|
2866
|
+
const response = await fetch("https://api.exa.ai/contents", {
|
|
2867
|
+
method: "POST",
|
|
2868
|
+
headers: {
|
|
2869
|
+
"Content-Type": "application/json",
|
|
2870
|
+
"x-api-key": apiKey
|
|
2871
|
+
},
|
|
2872
|
+
body: JSON.stringify({
|
|
2873
|
+
urls: input.urls,
|
|
2874
|
+
text: { maxCharacters: 1e4 }
|
|
2875
|
+
})
|
|
2876
|
+
});
|
|
2877
|
+
if (!response.ok) {
|
|
2878
|
+
const errorBody = await response.text();
|
|
2879
|
+
throw new Error(`Exa API error: ${response.status} ${response.statusText} - ${errorBody}`);
|
|
2880
|
+
}
|
|
2881
|
+
return await response.json();
|
|
2882
|
+
}
|
|
2883
|
+
};
|
|
2884
|
+
|
|
2885
|
+
// src/tools/create.ts
|
|
2886
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
2887
|
+
import { dirname, join as join8, relative as relative2 } from "path";
|
|
2888
|
+
import { z as z3 } from "zod";
|
|
2616
2889
|
|
|
2617
2890
|
// src/tools/diff.ts
|
|
2618
2891
|
function generateDiff(filePath, before, after) {
|
|
@@ -2741,9 +3014,9 @@ ${lines.join(`
|
|
|
2741
3014
|
}
|
|
2742
3015
|
|
|
2743
3016
|
// src/tools/create.ts
|
|
2744
|
-
var CreateSchema =
|
|
2745
|
-
path:
|
|
2746
|
-
content:
|
|
3017
|
+
var CreateSchema = z3.object({
|
|
3018
|
+
path: z3.string().describe("File path to write (absolute or relative to cwd)"),
|
|
3019
|
+
content: z3.string().describe("Full content to write to the file")
|
|
2747
3020
|
});
|
|
2748
3021
|
var createTool = {
|
|
2749
3022
|
name: "create",
|
|
@@ -2751,11 +3024,11 @@ var createTool = {
|
|
|
2751
3024
|
schema: CreateSchema,
|
|
2752
3025
|
execute: async (input) => {
|
|
2753
3026
|
const cwd = input.cwd ?? process.cwd();
|
|
2754
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
3027
|
+
const filePath = input.path.startsWith("/") ? input.path : join8(cwd, input.path);
|
|
2755
3028
|
const relPath = relative2(cwd, filePath);
|
|
2756
3029
|
const dir = dirname(filePath);
|
|
2757
3030
|
if (!existsSync5(dir))
|
|
2758
|
-
|
|
3031
|
+
mkdirSync3(dir, { recursive: true });
|
|
2759
3032
|
const file = Bun.file(filePath);
|
|
2760
3033
|
const created = !await file.exists();
|
|
2761
3034
|
const before = created ? "" : await file.text();
|
|
@@ -2766,15 +3039,15 @@ var createTool = {
|
|
|
2766
3039
|
};
|
|
2767
3040
|
|
|
2768
3041
|
// src/tools/glob.ts
|
|
2769
|
-
import { join as
|
|
2770
|
-
import { z as
|
|
3042
|
+
import { join as join10, relative as relative3 } from "path";
|
|
3043
|
+
import { z as z4 } from "zod";
|
|
2771
3044
|
|
|
2772
3045
|
// src/tools/ignore.ts
|
|
2773
|
-
import { join as
|
|
3046
|
+
import { join as join9 } from "path";
|
|
2774
3047
|
import ignore from "ignore";
|
|
2775
3048
|
async function loadGitignore(cwd) {
|
|
2776
3049
|
try {
|
|
2777
|
-
const gitignore = await Bun.file(
|
|
3050
|
+
const gitignore = await Bun.file(join9(cwd, ".gitignore")).text();
|
|
2778
3051
|
return ignore().add(gitignore);
|
|
2779
3052
|
} catch {
|
|
2780
3053
|
return null;
|
|
@@ -2782,9 +3055,9 @@ async function loadGitignore(cwd) {
|
|
|
2782
3055
|
}
|
|
2783
3056
|
|
|
2784
3057
|
// src/tools/glob.ts
|
|
2785
|
-
var GlobSchema =
|
|
2786
|
-
pattern:
|
|
2787
|
-
ignore:
|
|
3058
|
+
var GlobSchema = z4.object({
|
|
3059
|
+
pattern: z4.string().describe("Glob pattern to match files against, e.g. '**/*.ts'"),
|
|
3060
|
+
ignore: z4.array(z4.string()).optional().describe("Glob patterns to exclude")
|
|
2788
3061
|
});
|
|
2789
3062
|
var MAX_RESULTS = 500;
|
|
2790
3063
|
var globTool = {
|
|
@@ -2807,7 +3080,7 @@ var globTool = {
|
|
|
2807
3080
|
if (ignored)
|
|
2808
3081
|
continue;
|
|
2809
3082
|
try {
|
|
2810
|
-
const fullPath =
|
|
3083
|
+
const fullPath = join10(cwd, file);
|
|
2811
3084
|
const stat = await Bun.file(fullPath).stat?.() ?? null;
|
|
2812
3085
|
matches.push({ path: file, mtime: stat?.mtime?.getTime() ?? 0 });
|
|
2813
3086
|
} catch {
|
|
@@ -2820,14 +3093,14 @@ var globTool = {
|
|
|
2820
3093
|
if (truncated)
|
|
2821
3094
|
matches.pop();
|
|
2822
3095
|
matches.sort((a, b) => b.mtime - a.mtime);
|
|
2823
|
-
const files = matches.map((m) => relative3(cwd,
|
|
3096
|
+
const files = matches.map((m) => relative3(cwd, join10(cwd, m.path)));
|
|
2824
3097
|
return { files, count: files.length, truncated };
|
|
2825
3098
|
}
|
|
2826
3099
|
};
|
|
2827
3100
|
|
|
2828
3101
|
// src/tools/grep.ts
|
|
2829
|
-
import { join as
|
|
2830
|
-
import { z as
|
|
3102
|
+
import { join as join11 } from "path";
|
|
3103
|
+
import { z as z5 } from "zod";
|
|
2831
3104
|
|
|
2832
3105
|
// src/tools/hashline.ts
|
|
2833
3106
|
var FNV_OFFSET_BASIS = 2166136261;
|
|
@@ -2873,12 +3146,12 @@ function findLineByHash(lines, hash, hintLine) {
|
|
|
2873
3146
|
}
|
|
2874
3147
|
|
|
2875
3148
|
// src/tools/grep.ts
|
|
2876
|
-
var GrepSchema =
|
|
2877
|
-
pattern:
|
|
2878
|
-
include:
|
|
2879
|
-
contextLines:
|
|
2880
|
-
caseSensitive:
|
|
2881
|
-
maxResults:
|
|
3149
|
+
var GrepSchema = z5.object({
|
|
3150
|
+
pattern: z5.string().describe("Regular expression to search for"),
|
|
3151
|
+
include: z5.string().optional().describe("Glob pattern to filter files, e.g. '*.ts' or '*.{ts,tsx}'"),
|
|
3152
|
+
contextLines: z5.number().int().min(0).max(10).optional().default(2).describe("Lines of context to include around each match"),
|
|
3153
|
+
caseSensitive: z5.boolean().optional().default(true),
|
|
3154
|
+
maxResults: z5.number().int().min(1).max(200).optional().default(50)
|
|
2882
3155
|
});
|
|
2883
3156
|
var DEFAULT_IGNORE = [
|
|
2884
3157
|
"node_modules",
|
|
@@ -2917,7 +3190,7 @@ var grepTool = {
|
|
|
2917
3190
|
if (ignoreGlob.some((g) => g.match(relPath) || g.match(firstSegment))) {
|
|
2918
3191
|
continue;
|
|
2919
3192
|
}
|
|
2920
|
-
const fullPath =
|
|
3193
|
+
const fullPath = join11(cwd, relPath);
|
|
2921
3194
|
let text;
|
|
2922
3195
|
try {
|
|
2923
3196
|
text = await Bun.file(fullPath).text();
|
|
@@ -2968,8 +3241,8 @@ var grepTool = {
|
|
|
2968
3241
|
|
|
2969
3242
|
// src/tools/hooks.ts
|
|
2970
3243
|
import { constants, accessSync } from "fs";
|
|
2971
|
-
import { homedir as
|
|
2972
|
-
import { join as
|
|
3244
|
+
import { homedir as homedir7 } from "os";
|
|
3245
|
+
import { join as join12 } from "path";
|
|
2973
3246
|
function isExecutable(filePath) {
|
|
2974
3247
|
try {
|
|
2975
3248
|
accessSync(filePath, constants.X_OK);
|
|
@@ -2981,8 +3254,8 @@ function isExecutable(filePath) {
|
|
|
2981
3254
|
function findHook(toolName, cwd) {
|
|
2982
3255
|
const scriptName = `post-${toolName}`;
|
|
2983
3256
|
const candidates = [
|
|
2984
|
-
|
|
2985
|
-
|
|
3257
|
+
join12(cwd, ".agents", "hooks", scriptName),
|
|
3258
|
+
join12(homedir7(), ".agents", "hooks", scriptName)
|
|
2986
3259
|
];
|
|
2987
3260
|
for (const p of candidates) {
|
|
2988
3261
|
if (isExecutable(p))
|
|
@@ -3071,13 +3344,13 @@ function hookEnvForRead(input, cwd) {
|
|
|
3071
3344
|
}
|
|
3072
3345
|
|
|
3073
3346
|
// src/tools/insert.ts
|
|
3074
|
-
import { join as
|
|
3075
|
-
import { z as
|
|
3076
|
-
var InsertSchema =
|
|
3077
|
-
path:
|
|
3078
|
-
anchor:
|
|
3079
|
-
position:
|
|
3080
|
-
content:
|
|
3347
|
+
import { join as join13, relative as relative4 } from "path";
|
|
3348
|
+
import { z as z6 } from "zod";
|
|
3349
|
+
var InsertSchema = z6.object({
|
|
3350
|
+
path: z6.string().describe("File path to edit (absolute or relative to cwd)"),
|
|
3351
|
+
anchor: z6.string().describe('Anchor line from a prior read/grep, e.g. "11:a3"'),
|
|
3352
|
+
position: z6.enum(["before", "after"]).describe('Insert the content "before" or "after" the anchor line'),
|
|
3353
|
+
content: z6.string().describe("Text to insert")
|
|
3081
3354
|
});
|
|
3082
3355
|
var HASH_NOT_FOUND_ERROR = "Hash not found. Re-read the file to get current anchors.";
|
|
3083
3356
|
var insertTool = {
|
|
@@ -3086,7 +3359,7 @@ var insertTool = {
|
|
|
3086
3359
|
schema: InsertSchema,
|
|
3087
3360
|
execute: async (input) => {
|
|
3088
3361
|
const cwd = input.cwd ?? process.cwd();
|
|
3089
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
3362
|
+
const filePath = input.path.startsWith("/") ? input.path : join13(cwd, input.path);
|
|
3090
3363
|
const relPath = relative4(cwd, filePath);
|
|
3091
3364
|
const file = Bun.file(filePath);
|
|
3092
3365
|
if (!await file.exists()) {
|
|
@@ -3131,12 +3404,12 @@ function parseAnchor(value) {
|
|
|
3131
3404
|
}
|
|
3132
3405
|
|
|
3133
3406
|
// src/tools/read.ts
|
|
3134
|
-
import { join as
|
|
3135
|
-
import { z as
|
|
3136
|
-
var ReadSchema =
|
|
3137
|
-
path:
|
|
3138
|
-
line:
|
|
3139
|
-
count:
|
|
3407
|
+
import { join as join14, relative as relative5 } from "path";
|
|
3408
|
+
import { z as z7 } from "zod";
|
|
3409
|
+
var ReadSchema = z7.object({
|
|
3410
|
+
path: z7.string().describe("File path to read (absolute or relative to cwd)"),
|
|
3411
|
+
line: z7.number().int().min(1).optional().describe("1-indexed starting line (default: 1)"),
|
|
3412
|
+
count: z7.number().int().min(1).max(500).optional().describe("Lines to read (default: 500, max: 500)")
|
|
3140
3413
|
});
|
|
3141
3414
|
var MAX_COUNT = 500;
|
|
3142
3415
|
var MAX_BYTES = 1e6;
|
|
@@ -3146,7 +3419,7 @@ var readTool = {
|
|
|
3146
3419
|
schema: ReadSchema,
|
|
3147
3420
|
execute: async (input) => {
|
|
3148
3421
|
const cwd = input.cwd ?? process.cwd();
|
|
3149
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
3422
|
+
const filePath = input.path.startsWith("/") ? input.path : join14(cwd, input.path);
|
|
3150
3423
|
const file = Bun.file(filePath);
|
|
3151
3424
|
const exists = await file.exists();
|
|
3152
3425
|
if (!exists) {
|
|
@@ -3179,13 +3452,13 @@ var readTool = {
|
|
|
3179
3452
|
};
|
|
3180
3453
|
|
|
3181
3454
|
// src/tools/replace.ts
|
|
3182
|
-
import { join as
|
|
3183
|
-
import { z as
|
|
3184
|
-
var ReplaceSchema =
|
|
3185
|
-
path:
|
|
3186
|
-
startAnchor:
|
|
3187
|
-
endAnchor:
|
|
3188
|
-
newContent:
|
|
3455
|
+
import { join as join15, relative as relative6 } from "path";
|
|
3456
|
+
import { z as z8 } from "zod";
|
|
3457
|
+
var ReplaceSchema = z8.object({
|
|
3458
|
+
path: z8.string().describe("File path to edit (absolute or relative to cwd)"),
|
|
3459
|
+
startAnchor: z8.string().describe('Start anchor from a prior read/grep, e.g. "11:a3"'),
|
|
3460
|
+
endAnchor: z8.string().optional().describe('End anchor (inclusive), e.g. "33:0e". Omit to target only the startAnchor line.'),
|
|
3461
|
+
newContent: z8.string().optional().describe("Replacement text. Omit or pass empty string to delete the range.")
|
|
3189
3462
|
});
|
|
3190
3463
|
var HASH_NOT_FOUND_ERROR2 = "Hash not found. Re-read the file to get current anchors.";
|
|
3191
3464
|
var replaceTool = {
|
|
@@ -3194,7 +3467,7 @@ var replaceTool = {
|
|
|
3194
3467
|
schema: ReplaceSchema,
|
|
3195
3468
|
execute: async (input) => {
|
|
3196
3469
|
const cwd = input.cwd ?? process.cwd();
|
|
3197
|
-
const filePath = input.path.startsWith("/") ? input.path :
|
|
3470
|
+
const filePath = input.path.startsWith("/") ? input.path : join15(cwd, input.path);
|
|
3198
3471
|
const relPath = relative6(cwd, filePath);
|
|
3199
3472
|
const file = Bun.file(filePath);
|
|
3200
3473
|
if (!await file.exists()) {
|
|
@@ -3252,11 +3525,11 @@ function parseAnchor2(value, name) {
|
|
|
3252
3525
|
}
|
|
3253
3526
|
|
|
3254
3527
|
// src/tools/shell.ts
|
|
3255
|
-
import { z as
|
|
3256
|
-
var ShellSchema =
|
|
3257
|
-
command:
|
|
3258
|
-
timeout:
|
|
3259
|
-
env:
|
|
3528
|
+
import { z as z9 } from "zod";
|
|
3529
|
+
var ShellSchema = z9.object({
|
|
3530
|
+
command: z9.string().describe("Shell command to execute"),
|
|
3531
|
+
timeout: z9.number().int().min(1000).max(300000).optional().default(30000).describe("Timeout in milliseconds (default: 30s, max: 5min)"),
|
|
3532
|
+
env: z9.record(z9.string(), z9.string()).optional().describe("Additional environment variables to set")
|
|
3260
3533
|
});
|
|
3261
3534
|
var MAX_OUTPUT_BYTES = 1e4;
|
|
3262
3535
|
var shellTool = {
|
|
@@ -3268,6 +3541,7 @@ var shellTool = {
|
|
|
3268
3541
|
const timeout = input.timeout ?? 30000;
|
|
3269
3542
|
const env = Object.assign({}, process.env, input.env ?? {});
|
|
3270
3543
|
let timedOut = false;
|
|
3544
|
+
const wasRaw = process.stdin.isTTY ? process.stdin.isRaw : false;
|
|
3271
3545
|
const proc = Bun.spawn(["bash", "-c", input.command], {
|
|
3272
3546
|
cwd,
|
|
3273
3547
|
env,
|
|
@@ -3321,6 +3595,11 @@ var shellTool = {
|
|
|
3321
3595
|
} finally {
|
|
3322
3596
|
clearTimeout(timer);
|
|
3323
3597
|
restoreTerminal();
|
|
3598
|
+
if (wasRaw) {
|
|
3599
|
+
try {
|
|
3600
|
+
process.stdin.setRawMode(true);
|
|
3601
|
+
} catch {}
|
|
3602
|
+
}
|
|
3324
3603
|
}
|
|
3325
3604
|
return {
|
|
3326
3605
|
stdout: stdout.trimEnd(),
|
|
@@ -3333,12 +3612,12 @@ var shellTool = {
|
|
|
3333
3612
|
};
|
|
3334
3613
|
|
|
3335
3614
|
// src/tools/subagent.ts
|
|
3336
|
-
import { z as
|
|
3337
|
-
var SubagentInput =
|
|
3338
|
-
prompt:
|
|
3339
|
-
agentName:
|
|
3615
|
+
import { z as z10 } from "zod";
|
|
3616
|
+
var SubagentInput = z10.object({
|
|
3617
|
+
prompt: z10.string().describe("The task or question to give the subagent"),
|
|
3618
|
+
agentName: z10.string().optional().describe("Name of a custom agent to use (from .agents/agents/). Omit to use a generic subagent.")
|
|
3340
3619
|
});
|
|
3341
|
-
function createSubagentTool(runSubagent, availableAgents) {
|
|
3620
|
+
function createSubagentTool(runSubagent, availableAgents, parentLabel) {
|
|
3342
3621
|
const agentSection = availableAgents.size > 0 ? `
|
|
3343
3622
|
|
|
3344
3623
|
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(", ")}.` : "";
|
|
@@ -3347,7 +3626,7 @@ When the user's message contains @<agent-name>, delegate to that agent by settin
|
|
|
3347
3626
|
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. The subagent has access to all the same tools.${agentSection}`,
|
|
3348
3627
|
schema: SubagentInput,
|
|
3349
3628
|
execute: async (input) => {
|
|
3350
|
-
return runSubagent(input.prompt, input.agentName);
|
|
3629
|
+
return runSubagent(input.prompt, input.agentName, parentLabel);
|
|
3351
3630
|
}
|
|
3352
3631
|
};
|
|
3353
3632
|
}
|
|
@@ -3396,7 +3675,7 @@ function buildToolSet(opts) {
|
|
|
3396
3675
|
const { cwd, onHook } = opts;
|
|
3397
3676
|
const depth = opts.depth ?? 0;
|
|
3398
3677
|
const lookupHook = createHookCache(HOOKABLE_TOOLS, cwd);
|
|
3399
|
-
|
|
3678
|
+
const tools = [
|
|
3400
3679
|
withHooks(withCwdDefault(globTool, cwd), lookupHook, cwd, (_, input) => hookEnvForGlob(input, cwd), onHook),
|
|
3401
3680
|
withHooks(withCwdDefault(grepTool, cwd), lookupHook, cwd, (_, input) => hookEnvForGrep(input, cwd), onHook),
|
|
3402
3681
|
withHooks(withCwdDefault(readTool, cwd), lookupHook, cwd, (_, input) => hookEnvForRead(input, cwd), onHook),
|
|
@@ -3408,17 +3687,25 @@ function buildToolSet(opts) {
|
|
|
3408
3687
|
if (depth >= MAX_SUBAGENT_DEPTH) {
|
|
3409
3688
|
throw new Error(`Subagent depth limit reached (max ${MAX_SUBAGENT_DEPTH}). ` + `Cannot spawn another subagent from depth ${depth}.`);
|
|
3410
3689
|
}
|
|
3411
|
-
return opts.runSubagent(prompt, depth + 1, agentName);
|
|
3412
|
-
}, opts.availableAgents)
|
|
3690
|
+
return opts.runSubagent(prompt, depth + 1, agentName, undefined, opts.parentLabel);
|
|
3691
|
+
}, opts.availableAgents, opts.parentLabel)
|
|
3413
3692
|
];
|
|
3693
|
+
if (process.env.EXA_API_KEY) {
|
|
3694
|
+
tools.push(webSearchTool, webContentTool);
|
|
3695
|
+
}
|
|
3696
|
+
return tools;
|
|
3414
3697
|
}
|
|
3415
3698
|
function buildReadOnlyToolSet(opts) {
|
|
3416
3699
|
const { cwd } = opts;
|
|
3417
|
-
|
|
3700
|
+
const tools = [
|
|
3418
3701
|
withCwdDefault(globTool, cwd),
|
|
3419
3702
|
withCwdDefault(grepTool, cwd),
|
|
3420
3703
|
withCwdDefault(readTool, cwd)
|
|
3421
3704
|
];
|
|
3705
|
+
if (process.env.EXA_API_KEY) {
|
|
3706
|
+
tools.push(webSearchTool, webContentTool);
|
|
3707
|
+
}
|
|
3708
|
+
return tools;
|
|
3422
3709
|
}
|
|
3423
3710
|
|
|
3424
3711
|
// src/agent/agent.ts
|
|
@@ -3440,9 +3727,9 @@ async function getGitBranch(cwd) {
|
|
|
3440
3727
|
}
|
|
3441
3728
|
function loadContextFile(cwd) {
|
|
3442
3729
|
const candidates = [
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3730
|
+
join16(cwd, "AGENTS.md"),
|
|
3731
|
+
join16(cwd, "CLAUDE.md"),
|
|
3732
|
+
join16(getConfigDir(), "AGENTS.md")
|
|
3446
3733
|
];
|
|
3447
3734
|
for (const p of candidates) {
|
|
3448
3735
|
if (existsSync6(p)) {
|
|
@@ -3453,7 +3740,19 @@ function loadContextFile(cwd) {
|
|
|
3453
3740
|
}
|
|
3454
3741
|
return null;
|
|
3455
3742
|
}
|
|
3456
|
-
|
|
3743
|
+
var CODEX_AUTONOMY = `
|
|
3744
|
+
# Autonomy and persistence
|
|
3745
|
+
- You are an autonomous senior engineer. Once given a direction, proactively gather context, implement, test, and refine without waiting for additional prompts at each step.
|
|
3746
|
+
- Persist until the task is fully handled end-to-end within the current turn: do not stop at analysis or partial work; carry changes through to implementation and verification.
|
|
3747
|
+
- Bias to action: default to implementing with reasonable assumptions. Do not end your turn with clarifications or requests to "proceed" unless you are truly blocked on information only the user can provide.
|
|
3748
|
+
- Do NOT output an upfront plan, preamble, or status update before working. Start making tool calls immediately.
|
|
3749
|
+
- Do NOT ask "shall I proceed?", "shall I start?", "reply X to continue", or any equivalent. Just start.
|
|
3750
|
+
- If something is ambiguous, pick the most reasonable interpretation, implement it, and note the assumption at the end.`;
|
|
3751
|
+
function isCodexModel(modelString) {
|
|
3752
|
+
const { modelId } = parseModelString(modelString);
|
|
3753
|
+
return modelId.includes("codex");
|
|
3754
|
+
}
|
|
3755
|
+
function buildSystemPrompt(cwd, modelString) {
|
|
3457
3756
|
const contextFile = loadContextFile(cwd);
|
|
3458
3757
|
const cwdDisplay = tildePath(cwd);
|
|
3459
3758
|
const now = new Date().toLocaleString(undefined, { hour12: false });
|
|
@@ -3468,8 +3767,10 @@ Guidelines:
|
|
|
3468
3767
|
- Prefer small, targeted edits over large rewrites.
|
|
3469
3768
|
- Always read a file before editing it.
|
|
3470
3769
|
- Use glob to discover files, grep to find patterns, read to inspect contents.
|
|
3471
|
-
- Use shell for tests, builds, and git operations
|
|
3472
|
-
|
|
3770
|
+
- Use shell for tests, builds, and git operations.`;
|
|
3771
|
+
if (modelString && isCodexModel(modelString)) {
|
|
3772
|
+
prompt += CODEX_AUTONOMY;
|
|
3773
|
+
}
|
|
3473
3774
|
if (contextFile) {
|
|
3474
3775
|
prompt += `
|
|
3475
3776
|
|
|
@@ -3519,61 +3820,52 @@ async function runAgent(opts) {
|
|
|
3519
3820
|
}
|
|
3520
3821
|
let turnIndex = getMaxTurnIndex(session.id) + 1;
|
|
3521
3822
|
const coreHistory = [...session.messages];
|
|
3522
|
-
|
|
3823
|
+
let nextLaneId = 1;
|
|
3824
|
+
const activeLanes = new Set;
|
|
3825
|
+
const runSubagent = async (prompt, depth = 0, agentName, modelOverride, parentLabel) => {
|
|
3523
3826
|
const allAgents = loadAgents(cwd);
|
|
3524
3827
|
const agentConfig = agentName ? allAgents.get(agentName) : undefined;
|
|
3525
3828
|
if (agentName && !agentConfig) {
|
|
3526
3829
|
throw new Error(`Unknown agent "${agentName}". Available agents: ${[...allAgents.keys()].join(", ") || "(none)"}`);
|
|
3527
3830
|
}
|
|
3528
3831
|
const model = modelOverride ?? agentConfig?.model ?? currentModel;
|
|
3529
|
-
const systemPrompt = agentConfig?.systemPrompt ?? buildSystemPrompt(cwd);
|
|
3832
|
+
const systemPrompt = agentConfig?.systemPrompt ?? buildSystemPrompt(cwd, model);
|
|
3530
3833
|
const subMessages = [{ role: "user", content: prompt }];
|
|
3834
|
+
const laneId = nextLaneId++;
|
|
3835
|
+
activeLanes.add(laneId);
|
|
3836
|
+
const laneLabel = formatSubagentLabel(laneId, parentLabel);
|
|
3531
3837
|
const subTools = buildToolSet({
|
|
3532
3838
|
cwd,
|
|
3533
3839
|
depth,
|
|
3534
3840
|
runSubagent,
|
|
3535
3841
|
onHook: renderHook,
|
|
3536
|
-
availableAgents: allAgents
|
|
3842
|
+
availableAgents: allAgents,
|
|
3843
|
+
parentLabel: laneLabel
|
|
3537
3844
|
});
|
|
3538
3845
|
const subLlm = resolveModel(model);
|
|
3539
3846
|
let result = "";
|
|
3540
3847
|
let inputTokens = 0;
|
|
3541
3848
|
let outputTokens = 0;
|
|
3542
|
-
const activity = [];
|
|
3543
|
-
const pendingCalls = new Map;
|
|
3544
3849
|
const events = runTurn({
|
|
3545
3850
|
model: subLlm,
|
|
3851
|
+
modelString: model,
|
|
3546
3852
|
messages: subMessages,
|
|
3547
3853
|
tools: subTools,
|
|
3548
3854
|
systemPrompt
|
|
3549
3855
|
});
|
|
3550
3856
|
for await (const event of events) {
|
|
3857
|
+
spinner.stop();
|
|
3858
|
+
renderSubagentEvent(event, { laneId, parentLabel, activeLanes });
|
|
3859
|
+
spinner.start("thinking");
|
|
3551
3860
|
if (event.type === "text-delta")
|
|
3552
3861
|
result += event.delta;
|
|
3553
|
-
if (event.type === "tool-call-start") {
|
|
3554
|
-
pendingCalls.set(event.toolCallId, {
|
|
3555
|
-
toolName: event.toolName,
|
|
3556
|
-
args: event.args
|
|
3557
|
-
});
|
|
3558
|
-
}
|
|
3559
|
-
if (event.type === "tool-result") {
|
|
3560
|
-
const pending = pendingCalls.get(event.toolCallId);
|
|
3561
|
-
if (pending) {
|
|
3562
|
-
pendingCalls.delete(event.toolCallId);
|
|
3563
|
-
activity.push({
|
|
3564
|
-
toolName: pending.toolName,
|
|
3565
|
-
args: pending.args,
|
|
3566
|
-
result: event.result,
|
|
3567
|
-
isError: event.isError
|
|
3568
|
-
});
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
3862
|
if (event.type === "turn-complete") {
|
|
3572
3863
|
inputTokens = event.inputTokens;
|
|
3573
3864
|
outputTokens = event.outputTokens;
|
|
3574
3865
|
}
|
|
3575
3866
|
}
|
|
3576
|
-
|
|
3867
|
+
activeLanes.delete(laneId);
|
|
3868
|
+
return { result, inputTokens, outputTokens };
|
|
3577
3869
|
};
|
|
3578
3870
|
const agents = loadAgents(cwd);
|
|
3579
3871
|
const tools = buildToolSet({
|
|
@@ -3760,12 +4052,10 @@ ${out}
|
|
|
3760
4052
|
async function processUserInput(text, pastedImages = []) {
|
|
3761
4053
|
const abortController = new AbortController;
|
|
3762
4054
|
let wasAborted = false;
|
|
3763
|
-
|
|
4055
|
+
abortController.signal.addEventListener("abort", () => {
|
|
3764
4056
|
wasAborted = true;
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
};
|
|
3768
|
-
process.on("SIGINT", onSigInt);
|
|
4057
|
+
});
|
|
4058
|
+
const stopWatcher = watchForInterrupt(abortController);
|
|
3769
4059
|
const { text: resolvedText, images: refImages } = await resolveFileRefs(text, cwd);
|
|
3770
4060
|
const allImages = [...pastedImages, ...refImages];
|
|
3771
4061
|
const thisTurn = turnIndex++;
|
|
@@ -3774,7 +4064,7 @@ ${out}
|
|
|
3774
4064
|
|
|
3775
4065
|
<system-message>PLAN MODE ACTIVE: Help the user gather context for the plan -- READ ONLY</system-message>` : ralphMode ? `${resolvedText}
|
|
3776
4066
|
|
|
3777
|
-
<system-message>RALPH MODE: You are in an autonomous loop.
|
|
4067
|
+
<system-message>RALPH MODE: You are in an autonomous loop. You MUST make actual file changes (create, edit, or write files) to complete the requested task before outputting \`/ralph\`. Reading files, running tests, or exploring the codebase does NOT count as doing the work. Only output \`/ralph\` as your final message after all requested changes are implemented and tests pass.</system-message>` : resolvedText;
|
|
3778
4068
|
const userMsg = allImages.length > 0 ? {
|
|
3779
4069
|
role: "user",
|
|
3780
4070
|
content: [
|
|
@@ -3787,11 +4077,8 @@ ${out}
|
|
|
3787
4077
|
]
|
|
3788
4078
|
} : { role: "user", content: coreContent };
|
|
3789
4079
|
if (wasAborted) {
|
|
3790
|
-
|
|
3791
|
-
const stubMsg =
|
|
3792
|
-
role: "assistant",
|
|
3793
|
-
content: "[interrupted]"
|
|
3794
|
-
};
|
|
4080
|
+
stopWatcher();
|
|
4081
|
+
const stubMsg = makeInterruptMessage("user");
|
|
3795
4082
|
session.messages.push(userMsg, stubMsg);
|
|
3796
4083
|
saveMessages(session.id, [userMsg, stubMsg], thisTurn);
|
|
3797
4084
|
coreHistory.push(userMsg, stubMsg);
|
|
@@ -3803,26 +4090,15 @@ ${out}
|
|
|
3803
4090
|
saveMessages(session.id, [userMsg], thisTurn);
|
|
3804
4091
|
coreHistory.push(userMsg);
|
|
3805
4092
|
const llm = resolveModel(currentModel);
|
|
3806
|
-
const systemPrompt = buildSystemPrompt(cwd);
|
|
4093
|
+
const systemPrompt = buildSystemPrompt(cwd, currentModel);
|
|
3807
4094
|
let lastAssistantText = "";
|
|
3808
|
-
let
|
|
3809
|
-
const rollbackTurn = () => {
|
|
3810
|
-
if (turnRolledBack)
|
|
3811
|
-
return;
|
|
3812
|
-
turnRolledBack = true;
|
|
3813
|
-
coreHistory.pop();
|
|
3814
|
-
session.messages.pop();
|
|
3815
|
-
deleteLastTurn(session.id, thisTurn);
|
|
3816
|
-
if (snapped)
|
|
3817
|
-
deleteSnapshot(session.id, thisTurn);
|
|
3818
|
-
snapshotStack.pop();
|
|
3819
|
-
turnIndex--;
|
|
3820
|
-
};
|
|
4095
|
+
let errorStubSaved = false;
|
|
3821
4096
|
try {
|
|
3822
4097
|
snapshotStack.push(snapped ? thisTurn : null);
|
|
3823
4098
|
spinner.start("thinking");
|
|
3824
4099
|
const events = runTurn({
|
|
3825
4100
|
model: llm,
|
|
4101
|
+
modelString: currentModel,
|
|
3826
4102
|
messages: coreHistory,
|
|
3827
4103
|
tools: planMode ? [...buildReadOnlyToolSet({ cwd }), ...mcpTools] : tools,
|
|
3828
4104
|
systemPrompt,
|
|
@@ -3833,16 +4109,17 @@ ${out}
|
|
|
3833
4109
|
coreHistory.push(...newMessages);
|
|
3834
4110
|
session.messages.push(...newMessages);
|
|
3835
4111
|
saveMessages(session.id, newMessages, thisTurn);
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
4112
|
+
if (wasAborted) {
|
|
4113
|
+
const note = makeInterruptMessage("user");
|
|
4114
|
+
coreHistory.push(note);
|
|
4115
|
+
session.messages.push(note);
|
|
4116
|
+
saveMessages(session.id, [note], thisTurn);
|
|
4117
|
+
}
|
|
4118
|
+
} else {
|
|
4119
|
+
const stubMsg = makeInterruptMessage("user");
|
|
3841
4120
|
coreHistory.push(stubMsg);
|
|
3842
4121
|
session.messages.push(stubMsg);
|
|
3843
4122
|
saveMessages(session.id, [stubMsg], thisTurn);
|
|
3844
|
-
} else {
|
|
3845
|
-
rollbackTurn();
|
|
3846
4123
|
}
|
|
3847
4124
|
lastAssistantText = extractAssistantText(newMessages);
|
|
3848
4125
|
totalIn += inputTokens;
|
|
@@ -3850,10 +4127,16 @@ ${out}
|
|
|
3850
4127
|
lastContextTokens = contextTokens;
|
|
3851
4128
|
touchActiveSession(session);
|
|
3852
4129
|
} catch (err) {
|
|
3853
|
-
|
|
4130
|
+
if (!errorStubSaved) {
|
|
4131
|
+
errorStubSaved = true;
|
|
4132
|
+
const stubMsg = makeInterruptMessage("error");
|
|
4133
|
+
coreHistory.push(stubMsg);
|
|
4134
|
+
session.messages.push(stubMsg);
|
|
4135
|
+
saveMessages(session.id, [stubMsg], thisTurn);
|
|
4136
|
+
}
|
|
3854
4137
|
throw err;
|
|
3855
4138
|
} finally {
|
|
3856
|
-
|
|
4139
|
+
stopWatcher();
|
|
3857
4140
|
if (wasAborted)
|
|
3858
4141
|
ralphMode = false;
|
|
3859
4142
|
}
|
|
@@ -3878,6 +4161,10 @@ ${out}
|
|
|
3878
4161
|
});
|
|
3879
4162
|
}
|
|
3880
4163
|
}
|
|
4164
|
+
function makeInterruptMessage(reason) {
|
|
4165
|
+
const text = reason === "user" ? "<system-message>Response was interrupted by the user.</system-message>" : "<system-message>Response was interrupted due to an error.</system-message>";
|
|
4166
|
+
return { role: "assistant", content: text };
|
|
4167
|
+
}
|
|
3881
4168
|
function extractAssistantText(newMessages) {
|
|
3882
4169
|
const parts = [];
|
|
3883
4170
|
for (const msg of newMessages) {
|
|
@@ -3917,7 +4204,7 @@ ${skill.content}
|
|
|
3917
4204
|
result = result.slice(0, match.index) + replacement + result.slice((match.index ?? 0) + match[0].length);
|
|
3918
4205
|
continue;
|
|
3919
4206
|
}
|
|
3920
|
-
const filePath = ref.startsWith("/") ? ref :
|
|
4207
|
+
const filePath = ref.startsWith("/") ? ref : join16(cwd, ref);
|
|
3921
4208
|
if (isImageFilename(ref)) {
|
|
3922
4209
|
const attachment = await loadImageFile(filePath);
|
|
3923
4210
|
if (attachment) {
|
|
@@ -3945,6 +4232,7 @@ ${preview}
|
|
|
3945
4232
|
|
|
3946
4233
|
// src/index.ts
|
|
3947
4234
|
registerTerminalCleanup();
|
|
4235
|
+
initErrorLog();
|
|
3948
4236
|
function parseArgs(argv) {
|
|
3949
4237
|
const args = {
|
|
3950
4238
|
model: null,
|
|
@@ -4063,11 +4351,11 @@ async function main() {
|
|
|
4063
4351
|
agentOpts.initialPrompt = args.prompt;
|
|
4064
4352
|
await runAgent(agentOpts);
|
|
4065
4353
|
} catch (err) {
|
|
4066
|
-
renderError(err);
|
|
4354
|
+
renderError(err, "agent");
|
|
4067
4355
|
process.exit(1);
|
|
4068
4356
|
}
|
|
4069
4357
|
}
|
|
4070
4358
|
main().catch((err) => {
|
|
4071
|
-
|
|
4359
|
+
renderError(err, "main");
|
|
4072
4360
|
process.exit(1);
|
|
4073
4361
|
});
|