tensor-cli 0.1.3-beta-lite → 0.1.4-beta-lite
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/index.js +970 -937
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -69212,7 +69212,7 @@ var textareaInputRef = {
|
|
|
69212
69212
|
// src/tools/harness-orchestrator.ts
|
|
69213
69213
|
import { existsSync } from "fs";
|
|
69214
69214
|
import { mkdir } from "fs/promises";
|
|
69215
|
-
import { dirname } from "path";
|
|
69215
|
+
import { dirname as dirname2 } from "path";
|
|
69216
69216
|
|
|
69217
69217
|
// src/constants/google-models-context-window.ts
|
|
69218
69218
|
var DEFAULT_GOOGLE_CONTEXT_WINDOW = 32760;
|
|
@@ -329958,6 +329958,510 @@ var askJev = async (body) => {
|
|
|
329958
329958
|
};
|
|
329959
329959
|
var askJev_default = askJev;
|
|
329960
329960
|
|
|
329961
|
+
// src/tools/harness-core.ts
|
|
329962
|
+
import { dirname } from "path";
|
|
329963
|
+
|
|
329964
|
+
// src/tools/harness-processes.ts
|
|
329965
|
+
import { spawn } from "child_process";
|
|
329966
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
329967
|
+
var BG_TOOLS = new Set([
|
|
329968
|
+
"bg_start",
|
|
329969
|
+
"bg_list",
|
|
329970
|
+
"bg_logs",
|
|
329971
|
+
"bg_kill"
|
|
329972
|
+
]);
|
|
329973
|
+
var BG_READ_ONLY_TOOLS = new Set(["bg_list", "bg_logs"]);
|
|
329974
|
+
var isBgTool = (t2) => typeof t2 === "string" && BG_TOOLS.has(t2);
|
|
329975
|
+
var str = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
329976
|
+
function parseBgDecision(p) {
|
|
329977
|
+
if (!isBgTool(p.tool))
|
|
329978
|
+
return null;
|
|
329979
|
+
const tool = p.tool;
|
|
329980
|
+
const cmd = tool === "bg_start" ? str(p.cmd) ?? str(p.command) ?? str(p.content) : null;
|
|
329981
|
+
const named = str(p.target) ?? str(p.name) ?? str(p.label) ?? str(p.id) ?? (typeof p.id === "number" ? String(p.id) : null);
|
|
329982
|
+
const target = tool === "bg_start" ? named : named ?? str(p.targetFile) ?? str(p.content);
|
|
329983
|
+
return {
|
|
329984
|
+
tool,
|
|
329985
|
+
cmd,
|
|
329986
|
+
target,
|
|
329987
|
+
reasoning: (str(p.reasoning) ?? "").slice(0, 1000),
|
|
329988
|
+
needMoreSteps: Boolean(p.need_more_steps || p.needs_more_steps)
|
|
329989
|
+
};
|
|
329990
|
+
}
|
|
329991
|
+
var LONG_RUNNING_SHELL_RE = new RegExp([
|
|
329992
|
+
String.raw`\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|watch|preview)\b`,
|
|
329993
|
+
String.raw`\bbun\s+(?:--hot|--watch)\b`,
|
|
329994
|
+
String.raw`\bnext\s+(?:dev|start)\b`,
|
|
329995
|
+
String.raw`\bvite(?:\s+(?:dev|preview|serve))?\s*(?:--\S+\s*)*$`,
|
|
329996
|
+
String.raw`\bnodemon\b`,
|
|
329997
|
+
String.raw`\bhttp\.server\b`,
|
|
329998
|
+
String.raw`\brunserver\b`,
|
|
329999
|
+
String.raw`\buvicorn\b`,
|
|
330000
|
+
String.raw`\bflask\s+run\b`,
|
|
330001
|
+
String.raw`\bnpx\s+serve\b`,
|
|
330002
|
+
String.raw`(?:^|&&|;)\s*anvil\b`,
|
|
330003
|
+
String.raw`\bhardhat\s+node\b`,
|
|
330004
|
+
String.raw`\bsolana-test-validator\b`,
|
|
330005
|
+
String.raw`\bngrok\b`,
|
|
330006
|
+
String.raw`\bdocker(?:-|\s+)compose\s+up\b(?!.*\s-d\b)`,
|
|
330007
|
+
String.raw`\btail\s+-f\b`,
|
|
330008
|
+
String.raw`\s--watch\b`,
|
|
330009
|
+
String.raw`(?<!&)&\s*$`
|
|
330010
|
+
].join("|"), "i");
|
|
330011
|
+
var BG_TOOL_RULES = `BACKGROUND PROCESS TOOLS \u2014 extra values for "tool", for servers, watchers and anything that doesn't exit on its own:
|
|
330012
|
+
- "bg_start": "cmd" = the literal command; "target" = optional short name (e.g. "web"). Returns the first few seconds of output (and any URL) so you can tell whether it came up.
|
|
330013
|
+
- "bg_list": no other fields.
|
|
330014
|
+
- "bg_logs": "target" = process id, name or port. Use it to see what a process is doing or why it died.
|
|
330015
|
+
- "bg_kill": "target" = process id, name or port, or "all".
|
|
330016
|
+
Rules:
|
|
330017
|
+
- NEVER run a server/watcher through "shell" (it blocks until it times out). Use "bg_start".
|
|
330018
|
+
- "start/run/launch the server" -> bg_start. If the user gave no command, "read" package.json (or pyproject.toml, manage.py, foundry.toml, Cargo.toml ...) to find the dev/start script, then bg_start it.
|
|
330019
|
+
- "stop/kill/shut down/turn off the server" -> bg_kill on the matching tracked process. This always means the tracked process, never the machine, and never "shell". "stop everything" -> target "all".
|
|
330020
|
+
- "restart X" -> bg_kill X, then bg_start with the same cmd (it is listed under BACKGROUND PROCESSES).
|
|
330021
|
+
- "what's running / is X up / what's X doing / why did X die" -> answer from BACKGROUND PROCESSES, or bg_list / bg_logs for real output. Never guess.
|
|
330022
|
+
- If the thing to stop is not tracked (started outside this session), say so; if the user gave a port, "shell" with lsof -ti :PORT can find the owner.
|
|
330023
|
+
- Never start a duplicate of a running process. After bg_start, if it crashed, bg_logs it, fix the cause, retry.
|
|
330024
|
+
- For bg_* tools leave "targetFile", "content", "target"/"cmd" not mentioned above as null.`;
|
|
330025
|
+
var GAP = String.raw`[^.!?\n]{0,40}?`;
|
|
330026
|
+
var LEAD = String.raw`(?<!\w)`;
|
|
330027
|
+
var TRAIL = String.raw`(?!\w)`;
|
|
330028
|
+
var NEG_RE = /\b(?:don'?t|do\s+not|never|no\s+need\s+to|without|instead\s+of|rather\s+than)\b[^.!?\n]{0,25}?\b(?:start|run|launch|stop|kill|shut|restart|spin|boot)/i;
|
|
330029
|
+
var STOP_V = String.raw`(?:stop|kill|terminate|shut\s*(?:it\s*|them\s*)?(?:down|off)|take\s+(?:it\s+|them\s+)?down|bring\s+(?:it\s+|them\s+)?down|turn\s+(?:it\s+|them\s+)?off|halt)`;
|
|
330030
|
+
var PROC_N = String.raw`(?:servers?|processes|process|watchers?|daemons?|services?|dev|backend|frontend|api|app|everything|all|bg|background|p\d+|ports?\s*\d+|:\d{2,5}|localhost|anvil|vite|django|flask|uvicorn)`;
|
|
330031
|
+
var STOP_RE = new RegExp(String.raw`\b${STOP_V}\b${GAP}${LEAD}${PROC_N}${TRAIL}`, "i");
|
|
330032
|
+
var STOP_PRONOUN_RE = /\b(?:stop|kill|terminate|shut\s*(?:it\s*|them\s*)?down|turn\s+(?:it|them)\s+off)\s+(?:it|that|this|them|those)\b/i;
|
|
330033
|
+
var STOP_BARE_RE = /^\s*(?:please\s+)?(?:shut\s*down|stop|kill|terminate)(?:\s+(?:now|please))?\s*[.!]*\s*$/i;
|
|
330034
|
+
var STOP_EXCLUDE_RE = /\bfrom\s+\w+ing\b|\busing\b|\bbeing\b/i;
|
|
330035
|
+
var START_V = String.raw`(?:start|run|launch|spin\s*up|boot(?:\s+up)?|fire\s*up|bring\s*up|kick\s*off|serve)`;
|
|
330036
|
+
var START_N = String.raw`(?:servers?|dev|watcher|backend|frontend|api|app|site|website|anvil|localhost|daemon|in\s+(?:the\s+)?(?:background|bg)|background|bg)`;
|
|
330037
|
+
var START_RE = new RegExp(String.raw`\b${START_V}\b${GAP}${LEAD}${START_N}${TRAIL}`, "i");
|
|
330038
|
+
var START_EXCLUDE_RE = /\b(?:tests?|specs?|lint|typecheck|build|compile|migrat\w*|install|seed|script|format|generate|check|review|read|look(?:ing)?|by\s+\w+ing)\b/i;
|
|
330039
|
+
var BACKGROUND_RE = /\bbackground\b|\bbg\b/i;
|
|
330040
|
+
var RESTART_RE = new RegExp(String.raw`\b(?:restart|reboot|relaunch|bounce|cycle)\b${GAP}${LEAD}(?:servers?|dev|watcher|process(?:es)?|backend|frontend|api|app|it|them|everything|p\d+)${TRAIL}`, "i");
|
|
330041
|
+
var RESTART_BARE_RE = /^\s*(?:please\s+)?(?:restart|reboot|relaunch)(?:\s+it)?\s*[.!]*\s*$/i;
|
|
330042
|
+
var LOGS_A_RE = new RegExp(String.raw`\b(?:logs?|output|stdout|stderr|tail|what'?s\s+(?:it\s+)?(?:doing|going\s+on|happening)|how(?:'s|\s+is))\b${GAP}${LEAD}(?:server|process|dev|backend|frontend|p\d+|it|anvil|watcher|bg|background)${TRAIL}`, "i");
|
|
330043
|
+
var LOGS_B_RE = /\b(?:is\s+(?:it|the\s+[\w-]+)\s+(?:up|running|working|alive|crashed|dead)|did\s+(?:it|the\s+[\w-]+)\s+(?:crash|start|fail|die|work))\b/i;
|
|
330044
|
+
var LIST_RE = new RegExp(String.raw`\b(?:what(?:'s|\s+is|\s+are)?|which|show|list|any|are\s+there|see)\b${GAP}${LEAD}(?:running|processes|servers|background|bg)${TRAIL}`, "i");
|
|
330045
|
+
var HEAD = "USER INTENT (detected from their message; it is a hint, use your judgment if it is wrong):";
|
|
330046
|
+
var STOP_NOTE = `${HEAD} the user wants to STOP background process(es). Call "bg_kill" now. "target" = the id from BACKGROUND PROCESSES that matches what they named (name, id or port), or "all" if they said everything/all/shut down. If they named nothing and exactly one is running, that one. This means the tracked process, never the machine, and never "shell". If they also asked for something else, do it after.`;
|
|
330047
|
+
var START_NOTE = `${HEAD} the user wants to START a server / long-running process. Call "bg_start" (never "shell"). If they gave a command, use it verbatim. Otherwise "read" package.json (or the project's equivalent) first, pick the dev/start script, then bg_start with the full runner command (e.g. "bun run dev"). Don't start a duplicate of something already running.`;
|
|
330048
|
+
var RESTART_NOTE = `${HEAD} the user wants to RESTART a background process. Call "bg_kill" on it first (its cmd is listed under BACKGROUND PROCESSES), then "bg_start" with the same cmd.`;
|
|
330049
|
+
var LOGS_NOTE = `${HEAD} the user is asking how a background process is doing. Call "bg_logs" for the real output and base your answer on it.`;
|
|
330050
|
+
var LIST_NOTE = `${HEAD} the user is asking what is running. Answer from BACKGROUND PROCESSES (or "bg_list").`;
|
|
330051
|
+
var NOTHING_RUNNING_NOTE = `${HEAD} the user wants to stop something, but no background process is running. Say so plainly. If they named a port, a process started outside this session can be found with "shell" lsof -ti :PORT.`;
|
|
330052
|
+
var NONE_TRACKED_NOTE = `${HEAD} no background processes are tracked in this session. If the user is asking about running servers, say none were started here.`;
|
|
330053
|
+
function detectBgIntent(task, running, known) {
|
|
330054
|
+
const t2 = task.slice(0, 4000);
|
|
330055
|
+
if (!t2.trim() || NEG_RE.test(t2))
|
|
330056
|
+
return null;
|
|
330057
|
+
const needs = [];
|
|
330058
|
+
const notes = [];
|
|
330059
|
+
if (RESTART_RE.test(t2) || RESTART_BARE_RE.test(t2)) {
|
|
330060
|
+
if (running > 0)
|
|
330061
|
+
needs.push("bg_kill");
|
|
330062
|
+
needs.push("bg_start");
|
|
330063
|
+
notes.push(RESTART_NOTE);
|
|
330064
|
+
} else {
|
|
330065
|
+
const stopM = STOP_RE.exec(t2);
|
|
330066
|
+
const stopHit = stopM !== null && !STOP_EXCLUDE_RE.test(stopM[0]) || STOP_PRONOUN_RE.test(t2) || STOP_BARE_RE.test(t2);
|
|
330067
|
+
if (stopHit) {
|
|
330068
|
+
if (running > 0) {
|
|
330069
|
+
needs.push("bg_kill");
|
|
330070
|
+
notes.push(STOP_NOTE);
|
|
330071
|
+
} else {
|
|
330072
|
+
notes.push(NOTHING_RUNNING_NOTE);
|
|
330073
|
+
}
|
|
330074
|
+
}
|
|
330075
|
+
const startM = START_RE.exec(t2);
|
|
330076
|
+
if (startM && (!START_EXCLUDE_RE.test(startM[0]) || BACKGROUND_RE.test(startM[0]))) {
|
|
330077
|
+
needs.push("bg_start");
|
|
330078
|
+
notes.push(START_NOTE);
|
|
330079
|
+
}
|
|
330080
|
+
}
|
|
330081
|
+
const strong = needs.includes("bg_start") || needs.includes("bg_kill");
|
|
330082
|
+
if (!strong && notes.length === 0) {
|
|
330083
|
+
const asksLogs = LOGS_A_RE.test(t2) || LOGS_B_RE.test(t2);
|
|
330084
|
+
if (known > 0) {
|
|
330085
|
+
if (asksLogs) {
|
|
330086
|
+
needs.push("bg_logs");
|
|
330087
|
+
notes.push(LOGS_NOTE);
|
|
330088
|
+
} else if (LIST_RE.test(t2)) {
|
|
330089
|
+
notes.push(LIST_NOTE);
|
|
330090
|
+
}
|
|
330091
|
+
} else if (asksLogs || LIST_RE.test(t2)) {
|
|
330092
|
+
notes.push(NONE_TRACKED_NOTE);
|
|
330093
|
+
}
|
|
330094
|
+
}
|
|
330095
|
+
if (notes.length === 0)
|
|
330096
|
+
return null;
|
|
330097
|
+
return { needs, strong, note: notes.join(`
|
|
330098
|
+
`) };
|
|
330099
|
+
}
|
|
330100
|
+
var LOG_CAP = 64000;
|
|
330101
|
+
var TAIL_CHAR_CAP = 6000;
|
|
330102
|
+
var MAX_RUNNING = 8;
|
|
330103
|
+
var MAX_FINISHED_KEPT = 10;
|
|
330104
|
+
var STARTUP_GRACE_MS = 4000;
|
|
330105
|
+
var KILL_GRACE_MS = 3000;
|
|
330106
|
+
var EXIT_DRAIN_MS = 120;
|
|
330107
|
+
var ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07]*\x07/g;
|
|
330108
|
+
var READY_RE = /\b(?:ready|listening|compiled|started|local:|on port)\b/i;
|
|
330109
|
+
var URL_RE = /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1?\]):\d+[^\s'")]*/gi;
|
|
330110
|
+
var PORT_RE = /\bport\s*:?\s*(\d{2,5})\b/gi;
|
|
330111
|
+
var ALL_RE = /^(?:all|everything|\*|all\s+(?:the\s+)?(?:servers|processes|background(?:\s+processes)?))$/;
|
|
330112
|
+
var GENERIC_RE = /^(?:the\s+|that\s+|this\s+|my\s+)?(?:dev\s+|web\s+|local\s+|background\s+)?(?:server|process|watcher|app|service|it)$/;
|
|
330113
|
+
var clip = (s2, n3) => s2.length > n3 ? `${s2.slice(0, n3 - 1)}\u2026` : s2;
|
|
330114
|
+
function fmtDur(ms) {
|
|
330115
|
+
const s2 = Math.max(0, Math.floor(ms / 1000));
|
|
330116
|
+
if (s2 < 60)
|
|
330117
|
+
return `${s2}s`;
|
|
330118
|
+
const m = Math.floor(s2 / 60);
|
|
330119
|
+
if (m < 60)
|
|
330120
|
+
return `${m}m${s2 % 60}s`;
|
|
330121
|
+
return `${Math.floor(m / 60)}h${m % 60}m`;
|
|
330122
|
+
}
|
|
330123
|
+
function deriveLabel(cmd) {
|
|
330124
|
+
const words = cmd.split(/\s+/).filter((w) => w && !/^\w+=/.test(w));
|
|
330125
|
+
return clip(words.slice(0, 3).join(" "), 24);
|
|
330126
|
+
}
|
|
330127
|
+
var urlSuffix = (m) => m.urls.size ? ` \xB7 ${[...m.urls].join(", ")}` : "";
|
|
330128
|
+
|
|
330129
|
+
class ProcessManager {
|
|
330130
|
+
procs = new Map;
|
|
330131
|
+
seq = 0;
|
|
330132
|
+
constructor() {
|
|
330133
|
+
process.once("exit", () => this.killAllSync());
|
|
330134
|
+
}
|
|
330135
|
+
count() {
|
|
330136
|
+
return this.procs.size;
|
|
330137
|
+
}
|
|
330138
|
+
runningCount() {
|
|
330139
|
+
let n3 = 0;
|
|
330140
|
+
for (const p of this.procs.values())
|
|
330141
|
+
if (p.status === "running")
|
|
330142
|
+
n3++;
|
|
330143
|
+
return n3;
|
|
330144
|
+
}
|
|
330145
|
+
async start(opts) {
|
|
330146
|
+
const cmd = opts.cmd.trim().replace(/(?<!&)&\s*$/, "").trim();
|
|
330147
|
+
if (!cmd)
|
|
330148
|
+
throw new Error("empty command");
|
|
330149
|
+
const dup = [...this.procs.values()].find((p) => p.status === "running" && p.cmd === cmd && p.cwd === opts.cwd);
|
|
330150
|
+
if (dup) {
|
|
330151
|
+
return {
|
|
330152
|
+
kind: "duplicate",
|
|
330153
|
+
id: dup.id,
|
|
330154
|
+
report: `Already running as ${dup.id} "${dup.label}" (pid ${dup.pid}, up ${fmtDur(Date.now() - dup.startedAt)})` + `${urlSuffix(dup)}. Not starting a second copy; use bg_logs to inspect it or bg_kill to restart it.`
|
|
330155
|
+
};
|
|
330156
|
+
}
|
|
330157
|
+
if (this.runningCount() >= MAX_RUNNING) {
|
|
330158
|
+
throw new Error(`${MAX_RUNNING} background processes already running; kill one first`);
|
|
330159
|
+
}
|
|
330160
|
+
const id = `p${++this.seq}`;
|
|
330161
|
+
const label = (opts.label ?? "").trim().slice(0, 24) || deriveLabel(cmd);
|
|
330162
|
+
const child = spawn("bash", ["-lc", cmd], {
|
|
330163
|
+
cwd: opts.cwd,
|
|
330164
|
+
detached: process.platform !== "win32",
|
|
330165
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
330166
|
+
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", BROWSER: "none" }
|
|
330167
|
+
});
|
|
330168
|
+
const m = {
|
|
330169
|
+
id,
|
|
330170
|
+
label,
|
|
330171
|
+
cmd,
|
|
330172
|
+
cwd: opts.cwd,
|
|
330173
|
+
child,
|
|
330174
|
+
pid: child.pid,
|
|
330175
|
+
status: "running",
|
|
330176
|
+
startedAt: Date.now(),
|
|
330177
|
+
endedAt: null,
|
|
330178
|
+
exitCode: null,
|
|
330179
|
+
signal: null,
|
|
330180
|
+
killRequested: false,
|
|
330181
|
+
noticed: false,
|
|
330182
|
+
buf: "",
|
|
330183
|
+
totalChars: 0,
|
|
330184
|
+
urls: new Set,
|
|
330185
|
+
events: new EventEmitter2
|
|
330186
|
+
};
|
|
330187
|
+
this.procs.set(id, m);
|
|
330188
|
+
const onData = (d2) => this.append(m, d2.toString("utf8"));
|
|
330189
|
+
child.stdout?.on("data", onData);
|
|
330190
|
+
child.stderr?.on("data", onData);
|
|
330191
|
+
child.on("error", (err) => {
|
|
330192
|
+
this.append(m, `[spawn error] ${err.message}
|
|
330193
|
+
`);
|
|
330194
|
+
this.finish(m, null, null);
|
|
330195
|
+
});
|
|
330196
|
+
let drain;
|
|
330197
|
+
child.on("exit", (code, signal) => {
|
|
330198
|
+
drain = setTimeout(() => this.finish(m, code, signal), EXIT_DRAIN_MS);
|
|
330199
|
+
});
|
|
330200
|
+
child.on("close", (code, signal) => {
|
|
330201
|
+
if (drain)
|
|
330202
|
+
clearTimeout(drain);
|
|
330203
|
+
this.finish(m, code, signal);
|
|
330204
|
+
});
|
|
330205
|
+
try {
|
|
330206
|
+
child.unref();
|
|
330207
|
+
for (const s2 of [child.stdout, child.stderr]) {
|
|
330208
|
+
s2?.unref?.();
|
|
330209
|
+
}
|
|
330210
|
+
} catch {}
|
|
330211
|
+
await this.waitForStartup(m, opts.graceMs ?? STARTUP_GRACE_MS);
|
|
330212
|
+
if (m.status !== "running") {
|
|
330213
|
+
return {
|
|
330214
|
+
kind: "crashed",
|
|
330215
|
+
id,
|
|
330216
|
+
report: `${id} "${label}" exited ${m.exitCode !== null ? `with code ${m.exitCode}` : `(${m.signal ?? "unknown"})`} ` + `${fmtDur((m.endedAt ?? Date.now()) - m.startedAt)} after starting.
|
|
330217
|
+
Output:
|
|
330218
|
+
${this.tail(m, 30)}`
|
|
330219
|
+
};
|
|
330220
|
+
}
|
|
330221
|
+
return {
|
|
330222
|
+
kind: "started",
|
|
330223
|
+
id,
|
|
330224
|
+
report: `Started ${id} "${label}" (pid ${m.pid}) in the background: ${cmd}
|
|
330225
|
+
` + `Status: running${urlSuffix(m)}
|
|
330226
|
+
Initial output:
|
|
330227
|
+
${this.tail(m, 25)}
|
|
330228
|
+
` + `It keeps running after this turn. Refer to it as "${id}" with bg_logs / bg_kill.`
|
|
330229
|
+
};
|
|
330230
|
+
}
|
|
330231
|
+
formatList() {
|
|
330232
|
+
if (this.procs.size === 0)
|
|
330233
|
+
return "No background processes.";
|
|
330234
|
+
return [...this.procs.values()].map((m) => this.line(m)).join(`
|
|
330235
|
+
`);
|
|
330236
|
+
}
|
|
330237
|
+
logs(ref, lines = 60) {
|
|
330238
|
+
const f2 = this.find(ref);
|
|
330239
|
+
if ("error" in f2)
|
|
330240
|
+
return f2.error;
|
|
330241
|
+
const m = f2.m;
|
|
330242
|
+
return `${m.id} "${m.label}" [${m.status}] \u2014 last ${lines} lines (${m.totalChars} chars emitted in total):
|
|
330243
|
+
${this.tail(m, lines)}`;
|
|
330244
|
+
}
|
|
330245
|
+
promptBlock() {
|
|
330246
|
+
if (this.procs.size === 0)
|
|
330247
|
+
return "";
|
|
330248
|
+
const all3 = [...this.procs.values()];
|
|
330249
|
+
const shown = [
|
|
330250
|
+
...all3.filter((p) => p.status === "running"),
|
|
330251
|
+
...all3.filter((p) => p.status !== "running").slice(-3)
|
|
330252
|
+
];
|
|
330253
|
+
return `BACKGROUND PROCESSES (tracked by the harness; they keep running between turns):
|
|
330254
|
+
` + shown.map((m) => this.line(m)).join(`
|
|
330255
|
+
`) + `
|
|
330256
|
+
Use bg_logs with the id to see output, bg_kill to stop one. Never start a duplicate of a running process.`;
|
|
330257
|
+
}
|
|
330258
|
+
consumeExitNotices() {
|
|
330259
|
+
const out = [];
|
|
330260
|
+
for (const m of this.procs.values()) {
|
|
330261
|
+
if (m.status === "running" || m.noticed)
|
|
330262
|
+
continue;
|
|
330263
|
+
m.noticed = true;
|
|
330264
|
+
let s2 = `Background ${m.id} "${m.label}" ${m.status === "failed" ? `crashed (exit ${m.exitCode ?? m.signal})` : `exited (code ${m.exitCode ?? m.signal})`}`;
|
|
330265
|
+
if (m.status === "failed") {
|
|
330266
|
+
const last = m.buf.split(`
|
|
330267
|
+
`).map((l) => l.trim()).filter(Boolean).slice(-2).map((l) => clip(l, 160));
|
|
330268
|
+
if (last.length)
|
|
330269
|
+
s2 += `
|
|
330270
|
+
${last.join(`
|
|
330271
|
+
`)}`;
|
|
330272
|
+
}
|
|
330273
|
+
out.push(s2);
|
|
330274
|
+
}
|
|
330275
|
+
return out;
|
|
330276
|
+
}
|
|
330277
|
+
async kill(ref) {
|
|
330278
|
+
if (ALL_RE.test((ref ?? "").trim().toLowerCase()))
|
|
330279
|
+
return this.killAll();
|
|
330280
|
+
const f2 = this.find(ref);
|
|
330281
|
+
if ("error" in f2)
|
|
330282
|
+
return f2.error;
|
|
330283
|
+
const m = f2.m;
|
|
330284
|
+
if (m.status !== "running") {
|
|
330285
|
+
return `${m.id} "${m.label}" is not running (${m.status}, exit ${m.exitCode ?? m.signal}).`;
|
|
330286
|
+
}
|
|
330287
|
+
const up = fmtDur(Date.now() - m.startedAt);
|
|
330288
|
+
await this.terminate(m);
|
|
330289
|
+
return `Killed ${m.id} "${m.label}" (pid ${m.pid}, was up ${up}).`;
|
|
330290
|
+
}
|
|
330291
|
+
async killAll() {
|
|
330292
|
+
const running = [...this.procs.values()].filter((p) => p.status === "running");
|
|
330293
|
+
if (running.length === 0)
|
|
330294
|
+
return "No running background processes.";
|
|
330295
|
+
await Promise.all(running.map((m) => this.terminate(m)));
|
|
330296
|
+
return `Killed ${running.length} background process${running.length > 1 ? "es" : ""}: ${running.map((m) => m.id).join(", ")}.`;
|
|
330297
|
+
}
|
|
330298
|
+
line(m) {
|
|
330299
|
+
const when = m.status === "running" ? `up ${fmtDur(Date.now() - m.startedAt)}` : `exit ${m.exitCode ?? m.signal}, ${fmtDur(Date.now() - (m.endedAt ?? Date.now()))} ago`;
|
|
330300
|
+
return `${m.id} "${m.label}" [${m.status}] pid ${m.pid ?? "?"} \xB7 ${when}${urlSuffix(m)}
|
|
330301
|
+
$ ${clip(m.cmd, 120)}`;
|
|
330302
|
+
}
|
|
330303
|
+
find(ref) {
|
|
330304
|
+
const all3 = [...this.procs.values()];
|
|
330305
|
+
if (all3.length === 0)
|
|
330306
|
+
return { error: "Failed: no background processes exist." };
|
|
330307
|
+
const known = all3.map((p) => `${p.id} "${p.label}" [${p.status}]`).join(", ");
|
|
330308
|
+
const running = all3.filter((p) => p.status === "running");
|
|
330309
|
+
const key = (ref ?? "").trim().replace(/^["']|["']$/g, "").toLowerCase();
|
|
330310
|
+
if (!key) {
|
|
330311
|
+
const only = running.length === 1 ? running[0] : all3.length === 1 ? all3[0] : undefined;
|
|
330312
|
+
return only ? { m: only } : { error: `Failed: specify a process id. Known: ${known}` };
|
|
330313
|
+
}
|
|
330314
|
+
const byId = all3.find((p) => p.id === key);
|
|
330315
|
+
if (byId)
|
|
330316
|
+
return { m: byId };
|
|
330317
|
+
const exact = [...all3].reverse().find((p) => p.label.toLowerCase() === key);
|
|
330318
|
+
if (exact)
|
|
330319
|
+
return { m: exact };
|
|
330320
|
+
const pick2 = (cands) => {
|
|
330321
|
+
if (cands.length === 0)
|
|
330322
|
+
return null;
|
|
330323
|
+
const live = cands.filter((p) => p.status === "running");
|
|
330324
|
+
const pool = live.length > 0 ? live : cands;
|
|
330325
|
+
if (pool.length === 1)
|
|
330326
|
+
return { m: pool[0] };
|
|
330327
|
+
return {
|
|
330328
|
+
error: `Failed: "${ref}" is ambiguous. Candidates: ${pool.map((p) => `${p.id} "${p.label}" [${p.status}]`).join(", ")}`
|
|
330329
|
+
};
|
|
330330
|
+
};
|
|
330331
|
+
const port = /^(?:.*?[:\s])?(\d{2,5})$/.exec(key)?.[1];
|
|
330332
|
+
if (port) {
|
|
330333
|
+
const portRe = new RegExp(`\\b${port}\\b`);
|
|
330334
|
+
const hit = pick2(all3.filter((p) => [...p.urls].some((u) => u.includes(`:${port}`)) || portRe.test(p.cmd)));
|
|
330335
|
+
if (hit)
|
|
330336
|
+
return hit;
|
|
330337
|
+
}
|
|
330338
|
+
const partial2 = pick2(all3.filter((p) => {
|
|
330339
|
+
const label = p.label.toLowerCase();
|
|
330340
|
+
return label.length >= 3 && (label.includes(key) || key.includes(label)) || p.cmd.toLowerCase().includes(key);
|
|
330341
|
+
}));
|
|
330342
|
+
if (partial2)
|
|
330343
|
+
return partial2;
|
|
330344
|
+
if (GENERIC_RE.test(key) && running.length === 1)
|
|
330345
|
+
return { m: running[0] };
|
|
330346
|
+
return { error: `Failed: no process "${ref}". Known: ${known}` };
|
|
330347
|
+
}
|
|
330348
|
+
append(m, raw) {
|
|
330349
|
+
const text = raw.replace(ANSI_RE, "").replace(/\r\n/g, `
|
|
330350
|
+
`).replace(/[^\n]*\r/g, "");
|
|
330351
|
+
if (!text)
|
|
330352
|
+
return;
|
|
330353
|
+
m.totalChars += text.length;
|
|
330354
|
+
m.buf += text;
|
|
330355
|
+
if (m.buf.length > LOG_CAP) {
|
|
330356
|
+
m.buf = m.buf.slice(-LOG_CAP);
|
|
330357
|
+
m.buf = m.buf.slice(m.buf.indexOf(`
|
|
330358
|
+
`) + 1);
|
|
330359
|
+
}
|
|
330360
|
+
for (const u of text.match(URL_RE) ?? []) {
|
|
330361
|
+
if (m.urls.size < 5)
|
|
330362
|
+
m.urls.add(u.replace(/[.,;/]+$/, ""));
|
|
330363
|
+
}
|
|
330364
|
+
for (const pm of text.matchAll(PORT_RE)) {
|
|
330365
|
+
const p = pm[1];
|
|
330366
|
+
if (p && m.urls.size < 5 && ![...m.urls].some((u) => u.includes(`:${p}`))) {
|
|
330367
|
+
m.urls.add(`http://localhost:${p}`);
|
|
330368
|
+
}
|
|
330369
|
+
}
|
|
330370
|
+
if (m.urls.size > 0 || READY_RE.test(text))
|
|
330371
|
+
m.events.emit("ready");
|
|
330372
|
+
}
|
|
330373
|
+
tail(m, lines) {
|
|
330374
|
+
const out = m.buf.split(`
|
|
330375
|
+
`).slice(-lines).join(`
|
|
330376
|
+
`).trim();
|
|
330377
|
+
if (!out)
|
|
330378
|
+
return "(no output yet)";
|
|
330379
|
+
return out.length > TAIL_CHAR_CAP ? `\u2026${out.slice(-TAIL_CHAR_CAP)}` : out;
|
|
330380
|
+
}
|
|
330381
|
+
finish(m, code, signal) {
|
|
330382
|
+
if (m.status !== "running")
|
|
330383
|
+
return;
|
|
330384
|
+
m.endedAt = Date.now();
|
|
330385
|
+
m.exitCode = code;
|
|
330386
|
+
m.signal = signal;
|
|
330387
|
+
m.status = m.killRequested ? "killed" : code === 0 ? "exited" : "failed";
|
|
330388
|
+
m.noticed = m.killRequested;
|
|
330389
|
+
m.events.emit("exit");
|
|
330390
|
+
this.prune();
|
|
330391
|
+
}
|
|
330392
|
+
prune() {
|
|
330393
|
+
const finished2 = [...this.procs.values()].filter((p) => p.status !== "running");
|
|
330394
|
+
for (const p of finished2.slice(0, Math.max(0, finished2.length - MAX_FINISHED_KEPT))) {
|
|
330395
|
+
this.procs.delete(p.id);
|
|
330396
|
+
}
|
|
330397
|
+
}
|
|
330398
|
+
waitForStartup(m, ms) {
|
|
330399
|
+
return new Promise((resolve) => {
|
|
330400
|
+
if (m.status !== "running")
|
|
330401
|
+
return resolve();
|
|
330402
|
+
let settle3;
|
|
330403
|
+
const done = () => {
|
|
330404
|
+
clearTimeout(timer);
|
|
330405
|
+
if (settle3)
|
|
330406
|
+
clearTimeout(settle3);
|
|
330407
|
+
m.events.off("ready", onReady);
|
|
330408
|
+
m.events.off("exit", done);
|
|
330409
|
+
resolve();
|
|
330410
|
+
};
|
|
330411
|
+
const onReady = () => {
|
|
330412
|
+
if (!settle3)
|
|
330413
|
+
settle3 = setTimeout(done, 300);
|
|
330414
|
+
};
|
|
330415
|
+
const timer = setTimeout(done, ms);
|
|
330416
|
+
m.events.on("ready", onReady);
|
|
330417
|
+
m.events.once("exit", done);
|
|
330418
|
+
});
|
|
330419
|
+
}
|
|
330420
|
+
waitForExit(m, ms) {
|
|
330421
|
+
return new Promise((resolve) => {
|
|
330422
|
+
if (m.status !== "running")
|
|
330423
|
+
return resolve(true);
|
|
330424
|
+
const onExit = () => {
|
|
330425
|
+
clearTimeout(timer);
|
|
330426
|
+
resolve(true);
|
|
330427
|
+
};
|
|
330428
|
+
const timer = setTimeout(() => {
|
|
330429
|
+
m.events.off("exit", onExit);
|
|
330430
|
+
resolve(false);
|
|
330431
|
+
}, ms);
|
|
330432
|
+
m.events.once("exit", onExit);
|
|
330433
|
+
});
|
|
330434
|
+
}
|
|
330435
|
+
signalGroup(m, sig) {
|
|
330436
|
+
try {
|
|
330437
|
+
if (process.platform !== "win32" && m.pid)
|
|
330438
|
+
process.kill(-m.pid, sig);
|
|
330439
|
+
else
|
|
330440
|
+
m.child.kill(sig);
|
|
330441
|
+
} catch {
|
|
330442
|
+
try {
|
|
330443
|
+
m.child.kill(sig);
|
|
330444
|
+
} catch {}
|
|
330445
|
+
}
|
|
330446
|
+
}
|
|
330447
|
+
async terminate(m) {
|
|
330448
|
+
m.killRequested = true;
|
|
330449
|
+
this.signalGroup(m, "SIGTERM");
|
|
330450
|
+
if (await this.waitForExit(m, KILL_GRACE_MS + EXIT_DRAIN_MS))
|
|
330451
|
+
return;
|
|
330452
|
+
this.signalGroup(m, "SIGKILL");
|
|
330453
|
+
await this.waitForExit(m, 1000 + EXIT_DRAIN_MS);
|
|
330454
|
+
}
|
|
330455
|
+
killAllSync() {
|
|
330456
|
+
for (const m of this.procs.values()) {
|
|
330457
|
+
if (m.status === "running") {
|
|
330458
|
+
m.killRequested = true;
|
|
330459
|
+
this.signalGroup(m, "SIGTERM");
|
|
330460
|
+
}
|
|
330461
|
+
}
|
|
330462
|
+
}
|
|
330463
|
+
}
|
|
330464
|
+
|
|
329961
330465
|
// src/tools/harness-core.ts
|
|
329962
330466
|
var MAX_JSON_RETRIES = 3;
|
|
329963
330467
|
var MAX_WRITES_PER_FILE = 10;
|
|
@@ -330146,6 +330650,408 @@ async function isConversational(message) {
|
|
|
330146
330650
|
return false;
|
|
330147
330651
|
}
|
|
330148
330652
|
}
|
|
330653
|
+
var DEBUG_TRACE = Boolean(process.env.BASECODE_DEBUG);
|
|
330654
|
+
var CLEAR_LINE = "\r\x1B[2K";
|
|
330655
|
+
var WEB_SEARCH_FAILED_RE = /^\[Web search results[^\n]*\n(?:REJECTED|SEARCH FAILED):/;
|
|
330656
|
+
var TOOL_OUTPUT_CHAR_CAP = 16000;
|
|
330657
|
+
var SHELL_OUTPUT_BYTE_CAP = 256000;
|
|
330658
|
+
var RESULT_TAIL_CHARS = 8000;
|
|
330659
|
+
var INIT_PER_FILE_CHAR_CAP = 8000;
|
|
330660
|
+
var INIT_TOTAL_DUMP_CHAR_CAP = 200000;
|
|
330661
|
+
var HEAVY_KEEP_CHARS = 40000;
|
|
330662
|
+
var REPO_MAP_CHAR_CAP = 12000;
|
|
330663
|
+
var JEV_REPO_MAP_CHAR_CAP = 4000;
|
|
330664
|
+
var JEV_TASK_CHAR_CAP = 3000;
|
|
330665
|
+
var JEV_TAIL_MSGS = 3;
|
|
330666
|
+
var JEV_TAIL_CHARS = 1500;
|
|
330667
|
+
var PRIOR_MSG_CHAR_CAP = 2500;
|
|
330668
|
+
var SYNTHESIS_OVERVIEW_CHARS = 6000;
|
|
330669
|
+
var NOTICE_POLL_MS = 250;
|
|
330670
|
+
var VISION_MODEL_ID = "gemini-2.5-flash";
|
|
330671
|
+
var VISION_MAX_TOKENS = 1200;
|
|
330672
|
+
var VISION_TIMEOUT_MS = 60000;
|
|
330673
|
+
var VISION_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
330674
|
+
var VISION_CONCURRENCY = 3;
|
|
330675
|
+
var VISION_SYSTEM_PROMPT = "You describe images for a coding agent that cannot see them. " + "Transcribe all visible text, code, error messages and numbers verbatim. " + "Then describe layout, UI elements, colours, diagrams and anything relevant to a software task. " + "Be factual and compact (under 400 words) and do not speculate about intent. " + "Reply with the description only.";
|
|
330676
|
+
var DECISION_JSON_START_RE = /\{\s*"(?:tool|reasoning|need_more_steps|requires_confirmation)"\s*:/;
|
|
330677
|
+
var DECISION_JSON_PREFIX_RE = /^\{\s*(?:"[a-z_]{0,24}"?\s*:?)?$/;
|
|
330678
|
+
var DECISION_JSON_FENCE_TAIL_RE = /```(?:json)?\s*$/i;
|
|
330679
|
+
var DECISION_JSON_PARTIAL_FENCE_RE = /(?:`{1,2}|```(?:j(?:s(?:o(?:n)?)?)?)?\s*)$/i;
|
|
330680
|
+
var DECISION_JSON_HOLD_CHARS = 40;
|
|
330681
|
+
var isDecisionShaped = (v) => isRecord2(v) && (("tool" in v) && (v.tool === null || typeof v.tool === "string") || ("reasoning" in v) && (("need_more_steps" in v) || ("needs_more_steps" in v) || ("requires_confirmation" in v)));
|
|
330682
|
+
function jsonObjectEnd(s2, start) {
|
|
330683
|
+
let depth = 0;
|
|
330684
|
+
let inStr = false;
|
|
330685
|
+
let esc2 = false;
|
|
330686
|
+
for (let i2 = start;i2 < s2.length; i2++) {
|
|
330687
|
+
const c2 = s2[i2];
|
|
330688
|
+
if (inStr) {
|
|
330689
|
+
if (esc2)
|
|
330690
|
+
esc2 = false;
|
|
330691
|
+
else if (c2 === "\\")
|
|
330692
|
+
esc2 = true;
|
|
330693
|
+
else if (c2 === '"')
|
|
330694
|
+
inStr = false;
|
|
330695
|
+
continue;
|
|
330696
|
+
}
|
|
330697
|
+
if (c2 === '"')
|
|
330698
|
+
inStr = true;
|
|
330699
|
+
else if (c2 === "{")
|
|
330700
|
+
depth++;
|
|
330701
|
+
else if (c2 === "}") {
|
|
330702
|
+
depth--;
|
|
330703
|
+
if (depth === 0)
|
|
330704
|
+
return i2 + 1;
|
|
330705
|
+
}
|
|
330706
|
+
}
|
|
330707
|
+
return -1;
|
|
330708
|
+
}
|
|
330709
|
+
function salvageReasoning(partial2) {
|
|
330710
|
+
const raw = /"reasoning"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(partial2)?.[1];
|
|
330711
|
+
if (raw === undefined)
|
|
330712
|
+
return "";
|
|
330713
|
+
try {
|
|
330714
|
+
return String(JSON.parse(`"${raw}"`)).trim();
|
|
330715
|
+
} catch {
|
|
330716
|
+
return raw.trim();
|
|
330717
|
+
}
|
|
330718
|
+
}
|
|
330719
|
+
function decisionPackageText(json3) {
|
|
330720
|
+
let parsed;
|
|
330721
|
+
try {
|
|
330722
|
+
parsed = JSON.parse(json3);
|
|
330723
|
+
} catch {
|
|
330724
|
+
return salvageReasoning(json3);
|
|
330725
|
+
}
|
|
330726
|
+
if (!isDecisionShaped(parsed))
|
|
330727
|
+
return json3;
|
|
330728
|
+
return typeof parsed.reasoning === "string" ? parsed.reasoning.trim() : "";
|
|
330729
|
+
}
|
|
330730
|
+
function decisionJsonHoldFrom(s2) {
|
|
330731
|
+
let hold = s2.length;
|
|
330732
|
+
const open3 = s2.lastIndexOf("{");
|
|
330733
|
+
if (open3 >= 0 && s2.length - open3 <= DECISION_JSON_HOLD_CHARS && DECISION_JSON_PREFIX_RE.test(s2.slice(open3))) {
|
|
330734
|
+
hold = open3;
|
|
330735
|
+
}
|
|
330736
|
+
const fence = DECISION_JSON_PARTIAL_FENCE_RE.exec(s2.slice(0, hold));
|
|
330737
|
+
if (fence && fence.index >= hold - DECISION_JSON_HOLD_CHARS)
|
|
330738
|
+
hold = fence.index;
|
|
330739
|
+
return hold;
|
|
330740
|
+
}
|
|
330741
|
+
async function* guardDecisionJson(source) {
|
|
330742
|
+
let pending = "";
|
|
330743
|
+
let inPackage = false;
|
|
330744
|
+
let dropCloseFence = false;
|
|
330745
|
+
for await (const chunk of source) {
|
|
330746
|
+
const d2 = chunk.delta;
|
|
330747
|
+
if (!d2)
|
|
330748
|
+
continue;
|
|
330749
|
+
pending += d2;
|
|
330750
|
+
for (;; ) {
|
|
330751
|
+
if (inPackage) {
|
|
330752
|
+
const end = jsonObjectEnd(pending, 0);
|
|
330753
|
+
if (end < 0)
|
|
330754
|
+
break;
|
|
330755
|
+
const text = decisionPackageText(pending.slice(0, end));
|
|
330756
|
+
pending = pending.slice(end);
|
|
330757
|
+
inPackage = false;
|
|
330758
|
+
if (text)
|
|
330759
|
+
yield { delta: text };
|
|
330760
|
+
continue;
|
|
330761
|
+
}
|
|
330762
|
+
if (dropCloseFence) {
|
|
330763
|
+
const t2 = pending.trimStart();
|
|
330764
|
+
if (t2 === "" || /^`{1,2}$/.test(t2))
|
|
330765
|
+
break;
|
|
330766
|
+
dropCloseFence = false;
|
|
330767
|
+
if (t2.startsWith("```"))
|
|
330768
|
+
pending = t2.slice(3);
|
|
330769
|
+
}
|
|
330770
|
+
const hit = DECISION_JSON_START_RE.exec(pending);
|
|
330771
|
+
if (hit) {
|
|
330772
|
+
const head = pending.slice(0, hit.index);
|
|
330773
|
+
const fenced = DECISION_JSON_FENCE_TAIL_RE.test(head);
|
|
330774
|
+
pending = pending.slice(hit.index);
|
|
330775
|
+
inPackage = true;
|
|
330776
|
+
dropCloseFence = fenced;
|
|
330777
|
+
const keep = fenced ? head.replace(DECISION_JSON_FENCE_TAIL_RE, "") : head;
|
|
330778
|
+
if (keep)
|
|
330779
|
+
yield { delta: keep };
|
|
330780
|
+
continue;
|
|
330781
|
+
}
|
|
330782
|
+
const hold = decisionJsonHoldFrom(pending);
|
|
330783
|
+
const out = pending.slice(0, hold);
|
|
330784
|
+
pending = pending.slice(hold);
|
|
330785
|
+
if (out)
|
|
330786
|
+
yield { delta: out };
|
|
330787
|
+
break;
|
|
330788
|
+
}
|
|
330789
|
+
}
|
|
330790
|
+
if (inPackage) {
|
|
330791
|
+
const text = salvageReasoning(pending);
|
|
330792
|
+
if (text)
|
|
330793
|
+
yield { delta: text };
|
|
330794
|
+
} else if (dropCloseFence && /^[\s`]*$/.test(pending)) {} else if (pending) {
|
|
330795
|
+
yield { delta: pending };
|
|
330796
|
+
}
|
|
330797
|
+
}
|
|
330798
|
+
var BASECODE_POINTER = "BASECODE.md exists at the repo root (architecture, conventions, commands) but is NOT inlined, to save tokens. " + 'Read it once with the "read" tool if the task needs project background; skip it for self-contained tasks.';
|
|
330799
|
+
var NO_BASECODE_NOTE = "No BASECODE.md yet (the user can run /init to generate one).";
|
|
330800
|
+
var GIT_INTENT = /\b(git|commit|committed|merge|push|pull|rebase|branch|branches|checkout|stash|tag|cherry-?pick|reset|revert|amend)\b/i;
|
|
330801
|
+
var NO_INLINE_CONTENT_RULE = {
|
|
330802
|
+
role: "system",
|
|
330803
|
+
content: `OUTPUT REMINDER: "write"/"append": set "content" to "" (the file is generated in a separate step; never inline source). ` + `"edit": "target" and "replacement" are REQUIRED, copied verbatim from file contents shown in context, kept small. ` + `"shell": "cmd" is REQUIRED (the literal command). "web_search": "content" is REQUIRED \u2014 a short, specific query ` + `(3-10 words) with no secrets, local paths or code; other fields null. "reasoning": ONE short sentence. ` + `"bg_start": "cmd" REQUIRED (the literal command), "target" = optional short name; "bg_logs"/"bg_kill": "target" = ` + `process id, name or port ("bg_kill" accepts "all"); "bg_list": nothing else. Never run servers/watchers via "shell". ` + `Your whole JSON must fit in ${DECISION_MAX_TOKENS} tokens or the step is wasted. ` + `For project background read BASECODE.md (not inlined); if none exists, tell the user to run /init.`
|
|
330804
|
+
};
|
|
330805
|
+
var BG_RULES_MESSAGE = { role: "system", content: BG_TOOL_RULES };
|
|
330806
|
+
var MAX_ASKS_PER_TASK = 2;
|
|
330807
|
+
var ASK_MAX_QUESTIONS = 4;
|
|
330808
|
+
var ASK_MAX_OPTIONS = 4;
|
|
330809
|
+
var ASK_MAX_QUESTION_CHARS = 400;
|
|
330810
|
+
var ASK_MAX_OPTION_CHARS = 120;
|
|
330811
|
+
var PENDING_BLOCKED_TOOLS = new Set([
|
|
330812
|
+
"write",
|
|
330813
|
+
"edit",
|
|
330814
|
+
"append",
|
|
330815
|
+
"shell",
|
|
330816
|
+
"bg_start"
|
|
330817
|
+
]);
|
|
330818
|
+
var QUESTION_CONTEXT_RE = /^\[(?:USER ANSWER\vert{}QUESTIONS ASKED)\]|^[System] QUESTIONS:/;
|
|
330819
|
+
var QUESTION_BLOCK_RE = /\? Questions for you \(\d+\):\n(?: {2}\d+\. .*\n)+\n?/g;
|
|
330820
|
+
var clipText = (s2, n3) => s2.length > n3 ? `${s2.slice(0, n3 - 1)}\u2026` : s2;
|
|
330821
|
+
function cleanAskOptions(v) {
|
|
330822
|
+
if (!Array.isArray(v))
|
|
330823
|
+
return [];
|
|
330824
|
+
const seen = new Set;
|
|
330825
|
+
const out = [];
|
|
330826
|
+
for (const o2 of v) {
|
|
330827
|
+
if (typeof o2 !== "string")
|
|
330828
|
+
continue;
|
|
330829
|
+
const t2 = o2.replace(/\s+/g, " ").trim().slice(0, ASK_MAX_OPTION_CHARS);
|
|
330830
|
+
const key = t2.toLowerCase();
|
|
330831
|
+
if (!t2 || seen.has(key))
|
|
330832
|
+
continue;
|
|
330833
|
+
seen.add(key);
|
|
330834
|
+
out.push(t2);
|
|
330835
|
+
if (out.length >= ASK_MAX_OPTIONS)
|
|
330836
|
+
break;
|
|
330837
|
+
}
|
|
330838
|
+
return out;
|
|
330839
|
+
}
|
|
330840
|
+
function parseAskDecision(parsed) {
|
|
330841
|
+
if (!isRecord2(parsed) || parsed.tool !== "ask_user_question")
|
|
330842
|
+
return null;
|
|
330843
|
+
const rawItems = [];
|
|
330844
|
+
if (Array.isArray(parsed.questions))
|
|
330845
|
+
rawItems.push(...parsed.questions);
|
|
330846
|
+
else if (typeof parsed.question === "string") {
|
|
330847
|
+
rawItems.push({ question: parsed.question, options: parsed.options });
|
|
330848
|
+
}
|
|
330849
|
+
const questions = [];
|
|
330850
|
+
const seen = new Set;
|
|
330851
|
+
for (const item of rawItems) {
|
|
330852
|
+
let text = "";
|
|
330853
|
+
let options = [];
|
|
330854
|
+
if (typeof item === "string") {
|
|
330855
|
+
text = item;
|
|
330856
|
+
} else if (isRecord2(item) && typeof item.question === "string") {
|
|
330857
|
+
text = item.question;
|
|
330858
|
+
options = cleanAskOptions(item.options);
|
|
330859
|
+
}
|
|
330860
|
+
text = text.replace(/\s+/g, " ").trim().slice(0, ASK_MAX_QUESTION_CHARS);
|
|
330861
|
+
const key = text.toLowerCase();
|
|
330862
|
+
if (!text || seen.has(key))
|
|
330863
|
+
continue;
|
|
330864
|
+
seen.add(key);
|
|
330865
|
+
questions.push({ question: text, options });
|
|
330866
|
+
if (questions.length >= ASK_MAX_QUESTIONS)
|
|
330867
|
+
break;
|
|
330868
|
+
}
|
|
330869
|
+
return {
|
|
330870
|
+
questions,
|
|
330871
|
+
ignore: parsed.ignore === true,
|
|
330872
|
+
reasoning: typeof parsed.reasoning === "string" ? parsed.reasoning.slice(0, 1000) : ""
|
|
330873
|
+
};
|
|
330874
|
+
}
|
|
330875
|
+
var QUESTIONS_RULE = {
|
|
330876
|
+
role: "system",
|
|
330877
|
+
content: `ASK TOOL: to ask the user something, return {"tool":"ask_user_question","questions":[{"question":"...","options":["...","..."]}],` + `"reasoning":"...","need_more_steps":true}. 1-${ASK_MAX_QUESTIONS} questions, each self-contained, with 2-${ASK_MAX_OPTIONS} mutually ` + `exclusive options (recommended first) or an empty options array for an open question. A single {"question","options"} pair is also accepted. ` + `All other fields null. The questions are shown to the user as a questionnaire; after asking you MUST stop and wait. ` + `Never ask while questions are pending, never re-ask something already answered (the answers are listed under "USER ANSWERS" in the task), ` + `at most ${MAX_ASKS_PER_TASK} asks per task. To drop the pending questions because the user's message makes them moot, return ` + `{"tool":"ask_user_question","ignore":true,"reasoning":"..."}.`
|
|
330878
|
+
};
|
|
330879
|
+
var JEV_BYPASS = {
|
|
330880
|
+
unavailable: true,
|
|
330881
|
+
tool: "none",
|
|
330882
|
+
needMoreSteps: false,
|
|
330883
|
+
confidence: 0,
|
|
330884
|
+
reasoning: ""
|
|
330885
|
+
};
|
|
330886
|
+
var MEMORY_CHAR_CAP = 6000;
|
|
330887
|
+
var MEMORY_MAX_NEW_ENTRIES = 3;
|
|
330888
|
+
var MEMORY_MAX_ENTRY_CHARS = 250;
|
|
330889
|
+
var MEMORY_HEADER = `# BASECODE MEMORY
|
|
330890
|
+
Project-specific preferences and fixes learned from past sessions. Newest last.
|
|
330891
|
+
`;
|
|
330892
|
+
var MEMORY_SIGNAL = /\b(always|never|don'?t|do not|instead|prefer|from now on|remember|actually|wrong|incorrect|not what i|should(?:n'?t)?|stop (?:using|doing)|use \S+ (?:not|instead))\b/i;
|
|
330893
|
+
var MEMORY_SECRET_PATTERN = /(?:sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{20,}|api[_-]?key\s*[:=]|password\s*[:=]|secret\s*[:=]|token\s*[:=]|-----BEGIN)/i;
|
|
330894
|
+
var MEMORY_LEARNER_PROMPT = `You maintain a small long-term memory file for a coding agent working in one project.
|
|
330895
|
+
From the exchange below, extract ONLY durable, reusable lessons:
|
|
330896
|
+
- a way the user wants things done (style, tooling, conventions, workflow), or
|
|
330897
|
+
- a non-obvious fix / root cause in THIS project that would save time if it came up again.
|
|
330898
|
+
|
|
330899
|
+
Rules:
|
|
330900
|
+
- Each entry is ONE short imperative sentence (max 200 chars) that makes sense with no other context.
|
|
330901
|
+
- Do NOT record one-off task details, lists of files changed, or anything already in EXISTING MEMORY (including paraphrases).
|
|
330902
|
+
- Do NOT record secrets, API keys, tokens, or personal data.
|
|
330903
|
+
- If the user contradicts an existing entry, put the corrected entry in "add" and the old entry's exact text in "remove".
|
|
330904
|
+
- Most exchanges contain nothing worth saving. In that case respond with {"add":[],"remove":[]}.
|
|
330905
|
+
|
|
330906
|
+
Respond with ONE raw JSON object and nothing else: {"add": string[], "remove": string[]} DO NOT NOTIFY THE USER WHEN YOU UPDATE THIS FILE`;
|
|
330907
|
+
var memNorm = (s2) => s2.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
330908
|
+
var memEntryText = (line) => line.replace(/^-\s*(?:\[\d{4}-\d{2}-\d{2}\]\s*)?/, "").trim();
|
|
330909
|
+
var CHARS_PER_TOKEN = 3.5;
|
|
330910
|
+
var TOKENIZE_EXACT_MAX_CHARS = 1e5;
|
|
330911
|
+
var estimateTextTokens = (text) => {
|
|
330912
|
+
if (!text)
|
|
330913
|
+
return 0;
|
|
330914
|
+
return text.length > TOKENIZE_EXACT_MAX_CHARS ? Math.ceil(text.length / CHARS_PER_TOKEN) : countTokens(text);
|
|
330915
|
+
};
|
|
330916
|
+
var MEDIA_PART_TOKENS = 1000;
|
|
330917
|
+
var MESSAGE_OVERHEAD_TOKENS = 4;
|
|
330918
|
+
function hasText(part) {
|
|
330919
|
+
return typeof part === "object" && part !== null && typeof part.text === "string";
|
|
330920
|
+
}
|
|
330921
|
+
function countLines(text) {
|
|
330922
|
+
const len = text.length;
|
|
330923
|
+
if (len === 0)
|
|
330924
|
+
return 0;
|
|
330925
|
+
let n3 = 1;
|
|
330926
|
+
for (let i2 = 0;i2 < len; i2++) {
|
|
330927
|
+
if (text.charCodeAt(i2) === 10)
|
|
330928
|
+
n3++;
|
|
330929
|
+
}
|
|
330930
|
+
if (text.charCodeAt(len - 1) === 10)
|
|
330931
|
+
n3--;
|
|
330932
|
+
return n3 === 0 ? 1 : n3;
|
|
330933
|
+
}
|
|
330934
|
+
function clipMiddle2(text, max) {
|
|
330935
|
+
if (text.length <= max)
|
|
330936
|
+
return text;
|
|
330937
|
+
const head = Math.floor(max * 0.4);
|
|
330938
|
+
const tail = max - head;
|
|
330939
|
+
return `${text.slice(0, head)}
|
|
330940
|
+
\u2026[clipped ${text.length - max} chars]\u2026
|
|
330941
|
+
${text.slice(-tail)}`;
|
|
330942
|
+
}
|
|
330943
|
+
var repoMapMemo = new Map;
|
|
330944
|
+
function compactRepoMap(repoMap, cap) {
|
|
330945
|
+
const hit = repoMapMemo.get(cap);
|
|
330946
|
+
if (hit && hit.src === repoMap)
|
|
330947
|
+
return hit.out;
|
|
330948
|
+
let out;
|
|
330949
|
+
try {
|
|
330950
|
+
const paths = flattenRepoMap(repoMap);
|
|
330951
|
+
if (paths.length === 0) {
|
|
330952
|
+
out = repoMap.slice(0, cap);
|
|
330953
|
+
} else {
|
|
330954
|
+
const byDir = new Map;
|
|
330955
|
+
for (const p of paths) {
|
|
330956
|
+
const d2 = dirname(p);
|
|
330957
|
+
const base = d2 === "." ? p : p.slice(d2.length + 1);
|
|
330958
|
+
const list = byDir.get(d2);
|
|
330959
|
+
if (list)
|
|
330960
|
+
list.push(base);
|
|
330961
|
+
else
|
|
330962
|
+
byDir.set(d2, [base]);
|
|
330963
|
+
}
|
|
330964
|
+
const lines = [];
|
|
330965
|
+
let total = 0;
|
|
330966
|
+
let listed = 0;
|
|
330967
|
+
for (const [d2, files] of [...byDir].sort(([a2], [b]) => a2 < b ? -1 : a2 > b ? 1 : 0)) {
|
|
330968
|
+
const line = `${d2 === "." ? "./" : `${d2}/`} ${files.join(" ")}`;
|
|
330969
|
+
if (total + line.length > cap)
|
|
330970
|
+
break;
|
|
330971
|
+
lines.push(line);
|
|
330972
|
+
total += line.length + 1;
|
|
330973
|
+
listed += files.length;
|
|
330974
|
+
}
|
|
330975
|
+
const omitted = paths.length - listed;
|
|
330976
|
+
out = lines.join(`
|
|
330977
|
+
`) + (omitted > 0 ? `
|
|
330978
|
+
(+${omitted} more files not listed; use find/grep to explore)` : "");
|
|
330979
|
+
}
|
|
330980
|
+
} catch {
|
|
330981
|
+
out = repoMap.slice(0, cap);
|
|
330982
|
+
}
|
|
330983
|
+
repoMapMemo.set(cap, { src: repoMap, out });
|
|
330984
|
+
return out;
|
|
330985
|
+
}
|
|
330986
|
+
async function readCappedStream(stream4, cap = SHELL_OUTPUT_BYTE_CAP) {
|
|
330987
|
+
if (!stream4)
|
|
330988
|
+
return "";
|
|
330989
|
+
const reader = stream4.getReader();
|
|
330990
|
+
const decoder2 = new TextDecoder;
|
|
330991
|
+
const parts = [];
|
|
330992
|
+
let size = 0;
|
|
330993
|
+
let truncated = false;
|
|
330994
|
+
try {
|
|
330995
|
+
while (size < cap) {
|
|
330996
|
+
const { done, value } = await reader.read();
|
|
330997
|
+
if (done)
|
|
330998
|
+
break;
|
|
330999
|
+
if (!value)
|
|
331000
|
+
continue;
|
|
331001
|
+
size += value.byteLength;
|
|
331002
|
+
parts.push(decoder2.decode(value, { stream: true }));
|
|
331003
|
+
}
|
|
331004
|
+
if (size >= cap)
|
|
331005
|
+
truncated = true;
|
|
331006
|
+
} catch {} finally {
|
|
331007
|
+
try {
|
|
331008
|
+
await reader.cancel();
|
|
331009
|
+
} catch {}
|
|
331010
|
+
}
|
|
331011
|
+
let out = parts.join("");
|
|
331012
|
+
parts.length = 0;
|
|
331013
|
+
if (out.length > cap)
|
|
331014
|
+
out = out.slice(0, cap);
|
|
331015
|
+
return truncated ? `${out}
|
|
331016
|
+
\u2026[output truncated at ${cap} bytes]` : out;
|
|
331017
|
+
}
|
|
331018
|
+
function buildAttachmentPart(provider, mimeType, output2) {
|
|
331019
|
+
const key = provider.toUpperCase();
|
|
331020
|
+
const isText = mimeType.startsWith("text/");
|
|
331021
|
+
if (key === "GOOGLE") {
|
|
331022
|
+
return isText ? { text: output2 } : { inlineData: { mimeType, data: output2 } };
|
|
331023
|
+
}
|
|
331024
|
+
if (key === "ANTHROPIC") {
|
|
331025
|
+
return {
|
|
331026
|
+
type: mimeType.startsWith("image/") ? "image" : "document",
|
|
331027
|
+
source: { type: "base64", media_type: mimeType, data: output2 }
|
|
331028
|
+
};
|
|
331029
|
+
}
|
|
331030
|
+
if (key === "OPENAI" || key === "GROQ" || key === "VENICE") {
|
|
331031
|
+
return isText ? { type: "text", text: output2 } : { type: "image_url", image_url: { url: `data:${mimeType};base64,${output2}` } };
|
|
331032
|
+
}
|
|
331033
|
+
return { type: "text", text: output2 };
|
|
331034
|
+
}
|
|
331035
|
+
function foldMediaParts(messages) {
|
|
331036
|
+
let hasMedia = false;
|
|
331037
|
+
for (const m of messages) {
|
|
331038
|
+
if (m && Array.isArray(m.mediaParts) && m.mediaParts.length > 0) {
|
|
331039
|
+
hasMedia = true;
|
|
331040
|
+
break;
|
|
331041
|
+
}
|
|
331042
|
+
}
|
|
331043
|
+
if (!hasMedia)
|
|
331044
|
+
return messages;
|
|
331045
|
+
return messages.map((m) => {
|
|
331046
|
+
if (!m || !Array.isArray(m.mediaParts) || m.mediaParts.length === 0)
|
|
331047
|
+
return m;
|
|
331048
|
+
const { mediaParts, ...rest } = m;
|
|
331049
|
+
const text = typeof rest.content === "string" ? rest.content : "";
|
|
331050
|
+
const parts = [...text ? [{ type: "text", text }] : [], ...mediaParts];
|
|
331051
|
+
return { ...rest, content: parts };
|
|
331052
|
+
});
|
|
331053
|
+
}
|
|
331054
|
+
var TOOL_HISTORY_CAP = 20;
|
|
330149
331055
|
|
|
330150
331056
|
// src/tools/harness-jev.ts
|
|
330151
331057
|
function truncate(text, maxChars) {
|
|
@@ -330209,7 +331115,7 @@ async function classifyShellRisk(cmd) {
|
|
|
330209
331115
|
|
|
330210
331116
|
// src/tools/harness-jev-next-step-prompt.ts
|
|
330211
331117
|
var DEFAULT_JEV_MODEL = "jev-latest";
|
|
330212
|
-
function
|
|
331118
|
+
function clip2(s2, max) {
|
|
330213
331119
|
if (!s2)
|
|
330214
331120
|
return "";
|
|
330215
331121
|
return s2.length <= max ? s2 : `${s2.slice(0, max)}
|
|
@@ -330230,18 +331136,18 @@ async function classifyNextStep(input2) {
|
|
|
330230
331136
|
} = input2;
|
|
330231
331137
|
const state = [
|
|
330232
331138
|
`TASK:
|
|
330233
|
-
${
|
|
331139
|
+
${clip2(taskDescription, 1200)}`,
|
|
330234
331140
|
`STEP: ${step}/${maxSteps}`,
|
|
330235
331141
|
`PLAN_MODE: ${isPlanMode}`,
|
|
330236
331142
|
`ON_TASK_BRANCH: ${onTaskBranch}`,
|
|
330237
331143
|
`PROGRESS:
|
|
330238
|
-
${
|
|
331144
|
+
${clip2(progressSummary, 800) || "(none)"}`,
|
|
330239
331145
|
`RECENT_CONTEXT:
|
|
330240
|
-
${
|
|
331146
|
+
${clip2(recentContextTail, 1500) || "(none)"}`,
|
|
330241
331147
|
`REPO_MAP (paths):
|
|
330242
|
-
${
|
|
331148
|
+
${clip2(repoMap, 2500) || "(empty)"}`,
|
|
330243
331149
|
repoContext ? `REPO_CONTEXT:
|
|
330244
|
-
${
|
|
331150
|
+
${clip2(repoContext, 800)}` : ""
|
|
330245
331151
|
].filter(Boolean).join(`
|
|
330246
331152
|
|
|
330247
331153
|
`);
|
|
@@ -330441,507 +331347,6 @@ function buildShortReasoning(p) {
|
|
|
330441
331347
|
return `Next: ${p.tool} (git phase ${p.gitPhase}).`;
|
|
330442
331348
|
}
|
|
330443
331349
|
|
|
330444
|
-
// src/tools/harness-processes.ts
|
|
330445
|
-
import { spawn } from "child_process";
|
|
330446
|
-
import { EventEmitter as EventEmitter2 } from "events";
|
|
330447
|
-
var BG_TOOLS = new Set([
|
|
330448
|
-
"bg_start",
|
|
330449
|
-
"bg_list",
|
|
330450
|
-
"bg_logs",
|
|
330451
|
-
"bg_kill"
|
|
330452
|
-
]);
|
|
330453
|
-
var BG_READ_ONLY_TOOLS = new Set(["bg_list", "bg_logs"]);
|
|
330454
|
-
var isBgTool = (t2) => typeof t2 === "string" && BG_TOOLS.has(t2);
|
|
330455
|
-
var str = (v) => typeof v === "string" && v.trim() ? v.trim() : null;
|
|
330456
|
-
function parseBgDecision(p) {
|
|
330457
|
-
if (!isBgTool(p.tool))
|
|
330458
|
-
return null;
|
|
330459
|
-
const tool = p.tool;
|
|
330460
|
-
const cmd = tool === "bg_start" ? str(p.cmd) ?? str(p.command) ?? str(p.content) : null;
|
|
330461
|
-
const named = str(p.target) ?? str(p.name) ?? str(p.label) ?? str(p.id) ?? (typeof p.id === "number" ? String(p.id) : null);
|
|
330462
|
-
const target = tool === "bg_start" ? named : named ?? str(p.targetFile) ?? str(p.content);
|
|
330463
|
-
return {
|
|
330464
|
-
tool,
|
|
330465
|
-
cmd,
|
|
330466
|
-
target,
|
|
330467
|
-
reasoning: (str(p.reasoning) ?? "").slice(0, 1000),
|
|
330468
|
-
needMoreSteps: Boolean(p.need_more_steps || p.needs_more_steps)
|
|
330469
|
-
};
|
|
330470
|
-
}
|
|
330471
|
-
var LONG_RUNNING_SHELL_RE = new RegExp([
|
|
330472
|
-
String.raw`\b(?:npm|pnpm|yarn|bun)\s+(?:run\s+)?(?:dev|start|serve|watch|preview)\b`,
|
|
330473
|
-
String.raw`\bbun\s+(?:--hot|--watch)\b`,
|
|
330474
|
-
String.raw`\bnext\s+(?:dev|start)\b`,
|
|
330475
|
-
String.raw`\bvite(?:\s+(?:dev|preview|serve))?\s*(?:--\S+\s*)*$`,
|
|
330476
|
-
String.raw`\bnodemon\b`,
|
|
330477
|
-
String.raw`\bhttp\.server\b`,
|
|
330478
|
-
String.raw`\brunserver\b`,
|
|
330479
|
-
String.raw`\buvicorn\b`,
|
|
330480
|
-
String.raw`\bflask\s+run\b`,
|
|
330481
|
-
String.raw`\bnpx\s+serve\b`,
|
|
330482
|
-
String.raw`(?:^|&&|;)\s*anvil\b`,
|
|
330483
|
-
String.raw`\bhardhat\s+node\b`,
|
|
330484
|
-
String.raw`\bsolana-test-validator\b`,
|
|
330485
|
-
String.raw`\bngrok\b`,
|
|
330486
|
-
String.raw`\bdocker(?:-|\s+)compose\s+up\b(?!.*\s-d\b)`,
|
|
330487
|
-
String.raw`\btail\s+-f\b`,
|
|
330488
|
-
String.raw`\s--watch\b`,
|
|
330489
|
-
String.raw`(?<!&)&\s*$`
|
|
330490
|
-
].join("|"), "i");
|
|
330491
|
-
var BG_TOOL_RULES = `BACKGROUND PROCESS TOOLS \u2014 extra values for "tool", for servers, watchers and anything that doesn't exit on its own:
|
|
330492
|
-
- "bg_start": "cmd" = the literal command; "target" = optional short name (e.g. "web"). Returns the first few seconds of output (and any URL) so you can tell whether it came up.
|
|
330493
|
-
- "bg_list": no other fields.
|
|
330494
|
-
- "bg_logs": "target" = process id, name or port. Use it to see what a process is doing or why it died.
|
|
330495
|
-
- "bg_kill": "target" = process id, name or port, or "all".
|
|
330496
|
-
Rules:
|
|
330497
|
-
- NEVER run a server/watcher through "shell" (it blocks until it times out). Use "bg_start".
|
|
330498
|
-
- "start/run/launch the server" -> bg_start. If the user gave no command, "read" package.json (or pyproject.toml, manage.py, foundry.toml, Cargo.toml ...) to find the dev/start script, then bg_start it.
|
|
330499
|
-
- "stop/kill/shut down/turn off the server" -> bg_kill on the matching tracked process. This always means the tracked process, never the machine, and never "shell". "stop everything" -> target "all".
|
|
330500
|
-
- "restart X" -> bg_kill X, then bg_start with the same cmd (it is listed under BACKGROUND PROCESSES).
|
|
330501
|
-
- "what's running / is X up / what's X doing / why did X die" -> answer from BACKGROUND PROCESSES, or bg_list / bg_logs for real output. Never guess.
|
|
330502
|
-
- If the thing to stop is not tracked (started outside this session), say so; if the user gave a port, "shell" with lsof -ti :PORT can find the owner.
|
|
330503
|
-
- Never start a duplicate of a running process. After bg_start, if it crashed, bg_logs it, fix the cause, retry.
|
|
330504
|
-
- For bg_* tools leave "targetFile", "content", "target"/"cmd" not mentioned above as null.`;
|
|
330505
|
-
var GAP = String.raw`[^.!?\n]{0,40}?`;
|
|
330506
|
-
var LEAD = String.raw`(?<!\w)`;
|
|
330507
|
-
var TRAIL = String.raw`(?!\w)`;
|
|
330508
|
-
var NEG_RE = /\b(?:don'?t|do\s+not|never|no\s+need\s+to|without|instead\s+of|rather\s+than)\b[^.!?\n]{0,25}?\b(?:start|run|launch|stop|kill|shut|restart|spin|boot)/i;
|
|
330509
|
-
var STOP_V = String.raw`(?:stop|kill|terminate|shut\s*(?:it\s*|them\s*)?(?:down|off)|take\s+(?:it\s+|them\s+)?down|bring\s+(?:it\s+|them\s+)?down|turn\s+(?:it\s+|them\s+)?off|halt)`;
|
|
330510
|
-
var PROC_N = String.raw`(?:servers?|processes|process|watchers?|daemons?|services?|dev|backend|frontend|api|app|everything|all|bg|background|p\d+|ports?\s*\d+|:\d{2,5}|localhost|anvil|vite|django|flask|uvicorn)`;
|
|
330511
|
-
var STOP_RE = new RegExp(String.raw`\b${STOP_V}\b${GAP}${LEAD}${PROC_N}${TRAIL}`, "i");
|
|
330512
|
-
var STOP_PRONOUN_RE = /\b(?:stop|kill|terminate|shut\s*(?:it\s*|them\s*)?down|turn\s+(?:it|them)\s+off)\s+(?:it|that|this|them|those)\b/i;
|
|
330513
|
-
var STOP_BARE_RE = /^\s*(?:please\s+)?(?:shut\s*down|stop|kill|terminate)(?:\s+(?:now|please))?\s*[.!]*\s*$/i;
|
|
330514
|
-
var STOP_EXCLUDE_RE = /\bfrom\s+\w+ing\b|\busing\b|\bbeing\b/i;
|
|
330515
|
-
var START_V = String.raw`(?:start|run|launch|spin\s*up|boot(?:\s+up)?|fire\s*up|bring\s*up|kick\s*off|serve)`;
|
|
330516
|
-
var START_N = String.raw`(?:servers?|dev|watcher|backend|frontend|api|app|site|website|anvil|localhost|daemon|in\s+(?:the\s+)?(?:background|bg)|background|bg)`;
|
|
330517
|
-
var START_RE = new RegExp(String.raw`\b${START_V}\b${GAP}${LEAD}${START_N}${TRAIL}`, "i");
|
|
330518
|
-
var START_EXCLUDE_RE = /\b(?:tests?|specs?|lint|typecheck|build|compile|migrat\w*|install|seed|script|format|generate|check|review|read|look(?:ing)?|by\s+\w+ing)\b/i;
|
|
330519
|
-
var BACKGROUND_RE = /\bbackground\b|\bbg\b/i;
|
|
330520
|
-
var RESTART_RE = new RegExp(String.raw`\b(?:restart|reboot|relaunch|bounce|cycle)\b${GAP}${LEAD}(?:servers?|dev|watcher|process(?:es)?|backend|frontend|api|app|it|them|everything|p\d+)${TRAIL}`, "i");
|
|
330521
|
-
var RESTART_BARE_RE = /^\s*(?:please\s+)?(?:restart|reboot|relaunch)(?:\s+it)?\s*[.!]*\s*$/i;
|
|
330522
|
-
var LOGS_A_RE = new RegExp(String.raw`\b(?:logs?|output|stdout|stderr|tail|what'?s\s+(?:it\s+)?(?:doing|going\s+on|happening)|how(?:'s|\s+is))\b${GAP}${LEAD}(?:server|process|dev|backend|frontend|p\d+|it|anvil|watcher|bg|background)${TRAIL}`, "i");
|
|
330523
|
-
var LOGS_B_RE = /\b(?:is\s+(?:it|the\s+[\w-]+)\s+(?:up|running|working|alive|crashed|dead)|did\s+(?:it|the\s+[\w-]+)\s+(?:crash|start|fail|die|work))\b/i;
|
|
330524
|
-
var LIST_RE = new RegExp(String.raw`\b(?:what(?:'s|\s+is|\s+are)?|which|show|list|any|are\s+there|see)\b${GAP}${LEAD}(?:running|processes|servers|background|bg)${TRAIL}`, "i");
|
|
330525
|
-
var HEAD = "USER INTENT (detected from their message; it is a hint, use your judgment if it is wrong):";
|
|
330526
|
-
var STOP_NOTE = `${HEAD} the user wants to STOP background process(es). Call "bg_kill" now. "target" = the id from BACKGROUND PROCESSES that matches what they named (name, id or port), or "all" if they said everything/all/shut down. If they named nothing and exactly one is running, that one. This means the tracked process, never the machine, and never "shell". If they also asked for something else, do it after.`;
|
|
330527
|
-
var START_NOTE = `${HEAD} the user wants to START a server / long-running process. Call "bg_start" (never "shell"). If they gave a command, use it verbatim. Otherwise "read" package.json (or the project's equivalent) first, pick the dev/start script, then bg_start with the full runner command (e.g. "bun run dev"). Don't start a duplicate of something already running.`;
|
|
330528
|
-
var RESTART_NOTE = `${HEAD} the user wants to RESTART a background process. Call "bg_kill" on it first (its cmd is listed under BACKGROUND PROCESSES), then "bg_start" with the same cmd.`;
|
|
330529
|
-
var LOGS_NOTE = `${HEAD} the user is asking how a background process is doing. Call "bg_logs" for the real output and base your answer on it.`;
|
|
330530
|
-
var LIST_NOTE = `${HEAD} the user is asking what is running. Answer from BACKGROUND PROCESSES (or "bg_list").`;
|
|
330531
|
-
var NOTHING_RUNNING_NOTE = `${HEAD} the user wants to stop something, but no background process is running. Say so plainly. If they named a port, a process started outside this session can be found with "shell" lsof -ti :PORT.`;
|
|
330532
|
-
var NONE_TRACKED_NOTE = `${HEAD} no background processes are tracked in this session. If the user is asking about running servers, say none were started here.`;
|
|
330533
|
-
function detectBgIntent(task, running, known) {
|
|
330534
|
-
const t2 = task.slice(0, 4000);
|
|
330535
|
-
if (!t2.trim() || NEG_RE.test(t2))
|
|
330536
|
-
return null;
|
|
330537
|
-
const needs = [];
|
|
330538
|
-
const notes = [];
|
|
330539
|
-
if (RESTART_RE.test(t2) || RESTART_BARE_RE.test(t2)) {
|
|
330540
|
-
if (running > 0)
|
|
330541
|
-
needs.push("bg_kill");
|
|
330542
|
-
needs.push("bg_start");
|
|
330543
|
-
notes.push(RESTART_NOTE);
|
|
330544
|
-
} else {
|
|
330545
|
-
const stopM = STOP_RE.exec(t2);
|
|
330546
|
-
const stopHit = stopM !== null && !STOP_EXCLUDE_RE.test(stopM[0]) || STOP_PRONOUN_RE.test(t2) || STOP_BARE_RE.test(t2);
|
|
330547
|
-
if (stopHit) {
|
|
330548
|
-
if (running > 0) {
|
|
330549
|
-
needs.push("bg_kill");
|
|
330550
|
-
notes.push(STOP_NOTE);
|
|
330551
|
-
} else {
|
|
330552
|
-
notes.push(NOTHING_RUNNING_NOTE);
|
|
330553
|
-
}
|
|
330554
|
-
}
|
|
330555
|
-
const startM = START_RE.exec(t2);
|
|
330556
|
-
if (startM && (!START_EXCLUDE_RE.test(startM[0]) || BACKGROUND_RE.test(startM[0]))) {
|
|
330557
|
-
needs.push("bg_start");
|
|
330558
|
-
notes.push(START_NOTE);
|
|
330559
|
-
}
|
|
330560
|
-
}
|
|
330561
|
-
const strong = needs.includes("bg_start") || needs.includes("bg_kill");
|
|
330562
|
-
if (!strong && notes.length === 0) {
|
|
330563
|
-
const asksLogs = LOGS_A_RE.test(t2) || LOGS_B_RE.test(t2);
|
|
330564
|
-
if (known > 0) {
|
|
330565
|
-
if (asksLogs) {
|
|
330566
|
-
needs.push("bg_logs");
|
|
330567
|
-
notes.push(LOGS_NOTE);
|
|
330568
|
-
} else if (LIST_RE.test(t2)) {
|
|
330569
|
-
notes.push(LIST_NOTE);
|
|
330570
|
-
}
|
|
330571
|
-
} else if (asksLogs || LIST_RE.test(t2)) {
|
|
330572
|
-
notes.push(NONE_TRACKED_NOTE);
|
|
330573
|
-
}
|
|
330574
|
-
}
|
|
330575
|
-
if (notes.length === 0)
|
|
330576
|
-
return null;
|
|
330577
|
-
return { needs, strong, note: notes.join(`
|
|
330578
|
-
`) };
|
|
330579
|
-
}
|
|
330580
|
-
var LOG_CAP = 64000;
|
|
330581
|
-
var TAIL_CHAR_CAP = 6000;
|
|
330582
|
-
var MAX_RUNNING = 8;
|
|
330583
|
-
var MAX_FINISHED_KEPT = 10;
|
|
330584
|
-
var STARTUP_GRACE_MS = 4000;
|
|
330585
|
-
var KILL_GRACE_MS = 3000;
|
|
330586
|
-
var EXIT_DRAIN_MS = 120;
|
|
330587
|
-
var ANSI_RE = /\x1b\[[0-9;?]*[ -/]*[@-~]|\x1b\][^\x07]*\x07/g;
|
|
330588
|
-
var READY_RE = /\b(?:ready|listening|compiled|started|local:|on port)\b/i;
|
|
330589
|
-
var URL_RE = /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1?\]):\d+[^\s'")]*/gi;
|
|
330590
|
-
var PORT_RE = /\bport\s*:?\s*(\d{2,5})\b/gi;
|
|
330591
|
-
var ALL_RE = /^(?:all|everything|\*|all\s+(?:the\s+)?(?:servers|processes|background(?:\s+processes)?))$/;
|
|
330592
|
-
var GENERIC_RE = /^(?:the\s+|that\s+|this\s+|my\s+)?(?:dev\s+|web\s+|local\s+|background\s+)?(?:server|process|watcher|app|service|it)$/;
|
|
330593
|
-
var clip2 = (s2, n3) => s2.length > n3 ? `${s2.slice(0, n3 - 1)}\u2026` : s2;
|
|
330594
|
-
function fmtDur(ms) {
|
|
330595
|
-
const s2 = Math.max(0, Math.floor(ms / 1000));
|
|
330596
|
-
if (s2 < 60)
|
|
330597
|
-
return `${s2}s`;
|
|
330598
|
-
const m = Math.floor(s2 / 60);
|
|
330599
|
-
if (m < 60)
|
|
330600
|
-
return `${m}m${s2 % 60}s`;
|
|
330601
|
-
return `${Math.floor(m / 60)}h${m % 60}m`;
|
|
330602
|
-
}
|
|
330603
|
-
function deriveLabel(cmd) {
|
|
330604
|
-
const words = cmd.split(/\s+/).filter((w) => w && !/^\w+=/.test(w));
|
|
330605
|
-
return clip2(words.slice(0, 3).join(" "), 24);
|
|
330606
|
-
}
|
|
330607
|
-
var urlSuffix = (m) => m.urls.size ? ` \xB7 ${[...m.urls].join(", ")}` : "";
|
|
330608
|
-
|
|
330609
|
-
class ProcessManager {
|
|
330610
|
-
procs = new Map;
|
|
330611
|
-
seq = 0;
|
|
330612
|
-
constructor() {
|
|
330613
|
-
process.once("exit", () => this.killAllSync());
|
|
330614
|
-
}
|
|
330615
|
-
count() {
|
|
330616
|
-
return this.procs.size;
|
|
330617
|
-
}
|
|
330618
|
-
runningCount() {
|
|
330619
|
-
let n3 = 0;
|
|
330620
|
-
for (const p of this.procs.values())
|
|
330621
|
-
if (p.status === "running")
|
|
330622
|
-
n3++;
|
|
330623
|
-
return n3;
|
|
330624
|
-
}
|
|
330625
|
-
async start(opts) {
|
|
330626
|
-
const cmd = opts.cmd.trim().replace(/(?<!&)&\s*$/, "").trim();
|
|
330627
|
-
if (!cmd)
|
|
330628
|
-
throw new Error("empty command");
|
|
330629
|
-
const dup = [...this.procs.values()].find((p) => p.status === "running" && p.cmd === cmd && p.cwd === opts.cwd);
|
|
330630
|
-
if (dup) {
|
|
330631
|
-
return {
|
|
330632
|
-
kind: "duplicate",
|
|
330633
|
-
id: dup.id,
|
|
330634
|
-
report: `Already running as ${dup.id} "${dup.label}" (pid ${dup.pid}, up ${fmtDur(Date.now() - dup.startedAt)})` + `${urlSuffix(dup)}. Not starting a second copy; use bg_logs to inspect it or bg_kill to restart it.`
|
|
330635
|
-
};
|
|
330636
|
-
}
|
|
330637
|
-
if (this.runningCount() >= MAX_RUNNING) {
|
|
330638
|
-
throw new Error(`${MAX_RUNNING} background processes already running; kill one first`);
|
|
330639
|
-
}
|
|
330640
|
-
const id = `p${++this.seq}`;
|
|
330641
|
-
const label = (opts.label ?? "").trim().slice(0, 24) || deriveLabel(cmd);
|
|
330642
|
-
const child = spawn("bash", ["-lc", cmd], {
|
|
330643
|
-
cwd: opts.cwd,
|
|
330644
|
-
detached: process.platform !== "win32",
|
|
330645
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
330646
|
-
env: { ...process.env, FORCE_COLOR: "0", NO_COLOR: "1", BROWSER: "none" }
|
|
330647
|
-
});
|
|
330648
|
-
const m = {
|
|
330649
|
-
id,
|
|
330650
|
-
label,
|
|
330651
|
-
cmd,
|
|
330652
|
-
cwd: opts.cwd,
|
|
330653
|
-
child,
|
|
330654
|
-
pid: child.pid,
|
|
330655
|
-
status: "running",
|
|
330656
|
-
startedAt: Date.now(),
|
|
330657
|
-
endedAt: null,
|
|
330658
|
-
exitCode: null,
|
|
330659
|
-
signal: null,
|
|
330660
|
-
killRequested: false,
|
|
330661
|
-
noticed: false,
|
|
330662
|
-
buf: "",
|
|
330663
|
-
totalChars: 0,
|
|
330664
|
-
urls: new Set,
|
|
330665
|
-
events: new EventEmitter2
|
|
330666
|
-
};
|
|
330667
|
-
this.procs.set(id, m);
|
|
330668
|
-
const onData = (d2) => this.append(m, d2.toString("utf8"));
|
|
330669
|
-
child.stdout?.on("data", onData);
|
|
330670
|
-
child.stderr?.on("data", onData);
|
|
330671
|
-
child.on("error", (err) => {
|
|
330672
|
-
this.append(m, `[spawn error] ${err.message}
|
|
330673
|
-
`);
|
|
330674
|
-
this.finish(m, null, null);
|
|
330675
|
-
});
|
|
330676
|
-
let drain;
|
|
330677
|
-
child.on("exit", (code, signal) => {
|
|
330678
|
-
drain = setTimeout(() => this.finish(m, code, signal), EXIT_DRAIN_MS);
|
|
330679
|
-
});
|
|
330680
|
-
child.on("close", (code, signal) => {
|
|
330681
|
-
if (drain)
|
|
330682
|
-
clearTimeout(drain);
|
|
330683
|
-
this.finish(m, code, signal);
|
|
330684
|
-
});
|
|
330685
|
-
try {
|
|
330686
|
-
child.unref();
|
|
330687
|
-
for (const s2 of [child.stdout, child.stderr]) {
|
|
330688
|
-
s2?.unref?.();
|
|
330689
|
-
}
|
|
330690
|
-
} catch {}
|
|
330691
|
-
await this.waitForStartup(m, opts.graceMs ?? STARTUP_GRACE_MS);
|
|
330692
|
-
if (m.status !== "running") {
|
|
330693
|
-
return {
|
|
330694
|
-
kind: "crashed",
|
|
330695
|
-
id,
|
|
330696
|
-
report: `${id} "${label}" exited ${m.exitCode !== null ? `with code ${m.exitCode}` : `(${m.signal ?? "unknown"})`} ` + `${fmtDur((m.endedAt ?? Date.now()) - m.startedAt)} after starting.
|
|
330697
|
-
Output:
|
|
330698
|
-
${this.tail(m, 30)}`
|
|
330699
|
-
};
|
|
330700
|
-
}
|
|
330701
|
-
return {
|
|
330702
|
-
kind: "started",
|
|
330703
|
-
id,
|
|
330704
|
-
report: `Started ${id} "${label}" (pid ${m.pid}) in the background: ${cmd}
|
|
330705
|
-
` + `Status: running${urlSuffix(m)}
|
|
330706
|
-
Initial output:
|
|
330707
|
-
${this.tail(m, 25)}
|
|
330708
|
-
` + `It keeps running after this turn. Refer to it as "${id}" with bg_logs / bg_kill.`
|
|
330709
|
-
};
|
|
330710
|
-
}
|
|
330711
|
-
formatList() {
|
|
330712
|
-
if (this.procs.size === 0)
|
|
330713
|
-
return "No background processes.";
|
|
330714
|
-
return [...this.procs.values()].map((m) => this.line(m)).join(`
|
|
330715
|
-
`);
|
|
330716
|
-
}
|
|
330717
|
-
logs(ref, lines = 60) {
|
|
330718
|
-
const f2 = this.find(ref);
|
|
330719
|
-
if ("error" in f2)
|
|
330720
|
-
return f2.error;
|
|
330721
|
-
const m = f2.m;
|
|
330722
|
-
return `${m.id} "${m.label}" [${m.status}] \u2014 last ${lines} lines (${m.totalChars} chars emitted in total):
|
|
330723
|
-
${this.tail(m, lines)}`;
|
|
330724
|
-
}
|
|
330725
|
-
promptBlock() {
|
|
330726
|
-
if (this.procs.size === 0)
|
|
330727
|
-
return "";
|
|
330728
|
-
const all3 = [...this.procs.values()];
|
|
330729
|
-
const shown = [
|
|
330730
|
-
...all3.filter((p) => p.status === "running"),
|
|
330731
|
-
...all3.filter((p) => p.status !== "running").slice(-3)
|
|
330732
|
-
];
|
|
330733
|
-
return `BACKGROUND PROCESSES (tracked by the harness; they keep running between turns):
|
|
330734
|
-
` + shown.map((m) => this.line(m)).join(`
|
|
330735
|
-
`) + `
|
|
330736
|
-
Use bg_logs with the id to see output, bg_kill to stop one. Never start a duplicate of a running process.`;
|
|
330737
|
-
}
|
|
330738
|
-
consumeExitNotices() {
|
|
330739
|
-
const out = [];
|
|
330740
|
-
for (const m of this.procs.values()) {
|
|
330741
|
-
if (m.status === "running" || m.noticed)
|
|
330742
|
-
continue;
|
|
330743
|
-
m.noticed = true;
|
|
330744
|
-
let s2 = `Background ${m.id} "${m.label}" ${m.status === "failed" ? `crashed (exit ${m.exitCode ?? m.signal})` : `exited (code ${m.exitCode ?? m.signal})`}`;
|
|
330745
|
-
if (m.status === "failed") {
|
|
330746
|
-
const last = m.buf.split(`
|
|
330747
|
-
`).map((l) => l.trim()).filter(Boolean).slice(-2).map((l) => clip2(l, 160));
|
|
330748
|
-
if (last.length)
|
|
330749
|
-
s2 += `
|
|
330750
|
-
${last.join(`
|
|
330751
|
-
`)}`;
|
|
330752
|
-
}
|
|
330753
|
-
out.push(s2);
|
|
330754
|
-
}
|
|
330755
|
-
return out;
|
|
330756
|
-
}
|
|
330757
|
-
async kill(ref) {
|
|
330758
|
-
if (ALL_RE.test((ref ?? "").trim().toLowerCase()))
|
|
330759
|
-
return this.killAll();
|
|
330760
|
-
const f2 = this.find(ref);
|
|
330761
|
-
if ("error" in f2)
|
|
330762
|
-
return f2.error;
|
|
330763
|
-
const m = f2.m;
|
|
330764
|
-
if (m.status !== "running") {
|
|
330765
|
-
return `${m.id} "${m.label}" is not running (${m.status}, exit ${m.exitCode ?? m.signal}).`;
|
|
330766
|
-
}
|
|
330767
|
-
const up = fmtDur(Date.now() - m.startedAt);
|
|
330768
|
-
await this.terminate(m);
|
|
330769
|
-
return `Killed ${m.id} "${m.label}" (pid ${m.pid}, was up ${up}).`;
|
|
330770
|
-
}
|
|
330771
|
-
async killAll() {
|
|
330772
|
-
const running = [...this.procs.values()].filter((p) => p.status === "running");
|
|
330773
|
-
if (running.length === 0)
|
|
330774
|
-
return "No running background processes.";
|
|
330775
|
-
await Promise.all(running.map((m) => this.terminate(m)));
|
|
330776
|
-
return `Killed ${running.length} background process${running.length > 1 ? "es" : ""}: ${running.map((m) => m.id).join(", ")}.`;
|
|
330777
|
-
}
|
|
330778
|
-
line(m) {
|
|
330779
|
-
const when = m.status === "running" ? `up ${fmtDur(Date.now() - m.startedAt)}` : `exit ${m.exitCode ?? m.signal}, ${fmtDur(Date.now() - (m.endedAt ?? Date.now()))} ago`;
|
|
330780
|
-
return `${m.id} "${m.label}" [${m.status}] pid ${m.pid ?? "?"} \xB7 ${when}${urlSuffix(m)}
|
|
330781
|
-
$ ${clip2(m.cmd, 120)}`;
|
|
330782
|
-
}
|
|
330783
|
-
find(ref) {
|
|
330784
|
-
const all3 = [...this.procs.values()];
|
|
330785
|
-
if (all3.length === 0)
|
|
330786
|
-
return { error: "Failed: no background processes exist." };
|
|
330787
|
-
const known = all3.map((p) => `${p.id} "${p.label}" [${p.status}]`).join(", ");
|
|
330788
|
-
const running = all3.filter((p) => p.status === "running");
|
|
330789
|
-
const key = (ref ?? "").trim().replace(/^["']|["']$/g, "").toLowerCase();
|
|
330790
|
-
if (!key) {
|
|
330791
|
-
const only = running.length === 1 ? running[0] : all3.length === 1 ? all3[0] : undefined;
|
|
330792
|
-
return only ? { m: only } : { error: `Failed: specify a process id. Known: ${known}` };
|
|
330793
|
-
}
|
|
330794
|
-
const byId = all3.find((p) => p.id === key);
|
|
330795
|
-
if (byId)
|
|
330796
|
-
return { m: byId };
|
|
330797
|
-
const exact = [...all3].reverse().find((p) => p.label.toLowerCase() === key);
|
|
330798
|
-
if (exact)
|
|
330799
|
-
return { m: exact };
|
|
330800
|
-
const pick2 = (cands) => {
|
|
330801
|
-
if (cands.length === 0)
|
|
330802
|
-
return null;
|
|
330803
|
-
const live = cands.filter((p) => p.status === "running");
|
|
330804
|
-
const pool = live.length > 0 ? live : cands;
|
|
330805
|
-
if (pool.length === 1)
|
|
330806
|
-
return { m: pool[0] };
|
|
330807
|
-
return {
|
|
330808
|
-
error: `Failed: "${ref}" is ambiguous. Candidates: ${pool.map((p) => `${p.id} "${p.label}" [${p.status}]`).join(", ")}`
|
|
330809
|
-
};
|
|
330810
|
-
};
|
|
330811
|
-
const port = /^(?:.*?[:\s])?(\d{2,5})$/.exec(key)?.[1];
|
|
330812
|
-
if (port) {
|
|
330813
|
-
const portRe = new RegExp(`\\b${port}\\b`);
|
|
330814
|
-
const hit = pick2(all3.filter((p) => [...p.urls].some((u) => u.includes(`:${port}`)) || portRe.test(p.cmd)));
|
|
330815
|
-
if (hit)
|
|
330816
|
-
return hit;
|
|
330817
|
-
}
|
|
330818
|
-
const partial2 = pick2(all3.filter((p) => {
|
|
330819
|
-
const label = p.label.toLowerCase();
|
|
330820
|
-
return label.length >= 3 && (label.includes(key) || key.includes(label)) || p.cmd.toLowerCase().includes(key);
|
|
330821
|
-
}));
|
|
330822
|
-
if (partial2)
|
|
330823
|
-
return partial2;
|
|
330824
|
-
if (GENERIC_RE.test(key) && running.length === 1)
|
|
330825
|
-
return { m: running[0] };
|
|
330826
|
-
return { error: `Failed: no process "${ref}". Known: ${known}` };
|
|
330827
|
-
}
|
|
330828
|
-
append(m, raw) {
|
|
330829
|
-
const text = raw.replace(ANSI_RE, "").replace(/\r\n/g, `
|
|
330830
|
-
`).replace(/[^\n]*\r/g, "");
|
|
330831
|
-
if (!text)
|
|
330832
|
-
return;
|
|
330833
|
-
m.totalChars += text.length;
|
|
330834
|
-
m.buf += text;
|
|
330835
|
-
if (m.buf.length > LOG_CAP) {
|
|
330836
|
-
m.buf = m.buf.slice(-LOG_CAP);
|
|
330837
|
-
m.buf = m.buf.slice(m.buf.indexOf(`
|
|
330838
|
-
`) + 1);
|
|
330839
|
-
}
|
|
330840
|
-
for (const u of text.match(URL_RE) ?? []) {
|
|
330841
|
-
if (m.urls.size < 5)
|
|
330842
|
-
m.urls.add(u.replace(/[.,;/]+$/, ""));
|
|
330843
|
-
}
|
|
330844
|
-
for (const pm of text.matchAll(PORT_RE)) {
|
|
330845
|
-
const p = pm[1];
|
|
330846
|
-
if (p && m.urls.size < 5 && ![...m.urls].some((u) => u.includes(`:${p}`))) {
|
|
330847
|
-
m.urls.add(`http://localhost:${p}`);
|
|
330848
|
-
}
|
|
330849
|
-
}
|
|
330850
|
-
if (m.urls.size > 0 || READY_RE.test(text))
|
|
330851
|
-
m.events.emit("ready");
|
|
330852
|
-
}
|
|
330853
|
-
tail(m, lines) {
|
|
330854
|
-
const out = m.buf.split(`
|
|
330855
|
-
`).slice(-lines).join(`
|
|
330856
|
-
`).trim();
|
|
330857
|
-
if (!out)
|
|
330858
|
-
return "(no output yet)";
|
|
330859
|
-
return out.length > TAIL_CHAR_CAP ? `\u2026${out.slice(-TAIL_CHAR_CAP)}` : out;
|
|
330860
|
-
}
|
|
330861
|
-
finish(m, code, signal) {
|
|
330862
|
-
if (m.status !== "running")
|
|
330863
|
-
return;
|
|
330864
|
-
m.endedAt = Date.now();
|
|
330865
|
-
m.exitCode = code;
|
|
330866
|
-
m.signal = signal;
|
|
330867
|
-
m.status = m.killRequested ? "killed" : code === 0 ? "exited" : "failed";
|
|
330868
|
-
m.noticed = m.killRequested;
|
|
330869
|
-
m.events.emit("exit");
|
|
330870
|
-
this.prune();
|
|
330871
|
-
}
|
|
330872
|
-
prune() {
|
|
330873
|
-
const finished2 = [...this.procs.values()].filter((p) => p.status !== "running");
|
|
330874
|
-
for (const p of finished2.slice(0, Math.max(0, finished2.length - MAX_FINISHED_KEPT))) {
|
|
330875
|
-
this.procs.delete(p.id);
|
|
330876
|
-
}
|
|
330877
|
-
}
|
|
330878
|
-
waitForStartup(m, ms) {
|
|
330879
|
-
return new Promise((resolve) => {
|
|
330880
|
-
if (m.status !== "running")
|
|
330881
|
-
return resolve();
|
|
330882
|
-
let settle3;
|
|
330883
|
-
const done = () => {
|
|
330884
|
-
clearTimeout(timer);
|
|
330885
|
-
if (settle3)
|
|
330886
|
-
clearTimeout(settle3);
|
|
330887
|
-
m.events.off("ready", onReady);
|
|
330888
|
-
m.events.off("exit", done);
|
|
330889
|
-
resolve();
|
|
330890
|
-
};
|
|
330891
|
-
const onReady = () => {
|
|
330892
|
-
if (!settle3)
|
|
330893
|
-
settle3 = setTimeout(done, 300);
|
|
330894
|
-
};
|
|
330895
|
-
const timer = setTimeout(done, ms);
|
|
330896
|
-
m.events.on("ready", onReady);
|
|
330897
|
-
m.events.once("exit", done);
|
|
330898
|
-
});
|
|
330899
|
-
}
|
|
330900
|
-
waitForExit(m, ms) {
|
|
330901
|
-
return new Promise((resolve) => {
|
|
330902
|
-
if (m.status !== "running")
|
|
330903
|
-
return resolve(true);
|
|
330904
|
-
const onExit = () => {
|
|
330905
|
-
clearTimeout(timer);
|
|
330906
|
-
resolve(true);
|
|
330907
|
-
};
|
|
330908
|
-
const timer = setTimeout(() => {
|
|
330909
|
-
m.events.off("exit", onExit);
|
|
330910
|
-
resolve(false);
|
|
330911
|
-
}, ms);
|
|
330912
|
-
m.events.once("exit", onExit);
|
|
330913
|
-
});
|
|
330914
|
-
}
|
|
330915
|
-
signalGroup(m, sig) {
|
|
330916
|
-
try {
|
|
330917
|
-
if (process.platform !== "win32" && m.pid)
|
|
330918
|
-
process.kill(-m.pid, sig);
|
|
330919
|
-
else
|
|
330920
|
-
m.child.kill(sig);
|
|
330921
|
-
} catch {
|
|
330922
|
-
try {
|
|
330923
|
-
m.child.kill(sig);
|
|
330924
|
-
} catch {}
|
|
330925
|
-
}
|
|
330926
|
-
}
|
|
330927
|
-
async terminate(m) {
|
|
330928
|
-
m.killRequested = true;
|
|
330929
|
-
this.signalGroup(m, "SIGTERM");
|
|
330930
|
-
if (await this.waitForExit(m, KILL_GRACE_MS + EXIT_DRAIN_MS))
|
|
330931
|
-
return;
|
|
330932
|
-
this.signalGroup(m, "SIGKILL");
|
|
330933
|
-
await this.waitForExit(m, 1000 + EXIT_DRAIN_MS);
|
|
330934
|
-
}
|
|
330935
|
-
killAllSync() {
|
|
330936
|
-
for (const m of this.procs.values()) {
|
|
330937
|
-
if (m.status === "running") {
|
|
330938
|
-
m.killRequested = true;
|
|
330939
|
-
this.signalGroup(m, "SIGTERM");
|
|
330940
|
-
}
|
|
330941
|
-
}
|
|
330942
|
-
}
|
|
330943
|
-
}
|
|
330944
|
-
|
|
330945
331350
|
// src/tools/pipe.ts
|
|
330946
331351
|
var defaultIsRetryable = (err) => {
|
|
330947
331352
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -340389,268 +340794,6 @@ async function webSearch(query, budget, limit = DEFAULT_LIMIT) {
|
|
|
340389
340794
|
}
|
|
340390
340795
|
|
|
340391
340796
|
// src/tools/harness-orchestrator.ts
|
|
340392
|
-
var DEBUG_TRACE = Boolean(process.env.BASECODE_DEBUG);
|
|
340393
|
-
var CLEAR_LINE = "\r\x1B[2K";
|
|
340394
|
-
var WEB_SEARCH_FAILED_RE = /^\[Web search results[^\n]*\n(?:REJECTED|SEARCH FAILED):/;
|
|
340395
|
-
var TOOL_OUTPUT_CHAR_CAP = 16000;
|
|
340396
|
-
var SHELL_OUTPUT_BYTE_CAP = 256000;
|
|
340397
|
-
var RESULT_TAIL_CHARS = 8000;
|
|
340398
|
-
var INIT_PER_FILE_CHAR_CAP = 8000;
|
|
340399
|
-
var INIT_TOTAL_DUMP_CHAR_CAP = 200000;
|
|
340400
|
-
var HEAVY_KEEP_CHARS = 40000;
|
|
340401
|
-
var REPO_MAP_CHAR_CAP = 12000;
|
|
340402
|
-
var JEV_REPO_MAP_CHAR_CAP = 4000;
|
|
340403
|
-
var JEV_TASK_CHAR_CAP = 3000;
|
|
340404
|
-
var JEV_TAIL_MSGS = 3;
|
|
340405
|
-
var JEV_TAIL_CHARS = 1500;
|
|
340406
|
-
var PRIOR_MSG_CHAR_CAP = 2500;
|
|
340407
|
-
var SYNTHESIS_OVERVIEW_CHARS = 6000;
|
|
340408
|
-
var NOTICE_POLL_MS = 250;
|
|
340409
|
-
var VISION_MODEL_ID = "gemini-2.5-flash";
|
|
340410
|
-
var VISION_MAX_TOKENS = 1200;
|
|
340411
|
-
var VISION_TIMEOUT_MS = 60000;
|
|
340412
|
-
var VISION_MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
340413
|
-
var VISION_CONCURRENCY = 3;
|
|
340414
|
-
var VISION_SYSTEM_PROMPT = "You describe images for a coding agent that cannot see them. " + "Transcribe all visible text, code, error messages and numbers verbatim. " + "Then describe layout, UI elements, colours, diagrams and anything relevant to a software task. " + "Be factual and compact (under 400 words) and do not speculate about intent. " + "Reply with the description only.";
|
|
340415
|
-
var DECISION_JSON_START_RE = /\{\s*"(?:tool|reasoning|need_more_steps|requires_confirmation)"\s*:/;
|
|
340416
|
-
var DECISION_JSON_PREFIX_RE = /^\{\s*(?:"[a-z_]{0,24}"?\s*:?)?$/;
|
|
340417
|
-
var DECISION_JSON_FENCE_TAIL_RE = /```(?:json)?\s*$/i;
|
|
340418
|
-
var DECISION_JSON_PARTIAL_FENCE_RE = /(?:`{1,2}|```(?:j(?:s(?:o(?:n)?)?)?)?\s*)$/i;
|
|
340419
|
-
var DECISION_JSON_HOLD_CHARS = 40;
|
|
340420
|
-
var isDecisionShaped = (v) => isRecord2(v) && (("tool" in v) && (v.tool === null || typeof v.tool === "string") || ("reasoning" in v) && (("need_more_steps" in v) || ("needs_more_steps" in v) || ("requires_confirmation" in v)));
|
|
340421
|
-
function jsonObjectEnd(s2, start) {
|
|
340422
|
-
let depth = 0;
|
|
340423
|
-
let inStr = false;
|
|
340424
|
-
let esc2 = false;
|
|
340425
|
-
for (let i2 = start;i2 < s2.length; i2++) {
|
|
340426
|
-
const c2 = s2[i2];
|
|
340427
|
-
if (inStr) {
|
|
340428
|
-
if (esc2)
|
|
340429
|
-
esc2 = false;
|
|
340430
|
-
else if (c2 === "\\")
|
|
340431
|
-
esc2 = true;
|
|
340432
|
-
else if (c2 === '"')
|
|
340433
|
-
inStr = false;
|
|
340434
|
-
continue;
|
|
340435
|
-
}
|
|
340436
|
-
if (c2 === '"')
|
|
340437
|
-
inStr = true;
|
|
340438
|
-
else if (c2 === "{")
|
|
340439
|
-
depth++;
|
|
340440
|
-
else if (c2 === "}") {
|
|
340441
|
-
depth--;
|
|
340442
|
-
if (depth === 0)
|
|
340443
|
-
return i2 + 1;
|
|
340444
|
-
}
|
|
340445
|
-
}
|
|
340446
|
-
return -1;
|
|
340447
|
-
}
|
|
340448
|
-
function salvageReasoning(partial2) {
|
|
340449
|
-
const raw = /"reasoning"\s*:\s*"((?:[^"\\]|\\.)*)"/.exec(partial2)?.[1];
|
|
340450
|
-
if (raw === undefined)
|
|
340451
|
-
return "";
|
|
340452
|
-
try {
|
|
340453
|
-
return String(JSON.parse(`"${raw}"`)).trim();
|
|
340454
|
-
} catch {
|
|
340455
|
-
return raw.trim();
|
|
340456
|
-
}
|
|
340457
|
-
}
|
|
340458
|
-
function decisionPackageText(json3) {
|
|
340459
|
-
let parsed;
|
|
340460
|
-
try {
|
|
340461
|
-
parsed = JSON.parse(json3);
|
|
340462
|
-
} catch {
|
|
340463
|
-
return salvageReasoning(json3);
|
|
340464
|
-
}
|
|
340465
|
-
if (!isDecisionShaped(parsed))
|
|
340466
|
-
return json3;
|
|
340467
|
-
return typeof parsed.reasoning === "string" ? parsed.reasoning.trim() : "";
|
|
340468
|
-
}
|
|
340469
|
-
function decisionJsonHoldFrom(s2) {
|
|
340470
|
-
let hold = s2.length;
|
|
340471
|
-
const open3 = s2.lastIndexOf("{");
|
|
340472
|
-
if (open3 >= 0 && s2.length - open3 <= DECISION_JSON_HOLD_CHARS && DECISION_JSON_PREFIX_RE.test(s2.slice(open3))) {
|
|
340473
|
-
hold = open3;
|
|
340474
|
-
}
|
|
340475
|
-
const fence = DECISION_JSON_PARTIAL_FENCE_RE.exec(s2.slice(0, hold));
|
|
340476
|
-
if (fence && fence.index >= hold - DECISION_JSON_HOLD_CHARS)
|
|
340477
|
-
hold = fence.index;
|
|
340478
|
-
return hold;
|
|
340479
|
-
}
|
|
340480
|
-
async function* guardDecisionJson(source) {
|
|
340481
|
-
let pending2 = "";
|
|
340482
|
-
let inPackage = false;
|
|
340483
|
-
let dropCloseFence = false;
|
|
340484
|
-
for await (const chunk of source) {
|
|
340485
|
-
const d2 = chunk.delta;
|
|
340486
|
-
if (!d2)
|
|
340487
|
-
continue;
|
|
340488
|
-
pending2 += d2;
|
|
340489
|
-
for (;; ) {
|
|
340490
|
-
if (inPackage) {
|
|
340491
|
-
const end = jsonObjectEnd(pending2, 0);
|
|
340492
|
-
if (end < 0)
|
|
340493
|
-
break;
|
|
340494
|
-
const text = decisionPackageText(pending2.slice(0, end));
|
|
340495
|
-
pending2 = pending2.slice(end);
|
|
340496
|
-
inPackage = false;
|
|
340497
|
-
if (text)
|
|
340498
|
-
yield { delta: text };
|
|
340499
|
-
continue;
|
|
340500
|
-
}
|
|
340501
|
-
if (dropCloseFence) {
|
|
340502
|
-
const t2 = pending2.trimStart();
|
|
340503
|
-
if (t2 === "" || /^`{1,2}$/.test(t2))
|
|
340504
|
-
break;
|
|
340505
|
-
dropCloseFence = false;
|
|
340506
|
-
if (t2.startsWith("```"))
|
|
340507
|
-
pending2 = t2.slice(3);
|
|
340508
|
-
}
|
|
340509
|
-
const hit = DECISION_JSON_START_RE.exec(pending2);
|
|
340510
|
-
if (hit) {
|
|
340511
|
-
const head = pending2.slice(0, hit.index);
|
|
340512
|
-
const fenced = DECISION_JSON_FENCE_TAIL_RE.test(head);
|
|
340513
|
-
pending2 = pending2.slice(hit.index);
|
|
340514
|
-
inPackage = true;
|
|
340515
|
-
dropCloseFence = fenced;
|
|
340516
|
-
const keep = fenced ? head.replace(DECISION_JSON_FENCE_TAIL_RE, "") : head;
|
|
340517
|
-
if (keep)
|
|
340518
|
-
yield { delta: keep };
|
|
340519
|
-
continue;
|
|
340520
|
-
}
|
|
340521
|
-
const hold = decisionJsonHoldFrom(pending2);
|
|
340522
|
-
const out = pending2.slice(0, hold);
|
|
340523
|
-
pending2 = pending2.slice(hold);
|
|
340524
|
-
if (out)
|
|
340525
|
-
yield { delta: out };
|
|
340526
|
-
break;
|
|
340527
|
-
}
|
|
340528
|
-
}
|
|
340529
|
-
if (inPackage) {
|
|
340530
|
-
const text = salvageReasoning(pending2);
|
|
340531
|
-
if (text)
|
|
340532
|
-
yield { delta: text };
|
|
340533
|
-
} else if (dropCloseFence && /^[\s`]*$/.test(pending2)) {} else if (pending2) {
|
|
340534
|
-
yield { delta: pending2 };
|
|
340535
|
-
}
|
|
340536
|
-
}
|
|
340537
|
-
var BASECODE_POINTER = "BASECODE.md exists at the repo root (architecture, conventions, commands) but is NOT inlined, to save tokens. " + 'Read it once with the "read" tool if the task needs project background; skip it for self-contained tasks.';
|
|
340538
|
-
var NO_BASECODE_NOTE = "No BASECODE.md yet (the user can run /init to generate one).";
|
|
340539
|
-
var GIT_INTENT = /\b(git|commit|committed|merge|push|pull|rebase|branch|branches|checkout|stash|tag|cherry-?pick|reset|revert|amend)\b/i;
|
|
340540
|
-
var NO_INLINE_CONTENT_RULE = {
|
|
340541
|
-
role: "system",
|
|
340542
|
-
content: `OUTPUT REMINDER: "write"/"append": set "content" to "" (the file is generated in a separate step; never inline source). ` + `"edit": "target" and "replacement" are REQUIRED, copied verbatim from file contents shown in context, kept small. ` + `"shell": "cmd" is REQUIRED (the literal command). "web_search": "content" is REQUIRED \u2014 a short, specific query ` + `(3-10 words) with no secrets, local paths or code; other fields null. "reasoning": ONE short sentence. ` + `"bg_start": "cmd" REQUIRED (the literal command), "target" = optional short name; "bg_logs"/"bg_kill": "target" = ` + `process id, name or port ("bg_kill" accepts "all"); "bg_list": nothing else. Never run servers/watchers via "shell". ` + `Your whole JSON must fit in ${DECISION_MAX_TOKENS} tokens or the step is wasted. ` + `For project background read BASECODE.md (not inlined); if none exists, tell the user to run /init.`
|
|
340543
|
-
};
|
|
340544
|
-
var BG_RULES_MESSAGE = { role: "system", content: BG_TOOL_RULES };
|
|
340545
|
-
var MAX_ASKS_PER_TASK = 2;
|
|
340546
|
-
var ASK_MAX_QUESTIONS = 4;
|
|
340547
|
-
var ASK_MAX_OPTIONS = 4;
|
|
340548
|
-
var ASK_MAX_QUESTION_CHARS = 400;
|
|
340549
|
-
var ASK_MAX_OPTION_CHARS = 120;
|
|
340550
|
-
var PENDING_BLOCKED_TOOLS = new Set(["write", "edit", "append", "shell", "bg_start"]);
|
|
340551
|
-
var QUESTION_CONTEXT_RE = /^\[(?:USER ANSWER|QUESTIONS ASKED)\]|^\[System\] QUESTIONS:/;
|
|
340552
|
-
var QUESTION_BLOCK_RE = /\? Questions for you \(\d+\):\n(?: {2}\d+\. .*\n)+\n?/g;
|
|
340553
|
-
var clipText = (s2, n3) => s2.length > n3 ? `${s2.slice(0, n3 - 1)}\u2026` : s2;
|
|
340554
|
-
function cleanAskOptions(v) {
|
|
340555
|
-
if (!Array.isArray(v))
|
|
340556
|
-
return [];
|
|
340557
|
-
const seen = new Set;
|
|
340558
|
-
const out = [];
|
|
340559
|
-
for (const o2 of v) {
|
|
340560
|
-
if (typeof o2 !== "string")
|
|
340561
|
-
continue;
|
|
340562
|
-
const t2 = o2.replace(/\s+/g, " ").trim().slice(0, ASK_MAX_OPTION_CHARS);
|
|
340563
|
-
const key = t2.toLowerCase();
|
|
340564
|
-
if (!t2 || seen.has(key))
|
|
340565
|
-
continue;
|
|
340566
|
-
seen.add(key);
|
|
340567
|
-
out.push(t2);
|
|
340568
|
-
if (out.length >= ASK_MAX_OPTIONS)
|
|
340569
|
-
break;
|
|
340570
|
-
}
|
|
340571
|
-
return out;
|
|
340572
|
-
}
|
|
340573
|
-
function parseAskDecision(parsed) {
|
|
340574
|
-
if (!isRecord2(parsed) || parsed.tool !== "ask_user_question")
|
|
340575
|
-
return null;
|
|
340576
|
-
const rawItems = [];
|
|
340577
|
-
if (Array.isArray(parsed.questions))
|
|
340578
|
-
rawItems.push(...parsed.questions);
|
|
340579
|
-
else if (typeof parsed.question === "string") {
|
|
340580
|
-
rawItems.push({ question: parsed.question, options: parsed.options });
|
|
340581
|
-
}
|
|
340582
|
-
const questions = [];
|
|
340583
|
-
const seen = new Set;
|
|
340584
|
-
for (const item of rawItems) {
|
|
340585
|
-
let text = "";
|
|
340586
|
-
let options = [];
|
|
340587
|
-
if (typeof item === "string") {
|
|
340588
|
-
text = item;
|
|
340589
|
-
} else if (isRecord2(item) && typeof item.question === "string") {
|
|
340590
|
-
text = item.question;
|
|
340591
|
-
options = cleanAskOptions(item.options);
|
|
340592
|
-
}
|
|
340593
|
-
text = text.replace(/\s+/g, " ").trim().slice(0, ASK_MAX_QUESTION_CHARS);
|
|
340594
|
-
const key = text.toLowerCase();
|
|
340595
|
-
if (!text || seen.has(key))
|
|
340596
|
-
continue;
|
|
340597
|
-
seen.add(key);
|
|
340598
|
-
questions.push({ question: text, options });
|
|
340599
|
-
if (questions.length >= ASK_MAX_QUESTIONS)
|
|
340600
|
-
break;
|
|
340601
|
-
}
|
|
340602
|
-
return {
|
|
340603
|
-
questions,
|
|
340604
|
-
ignore: parsed.ignore === true,
|
|
340605
|
-
reasoning: typeof parsed.reasoning === "string" ? parsed.reasoning.slice(0, 1000) : ""
|
|
340606
|
-
};
|
|
340607
|
-
}
|
|
340608
|
-
var QUESTIONS_RULE = {
|
|
340609
|
-
role: "system",
|
|
340610
|
-
content: `ASK TOOL: to ask the user something, return {"tool":"ask_user_question","questions":[{"question":"...","options":["...","..."]}],` + `"reasoning":"...","need_more_steps":true}. 1-${ASK_MAX_QUESTIONS} questions, each self-contained, with 2-${ASK_MAX_OPTIONS} mutually ` + `exclusive options (recommended first) or an empty options array for an open question. A single {"question","options"} pair is also accepted. ` + `All other fields null. The questions are shown to the user as a questionnaire; after asking you MUST stop and wait. ` + `Never ask while questions are pending, never re-ask something already answered (the answers are listed under "USER ANSWERS" in the task), ` + `at most ${MAX_ASKS_PER_TASK} asks per task. To drop the pending questions because the user's message makes them moot, return ` + `{"tool":"ask_user_question","ignore":true,"reasoning":"..."}.`
|
|
340611
|
-
};
|
|
340612
|
-
var JEV_BYPASS = {
|
|
340613
|
-
unavailable: true,
|
|
340614
|
-
tool: "none",
|
|
340615
|
-
needMoreSteps: false,
|
|
340616
|
-
confidence: 0,
|
|
340617
|
-
reasoning: ""
|
|
340618
|
-
};
|
|
340619
|
-
var MEMORY_CHAR_CAP = 6000;
|
|
340620
|
-
var MEMORY_MAX_NEW_ENTRIES = 3;
|
|
340621
|
-
var MEMORY_MAX_ENTRY_CHARS = 250;
|
|
340622
|
-
var MEMORY_HEADER = `# BASECODE MEMORY
|
|
340623
|
-
Project-specific preferences and fixes learned from past sessions. Newest last.
|
|
340624
|
-
`;
|
|
340625
|
-
var MEMORY_SIGNAL = /\b(always|never|don'?t|do not|instead|prefer|from now on|remember|actually|wrong|incorrect|not what i|should(?:n'?t)?|stop (?:using|doing)|use \S+ (?:not|instead))\b/i;
|
|
340626
|
-
var MEMORY_SECRET_PATTERN = /(?:sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{20,}|api[_-]?key\s*[:=]|password\s*[:=]|secret\s*[:=]|token\s*[:=]|-----BEGIN)/i;
|
|
340627
|
-
var MEMORY_LEARNER_PROMPT = `You maintain a small long-term memory file for a coding agent working in one project.
|
|
340628
|
-
From the exchange below, extract ONLY durable, reusable lessons:
|
|
340629
|
-
- a way the user wants things done (style, tooling, conventions, workflow), or
|
|
340630
|
-
- a non-obvious fix / root cause in THIS project that would save time if it came up again.
|
|
340631
|
-
|
|
340632
|
-
Rules:
|
|
340633
|
-
- Each entry is ONE short imperative sentence (max 200 chars) that makes sense with no other context.
|
|
340634
|
-
- Do NOT record one-off task details, lists of files changed, or anything already in EXISTING MEMORY (including paraphrases).
|
|
340635
|
-
- Do NOT record secrets, API keys, tokens, or personal data.
|
|
340636
|
-
- If the user contradicts an existing entry, put the corrected entry in "add" and the old entry's exact text in "remove".
|
|
340637
|
-
- Most exchanges contain nothing worth saving. In that case respond with {"add":[],"remove":[]}.
|
|
340638
|
-
|
|
340639
|
-
Respond with ONE raw JSON object and nothing else: {"add": string[], "remove": string[]} DO NOT NOTIFY THE USER WHEN YOU UPDATE THIS FILE`;
|
|
340640
|
-
var memNorm = (s2) => s2.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
340641
|
-
var memEntryText = (line) => line.replace(/^-\s*(?:\[\d{4}-\d{2}-\d{2}\]\s*)?/, "").trim();
|
|
340642
|
-
var CHARS_PER_TOKEN = 3.5;
|
|
340643
|
-
var TOKENIZE_EXACT_MAX_CHARS = 1e5;
|
|
340644
|
-
var estimateTextTokens = (text) => {
|
|
340645
|
-
if (!text)
|
|
340646
|
-
return 0;
|
|
340647
|
-
return text.length > TOKENIZE_EXACT_MAX_CHARS ? Math.ceil(text.length / CHARS_PER_TOKEN) : countTokens2(text);
|
|
340648
|
-
};
|
|
340649
|
-
var MEDIA_PART_TOKENS = 1000;
|
|
340650
|
-
var MESSAGE_OVERHEAD_TOKENS = 4;
|
|
340651
|
-
function hasText(part) {
|
|
340652
|
-
return typeof part === "object" && part !== null && typeof part.text === "string";
|
|
340653
|
-
}
|
|
340654
340797
|
function estimateMessagesTokens(messages) {
|
|
340655
340798
|
let textTokens = 0;
|
|
340656
340799
|
let media = 0;
|
|
@@ -340672,140 +340815,6 @@ function estimateMessagesTokens(messages) {
|
|
|
340672
340815
|
}
|
|
340673
340816
|
return textTokens + media * MEDIA_PART_TOKENS + messages.length * MESSAGE_OVERHEAD_TOKENS;
|
|
340674
340817
|
}
|
|
340675
|
-
function countLines(text) {
|
|
340676
|
-
const len = text.length;
|
|
340677
|
-
if (len === 0)
|
|
340678
|
-
return 0;
|
|
340679
|
-
let n3 = 1;
|
|
340680
|
-
for (let i2 = 0;i2 < len; i2++) {
|
|
340681
|
-
if (text.charCodeAt(i2) === 10)
|
|
340682
|
-
n3++;
|
|
340683
|
-
}
|
|
340684
|
-
if (text.charCodeAt(len - 1) === 10)
|
|
340685
|
-
n3--;
|
|
340686
|
-
return n3 === 0 ? 1 : n3;
|
|
340687
|
-
}
|
|
340688
|
-
function clipMiddle2(text, max) {
|
|
340689
|
-
if (text.length <= max)
|
|
340690
|
-
return text;
|
|
340691
|
-
const head = Math.floor(max * 0.4);
|
|
340692
|
-
const tail = max - head;
|
|
340693
|
-
return `${text.slice(0, head)}
|
|
340694
|
-
\u2026[clipped ${text.length - max} chars]\u2026
|
|
340695
|
-
${text.slice(-tail)}`;
|
|
340696
|
-
}
|
|
340697
|
-
var repoMapMemo = new Map;
|
|
340698
|
-
function compactRepoMap(repoMap, cap) {
|
|
340699
|
-
const hit = repoMapMemo.get(cap);
|
|
340700
|
-
if (hit && hit.src === repoMap)
|
|
340701
|
-
return hit.out;
|
|
340702
|
-
let out;
|
|
340703
|
-
try {
|
|
340704
|
-
const paths = flattenRepoMap(repoMap);
|
|
340705
|
-
if (paths.length === 0) {
|
|
340706
|
-
out = repoMap.slice(0, cap);
|
|
340707
|
-
} else {
|
|
340708
|
-
const byDir = new Map;
|
|
340709
|
-
for (const p of paths) {
|
|
340710
|
-
const d2 = dirname(p);
|
|
340711
|
-
const base = d2 === "." ? p : p.slice(d2.length + 1);
|
|
340712
|
-
const list = byDir.get(d2);
|
|
340713
|
-
if (list)
|
|
340714
|
-
list.push(base);
|
|
340715
|
-
else
|
|
340716
|
-
byDir.set(d2, [base]);
|
|
340717
|
-
}
|
|
340718
|
-
const lines = [];
|
|
340719
|
-
let total = 0;
|
|
340720
|
-
let listed = 0;
|
|
340721
|
-
for (const [d2, files] of [...byDir].sort(([a2], [b]) => a2 < b ? -1 : a2 > b ? 1 : 0)) {
|
|
340722
|
-
const line = `${d2 === "." ? "./" : `${d2}/`} ${files.join(" ")}`;
|
|
340723
|
-
if (total + line.length > cap)
|
|
340724
|
-
break;
|
|
340725
|
-
lines.push(line);
|
|
340726
|
-
total += line.length + 1;
|
|
340727
|
-
listed += files.length;
|
|
340728
|
-
}
|
|
340729
|
-
const omitted = paths.length - listed;
|
|
340730
|
-
out = lines.join(`
|
|
340731
|
-
`) + (omitted > 0 ? `
|
|
340732
|
-
(+${omitted} more files not listed; use find/grep to explore)` : "");
|
|
340733
|
-
}
|
|
340734
|
-
} catch {
|
|
340735
|
-
out = repoMap.slice(0, cap);
|
|
340736
|
-
}
|
|
340737
|
-
repoMapMemo.set(cap, { src: repoMap, out });
|
|
340738
|
-
return out;
|
|
340739
|
-
}
|
|
340740
|
-
async function readCappedStream(stream4, cap = SHELL_OUTPUT_BYTE_CAP) {
|
|
340741
|
-
if (!stream4)
|
|
340742
|
-
return "";
|
|
340743
|
-
const reader = stream4.getReader();
|
|
340744
|
-
const decoder3 = new TextDecoder;
|
|
340745
|
-
const parts = [];
|
|
340746
|
-
let size = 0;
|
|
340747
|
-
let truncated = false;
|
|
340748
|
-
try {
|
|
340749
|
-
while (size < cap) {
|
|
340750
|
-
const { done, value } = await reader.read();
|
|
340751
|
-
if (done)
|
|
340752
|
-
break;
|
|
340753
|
-
if (!value)
|
|
340754
|
-
continue;
|
|
340755
|
-
size += value.byteLength;
|
|
340756
|
-
parts.push(decoder3.decode(value, { stream: true }));
|
|
340757
|
-
}
|
|
340758
|
-
if (size >= cap)
|
|
340759
|
-
truncated = true;
|
|
340760
|
-
} catch {} finally {
|
|
340761
|
-
try {
|
|
340762
|
-
await reader.cancel();
|
|
340763
|
-
} catch {}
|
|
340764
|
-
}
|
|
340765
|
-
let out = parts.join("");
|
|
340766
|
-
parts.length = 0;
|
|
340767
|
-
if (out.length > cap)
|
|
340768
|
-
out = out.slice(0, cap);
|
|
340769
|
-
return truncated ? `${out}
|
|
340770
|
-
\u2026[output truncated at ${cap} bytes]` : out;
|
|
340771
|
-
}
|
|
340772
|
-
function buildAttachmentPart(provider, mimeType, output2) {
|
|
340773
|
-
const key = provider.toUpperCase();
|
|
340774
|
-
const isText = mimeType.startsWith("text/");
|
|
340775
|
-
if (key === "GOOGLE") {
|
|
340776
|
-
return isText ? { text: output2 } : { inlineData: { mimeType, data: output2 } };
|
|
340777
|
-
}
|
|
340778
|
-
if (key === "ANTHROPIC") {
|
|
340779
|
-
return {
|
|
340780
|
-
type: mimeType.startsWith("image/") ? "image" : "document",
|
|
340781
|
-
source: { type: "base64", media_type: mimeType, data: output2 }
|
|
340782
|
-
};
|
|
340783
|
-
}
|
|
340784
|
-
if (key === "OPENAI" || key === "GROQ" || key === "VENICE") {
|
|
340785
|
-
return isText ? { type: "text", text: output2 } : { type: "image_url", image_url: { url: `data:${mimeType};base64,${output2}` } };
|
|
340786
|
-
}
|
|
340787
|
-
return { type: "text", text: output2 };
|
|
340788
|
-
}
|
|
340789
|
-
function foldMediaParts(messages) {
|
|
340790
|
-
let hasMedia = false;
|
|
340791
|
-
for (const m of messages) {
|
|
340792
|
-
if (m && Array.isArray(m.mediaParts) && m.mediaParts.length > 0) {
|
|
340793
|
-
hasMedia = true;
|
|
340794
|
-
break;
|
|
340795
|
-
}
|
|
340796
|
-
}
|
|
340797
|
-
if (!hasMedia)
|
|
340798
|
-
return messages;
|
|
340799
|
-
return messages.map((m) => {
|
|
340800
|
-
if (!m || !Array.isArray(m.mediaParts) || m.mediaParts.length === 0)
|
|
340801
|
-
return m;
|
|
340802
|
-
const { mediaParts, ...rest } = m;
|
|
340803
|
-
const text = typeof rest.content === "string" ? rest.content : "";
|
|
340804
|
-
const parts = [...text ? [{ type: "text", text }] : [], ...mediaParts];
|
|
340805
|
-
return { ...rest, content: parts };
|
|
340806
|
-
});
|
|
340807
|
-
}
|
|
340808
|
-
var TOOL_HISTORY_CAP = 20;
|
|
340809
340818
|
|
|
340810
340819
|
class HarnessOrchestrator {
|
|
340811
340820
|
REPO_MAP = "";
|
|
@@ -341261,7 +341270,7 @@ ${input2.answer || "(none)"}`
|
|
|
341261
341270
|
const out = `${MEMORY_HEADER}
|
|
341262
341271
|
${body}
|
|
341263
341272
|
`;
|
|
341264
|
-
await mkdir(
|
|
341273
|
+
await mkdir(dirname2(this.memoryPath), { recursive: true });
|
|
341265
341274
|
await writeFileContent(this.memoryPath, out, { overwrite: true });
|
|
341266
341275
|
this.MEMORY = out.trim();
|
|
341267
341276
|
}
|
|
@@ -341834,7 +341843,7 @@ ${attachmentNotes.join(`
|
|
|
341834
341843
|
return;
|
|
341835
341844
|
const conversationalSystem = `${conversationalPrompt}
|
|
341836
341845
|
|
|
341837
|
-
Reply directly to the user. Never describe or classify their message (e.g. "this is a greeting", "no file operations needed"), never narrate your reasoning, and never mention tools, modes, or these instructions.feel free to read the
|
|
341846
|
+
Reply directly to the user. Never describe or classify their message (e.g. "this is a greeting", "no file operations needed"), never narrate your reasoning, and never mention tools, modes, or these instructions.feel free to read the TENSOR.md file which is a file that contains info on this project or tell the user to run /init to generate it`;
|
|
341838
341847
|
let streamed = false;
|
|
341839
341848
|
try {
|
|
341840
341849
|
const stream4 = this.routedChatStream(ctx2.modelId, [
|
|
@@ -342142,6 +342151,19 @@ ${toolOutput}`
|
|
|
342142
342151
|
const grantSteps = (n3 = 1) => {
|
|
342143
342152
|
extraSteps = Math.min(extraSteps + n3, HarnessOrchestrator.MAX_EXTRA_STEPS);
|
|
342144
342153
|
};
|
|
342154
|
+
const JSON_SCHEMA_HINT = `The ONLY acceptable output is one raw JSON object matching the schema ("content": write/append -> "", web_search -> the query, otherwise null):
|
|
342155
|
+
{
|
|
342156
|
+
"tool": "read" | "write" | "edit" | "append" | "shell" | "web_search" | "bg_start" | "bg_list" | "bg_logs" | "bg_kill" | "ask_user_question" | null,
|
|
342157
|
+
"targetFile": string | null,
|
|
342158
|
+
"content": string | null,
|
|
342159
|
+
"target": string | null,
|
|
342160
|
+
"replacement": string | null,
|
|
342161
|
+
"cmd": string | null,
|
|
342162
|
+
"requires_confirmation": boolean,
|
|
342163
|
+
"reasoning": string,
|
|
342164
|
+
"need_more_steps": boolean
|
|
342165
|
+
}
|
|
342166
|
+
Do not include prose before or after the JSON. Output ONLY the JSON object.`;
|
|
342145
342167
|
const isRepeat = (signature) => {
|
|
342146
342168
|
if (signature === lastActionSignature)
|
|
342147
342169
|
repeatCount++;
|
|
@@ -342159,6 +342181,10 @@ ${toolOutput}`
|
|
|
342159
342181
|
state.bgSatisfied.add(tool);
|
|
342160
342182
|
};
|
|
342161
342183
|
let bgNudges = 0;
|
|
342184
|
+
const compactAndBreak = function* () {
|
|
342185
|
+
this.compactContext(ctx, state);
|
|
342186
|
+
return;
|
|
342187
|
+
}.bind(this);
|
|
342162
342188
|
for (let step = 1;step <= maxSteps + extraSteps; step++) {
|
|
342163
342189
|
const budget = maxSteps + extraSteps;
|
|
342164
342190
|
const stepsRemaining = budget - step + 1;
|
|
@@ -342185,7 +342211,7 @@ ${toolOutput}`
|
|
|
342185
342211
|
});
|
|
342186
342212
|
await this.refreshRepoMapIfDirty();
|
|
342187
342213
|
const decisionSystemPrompt = this.buildDecisionPrompt(ctx.gitDetail);
|
|
342188
|
-
const progressSummary = ctx.completedActions.length > 0 ? ctx.completedActions.map((a2) => `- ${a2}`).join(`
|
|
342214
|
+
const progressSummary = ctx.completedActions.length > 0 ? ctx.completedActions.map((a2) => `- ${a2.slice(0, 160)}`).join(`
|
|
342189
342215
|
`) : "";
|
|
342190
342216
|
const jevTail = this.jevTail(ctx, lastAgentProse);
|
|
342191
342217
|
const jevChoice = bgPending() || !this.isAnswered ? JEV_BYPASS : await this.classify(ctx, {
|
|
@@ -342280,32 +342306,21 @@ Please try a different model(retry in 30 seconds) or check with your provider.
|
|
|
342280
342306
|
|
|
342281
342307
|
`
|
|
342282
342308
|
};
|
|
342309
|
+
this.compactContext(ctx, state);
|
|
342283
342310
|
break;
|
|
342284
342311
|
}
|
|
342285
342312
|
const { hint } = await classifyMalformedOutput(res.content);
|
|
342286
342313
|
const toolUseBlockCount = (res.content.match(/<tool_call\b|<tool_use\b/gi) ?? []).length;
|
|
342287
342314
|
ctx.contextMessages.push({
|
|
342288
342315
|
role: "user",
|
|
342289
|
-
content: `[System Corrector] Your last response failed to parse as the required JSON \u2014 classified as: ${hint} ` + `YOU DO HAVE TOOLS \u2014 do not claim otherwise. Do NOT use XML tags, angle brackets ("<" / ">"), native ` + `function blocks, or any markup syntax. Never emit "<tool_call>", "<function=", "<parameter=", "<tool_use>", or "<file_path>".` + (toolUseBlockCount > 1 ? ` You also attempted to batch ${toolUseBlockCount} tool calls in one response \u2014 you get exactly ONE tool call per turn.` : "") + `
|
|
342290
|
-
{
|
|
342291
|
-
"tool": "read" | "write" | "edit" | "append" | "shell" | "web_search" | "bg_start" | "bg_list" | "bg_logs" | "bg_kill" | "ask_user_question" | null,
|
|
342292
|
-
"targetFile": string | null,
|
|
342293
|
-
"content": string | null,
|
|
342294
|
-
"target": string | null,
|
|
342295
|
-
"replacement": string | null,
|
|
342296
|
-
"cmd": string | null,
|
|
342297
|
-
"requires_confirmation": boolean,
|
|
342298
|
-
"reasoning": string,
|
|
342299
|
-
"need_more_steps": boolean
|
|
342300
|
-
}
|
|
342301
|
-
Do not include prose before or after the JSON. Output ONLY the JSON object.` + (jevTool ? ` Remember: "tool" must be exactly "${jevTool}".` : "")
|
|
342316
|
+
content: `[System Corrector] Your last response failed to parse as the required JSON \u2014 classified as: ${hint} ` + `YOU DO HAVE TOOLS \u2014 do not claim otherwise. Do NOT use XML tags, angle brackets ("<" / ">"), native ` + `function blocks, or any markup syntax. Never emit "<tool_call>", "<function=", "<parameter=", "<tool_use>", or "<file_path>".` + (toolUseBlockCount > 1 ? ` You also attempted to batch ${toolUseBlockCount} tool calls in one response \u2014 you get exactly ONE tool call per turn.` : "") + ` ${JSON_SCHEMA_HINT}` + (jevTool ? ` Remember: "tool" must be exactly "${jevTool}".` : "")
|
|
342302
342317
|
});
|
|
342303
342318
|
continue;
|
|
342304
342319
|
}
|
|
342305
342320
|
const bgParsed = isRecord2(parsed) ? parseBgDecision(parsed) : null;
|
|
342306
342321
|
if (bgParsed) {
|
|
342307
342322
|
jsonFailureCount = 0;
|
|
342308
|
-
lastAgentProse = bgParsed.reasoning;
|
|
342323
|
+
lastAgentProse = (bgParsed.reasoning ?? "").slice(0, 1000);
|
|
342309
342324
|
if (!this.isAnswered && bgParsed.tool === "bg_start") {
|
|
342310
342325
|
const held = this.recordToolCall(step, {
|
|
342311
342326
|
tool: bgParsed.tool,
|
|
@@ -342321,22 +342336,27 @@ Do not include prose before or after the JSON. Output ONLY the JSON object.` + (
|
|
|
342321
342336
|
role: "user",
|
|
342322
342337
|
content: `[System] QUESTIONS: unanswered questions are pending, so write/edit/append/shell/bg_start are on hold. Wait for the user or return {"tool": null}.`
|
|
342323
342338
|
});
|
|
342339
|
+
this.compactContext(ctx, state);
|
|
342324
342340
|
break;
|
|
342325
342341
|
}
|
|
342326
342342
|
const r = yield* this.runBgTool(ctx, state, step, bgParsed, isRepeat);
|
|
342327
342343
|
if (r.ok)
|
|
342328
342344
|
markBgDone(bgParsed.tool);
|
|
342329
|
-
if (r.stop)
|
|
342345
|
+
if (r.stop) {
|
|
342346
|
+
this.compactContext(ctx, state);
|
|
342330
342347
|
break;
|
|
342348
|
+
}
|
|
342331
342349
|
continue;
|
|
342332
342350
|
}
|
|
342333
342351
|
const askParsed = parseAskDecision(parsed);
|
|
342334
342352
|
if (askParsed) {
|
|
342335
342353
|
jsonFailureCount = 0;
|
|
342336
|
-
lastAgentProse = askParsed.reasoning;
|
|
342354
|
+
lastAgentProse = (askParsed.reasoning ?? "").slice(0, 1000);
|
|
342337
342355
|
const r = yield* this.runAskTool(ctx, step, askParsed);
|
|
342338
|
-
if (r.stop)
|
|
342356
|
+
if (r.stop) {
|
|
342357
|
+
this.compactContext(ctx, state);
|
|
342339
342358
|
break;
|
|
342359
|
+
}
|
|
342340
342360
|
continue;
|
|
342341
342361
|
}
|
|
342342
342362
|
let decision;
|
|
@@ -342352,6 +342372,7 @@ Do not include prose before or after the JSON. Output ONLY the JSON object.` + (
|
|
|
342352
342372
|
I didn't quite get that am having this issue:${issueSummary},please retry in 30 seconds
|
|
342353
342373
|
`
|
|
342354
342374
|
};
|
|
342375
|
+
this.compactContext(ctx, state);
|
|
342355
342376
|
break;
|
|
342356
342377
|
}
|
|
342357
342378
|
ctx.contextMessages.push({
|
|
@@ -342380,6 +342401,7 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
|
|
|
342380
342401
|
role: "user",
|
|
342381
342402
|
content: `[System] QUESTIONS: unanswered questions are pending, so write/edit/append/shell/bg_start are on hold. Wait for the user or return {"tool": null}.`
|
|
342382
342403
|
});
|
|
342404
|
+
this.compactContext(ctx, state);
|
|
342383
342405
|
break;
|
|
342384
342406
|
}
|
|
342385
342407
|
if (!ctx.isPlanMode && decision.tool === "shell" && decision.cmd && LONG_RUNNING_SHELL_RE.test(decision.cmd)) {
|
|
@@ -342395,8 +342417,10 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
|
|
|
342395
342417
|
const r = yield* this.runBgTool(ctx, state, step, rerouted, isRepeat);
|
|
342396
342418
|
if (r.ok)
|
|
342397
342419
|
markBgDone("bg_start");
|
|
342398
|
-
if (r.stop)
|
|
342420
|
+
if (r.stop) {
|
|
342421
|
+
this.compactContext(ctx, state);
|
|
342399
342422
|
break;
|
|
342423
|
+
}
|
|
342400
342424
|
continue;
|
|
342401
342425
|
}
|
|
342402
342426
|
const call = decision.tool ? this.recordToolCall(step, {
|
|
@@ -342415,6 +342439,7 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
|
|
|
342415
342439
|
role: "user",
|
|
342416
342440
|
content: `[System] Plan mode only allows "read", "web_search", "bg_list", "bg_logs" and "ask_user_question". Use one of those or return {"tool": null}.`
|
|
342417
342441
|
});
|
|
342442
|
+
this.compactContext(ctx, state);
|
|
342418
342443
|
break;
|
|
342419
342444
|
}
|
|
342420
342445
|
if ((decision.tool === "write" || decision.tool === "append") && typeof decision.content === "string" && decision.content.length > HarnessOrchestrator.INLINE_CONTENT_CHAR_BUDGET) {
|
|
@@ -342423,8 +342448,10 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
|
|
|
342423
342448
|
try {
|
|
342424
342449
|
let verifiedContentOverride = null;
|
|
342425
342450
|
if (!decision.tool) {
|
|
342426
|
-
if (!this.isAnswered)
|
|
342451
|
+
if (!this.isAnswered) {
|
|
342452
|
+
this.compactContext(ctx, state);
|
|
342427
342453
|
break;
|
|
342454
|
+
}
|
|
342428
342455
|
if (bgPending() && bgNudges < 2) {
|
|
342429
342456
|
bgNudges++;
|
|
342430
342457
|
grantSteps();
|
|
@@ -342451,10 +342478,12 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
|
|
|
342451
342478
|
});
|
|
342452
342479
|
continue;
|
|
342453
342480
|
}
|
|
342481
|
+
this.compactContext(ctx, state);
|
|
342454
342482
|
break;
|
|
342455
342483
|
}
|
|
342456
342484
|
if (decision.tool !== "shell" && decision.tool !== "web_search" && !decision.targetFile) {
|
|
342457
342485
|
this.settleToolCall(call, "skipped", "missing targetFile");
|
|
342486
|
+
this.compactContext(ctx, state);
|
|
342458
342487
|
break;
|
|
342459
342488
|
}
|
|
342460
342489
|
if (decision.tool === "shell" && !decision.cmd) {
|
|
@@ -342522,6 +342551,7 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
|
|
|
342522
342551
|
role: "user",
|
|
342523
342552
|
content: `[System] Same tool call issued twice in a row. Stopping tool loop.`
|
|
342524
342553
|
});
|
|
342554
|
+
this.compactContext(ctx, state);
|
|
342525
342555
|
break;
|
|
342526
342556
|
}
|
|
342527
342557
|
if (decision.tool === "shell" && decision.cmd) {
|
|
@@ -342654,9 +342684,10 @@ ${indent}`);
|
|
|
342654
342684
|
if (decision.tool === "read") {
|
|
342655
342685
|
try {
|
|
342656
342686
|
readRaw = await readFileContent(filePath);
|
|
342657
|
-
const
|
|
342687
|
+
const totalLen = readRaw.length;
|
|
342658
342688
|
const head = readRaw.slice(0, 100).replace(/\s+/g, " ").trim();
|
|
342659
|
-
|
|
342689
|
+
const lineCount = countLines(readRaw);
|
|
342690
|
+
readPreview = breakOnSemicolons(`${head}${totalLen > 100 ? "\u2026" : ""} (${lineCount > 20 ? lineCount - 5 : lineCount} ${lineCount === 1 ? "line" : "lines+"})
|
|
342660
342691
|
`);
|
|
342661
342692
|
} catch {
|
|
342662
342693
|
readPreview = "(unable to preview)";
|
|
@@ -342674,7 +342705,7 @@ ${indent}`);
|
|
|
342674
342705
|
yield { delta: `${actionLabel}
|
|
342675
342706
|
` };
|
|
342676
342707
|
if (decision.tool === "write" || decision.tool === "append") {
|
|
342677
|
-
const dir =
|
|
342708
|
+
const dir = dirname2(filePath);
|
|
342678
342709
|
if (!knownDirs.has(dir)) {
|
|
342679
342710
|
try {
|
|
342680
342711
|
await mkdir(dir, { recursive: true });
|
|
@@ -342690,8 +342721,9 @@ ${indent}`);
|
|
|
342690
342721
|
toolOutput = "Failed to read file.";
|
|
342691
342722
|
break;
|
|
342692
342723
|
}
|
|
342693
|
-
|
|
342694
|
-
|
|
342724
|
+
const totalLen = readRaw.length;
|
|
342725
|
+
toolOutput = totalLen > TOOL_OUTPUT_CHAR_CAP ? `${readRaw.slice(0, TOOL_OUTPUT_CHAR_CAP)}
|
|
342726
|
+
\u2026[truncated: file is ${totalLen} chars; use shell "sed -n 'START,ENDp' <file>" or "grep -n" to view other ranges]` : readRaw;
|
|
342695
342727
|
readRaw = null;
|
|
342696
342728
|
executedReads.add(readKey);
|
|
342697
342729
|
break;
|
|
@@ -342773,7 +342805,7 @@ ${indent}`);
|
|
|
342773
342805
|
toolOutput = `Exit code ${exitCode}
|
|
342774
342806
|
${truncateForContext(combined)}`;
|
|
342775
342807
|
if (exitCode === 0) {
|
|
342776
|
-
ctx.completedActions.push(`Ran: ${cmd}`);
|
|
342808
|
+
ctx.completedActions.push(`Ran: ${cmd.slice(0, 120)}`);
|
|
342777
342809
|
if (HarnessOrchestrator.TREE_MUTATING_SHELL.test(cmd))
|
|
342778
342810
|
this.markRepoMapDirty();
|
|
342779
342811
|
if (HarnessOrchestrator.BRANCH_CREATE_PATTERN.test(cmd)) {
|
|
@@ -342832,7 +342864,7 @@ ${toolOutput}`
|
|
|
342832
342864
|
});
|
|
342833
342865
|
}
|
|
342834
342866
|
toolOutput = "";
|
|
342835
|
-
if (mutationSucceeded && decision.content
|
|
342867
|
+
if (mutationSucceeded && decision.content) {
|
|
342836
342868
|
decision = { ...decision, content: "" };
|
|
342837
342869
|
}
|
|
342838
342870
|
if (mutationSucceeded) {
|
|
@@ -342862,6 +342894,7 @@ ${toolOutput}`
|
|
|
342862
342894
|
yield { delta: `
|
|
342863
342895
|
[Tool loop stopped: unexpected error \u2014 ${errorMessage(e3)}]
|
|
342864
342896
|
` };
|
|
342897
|
+
this.compactContext(ctx, state);
|
|
342865
342898
|
break;
|
|
342866
342899
|
}
|
|
342867
342900
|
}
|
|
@@ -344089,7 +344122,7 @@ var ModelProvider = ({ children }) => {
|
|
|
344089
344122
|
const fetchCurrentSavedActiveAgent = async () => {
|
|
344090
344123
|
try {
|
|
344091
344124
|
const fileName = "ACTIVE_AGENT.json";
|
|
344092
|
-
const storagePath = path6.join(os3.homedir(), ".
|
|
344125
|
+
const storagePath = path6.join(os3.homedir(), ".tensor", fileName);
|
|
344093
344126
|
const agent2 = await readFromFile(storagePath);
|
|
344094
344127
|
setActiveModel(JSON.parse(agent2));
|
|
344095
344128
|
} catch (_e) {
|
|
@@ -344443,7 +344476,7 @@ var Header = () => {
|
|
|
344443
344476
|
}, undefined, false, undefined, this),
|
|
344444
344477
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
344445
344478
|
fg: t2.inkFaint,
|
|
344446
|
-
children: "v0.1.
|
|
344479
|
+
children: "v0.1.4@beta-lite"
|
|
344447
344480
|
}, undefined, false, undefined, this)
|
|
344448
344481
|
]
|
|
344449
344482
|
}, undefined, true, undefined, this),
|
|
@@ -355432,7 +355465,7 @@ var Message_default = Index;
|
|
|
355432
355465
|
// src/components/DiffView/index.tsx
|
|
355433
355466
|
import { rmSync, statSync as statSync2 } from "fs";
|
|
355434
355467
|
import { tmpdir } from "os";
|
|
355435
|
-
import { dirname as
|
|
355468
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
355436
355469
|
var COLORS2 = {
|
|
355437
355470
|
addBg: "#1a2e1a",
|
|
355438
355471
|
delBg: "#3e1a1a",
|
|
@@ -355467,7 +355500,7 @@ var modifiedAt = (path19) => {
|
|
|
355467
355500
|
return statSync2(join3(root, path19)).mtimeMs;
|
|
355468
355501
|
} catch {
|
|
355469
355502
|
try {
|
|
355470
|
-
return statSync2(join3(root,
|
|
355503
|
+
return statSync2(join3(root, dirname3(path19))).mtimeMs;
|
|
355471
355504
|
} catch {
|
|
355472
355505
|
return 0;
|
|
355473
355506
|
}
|