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.
- package/dist/index.js +1303 -899
- 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
|
-
|
|
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
|
|
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
|
|
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) =>
|
|
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
|
-
$ ${
|
|
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-
|
|
340392
|
-
var
|
|
340393
|
-
var
|
|
340394
|
-
var
|
|
340395
|
-
var
|
|
340396
|
-
var
|
|
340397
|
-
var
|
|
340398
|
-
|
|
340399
|
-
|
|
340400
|
-
|
|
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
|
-
|
|
340449
|
-
|
|
340450
|
-
|
|
340451
|
-
|
|
340452
|
-
|
|
340453
|
-
|
|
340454
|
-
|
|
340455
|
-
|
|
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
|
|
340459
|
-
|
|
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
|
-
|
|
340462
|
-
|
|
340463
|
-
|
|
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
|
|
340470
|
-
|
|
340471
|
-
|
|
340472
|
-
|
|
340473
|
-
|
|
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
|
-
|
|
340476
|
-
|
|
340477
|
-
|
|
340478
|
-
|
|
340479
|
-
|
|
340480
|
-
|
|
340481
|
-
|
|
340482
|
-
|
|
340483
|
-
|
|
340484
|
-
|
|
340485
|
-
|
|
340486
|
-
if (
|
|
340487
|
-
|
|
340488
|
-
|
|
340489
|
-
|
|
340490
|
-
|
|
340491
|
-
|
|
340492
|
-
|
|
340493
|
-
|
|
340494
|
-
|
|
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
|
-
|
|
340530
|
-
|
|
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
|
|
340574
|
-
|
|
340575
|
-
|
|
340576
|
-
|
|
340577
|
-
if (
|
|
340578
|
-
|
|
340579
|
-
|
|
340580
|
-
|
|
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
|
|
340583
|
-
|
|
340584
|
-
|
|
340585
|
-
|
|
340586
|
-
|
|
340587
|
-
|
|
340588
|
-
|
|
340589
|
-
|
|
340590
|
-
|
|
340591
|
-
|
|
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
|
-
|
|
340594
|
-
|
|
340595
|
-
|
|
340596
|
-
|
|
340597
|
-
|
|
340598
|
-
|
|
340599
|
-
if (
|
|
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
|
-
|
|
340603
|
-
|
|
340604
|
-
|
|
340605
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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.` : "") + `
|
|
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
|
|
343058
|
+
const totalLen = readRaw.length;
|
|
342658
343059
|
const head = readRaw.slice(0, 100).replace(/\s+/g, " ").trim();
|
|
342659
|
-
|
|
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 =
|
|
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
|
-
|
|
342694
|
-
|
|
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
|
|
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(), ".
|
|
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.
|
|
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
|
|
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,
|
|
355874
|
+
return statSync2(join3(root, dirname3(path19))).mtimeMs;
|
|
355471
355875
|
} catch {
|
|
355472
355876
|
return 0;
|
|
355473
355877
|
}
|