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.
Files changed (2) hide show
  1. package/dist/index.js +970 -937
  2. 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 clip(s2, max) {
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
- ${clip(taskDescription, 1200)}`,
331139
+ ${clip2(taskDescription, 1200)}`,
330234
331140
  `STEP: ${step}/${maxSteps}`,
330235
331141
  `PLAN_MODE: ${isPlanMode}`,
330236
331142
  `ON_TASK_BRANCH: ${onTaskBranch}`,
330237
331143
  `PROGRESS:
330238
- ${clip(progressSummary, 800) || "(none)"}`,
331144
+ ${clip2(progressSummary, 800) || "(none)"}`,
330239
331145
  `RECENT_CONTEXT:
330240
- ${clip(recentContextTail, 1500) || "(none)"}`,
331146
+ ${clip2(recentContextTail, 1500) || "(none)"}`,
330241
331147
  `REPO_MAP (paths):
330242
- ${clip(repoMap, 2500) || "(empty)"}`,
331148
+ ${clip2(repoMap, 2500) || "(empty)"}`,
330243
331149
  repoContext ? `REPO_CONTEXT:
330244
- ${clip(repoContext, 800)}` : ""
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(dirname(this.memoryPath), { recursive: true });
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 BASECODE.md file which is a file that contains info on this project or tell the user to run /init to generate it`;
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.` : "") + ` The ONLY acceptable output is one raw JSON object matching the schema ("content": write/append -> "", web_search -> the query, otherwise null):
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 lineCount = countLines(readRaw);
342687
+ const totalLen = readRaw.length;
342658
342688
  const head = readRaw.slice(0, 100).replace(/\s+/g, " ").trim();
342659
- readPreview = breakOnSemicolons(`${head}${readRaw.length > 100 ? "\u2026" : ""} (${lineCount > 20 ? lineCount - 5 : lineCount} ${lineCount === 1 ? "line" : "lines+"})
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 = dirname(filePath);
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
- toolOutput = readRaw.length > TOOL_OUTPUT_CHAR_CAP ? `${readRaw.slice(0, TOOL_OUTPUT_CHAR_CAP)}
342694
- \u2026[truncated: file is ${readRaw.length} chars; use shell "sed -n 'START,ENDp' <file>" or "grep -n" to view other ranges]` : readRaw;
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 && decision.content.length > 4000) {
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(), ".basecode", fileName);
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.3@beta-lite"
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 dirname2, join as join3 } from "path";
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, dirname2(path19))).mtimeMs;
355503
+ return statSync2(join3(root, dirname3(path19))).mtimeMs;
355471
355504
  } catch {
355472
355505
  return 0;
355473
355506
  }