tensor-cli 0.1.3-beta-lite → 0.1.5-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 +1303 -899
  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;
@@ -329959,487 +329959,7 @@ var askJev = async (body) => {
329959
329959
  var askJev_default = askJev;
329960
329960
 
329961
329961
  // src/tools/harness-core.ts
329962
- var MAX_JSON_RETRIES = 3;
329963
- var MAX_WRITES_PER_FILE = 10;
329964
- var MAX_VERIFY_CHARS = 4000;
329965
- var CONTEXT_WINDOW_SIZE = 24;
329966
- var DECISION_MAX_TOKENS = 2500;
329967
- var CONTENT_MAX_TOKENS = 1e4;
329968
- var SHELL_TIMEOUT_MS = 60000;
329969
- var MODEL_PROVIDERS = ["GOOGLE", "CENCORI", "VENICE"];
329970
- var MUTATING_TOOLS = new Set([
329971
- "write",
329972
- "edit",
329973
- "append"
329974
- ]);
329975
- var VALID_TOOLS = new Set([
329976
- "read",
329977
- "write",
329978
- "edit",
329979
- "append",
329980
- "shell"
329981
- ]);
329982
- function errorMessage(e2) {
329983
- if (e2 instanceof Error)
329984
- return e2.message;
329985
- if (typeof e2 === "string")
329986
- return e2;
329987
- return "unknown error";
329988
- }
329989
- function isRecord2(v) {
329990
- return typeof v === "object" && v !== null;
329991
- }
329992
- function summarizeZodError(e2) {
329993
- if (!isRecord2(e2))
329994
- return "validation error";
329995
- const err = e2;
329996
- if (Array.isArray(err.issues)) {
329997
- return err.issues.map((i2) => `${(i2.path ?? []).join(".") || "<root>"}: ${i2.message}`).join("; ");
329998
- }
329999
- return err.message ?? "validation error";
330000
- }
330001
- function extractFirstJsonObject(text) {
330002
- const start = text.indexOf("{");
330003
- if (start === -1)
330004
- return null;
330005
- let depth = 0;
330006
- let inString = false;
330007
- let escaped = false;
330008
- for (let i2 = start;i2 < text.length; i2++) {
330009
- const ch = text[i2];
330010
- if (escaped) {
330011
- escaped = false;
330012
- continue;
330013
- }
330014
- if (ch === "\\") {
330015
- escaped = true;
330016
- continue;
330017
- }
330018
- if (ch === '"') {
330019
- inString = !inString;
330020
- continue;
330021
- }
330022
- if (inString)
330023
- continue;
330024
- if (ch === "{") {
330025
- depth++;
330026
- } else if (ch === "}") {
330027
- depth--;
330028
- if (depth === 0)
330029
- return text.slice(start, i2 + 1);
330030
- }
330031
- }
330032
- return null;
330033
- }
330034
- function salvagePartialDecision(text) {
330035
- const grab = (key) => {
330036
- const re = new RegExp(`"${key}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`);
330037
- const m = re.exec(text);
330038
- const captured = m?.[1];
330039
- if (captured === undefined)
330040
- return;
330041
- return captured.replace(/\\"/g, '"').replace(/\\n/g, `
330042
- `).replace(/\\\\/g, "\\");
330043
- };
330044
- const tool = grab("tool");
330045
- const targetFile = grab("targetFile") ?? grab("file") ?? grab("target_file");
330046
- const cmd = grab("cmd");
330047
- if (!tool && !targetFile && !cmd)
330048
- return null;
330049
- return {
330050
- tool,
330051
- targetFile,
330052
- target: grab("target"),
330053
- replacement: grab("replacement"),
330054
- cmd,
330055
- reasoning: grab("reasoning")
330056
- };
330057
- }
330058
- function truncateForContext(s2) {
330059
- if (s2.length <= MAX_VERIFY_CHARS)
330060
- return s2;
330061
- const slice = s2.slice(0, MAX_VERIFY_CHARS);
330062
- const lastNewline = slice.lastIndexOf(`
330063
- `);
330064
- const cut = lastNewline > MAX_VERIFY_CHARS * 0.5 ? lastNewline : MAX_VERIFY_CHARS;
330065
- return `${s2.slice(0, cut)}
330066
- ...[truncated, ${s2.length - cut} more characters]`;
330067
- }
330068
- function tryUnwrapDecisionJson(text) {
330069
- const trimmed = text.trim();
330070
- if (!trimmed.startsWith("{"))
330071
- return { unwrapped: null, wasWrapper: false };
330072
- try {
330073
- const parsed = JSON.parse(trimmed);
330074
- if (!isRecord2(parsed))
330075
- return { unwrapped: null, wasWrapper: false };
330076
- const looksLikeDecision = "tool" in parsed || "targetFile" in parsed;
330077
- const content = parsed.content;
330078
- if (looksLikeDecision && typeof content === "string") {
330079
- return { unwrapped: content, wasWrapper: true };
330080
- }
330081
- } catch {}
330082
- return { unwrapped: null, wasWrapper: false };
330083
- }
330084
- var _CONVERSATIONAL_WORDS = new Set([
330085
- "hi",
330086
- "hey",
330087
- "hello",
330088
- "yo",
330089
- "sup",
330090
- "howdy",
330091
- "bye",
330092
- "goodbye",
330093
- "later",
330094
- "cya",
330095
- "thanks",
330096
- "thank",
330097
- "thx",
330098
- "ty",
330099
- "appreciate",
330100
- "appreciated",
330101
- "ok",
330102
- "okay",
330103
- "k",
330104
- "cool",
330105
- "nice",
330106
- "great",
330107
- "perfect",
330108
- "awesome",
330109
- "sweet",
330110
- "alright",
330111
- "noted",
330112
- "yes",
330113
- "yeah",
330114
- "yep",
330115
- "yup",
330116
- "sure",
330117
- "no",
330118
- "nope",
330119
- "nah",
330120
- "maybe",
330121
- "lol",
330122
- "lmao",
330123
- "haha",
330124
- "hehe",
330125
- "please",
330126
- "pls",
330127
- "sorry",
330128
- "np",
330129
- "worries"
330130
- ]);
330131
- async function isConversational(message) {
330132
- const trimmed = message.trim();
330133
- try {
330134
- const res = await askJev_default({
330135
- state: trimmed,
330136
- model: "jev-latest",
330137
- questions: {
330138
- is_conversational: {
330139
- type: "noul",
330140
- instructions: "The user is greeting, chatting, or making small talk, rather than asking an informational question or requesting a task be executed."
330141
- }
330142
- }
330143
- });
330144
- return (res?.answers?.is_conversational?.noul ?? 0) > 0.5;
330145
- } catch {
330146
- return false;
330147
- }
330148
- }
330149
-
330150
- // src/tools/harness-jev.ts
330151
- function truncate(text, maxChars) {
330152
- if (!text)
330153
- return "";
330154
- return text.length <= maxChars ? text : `${text.slice(0, maxChars)}
330155
- ...[truncated]`;
330156
- }
330157
- var MALFORMED_CRITERIA = {
330158
- xml_tool_call: "Uses an XML/angle-bracket function-call style like <tool_call><function=...><parameter=...> instead of a raw JSON object.",
330159
- xml_tool_use: 'Uses an XML "<tool_use><tool_name>...</tool_name><file_path>...</file_path></tool_use>" style instead of a raw JSON object.',
330160
- generic_xml_tag: "Uses some other XML/HTML-like angle-bracket markup instead of a raw JSON object, not matching the tool_call or tool_use patterns.",
330161
- refusal: "Claims it does not have access to file or shell tools, or otherwise declines to make a tool decision.",
330162
- empty_or_no_json: "Contains no JSON object at all, or is empty/blank/pure prose with nothing resembling the required schema.",
330163
- other: "Malformed for some other reason not covered above (e.g. JSON-like but severely corrupted)."
330164
- };
330165
- async function classifyMalformedOutput(rawContent) {
330166
- try {
330167
- const res = await askJev_default({
330168
- state: truncate(rawContent, 2000),
330169
- model: "jev-latest",
330170
- questions: {
330171
- malformed_reason: {
330172
- type: "choice",
330173
- instructions: "The response below was supposed to be exactly one raw JSON object matching a fixed schema, and it failed to parse as such. Why did it fail?",
330174
- criteria: MALFORMED_CRITERIA
330175
- }
330176
- }
330177
- });
330178
- const reason = res.answers.malformed_reason?.choice ?? "other";
330179
- return { reason, hint: MALFORMED_CRITERIA[reason] };
330180
- } catch {
330181
- return { reason: "other", hint: MALFORMED_CRITERIA.other };
330182
- }
330183
- }
330184
- var SHELL_RISK_CRITERIA = {
330185
- auto: "Read-only or locally-reversible: no network access, no effect outside the local git object store or the workspace files already being worked on (e.g. ls, cat, find, grep, git status/log/diff/branch, running existing test/lint/build scripts, git init, creating/switching a local branch, a local-only git commit).",
330186
- confirm: "Side-effecting and needs human sign-off before running: installing a dependency, merging a branch back into the base branch, or any command with effects reaching outside the local workspace.",
330187
- blocked: "Destructive, irreversible, or dangerous beyond the current task's scope \u2014 e.g. force-pushing, deleting broad swaths of the filesystem, modifying system-level configuration, or anything risking irreversible data loss or affecting systems outside this workspace."
330188
- };
330189
- async function classifyShellRisk(cmd) {
330190
- try {
330191
- const res = await askJev_default({
330192
- state: cmd,
330193
- model: "jev-latest",
330194
- questions: {
330195
- risk_tier: {
330196
- type: "choice",
330197
- instructions: "Classify the risk tier of running this exact shell command in an automated coding agent session.",
330198
- criteria: SHELL_RISK_CRITERIA
330199
- }
330200
- }
330201
- });
330202
- const riskTier = res.answers.risk_tier;
330203
- const tier = riskTier?.choice ?? "confirm";
330204
- return { tier, confidence: riskTier?.confidence ?? 0 };
330205
- } catch {
330206
- return { tier: "confirm", confidence: 0 };
330207
- }
330208
- }
330209
-
330210
- // src/tools/harness-jev-next-step-prompt.ts
330211
- var DEFAULT_JEV_MODEL = "jev-latest";
330212
- function clip(s2, max) {
330213
- if (!s2)
330214
- return "";
330215
- return s2.length <= max ? s2 : `${s2.slice(0, max)}
330216
- \u2026[truncated]`;
330217
- }
330218
- async function classifyNextStep(input2) {
330219
- const {
330220
- taskDescription,
330221
- repoMap,
330222
- repoContext = "",
330223
- progressSummary,
330224
- recentContextTail,
330225
- step,
330226
- maxSteps,
330227
- isPlanMode,
330228
- onTaskBranch,
330229
- jevModel = DEFAULT_JEV_MODEL
330230
- } = input2;
330231
- const state = [
330232
- `TASK:
330233
- ${clip(taskDescription, 1200)}`,
330234
- `STEP: ${step}/${maxSteps}`,
330235
- `PLAN_MODE: ${isPlanMode}`,
330236
- `ON_TASK_BRANCH: ${onTaskBranch}`,
330237
- `PROGRESS:
330238
- ${clip(progressSummary, 800) || "(none)"}`,
330239
- `RECENT_CONTEXT:
330240
- ${clip(recentContextTail, 1500) || "(none)"}`,
330241
- `REPO_MAP (paths):
330242
- ${clip(repoMap, 2500) || "(empty)"}`,
330243
- repoContext ? `REPO_CONTEXT:
330244
- ${clip(repoContext, 800)}` : ""
330245
- ].filter(Boolean).join(`
330246
-
330247
- `);
330248
- try {
330249
- const res = await askJev_default({
330250
- state,
330251
- model: jevModel,
330252
- questions: {
330253
- confirm_gate: {
330254
- type: "choice",
330255
- instructions: "Human approval gate for shell. Pick exactly one. " + "denied = a prior shell proposal was followed by [DENIED BY USER]. " + "pending = a requires_confirmation shell was proposed and neither [APPROVED BY USER] nor [DENIED BY USER] appeared yet. " + "clear = neither of the above.",
330256
- criteria: {
330257
- clear: "No outstanding confirmation; safe to continue.",
330258
- pending: "Waiting on user approve/deny for a previous shell command.",
330259
- denied: "User denied a prior shell command; do not re-propose the same effect."
330260
- }
330261
- },
330262
- is_conversational_only: {
330263
- type: "choice",
330264
- instructions: "Is this only a greeting, chat, or question answerable without reading or changing files? " + "If repo context already answers a codebase question with no file I/O needed, choose yes.",
330265
- criteria: {
330266
- yes: "No read/write/edit/append/shell needed this turn.",
330267
- no: "Task needs tools or git lifecycle."
330268
- }
330269
- },
330270
- git_phase: {
330271
- type: "choice",
330272
- instructions: "Pick the single next git-lifecycle phase. File work is only allowed in task_work and only when ON_TASK_BRANCH is true. " + "Before ever creating a new task branch, always look for an existing branch that already covers this fix/task. " + 'Matching is by context, not exact string \u2014 a branch named "fix/login-timeout" should be picked up for a request about the login timing out, even if the wording differs. ' + "If a reasonably close match exists, checkout that branch and continue work there instead of spawning a new one. " + "Only create a new branch when nothing existing is a plausible match. " + "done only if merge was approved and succeeded. " + "blocked_on_confirm if confirm_gate is pending or a denied merge/install blocks progress.",
330273
- criteria: {
330274
- check_repo: "Unknown if workspace is a git repo \u2014 run rev-parse style check.",
330275
- init_repo: "Not a git repo yet \u2014 init base branch.",
330276
- find_branch: "Repo exists but not yet on a task branch \u2014 list local (and remote-tracked) branches and check whether any of them already relates to this task/fix by topic, name, or recent commit context. Do this before deciding to create anything.",
330277
- checkout_branch: "find_branch located an existing branch that plausibly matches this task \u2014 check it out and resume work there. Treat it as the task branch going forward even if its name does not follow the agent/<slug> convention.",
330278
- create_branch: "find_branch found no existing branch that reasonably matches this task \u2014 create a new agent/<slug> branch from base.",
330279
- task_work: "On task branch (or plan-mode reads); do task file/shell work.",
330280
- commit: "Task file work done; need local commit on task branch.",
330281
- merge: "Committed; need merge back to base (always needs confirmation).",
330282
- done: "Merge approved and succeeded; task complete.",
330283
- blocked_on_confirm: "Must wait or halt due to confirm gate."
330284
- }
330285
- },
330286
- tool: {
330287
- type: "choice",
330288
- instructions: "Single next tool. Prefer none when conversational, pending confirm, done, or when only waiting. " + "In plan mode only read or none. " + "New file not in REPO_MAP \u2192 write (no read first). " + "Existing file not yet in context \u2192 read first. " + "Small localized change with contents in context \u2192 edit. " + "Full rewrite \u2192 write. Pure end addition \u2192 append. " + "Install/test/build/git commands \u2192 shell. Never shell to edit file contents.",
330289
- criteria: {
330290
- read: "Read an existing file into context.",
330291
- write: "Create new file or full-file rewrite.",
330292
- edit: "Small exact substring replace; contents already in context.",
330293
- append: "Append only to end of existing/new file.",
330294
- shell: "Run a command (git, test, install, etc.).",
330295
- none: "No tool this step (done, wait, conversational, or cannot proceed)."
330296
- }
330297
- },
330298
- shell_risk: {
330299
- type: "choice",
330300
- instructions: "If tool is not shell, choose n/a. Otherwise classify the *intended* command class from the task (not a literal cmd). " + "AUTO = read-only or local git/test/build. CONFIRM = install or merge to base. BLOCKED = destructive/unsafe (rm -rf, force push, curl|sh, secrets exfil, etc.).",
330301
- criteria: {
330302
- auto: "Read-only or locally reversible; no network package install; local git only.",
330303
- confirm: "Side-effect needing human sign-off (dependency install, merge to base).",
330304
- blocked: "Must never run; refuse or suggest narrower auto alternative.",
330305
- "n/a": "Tool is not shell."
330306
- }
330307
- },
330308
- file_in_map: {
330309
- type: "choice",
330310
- instructions: "If the next file action targets a path, does that path already appear in REPO_MAP? " + "If tool is shell or none, choose unknown.",
330311
- criteria: {
330312
- yes: "Path listed in REPO_MAP.",
330313
- no: "New path / not in map.",
330314
- unknown: "Not a file tool or path unclear."
330315
- }
330316
- },
330317
- contents_in_context: {
330318
- type: "choice",
330319
- instructions: "For a file mutation, are sufficient current contents already present in RECENT_CONTEXT " + "(e.g. a prior read or [Verified current contents])? If not a file mutation, unknown.",
330320
- criteria: {
330321
- yes: "Enough contents already in context to edit safely.",
330322
- no: "Need read (or write-from-scratch) before mutating.",
330323
- unknown: "Not applicable."
330324
- }
330325
- },
330326
- need_more_steps: {
330327
- type: "choice",
330328
- instructions: "Will finishing the overall task clearly need more turns after this one under the normal budget? " + "Also, if the agent's last message implies it intends to perform more work, choose yes even if it thinks it is done.",
330329
- criteria: {
330330
- yes: "More steps needed after this action or agent prose implies intent to continue.",
330331
- no: "This is likely the last needed action or a terminal wait/done."
330332
- }
330333
- },
330334
- confidence: {
330335
- type: "score",
330336
- instructions: "How confident are you in the tool + git_phase combination for this step?",
330337
- criteria: {
330338
- "0": "Guessing / contradictory signals.",
330339
- "50": "Plausible but ambiguous.",
330340
- "100": "Clear next action."
330341
- }
330342
- }
330343
- }
330344
- });
330345
- const a2 = res.answers;
330346
- const confirmGate = a2.confirm_gate?.choice ?? "clear";
330347
- let gitPhase = a2.git_phase?.choice ?? "task_work";
330348
- let tool = a2.tool?.choice ?? "none";
330349
- let shellRisk = a2.shell_risk?.choice ?? "n/a";
330350
- const fileInMapChoice = a2.file_in_map?.choice;
330351
- const contentsChoice = a2.contents_in_context?.choice;
330352
- const needMore = a2.need_more_steps?.choice === "yes";
330353
- const conversational = a2.is_conversational_only?.choice === "yes";
330354
- const conf = typeof a2.confidence?.score === "number" ? a2.confidence.score / 100 : a2.confirm_gate?.confidence ?? 0.5;
330355
- if (confirmGate === "pending") {
330356
- gitPhase = "blocked_on_confirm";
330357
- tool = "none";
330358
- shellRisk = "n/a";
330359
- } else if (confirmGate === "denied" && tool === "shell" && shellRisk !== "auto") {
330360
- tool = "none";
330361
- gitPhase = "blocked_on_confirm";
330362
- }
330363
- if (conversational && confirmGate === "clear") {
330364
- tool = "none";
330365
- gitPhase = "done";
330366
- }
330367
- if (isPlanMode && tool !== "read" && tool !== "none") {
330368
- tool = "read";
330369
- }
330370
- if (!onTaskBranch && (tool === "write" || tool === "edit" || tool === "append")) {
330371
- tool = "shell";
330372
- gitPhase = gitPhase === "task_work" ? "create_branch" : gitPhase;
330373
- shellRisk = "auto";
330374
- }
330375
- if (tool === "shell" && shellRisk === "blocked") {
330376
- tool = "none";
330377
- }
330378
- if (gitPhase === "merge") {
330379
- tool = "shell";
330380
- shellRisk = "confirm";
330381
- }
330382
- if (gitPhase === "done") {
330383
- tool = "none";
330384
- }
330385
- const requiresConfirmation = tool === "shell" && (shellRisk === "confirm" || gitPhase === "merge");
330386
- const fileExistsInMap = fileInMapChoice === "yes" ? true : fileInMapChoice === "no" ? false : null;
330387
- const contentsAlreadyInContext = contentsChoice === "yes" ? true : contentsChoice === "no" ? false : null;
330388
- const canSkipDecisionLlm = tool === "none" || false;
330389
- const reasoning = buildShortReasoning({
330390
- confirmGate,
330391
- gitPhase,
330392
- tool,
330393
- shellRisk,
330394
- conversational,
330395
- isPlanMode
330396
- });
330397
- return {
330398
- unavailable: false,
330399
- tool,
330400
- gitPhase,
330401
- confirmGate,
330402
- shellRisk,
330403
- requiresConfirmation,
330404
- needMoreSteps: needMore || confirmGate === "pending",
330405
- fileExistsInMap,
330406
- contentsAlreadyInContext,
330407
- confidence: conf,
330408
- reasoning,
330409
- canSkipDecisionLlm
330410
- };
330411
- } catch {
330412
- return {
330413
- unavailable: true,
330414
- tool: "none",
330415
- gitPhase: "task_work",
330416
- confirmGate: "clear",
330417
- shellRisk: "n/a",
330418
- requiresConfirmation: false,
330419
- needMoreSteps: false,
330420
- fileExistsInMap: null,
330421
- contentsAlreadyInContext: null,
330422
- confidence: 0,
330423
- reasoning: "Jev unavailable; fall back to user-model router.",
330424
- canSkipDecisionLlm: false
330425
- };
330426
- }
330427
- }
330428
- function buildShortReasoning(p) {
330429
- if (p.confirmGate === "pending")
330430
- return "Awaiting user decision on pending command.";
330431
- if (p.confirmGate === "denied")
330432
- return "Prior command denied; not re-proposing same effect.";
330433
- if (p.conversational)
330434
- return "Conversational only; no tools.";
330435
- if (p.gitPhase === "done")
330436
- return "Git merge complete; task done.";
330437
- if (p.isPlanMode)
330438
- return `Plan mode: ${p.tool}.`;
330439
- if (p.tool === "shell")
330440
- return `Shell (${p.shellRisk}); git phase ${p.gitPhase}.`;
330441
- return `Next: ${p.tool} (git phase ${p.gitPhase}).`;
330442
- }
329962
+ import { dirname } from "path";
330443
329963
 
330444
329964
  // src/tools/harness-processes.ts
330445
329965
  import { spawn } from "child_process";
@@ -330590,7 +330110,7 @@ var URL_RE = /https?:\/\/(?:localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1?\]):\d+[^\s'
330590
330110
  var PORT_RE = /\bport\s*:?\s*(\d{2,5})\b/gi;
330591
330111
  var ALL_RE = /^(?:all|everything|\*|all\s+(?:the\s+)?(?:servers|processes|background(?:\s+processes)?))$/;
330592
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)$/;
330593
- var clip2 = (s2, n3) => s2.length > n3 ? `${s2.slice(0, n3 - 1)}\u2026` : s2;
330113
+ var clip = (s2, n3) => s2.length > n3 ? `${s2.slice(0, n3 - 1)}\u2026` : s2;
330594
330114
  function fmtDur(ms) {
330595
330115
  const s2 = Math.max(0, Math.floor(ms / 1000));
330596
330116
  if (s2 < 60)
@@ -330602,7 +330122,7 @@ function fmtDur(ms) {
330602
330122
  }
330603
330123
  function deriveLabel(cmd) {
330604
330124
  const words = cmd.split(/\s+/).filter((w) => w && !/^\w+=/.test(w));
330605
- return clip2(words.slice(0, 3).join(" "), 24);
330125
+ return clip(words.slice(0, 3).join(" "), 24);
330606
330126
  }
330607
330127
  var urlSuffix = (m) => m.urls.size ? ` \xB7 ${[...m.urls].join(", ")}` : "";
330608
330128
 
@@ -330744,7 +330264,7 @@ Use bg_logs with the id to see output, bg_kill to stop one. Never start a duplic
330744
330264
  let s2 = `Background ${m.id} "${m.label}" ${m.status === "failed" ? `crashed (exit ${m.exitCode ?? m.signal})` : `exited (code ${m.exitCode ?? m.signal})`}`;
330745
330265
  if (m.status === "failed") {
330746
330266
  const last = m.buf.split(`
330747
- `).map((l) => l.trim()).filter(Boolean).slice(-2).map((l) => clip2(l, 160));
330267
+ `).map((l) => l.trim()).filter(Boolean).slice(-2).map((l) => clip(l, 160));
330748
330268
  if (last.length)
330749
330269
  s2 += `
330750
330270
  ${last.join(`
@@ -330778,7 +330298,7 @@ Use bg_logs with the id to see output, bg_kill to stop one. Never start a duplic
330778
330298
  line(m) {
330779
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`;
330780
330300
  return `${m.id} "${m.label}" [${m.status}] pid ${m.pid ?? "?"} \xB7 ${when}${urlSuffix(m)}
330781
- $ ${clip2(m.cmd, 120)}`;
330301
+ $ ${clip(m.cmd, 120)}`;
330782
330302
  }
330783
330303
  find(ref) {
330784
330304
  const all3 = [...this.procs.values()];
@@ -330942,6 +330462,891 @@ Use bg_logs with the id to see output, bg_kill to stop one. Never start a duplic
330942
330462
  }
330943
330463
  }
330944
330464
 
330465
+ // src/tools/harness-core.ts
330466
+ var MAX_JSON_RETRIES = 3;
330467
+ var MAX_WRITES_PER_FILE = 10;
330468
+ var MAX_VERIFY_CHARS = 4000;
330469
+ var CONTEXT_WINDOW_SIZE = 24;
330470
+ var DECISION_MAX_TOKENS = 2500;
330471
+ var CONTENT_MAX_TOKENS = 1e4;
330472
+ var SHELL_TIMEOUT_MS = 60000;
330473
+ var MODEL_PROVIDERS = ["GOOGLE", "CENCORI", "VENICE"];
330474
+ var MUTATING_TOOLS = new Set([
330475
+ "write",
330476
+ "edit",
330477
+ "append"
330478
+ ]);
330479
+ var VALID_TOOLS = new Set([
330480
+ "read",
330481
+ "write",
330482
+ "edit",
330483
+ "append",
330484
+ "shell"
330485
+ ]);
330486
+ function errorMessage(e2) {
330487
+ if (e2 instanceof Error)
330488
+ return e2.message;
330489
+ if (typeof e2 === "string")
330490
+ return e2;
330491
+ return "unknown error";
330492
+ }
330493
+ function isRecord2(v) {
330494
+ return typeof v === "object" && v !== null;
330495
+ }
330496
+ function summarizeZodError(e2) {
330497
+ if (!isRecord2(e2))
330498
+ return "validation error";
330499
+ const err = e2;
330500
+ if (Array.isArray(err.issues)) {
330501
+ return err.issues.map((i2) => `${(i2.path ?? []).join(".") || "<root>"}: ${i2.message}`).join("; ");
330502
+ }
330503
+ return err.message ?? "validation error";
330504
+ }
330505
+ function extractFirstJsonObject(text) {
330506
+ const start = text.indexOf("{");
330507
+ if (start === -1)
330508
+ return null;
330509
+ let depth = 0;
330510
+ let inString = false;
330511
+ let escaped = false;
330512
+ for (let i2 = start;i2 < text.length; i2++) {
330513
+ const ch = text[i2];
330514
+ if (escaped) {
330515
+ escaped = false;
330516
+ continue;
330517
+ }
330518
+ if (ch === "\\") {
330519
+ escaped = true;
330520
+ continue;
330521
+ }
330522
+ if (ch === '"') {
330523
+ inString = !inString;
330524
+ continue;
330525
+ }
330526
+ if (inString)
330527
+ continue;
330528
+ if (ch === "{") {
330529
+ depth++;
330530
+ } else if (ch === "}") {
330531
+ depth--;
330532
+ if (depth === 0)
330533
+ return text.slice(start, i2 + 1);
330534
+ }
330535
+ }
330536
+ return null;
330537
+ }
330538
+ function salvagePartialDecision(text) {
330539
+ const grab = (key) => {
330540
+ const re = new RegExp(`"${key}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`);
330541
+ const m = re.exec(text);
330542
+ const captured = m?.[1];
330543
+ if (captured === undefined)
330544
+ return;
330545
+ return captured.replace(/\\"/g, '"').replace(/\\n/g, `
330546
+ `).replace(/\\\\/g, "\\");
330547
+ };
330548
+ const tool = grab("tool");
330549
+ const targetFile = grab("targetFile") ?? grab("file") ?? grab("target_file");
330550
+ const cmd = grab("cmd");
330551
+ if (!tool && !targetFile && !cmd)
330552
+ return null;
330553
+ return {
330554
+ tool,
330555
+ targetFile,
330556
+ target: grab("target"),
330557
+ replacement: grab("replacement"),
330558
+ cmd,
330559
+ reasoning: grab("reasoning")
330560
+ };
330561
+ }
330562
+ function truncateForContext(s2) {
330563
+ if (s2.length <= MAX_VERIFY_CHARS)
330564
+ return s2;
330565
+ const slice = s2.slice(0, MAX_VERIFY_CHARS);
330566
+ const lastNewline = slice.lastIndexOf(`
330567
+ `);
330568
+ const cut = lastNewline > MAX_VERIFY_CHARS * 0.5 ? lastNewline : MAX_VERIFY_CHARS;
330569
+ return `${s2.slice(0, cut)}
330570
+ ...[truncated, ${s2.length - cut} more characters]`;
330571
+ }
330572
+ function tryUnwrapDecisionJson(text) {
330573
+ const trimmed = text.trim();
330574
+ if (!trimmed.startsWith("{"))
330575
+ return { unwrapped: null, wasWrapper: false };
330576
+ try {
330577
+ const parsed = JSON.parse(trimmed);
330578
+ if (!isRecord2(parsed))
330579
+ return { unwrapped: null, wasWrapper: false };
330580
+ const looksLikeDecision = "tool" in parsed || "targetFile" in parsed;
330581
+ const content = parsed.content;
330582
+ if (looksLikeDecision && typeof content === "string") {
330583
+ return { unwrapped: content, wasWrapper: true };
330584
+ }
330585
+ } catch {}
330586
+ return { unwrapped: null, wasWrapper: false };
330587
+ }
330588
+ var _CONVERSATIONAL_WORDS = new Set([
330589
+ "hi",
330590
+ "hey",
330591
+ "hello",
330592
+ "yo",
330593
+ "sup",
330594
+ "howdy",
330595
+ "bye",
330596
+ "goodbye",
330597
+ "later",
330598
+ "cya",
330599
+ "thanks",
330600
+ "thank",
330601
+ "thx",
330602
+ "ty",
330603
+ "appreciate",
330604
+ "appreciated",
330605
+ "ok",
330606
+ "okay",
330607
+ "k",
330608
+ "cool",
330609
+ "nice",
330610
+ "great",
330611
+ "perfect",
330612
+ "awesome",
330613
+ "sweet",
330614
+ "alright",
330615
+ "noted",
330616
+ "yes",
330617
+ "yeah",
330618
+ "yep",
330619
+ "yup",
330620
+ "sure",
330621
+ "no",
330622
+ "nope",
330623
+ "nah",
330624
+ "maybe",
330625
+ "lol",
330626
+ "lmao",
330627
+ "haha",
330628
+ "hehe",
330629
+ "please",
330630
+ "pls",
330631
+ "sorry",
330632
+ "np",
330633
+ "worries"
330634
+ ]);
330635
+ async function isConversational(message) {
330636
+ const trimmed = message.trim();
330637
+ try {
330638
+ const res = await askJev_default({
330639
+ state: trimmed,
330640
+ model: "jev-latest",
330641
+ questions: {
330642
+ is_conversational: {
330643
+ type: "noul",
330644
+ instructions: "The user is greeting, chatting, or making small talk, rather than asking an informational question or requesting a task be executed."
330645
+ }
330646
+ }
330647
+ });
330648
+ return (res?.answers?.is_conversational?.noul ?? 0) > 0.5;
330649
+ } catch {
330650
+ return false;
330651
+ }
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;
331055
+
331056
+ // src/tools/harness-jev.ts
331057
+ function truncate(text, maxChars) {
331058
+ if (!text)
331059
+ return "";
331060
+ return text.length <= maxChars ? text : `${text.slice(0, maxChars)}
331061
+ ...[truncated]`;
331062
+ }
331063
+ var MALFORMED_CRITERIA = {
331064
+ xml_tool_call: "Uses an XML/angle-bracket function-call style like <tool_call><function=...><parameter=...> instead of a raw JSON object.",
331065
+ xml_tool_use: 'Uses an XML "<tool_use><tool_name>...</tool_name><file_path>...</file_path></tool_use>" style instead of a raw JSON object.',
331066
+ generic_xml_tag: "Uses some other XML/HTML-like angle-bracket markup instead of a raw JSON object, not matching the tool_call or tool_use patterns.",
331067
+ refusal: "Claims it does not have access to file or shell tools, or otherwise declines to make a tool decision.",
331068
+ empty_or_no_json: "Contains no JSON object at all, or is empty/blank/pure prose with nothing resembling the required schema.",
331069
+ other: "Malformed for some other reason not covered above (e.g. JSON-like but severely corrupted)."
331070
+ };
331071
+ async function classifyMalformedOutput(rawContent) {
331072
+ try {
331073
+ const res = await askJev_default({
331074
+ state: truncate(rawContent, 2000),
331075
+ model: "jev-latest",
331076
+ questions: {
331077
+ malformed_reason: {
331078
+ type: "choice",
331079
+ instructions: "The response below was supposed to be exactly one raw JSON object matching a fixed schema, and it failed to parse as such. Why did it fail?",
331080
+ criteria: MALFORMED_CRITERIA
331081
+ }
331082
+ }
331083
+ });
331084
+ const reason = res.answers.malformed_reason?.choice ?? "other";
331085
+ return { reason, hint: MALFORMED_CRITERIA[reason] };
331086
+ } catch {
331087
+ return { reason: "other", hint: MALFORMED_CRITERIA.other };
331088
+ }
331089
+ }
331090
+ var SHELL_RISK_CRITERIA = {
331091
+ auto: "Read-only or locally-reversible: no network access, no effect outside the local git object store or the workspace files already being worked on (e.g. ls, cat, find, grep, git status/log/diff/branch, running existing test/lint/build scripts, git init, creating/switching a local branch, a local-only git commit).",
331092
+ confirm: "Side-effecting and needs human sign-off before running: installing a dependency, merging a branch back into the base branch, or any command with effects reaching outside the local workspace.",
331093
+ blocked: "Destructive, irreversible, or dangerous beyond the current task's scope \u2014 e.g. force-pushing, deleting broad swaths of the filesystem, modifying system-level configuration, or anything risking irreversible data loss or affecting systems outside this workspace."
331094
+ };
331095
+ async function classifyShellRisk(cmd) {
331096
+ try {
331097
+ const res = await askJev_default({
331098
+ state: cmd,
331099
+ model: "jev-latest",
331100
+ questions: {
331101
+ risk_tier: {
331102
+ type: "choice",
331103
+ instructions: "Classify the risk tier of running this exact shell command in an automated coding agent session.",
331104
+ criteria: SHELL_RISK_CRITERIA
331105
+ }
331106
+ }
331107
+ });
331108
+ const riskTier = res.answers.risk_tier;
331109
+ const tier = riskTier?.choice ?? "confirm";
331110
+ return { tier, confidence: riskTier?.confidence ?? 0 };
331111
+ } catch {
331112
+ return { tier: "confirm", confidence: 0 };
331113
+ }
331114
+ }
331115
+
331116
+ // src/tools/harness-jev-next-step-prompt.ts
331117
+ var DEFAULT_JEV_MODEL = "jev-latest";
331118
+ function clip2(s2, max) {
331119
+ if (!s2)
331120
+ return "";
331121
+ return s2.length <= max ? s2 : `${s2.slice(0, max)}
331122
+ \u2026[truncated]`;
331123
+ }
331124
+ async function classifyNextStep(input2) {
331125
+ const {
331126
+ taskDescription,
331127
+ repoMap,
331128
+ repoContext = "",
331129
+ progressSummary,
331130
+ recentContextTail,
331131
+ step,
331132
+ maxSteps,
331133
+ isPlanMode,
331134
+ onTaskBranch,
331135
+ jevModel = DEFAULT_JEV_MODEL
331136
+ } = input2;
331137
+ const state = [
331138
+ `TASK:
331139
+ ${clip2(taskDescription, 1200)}`,
331140
+ `STEP: ${step}/${maxSteps}`,
331141
+ `PLAN_MODE: ${isPlanMode}`,
331142
+ `ON_TASK_BRANCH: ${onTaskBranch}`,
331143
+ `PROGRESS:
331144
+ ${clip2(progressSummary, 800) || "(none)"}`,
331145
+ `RECENT_CONTEXT:
331146
+ ${clip2(recentContextTail, 1500) || "(none)"}`,
331147
+ `REPO_MAP (paths):
331148
+ ${clip2(repoMap, 2500) || "(empty)"}`,
331149
+ repoContext ? `REPO_CONTEXT:
331150
+ ${clip2(repoContext, 800)}` : ""
331151
+ ].filter(Boolean).join(`
331152
+
331153
+ `);
331154
+ try {
331155
+ const res = await askJev_default({
331156
+ state,
331157
+ model: jevModel,
331158
+ questions: {
331159
+ confirm_gate: {
331160
+ type: "choice",
331161
+ instructions: "Human approval gate for shell. Pick exactly one. " + "denied = a prior shell proposal was followed by [DENIED BY USER]. " + "pending = a requires_confirmation shell was proposed and neither [APPROVED BY USER] nor [DENIED BY USER] appeared yet. " + "clear = neither of the above.",
331162
+ criteria: {
331163
+ clear: "No outstanding confirmation; safe to continue.",
331164
+ pending: "Waiting on user approve/deny for a previous shell command.",
331165
+ denied: "User denied a prior shell command; do not re-propose the same effect."
331166
+ }
331167
+ },
331168
+ is_conversational_only: {
331169
+ type: "choice",
331170
+ instructions: "Is this only a greeting, chat, or question answerable without reading or changing files? " + "If repo context already answers a codebase question with no file I/O needed, choose yes.",
331171
+ criteria: {
331172
+ yes: "No read/write/edit/append/shell needed this turn.",
331173
+ no: "Task needs tools or git lifecycle."
331174
+ }
331175
+ },
331176
+ git_phase: {
331177
+ type: "choice",
331178
+ instructions: "Pick the single next git-lifecycle phase. File work is only allowed in task_work and only when ON_TASK_BRANCH is true. " + "Before ever creating a new task branch, always look for an existing branch that already covers this fix/task. " + 'Matching is by context, not exact string \u2014 a branch named "fix/login-timeout" should be picked up for a request about the login timing out, even if the wording differs. ' + "If a reasonably close match exists, checkout that branch and continue work there instead of spawning a new one. " + "Only create a new branch when nothing existing is a plausible match. " + "done only if merge was approved and succeeded. " + "blocked_on_confirm if confirm_gate is pending or a denied merge/install blocks progress.",
331179
+ criteria: {
331180
+ check_repo: "Unknown if workspace is a git repo \u2014 run rev-parse style check.",
331181
+ init_repo: "Not a git repo yet \u2014 init base branch.",
331182
+ find_branch: "Repo exists but not yet on a task branch \u2014 list local (and remote-tracked) branches and check whether any of them already relates to this task/fix by topic, name, or recent commit context. Do this before deciding to create anything.",
331183
+ checkout_branch: "find_branch located an existing branch that plausibly matches this task \u2014 check it out and resume work there. Treat it as the task branch going forward even if its name does not follow the agent/<slug> convention.",
331184
+ create_branch: "find_branch found no existing branch that reasonably matches this task \u2014 create a new agent/<slug> branch from base.",
331185
+ task_work: "On task branch (or plan-mode reads); do task file/shell work.",
331186
+ commit: "Task file work done; need local commit on task branch.",
331187
+ merge: "Committed; need merge back to base (always needs confirmation).",
331188
+ done: "Merge approved and succeeded; task complete.",
331189
+ blocked_on_confirm: "Must wait or halt due to confirm gate."
331190
+ }
331191
+ },
331192
+ tool: {
331193
+ type: "choice",
331194
+ instructions: "Single next tool. Prefer none when conversational, pending confirm, done, or when only waiting. " + "In plan mode only read or none. " + "New file not in REPO_MAP \u2192 write (no read first). " + "Existing file not yet in context \u2192 read first. " + "Small localized change with contents in context \u2192 edit. " + "Full rewrite \u2192 write. Pure end addition \u2192 append. " + "Install/test/build/git commands \u2192 shell. Never shell to edit file contents.",
331195
+ criteria: {
331196
+ read: "Read an existing file into context.",
331197
+ write: "Create new file or full-file rewrite.",
331198
+ edit: "Small exact substring replace; contents already in context.",
331199
+ append: "Append only to end of existing/new file.",
331200
+ shell: "Run a command (git, test, install, etc.).",
331201
+ none: "No tool this step (done, wait, conversational, or cannot proceed)."
331202
+ }
331203
+ },
331204
+ shell_risk: {
331205
+ type: "choice",
331206
+ instructions: "If tool is not shell, choose n/a. Otherwise classify the *intended* command class from the task (not a literal cmd). " + "AUTO = read-only or local git/test/build. CONFIRM = install or merge to base. BLOCKED = destructive/unsafe (rm -rf, force push, curl|sh, secrets exfil, etc.).",
331207
+ criteria: {
331208
+ auto: "Read-only or locally reversible; no network package install; local git only.",
331209
+ confirm: "Side-effect needing human sign-off (dependency install, merge to base).",
331210
+ blocked: "Must never run; refuse or suggest narrower auto alternative.",
331211
+ "n/a": "Tool is not shell."
331212
+ }
331213
+ },
331214
+ file_in_map: {
331215
+ type: "choice",
331216
+ instructions: "If the next file action targets a path, does that path already appear in REPO_MAP? " + "If tool is shell or none, choose unknown.",
331217
+ criteria: {
331218
+ yes: "Path listed in REPO_MAP.",
331219
+ no: "New path / not in map.",
331220
+ unknown: "Not a file tool or path unclear."
331221
+ }
331222
+ },
331223
+ contents_in_context: {
331224
+ type: "choice",
331225
+ instructions: "For a file mutation, are sufficient current contents already present in RECENT_CONTEXT " + "(e.g. a prior read or [Verified current contents])? If not a file mutation, unknown.",
331226
+ criteria: {
331227
+ yes: "Enough contents already in context to edit safely.",
331228
+ no: "Need read (or write-from-scratch) before mutating.",
331229
+ unknown: "Not applicable."
331230
+ }
331231
+ },
331232
+ need_more_steps: {
331233
+ type: "choice",
331234
+ instructions: "Will finishing the overall task clearly need more turns after this one under the normal budget? " + "Also, if the agent's last message implies it intends to perform more work, choose yes even if it thinks it is done.",
331235
+ criteria: {
331236
+ yes: "More steps needed after this action or agent prose implies intent to continue.",
331237
+ no: "This is likely the last needed action or a terminal wait/done."
331238
+ }
331239
+ },
331240
+ confidence: {
331241
+ type: "score",
331242
+ instructions: "How confident are you in the tool + git_phase combination for this step?",
331243
+ criteria: {
331244
+ "0": "Guessing / contradictory signals.",
331245
+ "50": "Plausible but ambiguous.",
331246
+ "100": "Clear next action."
331247
+ }
331248
+ }
331249
+ }
331250
+ });
331251
+ const a2 = res.answers;
331252
+ const confirmGate = a2.confirm_gate?.choice ?? "clear";
331253
+ let gitPhase = a2.git_phase?.choice ?? "task_work";
331254
+ let tool = a2.tool?.choice ?? "none";
331255
+ let shellRisk = a2.shell_risk?.choice ?? "n/a";
331256
+ const fileInMapChoice = a2.file_in_map?.choice;
331257
+ const contentsChoice = a2.contents_in_context?.choice;
331258
+ const needMore = a2.need_more_steps?.choice === "yes";
331259
+ const conversational = a2.is_conversational_only?.choice === "yes";
331260
+ const conf = typeof a2.confidence?.score === "number" ? a2.confidence.score / 100 : a2.confirm_gate?.confidence ?? 0.5;
331261
+ if (confirmGate === "pending") {
331262
+ gitPhase = "blocked_on_confirm";
331263
+ tool = "none";
331264
+ shellRisk = "n/a";
331265
+ } else if (confirmGate === "denied" && tool === "shell" && shellRisk !== "auto") {
331266
+ tool = "none";
331267
+ gitPhase = "blocked_on_confirm";
331268
+ }
331269
+ if (conversational && confirmGate === "clear") {
331270
+ tool = "none";
331271
+ gitPhase = "done";
331272
+ }
331273
+ if (isPlanMode && tool !== "read" && tool !== "none") {
331274
+ tool = "read";
331275
+ }
331276
+ if (!onTaskBranch && (tool === "write" || tool === "edit" || tool === "append")) {
331277
+ tool = "shell";
331278
+ gitPhase = gitPhase === "task_work" ? "create_branch" : gitPhase;
331279
+ shellRisk = "auto";
331280
+ }
331281
+ if (tool === "shell" && shellRisk === "blocked") {
331282
+ tool = "none";
331283
+ }
331284
+ if (gitPhase === "merge") {
331285
+ tool = "shell";
331286
+ shellRisk = "confirm";
331287
+ }
331288
+ if (gitPhase === "done") {
331289
+ tool = "none";
331290
+ }
331291
+ const requiresConfirmation = tool === "shell" && (shellRisk === "confirm" || gitPhase === "merge");
331292
+ const fileExistsInMap = fileInMapChoice === "yes" ? true : fileInMapChoice === "no" ? false : null;
331293
+ const contentsAlreadyInContext = contentsChoice === "yes" ? true : contentsChoice === "no" ? false : null;
331294
+ const canSkipDecisionLlm = tool === "none" || false;
331295
+ const reasoning = buildShortReasoning({
331296
+ confirmGate,
331297
+ gitPhase,
331298
+ tool,
331299
+ shellRisk,
331300
+ conversational,
331301
+ isPlanMode
331302
+ });
331303
+ return {
331304
+ unavailable: false,
331305
+ tool,
331306
+ gitPhase,
331307
+ confirmGate,
331308
+ shellRisk,
331309
+ requiresConfirmation,
331310
+ needMoreSteps: needMore || confirmGate === "pending",
331311
+ fileExistsInMap,
331312
+ contentsAlreadyInContext,
331313
+ confidence: conf,
331314
+ reasoning,
331315
+ canSkipDecisionLlm
331316
+ };
331317
+ } catch {
331318
+ return {
331319
+ unavailable: true,
331320
+ tool: "none",
331321
+ gitPhase: "task_work",
331322
+ confirmGate: "clear",
331323
+ shellRisk: "n/a",
331324
+ requiresConfirmation: false,
331325
+ needMoreSteps: false,
331326
+ fileExistsInMap: null,
331327
+ contentsAlreadyInContext: null,
331328
+ confidence: 0,
331329
+ reasoning: "Jev unavailable; fall back to user-model router.",
331330
+ canSkipDecisionLlm: false
331331
+ };
331332
+ }
331333
+ }
331334
+ function buildShortReasoning(p) {
331335
+ if (p.confirmGate === "pending")
331336
+ return "Awaiting user decision on pending command.";
331337
+ if (p.confirmGate === "denied")
331338
+ return "Prior command denied; not re-proposing same effect.";
331339
+ if (p.conversational)
331340
+ return "Conversational only; no tools.";
331341
+ if (p.gitPhase === "done")
331342
+ return "Git merge complete; task done.";
331343
+ if (p.isPlanMode)
331344
+ return `Plan mode: ${p.tool}.`;
331345
+ if (p.tool === "shell")
331346
+ return `Shell (${p.shellRisk}); git phase ${p.gitPhase}.`;
331347
+ return `Next: ${p.tool} (git phase ${p.gitPhase}).`;
331348
+ }
331349
+
330945
331350
  // src/tools/pipe.ts
330946
331351
  var defaultIsRetryable = (err) => {
330947
331352
  const msg = err instanceof Error ? err.message : String(err);
@@ -340388,269 +340793,223 @@ async function webSearch(query, budget, limit = DEFAULT_LIMIT) {
340388
340793
  }
340389
340794
  }
340390
340795
 
340391
- // 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;
340796
+ // src/tools/harness-verify.ts
340797
+ var VERIFY_MAX_REVISIONS = 1;
340798
+ var VERIFY_MIN_CHARS = 400;
340799
+ var AUTO_FULL_MIN_CHARS = 1500;
340800
+ var VERIFY_CONTENT_CHAR_CAP = 24000;
340801
+ var VERIFY_SOURCE_CHAR_CAP = 12000;
340802
+ var VERIFIER_MAX_TOKENS = 600;
340803
+ function formatFindings(findings) {
340804
+ return findings.map((f2, i2) => `${i2 + 1}. ${f2.issue}${f2.evidence ? ` (${f2.evidence})` : ""}`).join(`
340805
+ `);
340447
340806
  }
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
- }
340807
+ var TOOL_TAG_RE = /<tool_call\b|<function=|<parameter=|<tool_use\b|<file_path>/i;
340808
+ var ELISION_RE = /^[ \t]*(?:\/\/|#|\/\*|<!--)[ \t]*(?:\.{3}|\u2026)[ \t]*(?:rest|existing|remaining|other|previous|unchanged|same)\b|^[ \t]*(?:\/\/|#|\/\*)[ \t]*(?:rest of|remaining|existing)[ \t]+(?:the[ \t]+)?(?:code|file|implementation|content)\b/im;
340809
+ var LOADERS = {
340810
+ ".ts": "ts",
340811
+ ".tsx": "tsx",
340812
+ ".js": "js",
340813
+ ".mjs": "js",
340814
+ ".cjs": "js",
340815
+ ".jsx": "jsx"
340816
+ };
340817
+ function extOf(file2) {
340818
+ const dot = file2.lastIndexOf(".");
340819
+ return dot > file2.lastIndexOf("/") ? file2.slice(dot).toLowerCase() : "";
340457
340820
  }
340458
- function decisionPackageText(json3) {
340459
- let parsed;
340821
+ function syntaxError(file2, content) {
340822
+ const loader = LOADERS[extOf(file2)];
340823
+ if (!loader)
340824
+ return null;
340825
+ const Transpiler = globalThis.Bun?.Transpiler;
340826
+ if (!Transpiler)
340827
+ return null;
340460
340828
  try {
340461
- parsed = JSON.parse(json3);
340462
- } catch {
340463
- return salvageReasoning(json3);
340829
+ new Transpiler({ loader }).transformSync(content);
340830
+ return null;
340831
+ } catch (e3) {
340832
+ const errs = e3.errors;
340833
+ const msg = Array.isArray(errs) && errs.length > 0 ? errs.slice(0, 3).map((x) => x.message ?? String(x)).join("; ") : e3 instanceof Error ? e3.message : String(e3);
340834
+ return clipText(msg, 300);
340464
340835
  }
340465
- if (!isDecisionShaped(parsed))
340466
- return json3;
340467
- return typeof parsed.reasoning === "string" ? parsed.reasoning.trim() : "";
340468
340836
  }
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;
340837
+ function deterministicChecks(file2, content, mode) {
340838
+ if (content.trim() === "")
340839
+ return [{ issue: "content is empty", hard: true }];
340840
+ const out = [];
340841
+ const ext = extOf(file2);
340842
+ if (TOOL_TAG_RE.test(content)) {
340843
+ out.push({ issue: "content contains leaked tool-call markup", hard: true });
340474
340844
  }
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;
340845
+ if (/^```/.test(content.trimStart()) && ext !== ".md" && ext !== ".mdx") {
340846
+ out.push({ issue: "content starts with a markdown code fence", hard: true });
340847
+ }
340848
+ if (mode === "write") {
340849
+ const elision = DOC_EXT.has(ext) ? null : ELISION_RE.exec(content);
340850
+ if (elision) {
340851
+ out.push({
340852
+ issue: "content contains an elision placeholder (e.g. 'rest of file unchanged'); a write must contain the complete file",
340853
+ evidence: clipText(elision[0].trim(), 80)
340854
+ });
340855
+ }
340856
+ if (ext === ".json" && !/(^|\/)(tsconfig|jsconfig)[^/]*\.json$/.test(file2)) {
340857
+ try {
340858
+ JSON.parse(content);
340859
+ } catch (e3) {
340860
+ out.push({
340861
+ issue: "invalid JSON",
340862
+ evidence: clipText(e3 instanceof Error ? e3.message : String(e3), 200),
340863
+ hard: true
340864
+ });
340520
340865
  }
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
340866
  }
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;
340867
+ const se = syntaxError(file2, content);
340868
+ if (se)
340869
+ out.push({ issue: "syntax error", evidence: se, hard: true });
340570
340870
  }
340571
340871
  return out;
340572
340872
  }
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 });
340873
+ function parseVerifyMode(v) {
340874
+ const x = (v ?? "").trim().toLowerCase();
340875
+ if (x === "off" || x === "0" || x === "false")
340876
+ return "off";
340877
+ if (x === "checks")
340878
+ return "checks";
340879
+ if (x === "full" || x === "1" || x === "true")
340880
+ return "full";
340881
+ return "auto";
340882
+ }
340883
+ var DOC_EXT = new Set([".md", ".mdx", ".txt", ".rst"]);
340884
+ function chooseVerifyLevel(a2) {
340885
+ if (a2.mode === "checks" || a2.mode === "full")
340886
+ return a2.mode;
340887
+ const shrinks = a2.tool === "write" && a2.currentFile.length >= 400 && a2.content.length < a2.currentFile.length * 0.5;
340888
+ if (shrinks)
340889
+ return "full";
340890
+ if (DOC_EXT.has(extOf(a2.file)))
340891
+ return "checks";
340892
+ const overwrite = a2.tool === "write" && a2.currentFile.trim() !== "";
340893
+ if (overwrite && a2.content.length >= VERIFY_MIN_CHARS)
340894
+ return "full";
340895
+ if (a2.content.length >= AUTO_FULL_MIN_CHARS)
340896
+ return "full";
340897
+ return "checks";
340898
+ }
340899
+ var VERIFIER_SYSTEM = `You are a strict reviewer. You did NOT write the proposed content below; your only job is to find concrete, checkable problems in it. Do not praise it.
340900
+ ` + `Check ONLY these:
340901
+ ` + `1. It does what the TASK and INTENT ask. Missing or contradicted requirements count.
340902
+ ` + `2. It does not reference imports, functions, files, fields or APIs that appear in neither the SOURCES nor the standard library / well-known packages.
340903
+ ` + `3. For a whole-file REPLACE: it does not silently drop existing behaviour from CURRENT FILE that the task did not ask to remove.
340904
+ ` + `4. It is complete: no TODO stubs standing in for required logic, no "rest unchanged" elisions, no truncated ending.
340905
+ ` + `Do NOT fail for style, naming, formatting or optional improvements. If you cannot tell from the SOURCES, pass.
340906
+ ` + `Respond with ONLY one JSON object: {"pass": boolean, "failures": [{"issue": string, "evidence": string}]}. ` + `"failures" is empty when pass is true. Each evidence is a short quote or location from the content.`;
340907
+ async function modelVerify(a2, content) {
340908
+ const target = a2.mode === "append" ? "the content below will be APPENDED to the end of the file" : a2.currentFile ? "the content below will REPLACE the whole file" : "this is a NEW file";
340909
+ const sources = a2.sources.map((s2, i2) => `[Source ${i2 + 1}]
340910
+ ${clipText(s2, 5000)}`).join(`
340911
+
340912
+ `);
340913
+ const user = `TASK (the user's request):
340914
+ ${clipText(a2.task, 1500)}
340915
+
340916
+ ` + `INTENT of this step:
340917
+ ${clipText(a2.intent, 500)}
340918
+
340919
+ ` + `TARGET FILE: ${a2.file} (${target})
340920
+
340921
+ ` + (a2.currentFile ? `CURRENT FILE ON DISK:
340922
+ ${clipText(a2.currentFile, VERIFY_SOURCE_CHAR_CAP / 2)}
340923
+
340924
+ ` : "") + (sources ? `SOURCES (files the agent read):
340925
+ ${sources}
340926
+
340927
+ ` : "") + `PROPOSED CONTENT:
340928
+ ${clipText(content, VERIFY_CONTENT_CHAR_CAP)}`;
340929
+ let raw = "";
340930
+ try {
340931
+ raw = await a2.chat([
340932
+ { role: "system", content: VERIFIER_SYSTEM },
340933
+ { role: "user", content: user }
340934
+ ], VERIFIER_MAX_TOKENS);
340935
+ } catch {
340936
+ return { available: false, failures: [] };
340581
340937
  }
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);
340938
+ const json3 = extractFirstJsonObject(raw);
340939
+ if (!json3)
340940
+ return { available: false, failures: [] };
340941
+ let parsed;
340942
+ try {
340943
+ parsed = JSON.parse(json3);
340944
+ } catch {
340945
+ return { available: false, failures: [] };
340946
+ }
340947
+ if (!isRecord2(parsed) || typeof parsed.pass !== "boolean") {
340948
+ return { available: false, failures: [] };
340949
+ }
340950
+ if (parsed.pass)
340951
+ return { available: true, failures: [] };
340952
+ const failures = (Array.isArray(parsed.failures) ? parsed.failures : []).filter(isRecord2).map((f2) => ({
340953
+ issue: String(f2.issue ?? "").trim().slice(0, 300),
340954
+ evidence: f2.evidence ? String(f2.evidence).trim().slice(0, 300) : undefined
340955
+ })).filter((f2) => f2.issue !== "").slice(0, 5);
340956
+ return { available: true, failures };
340957
+ }
340958
+ async function verifyBeforeWrite(a2) {
340959
+ let content = a2.content;
340960
+ let revisions = 0;
340961
+ let verifierCalls = 0;
340962
+ let verifierUnavailable = false;
340963
+ let findings = [];
340964
+ let cleanDraft = null;
340965
+ let cleanDraftFindings = [];
340966
+ const result = (c2, status, f2) => ({
340967
+ content: c2,
340968
+ status,
340969
+ findings: f2,
340970
+ revisions,
340971
+ verifierCalls,
340972
+ verifierUnavailable
340973
+ });
340974
+ for (;; ) {
340975
+ const det = deterministicChecks(a2.file, content, a2.mode);
340976
+ findings = det;
340977
+ if (a2.level === "full" && det.length === 0 && !a2.isCancelled?.()) {
340978
+ verifierCalls++;
340979
+ const v = await modelVerify(a2, content);
340980
+ if (v.available)
340981
+ findings = v.failures;
340982
+ else
340983
+ verifierUnavailable = true;
340592
340984
  }
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)
340985
+ if (!findings.some((f2) => f2.hard)) {
340986
+ cleanDraft = content;
340987
+ cleanDraftFindings = findings;
340988
+ }
340989
+ if (findings.length === 0)
340990
+ return result(content, "verified", []);
340991
+ if (revisions >= VERIFY_MAX_REVISIONS || a2.isCancelled?.())
340992
+ break;
340993
+ revisions++;
340994
+ const first = findings[0];
340995
+ a2.log?.(`Revising ${a2.file}: ${first?.issue ?? "failed check"}${first?.evidence ? ` [${first.evidence}]` : ""}`);
340996
+ let revised = "";
340997
+ try {
340998
+ revised = await a2.revise(content, findings);
340999
+ } catch {}
341000
+ if (revised.trim() === "" || revised === content)
340600
341001
  break;
341002
+ content = revised;
340601
341003
  }
340602
- return {
340603
- questions,
340604
- ignore: parsed.ignore === true,
340605
- reasoning: typeof parsed.reasoning === "string" ? parsed.reasoning.slice(0, 1000) : ""
340606
- };
341004
+ if (findings.some((f2) => f2.hard)) {
341005
+ if (cleanDraft !== null)
341006
+ return result(cleanDraft, "unverified", cleanDraftFindings);
341007
+ return result(content, "blocked", findings);
341008
+ }
341009
+ return result(content, "unverified", findings);
340607
341010
  }
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
341011
 
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
- }
341012
+ // src/tools/harness-orchestrator.ts
340654
341013
  function estimateMessagesTokens(messages) {
340655
341014
  let textTokens = 0;
340656
341015
  let media = 0;
@@ -340672,140 +341031,6 @@ function estimateMessagesTokens(messages) {
340672
341031
  }
340673
341032
  return textTokens + media * MEDIA_PART_TOKENS + messages.length * MESSAGE_OVERHEAD_TOKENS;
340674
341033
  }
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
341034
 
340810
341035
  class HarnessOrchestrator {
340811
341036
  REPO_MAP = "";
@@ -340831,6 +341056,124 @@ class HarnessOrchestrator {
340831
341056
  this.totalUsedContext = countTokens2(this.buildDecisionPrompt(false));
340832
341057
  }
340833
341058
  filePluginHandler = null;
341059
+ static VERIFY_MODE = parseVerifyMode("full");
341060
+ verifyStats = {
341061
+ checked: 0,
341062
+ full: 0,
341063
+ revised: 0,
341064
+ verifierCalls: 0,
341065
+ unverified: 0,
341066
+ blocked: 0
341067
+ };
341068
+ getVerifyStats() {
341069
+ return { ...this.verifyStats };
341070
+ }
341071
+ async* preWriteGate(ctx, a2) {
341072
+ const mode = HarnessOrchestrator.VERIFY_MODE;
341073
+ if (mode === "off") {
341074
+ return {
341075
+ content: a2.content,
341076
+ status: "verified",
341077
+ findings: [],
341078
+ revisions: 0,
341079
+ verifierCalls: 0,
341080
+ verifierUnavailable: false
341081
+ };
341082
+ }
341083
+ const currentFile = existsSync(a2.filePath) ? await readFileContent(a2.filePath).catch(() => "") : "";
341084
+ const level = chooseVerifyLevel({
341085
+ mode,
341086
+ file: a2.displayPath,
341087
+ tool: a2.tool,
341088
+ content: a2.content,
341089
+ currentFile
341090
+ });
341091
+ const lines = [];
341092
+ const out = yield* this.pump(verifyBeforeWrite({
341093
+ file: a2.displayPath,
341094
+ mode: a2.tool,
341095
+ content: a2.content,
341096
+ task: ctx.userTask,
341097
+ intent: a2.intent,
341098
+ currentFile,
341099
+ sources: level === "full" ? this.verifySources(ctx, a2.displayPath) : [],
341100
+ level,
341101
+ isCancelled: this.isCancelled,
341102
+ log: (l) => lines.push(l),
341103
+ chat: (messages, maxTokens) => this.routedChat(a2.verifierModelId, messages, 0.1, maxTokens).then((r) => r.content),
341104
+ revise: (draft, findings) => this.reviseContent(ctx, a2, draft, findings)
341105
+ }));
341106
+ const s2 = this.verifyStats;
341107
+ s2.checked++;
341108
+ if (level === "full")
341109
+ s2.full++;
341110
+ s2.revised += out.revisions;
341111
+ s2.verifierCalls += out.verifierCalls;
341112
+ if (out.status === "unverified")
341113
+ s2.unverified++;
341114
+ if (out.status === "blocked")
341115
+ s2.blocked++;
341116
+ for (const l of lines)
341117
+ yield { delta: `- ${l}
341118
+ ` };
341119
+ if (out.status === "unverified") {
341120
+ yield {
341121
+ delta: `- ${a2.displayPath} did not pass every check (${out.findings[0]?.issue ?? "see findings"}); writing it anyway and flagging it
341122
+
341123
+ `
341124
+ };
341125
+ }
341126
+ if (DEBUG_TRACE) {
341127
+ yield {
341128
+ delta: `[debug] pre-write gate: ${out.status}, ${out.verifierCalls} verifier call(s), ${out.revisions} revision(s)
341129
+ `
341130
+ };
341131
+ }
341132
+ return out;
341133
+ }
341134
+ async reviseContent(ctx, a2, draft, findings) {
341135
+ const res = await this.routedChat(ctx.modelId, [
341136
+ {
341137
+ role: "system",
341138
+ content: `RAW CODE OUTPUT MODE. Respond with ONLY the raw ${a2.tool === "append" ? "text to append" : "complete file"}. ` + `No JSON, no markdown fences, no commentary. The first character of your response must be the first character of the content.`
341139
+ },
341140
+ ...this.memoryMessages(),
341141
+ {
341142
+ role: "user",
341143
+ content: `Task: ${clipText(ctx.userTask, 3000)}
341144
+ ` + `Target file: ${a2.targetFile}
341145
+
341146
+ ` + `Your previous draft was rejected for these problems:
341147
+ ${formatFindings(findings)}
341148
+
341149
+ ` + `Previous draft:
341150
+ ${draft}
341151
+
341152
+ ` + `Return the corrected ${a2.tool === "append" ? "text to append" : "complete file"}. ` + `Fix only the listed problems and keep everything else unchanged.`
341153
+ }
341154
+ ], 0.2, CONTENT_MAX_TOKENS);
341155
+ if (res.content.startsWith("AGENT LOOP FAILED"))
341156
+ return "";
341157
+ let text = res.content.replace(/^```[a-z]*\n?|\n?```$/g, "").trim();
341158
+ const { unwrapped, wasWrapper } = tryUnwrapDecisionJson(text);
341159
+ if (wasWrapper)
341160
+ text = (unwrapped ?? "").trim();
341161
+ return text;
341162
+ }
341163
+ verifySources(ctx, displayPath) {
341164
+ const out = [];
341165
+ for (let i2 = ctx.contextMessages.length - 1;i2 >= 0 && out.length < 3; i2--) {
341166
+ const c2 = ctx.contextMessages[i2]?.content;
341167
+ if (typeof c2 !== "string" || !c2.startsWith("[Tool Output \u2014 read on "))
341168
+ continue;
341169
+ if (c2.includes(`read on ${displayPath}]`))
341170
+ continue;
341171
+ if (c2.includes("elided to save context"))
341172
+ continue;
341173
+ out.push(c2.slice(0, 5000));
341174
+ }
341175
+ return out;
341176
+ }
340834
341177
  registerFileHandler(handler) {
340835
341178
  this.filePluginHandler = handler;
340836
341179
  }
@@ -341261,7 +341604,7 @@ ${input2.answer || "(none)"}`
341261
341604
  const out = `${MEMORY_HEADER}
341262
341605
  ${body}
341263
341606
  `;
341264
- await mkdir(dirname(this.memoryPath), { recursive: true });
341607
+ await mkdir(dirname2(this.memoryPath), { recursive: true });
341265
341608
  await writeFileContent(this.memoryPath, out, { overwrite: true });
341266
341609
  this.MEMORY = out.trim();
341267
341610
  }
@@ -341834,7 +342177,7 @@ ${attachmentNotes.join(`
341834
342177
  return;
341835
342178
  const conversationalSystem = `${conversationalPrompt}
341836
342179
 
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`;
342180
+ 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
342181
  let streamed = false;
341839
342182
  try {
341840
342183
  const stream4 = this.routedChatStream(ctx2.modelId, [
@@ -342142,6 +342485,19 @@ ${toolOutput}`
342142
342485
  const grantSteps = (n3 = 1) => {
342143
342486
  extraSteps = Math.min(extraSteps + n3, HarnessOrchestrator.MAX_EXTRA_STEPS);
342144
342487
  };
342488
+ 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):
342489
+ {
342490
+ "tool": "read" | "write" | "edit" | "append" | "shell" | "web_search" | "bg_start" | "bg_list" | "bg_logs" | "bg_kill" | "ask_user_question" | null,
342491
+ "targetFile": string | null,
342492
+ "content": string | null,
342493
+ "target": string | null,
342494
+ "replacement": string | null,
342495
+ "cmd": string | null,
342496
+ "requires_confirmation": boolean,
342497
+ "reasoning": string,
342498
+ "need_more_steps": boolean
342499
+ }
342500
+ Do not include prose before or after the JSON. Output ONLY the JSON object.`;
342145
342501
  const isRepeat = (signature) => {
342146
342502
  if (signature === lastActionSignature)
342147
342503
  repeatCount++;
@@ -342159,6 +342515,10 @@ ${toolOutput}`
342159
342515
  state.bgSatisfied.add(tool);
342160
342516
  };
342161
342517
  let bgNudges = 0;
342518
+ const compactAndBreak = function* () {
342519
+ this.compactContext(ctx, state);
342520
+ return;
342521
+ }.bind(this);
342162
342522
  for (let step = 1;step <= maxSteps + extraSteps; step++) {
342163
342523
  const budget = maxSteps + extraSteps;
342164
342524
  const stepsRemaining = budget - step + 1;
@@ -342185,7 +342545,7 @@ ${toolOutput}`
342185
342545
  });
342186
342546
  await this.refreshRepoMapIfDirty();
342187
342547
  const decisionSystemPrompt = this.buildDecisionPrompt(ctx.gitDetail);
342188
- const progressSummary = ctx.completedActions.length > 0 ? ctx.completedActions.map((a2) => `- ${a2}`).join(`
342548
+ const progressSummary = ctx.completedActions.length > 0 ? ctx.completedActions.map((a2) => `- ${a2.slice(0, 160)}`).join(`
342189
342549
  `) : "";
342190
342550
  const jevTail = this.jevTail(ctx, lastAgentProse);
342191
342551
  const jevChoice = bgPending() || !this.isAnswered ? JEV_BYPASS : await this.classify(ctx, {
@@ -342280,32 +342640,21 @@ Please try a different model(retry in 30 seconds) or check with your provider.
342280
342640
 
342281
342641
  `
342282
342642
  };
342643
+ this.compactContext(ctx, state);
342283
342644
  break;
342284
342645
  }
342285
342646
  const { hint } = await classifyMalformedOutput(res.content);
342286
342647
  const toolUseBlockCount = (res.content.match(/<tool_call\b|<tool_use\b/gi) ?? []).length;
342287
342648
  ctx.contextMessages.push({
342288
342649
  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}".` : "")
342650
+ 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
342651
  });
342303
342652
  continue;
342304
342653
  }
342305
342654
  const bgParsed = isRecord2(parsed) ? parseBgDecision(parsed) : null;
342306
342655
  if (bgParsed) {
342307
342656
  jsonFailureCount = 0;
342308
- lastAgentProse = bgParsed.reasoning;
342657
+ lastAgentProse = (bgParsed.reasoning ?? "").slice(0, 1000);
342309
342658
  if (!this.isAnswered && bgParsed.tool === "bg_start") {
342310
342659
  const held = this.recordToolCall(step, {
342311
342660
  tool: bgParsed.tool,
@@ -342321,22 +342670,27 @@ Do not include prose before or after the JSON. Output ONLY the JSON object.` + (
342321
342670
  role: "user",
342322
342671
  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
342672
  });
342673
+ this.compactContext(ctx, state);
342324
342674
  break;
342325
342675
  }
342326
342676
  const r = yield* this.runBgTool(ctx, state, step, bgParsed, isRepeat);
342327
342677
  if (r.ok)
342328
342678
  markBgDone(bgParsed.tool);
342329
- if (r.stop)
342679
+ if (r.stop) {
342680
+ this.compactContext(ctx, state);
342330
342681
  break;
342682
+ }
342331
342683
  continue;
342332
342684
  }
342333
342685
  const askParsed = parseAskDecision(parsed);
342334
342686
  if (askParsed) {
342335
342687
  jsonFailureCount = 0;
342336
- lastAgentProse = askParsed.reasoning;
342688
+ lastAgentProse = (askParsed.reasoning ?? "").slice(0, 1000);
342337
342689
  const r = yield* this.runAskTool(ctx, step, askParsed);
342338
- if (r.stop)
342690
+ if (r.stop) {
342691
+ this.compactContext(ctx, state);
342339
342692
  break;
342693
+ }
342340
342694
  continue;
342341
342695
  }
342342
342696
  let decision;
@@ -342352,6 +342706,7 @@ Do not include prose before or after the JSON. Output ONLY the JSON object.` + (
342352
342706
  I didn't quite get that am having this issue:${issueSummary},please retry in 30 seconds
342353
342707
  `
342354
342708
  };
342709
+ this.compactContext(ctx, state);
342355
342710
  break;
342356
342711
  }
342357
342712
  ctx.contextMessages.push({
@@ -342380,6 +342735,7 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
342380
342735
  role: "user",
342381
342736
  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
342737
  });
342738
+ this.compactContext(ctx, state);
342383
342739
  break;
342384
342740
  }
342385
342741
  if (!ctx.isPlanMode && decision.tool === "shell" && decision.cmd && LONG_RUNNING_SHELL_RE.test(decision.cmd)) {
@@ -342395,8 +342751,10 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
342395
342751
  const r = yield* this.runBgTool(ctx, state, step, rerouted, isRepeat);
342396
342752
  if (r.ok)
342397
342753
  markBgDone("bg_start");
342398
- if (r.stop)
342754
+ if (r.stop) {
342755
+ this.compactContext(ctx, state);
342399
342756
  break;
342757
+ }
342400
342758
  continue;
342401
342759
  }
342402
342760
  const call = decision.tool ? this.recordToolCall(step, {
@@ -342415,6 +342773,7 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
342415
342773
  role: "user",
342416
342774
  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
342775
  });
342776
+ this.compactContext(ctx, state);
342418
342777
  break;
342419
342778
  }
342420
342779
  if ((decision.tool === "write" || decision.tool === "append") && typeof decision.content === "string" && decision.content.length > HarnessOrchestrator.INLINE_CONTENT_CHAR_BUDGET) {
@@ -342423,8 +342782,10 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
342423
342782
  try {
342424
342783
  let verifiedContentOverride = null;
342425
342784
  if (!decision.tool) {
342426
- if (!this.isAnswered)
342785
+ if (!this.isAnswered) {
342786
+ this.compactContext(ctx, state);
342427
342787
  break;
342788
+ }
342428
342789
  if (bgPending() && bgNudges < 2) {
342429
342790
  bgNudges++;
342430
342791
  grantSteps();
@@ -342451,10 +342812,12 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
342451
342812
  });
342452
342813
  continue;
342453
342814
  }
342815
+ this.compactContext(ctx, state);
342454
342816
  break;
342455
342817
  }
342456
342818
  if (decision.tool !== "shell" && decision.tool !== "web_search" && !decision.targetFile) {
342457
342819
  this.settleToolCall(call, "skipped", "missing targetFile");
342820
+ this.compactContext(ctx, state);
342458
342821
  break;
342459
342822
  }
342460
342823
  if (decision.tool === "shell" && !decision.cmd) {
@@ -342522,6 +342885,7 @@ I didn't quite get that am having this issue:${issueSummary},please retry in 30
342522
342885
  role: "user",
342523
342886
  content: `[System] Same tool call issued twice in a row. Stopping tool loop.`
342524
342887
  });
342888
+ this.compactContext(ctx, state);
342525
342889
  break;
342526
342890
  }
342527
342891
  if (decision.tool === "shell" && decision.cmd) {
@@ -342645,6 +343009,43 @@ ${truncateForContext(priorFileSnapshot)}
342645
343009
  }
342646
343010
  }
342647
343011
  ctx.updateThinkingWord?.("toolLeveraging");
343012
+ if (HarnessOrchestrator.VERIFY_MODE !== "off" && (decision.tool === "write" || decision.tool === "append") && typeof decision.content === "string" && decision.content.trim() !== "") {
343013
+ const draft = decision.content;
343014
+ const gate = yield* this.preWriteGate(ctx, {
343015
+ tool: decision.tool,
343016
+ targetFile: decision.targetFile,
343017
+ displayPath,
343018
+ filePath,
343019
+ content: draft,
343020
+ intent: decision.reasoning ?? "",
343021
+ verifierModelId: decisionModelId
343022
+ });
343023
+ if (gate.status === "blocked") {
343024
+ this.settleToolCall(call, "refused", `pre-write check failed: ${gate.findings[0]?.issue ?? "invalid content"}`);
343025
+ yield { delta: `- Not written: ${displayPath} failed pre-write checks
343026
+
343027
+ ` };
343028
+ ctx.contextMessages.push({
343029
+ role: "user",
343030
+ content: `[System] The content for "${decision.targetFile}" failed checks and was NOT written:
343031
+ ${formatFindings(gate.findings)}
343032
+ Fix these and try again, or return {"tool": null}.`
343033
+ });
343034
+ continue;
343035
+ }
343036
+ if (gate.content !== draft) {
343037
+ decision = { ...decision, content: gate.content };
343038
+ verifiedContentOverride = null;
343039
+ }
343040
+ if (gate.status === "unverified") {
343041
+ ctx.contextMessages.push({
343042
+ role: "user",
343043
+ content: `[System] "${decision.targetFile}" is being written but did not pass every check:
343044
+ ${formatFindings(gate.findings)}
343045
+ Tell the user about these in your final answer.`
343046
+ });
343047
+ }
343048
+ }
342648
343049
  const breakOnSemicolons = (text, indent = " ") => {
342649
343050
  return text.replace(/; ?/g, `;
342650
343051
  ${indent}`);
@@ -342654,9 +343055,10 @@ ${indent}`);
342654
343055
  if (decision.tool === "read") {
342655
343056
  try {
342656
343057
  readRaw = await readFileContent(filePath);
342657
- const lineCount = countLines(readRaw);
343058
+ const totalLen = readRaw.length;
342658
343059
  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+"})
343060
+ const lineCount = countLines(readRaw);
343061
+ readPreview = breakOnSemicolons(`${head}${totalLen > 100 ? "\u2026" : ""} (${lineCount > 20 ? lineCount - 5 : lineCount} ${lineCount === 1 ? "line" : "lines+"})
342660
343062
  `);
342661
343063
  } catch {
342662
343064
  readPreview = "(unable to preview)";
@@ -342674,7 +343076,7 @@ ${indent}`);
342674
343076
  yield { delta: `${actionLabel}
342675
343077
  ` };
342676
343078
  if (decision.tool === "write" || decision.tool === "append") {
342677
- const dir = dirname(filePath);
343079
+ const dir = dirname2(filePath);
342678
343080
  if (!knownDirs.has(dir)) {
342679
343081
  try {
342680
343082
  await mkdir(dir, { recursive: true });
@@ -342690,8 +343092,9 @@ ${indent}`);
342690
343092
  toolOutput = "Failed to read file.";
342691
343093
  break;
342692
343094
  }
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;
343095
+ const totalLen = readRaw.length;
343096
+ toolOutput = totalLen > TOOL_OUTPUT_CHAR_CAP ? `${readRaw.slice(0, TOOL_OUTPUT_CHAR_CAP)}
343097
+ \u2026[truncated: file is ${totalLen} chars; use shell "sed -n 'START,ENDp' <file>" or "grep -n" to view other ranges]` : readRaw;
342695
343098
  readRaw = null;
342696
343099
  executedReads.add(readKey);
342697
343100
  break;
@@ -342773,7 +343176,7 @@ ${indent}`);
342773
343176
  toolOutput = `Exit code ${exitCode}
342774
343177
  ${truncateForContext(combined)}`;
342775
343178
  if (exitCode === 0) {
342776
- ctx.completedActions.push(`Ran: ${cmd}`);
343179
+ ctx.completedActions.push(`Ran: ${cmd.slice(0, 120)}`);
342777
343180
  if (HarnessOrchestrator.TREE_MUTATING_SHELL.test(cmd))
342778
343181
  this.markRepoMapDirty();
342779
343182
  if (HarnessOrchestrator.BRANCH_CREATE_PATTERN.test(cmd)) {
@@ -342832,7 +343235,7 @@ ${toolOutput}`
342832
343235
  });
342833
343236
  }
342834
343237
  toolOutput = "";
342835
- if (mutationSucceeded && decision.content && decision.content.length > 4000) {
343238
+ if (mutationSucceeded && decision.content) {
342836
343239
  decision = { ...decision, content: "" };
342837
343240
  }
342838
343241
  if (mutationSucceeded) {
@@ -342862,6 +343265,7 @@ ${toolOutput}`
342862
343265
  yield { delta: `
342863
343266
  [Tool loop stopped: unexpected error \u2014 ${errorMessage(e3)}]
342864
343267
  ` };
343268
+ this.compactContext(ctx, state);
342865
343269
  break;
342866
343270
  }
342867
343271
  }
@@ -344089,7 +344493,7 @@ var ModelProvider = ({ children }) => {
344089
344493
  const fetchCurrentSavedActiveAgent = async () => {
344090
344494
  try {
344091
344495
  const fileName = "ACTIVE_AGENT.json";
344092
- const storagePath = path6.join(os3.homedir(), ".basecode", fileName);
344496
+ const storagePath = path6.join(os3.homedir(), ".tensor", fileName);
344093
344497
  const agent2 = await readFromFile(storagePath);
344094
344498
  setActiveModel(JSON.parse(agent2));
344095
344499
  } catch (_e) {
@@ -344443,7 +344847,7 @@ var Header = () => {
344443
344847
  }, undefined, false, undefined, this),
344444
344848
  /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
344445
344849
  fg: t2.inkFaint,
344446
- children: "v0.1.3@beta-lite"
344850
+ children: "v0.1.5@beta-lite"
344447
344851
  }, undefined, false, undefined, this)
344448
344852
  ]
344449
344853
  }, undefined, true, undefined, this),
@@ -355432,7 +355836,7 @@ var Message_default = Index;
355432
355836
  // src/components/DiffView/index.tsx
355433
355837
  import { rmSync, statSync as statSync2 } from "fs";
355434
355838
  import { tmpdir } from "os";
355435
- import { dirname as dirname2, join as join3 } from "path";
355839
+ import { dirname as dirname3, join as join3 } from "path";
355436
355840
  var COLORS2 = {
355437
355841
  addBg: "#1a2e1a",
355438
355842
  delBg: "#3e1a1a",
@@ -355467,7 +355871,7 @@ var modifiedAt = (path19) => {
355467
355871
  return statSync2(join3(root, path19)).mtimeMs;
355468
355872
  } catch {
355469
355873
  try {
355470
- return statSync2(join3(root, dirname2(path19))).mtimeMs;
355874
+ return statSync2(join3(root, dirname3(path19))).mtimeMs;
355471
355875
  } catch {
355472
355876
  return 0;
355473
355877
  }