micro-models-agent 0.52.0 → 0.52.1
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/README.md +358 -358
- package/dist/main.js +244 -122
- package/dist/modules/browser/bridge-server.mjs +202 -202
- package/dist/skills/builtin/git.md +36 -36
- package/dist/skills/builtin/typescript.md +35 -35
- package/package.json +1 -1
- package/dist/cli/commands.js +0 -447
- package/dist/cli/completer.js +0 -167
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -153
- package/dist/cli/plugin-commands.js +0 -36
- package/dist/cli/repl-commands.js +0 -761
- package/dist/cli/repl.js +0 -702
- package/dist/cli/run-result.js +0 -33
- package/dist/cli/security-commands.js +0 -164
- package/dist/cli/setup.js +0 -237
- package/dist/config/config.js +0 -276
- package/dist/config/defaults.js +0 -141
- package/dist/config/domains.js +0 -179
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -4
- package/dist/config/security.js +0 -213
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -102
- package/dist/core/agent.js +0 -1018
- package/dist/core/bootstrap.js +0 -481
- package/dist/core/crash-handler.js +0 -51
- package/dist/core/environment.js +0 -199
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -76
- package/dist/core/session-logger.js +0 -251
- package/dist/core/types.js +0 -1
- package/dist/core/version.js +0 -26
- package/dist/core/workspace.js +0 -76
- package/dist/i18n/en.json +0 -679
- package/dist/i18n/index.js +0 -46
- package/dist/i18n/ru.json +0 -679
- package/dist/index.js +0 -22
- package/dist/llm/image-utils.js +0 -143
- package/dist/llm/index.js +0 -4
- package/dist/llm/model-loader.js +0 -78
- package/dist/llm/openai-compat.js +0 -497
- package/dist/llm/orchestrator.js +0 -200
- package/dist/llm/provider.js +0 -10
- package/dist/llm/response.js +0 -39
- package/dist/llm/token-counter.js +0 -39
- package/dist/llm/types.js +0 -1
- package/dist/logger/app-logger.js +0 -189
- package/dist/logger/file-log.js +0 -151
- package/dist/logger/index.js +0 -1
- package/dist/migration/backup.js +0 -45
- package/dist/migration/detect.js +0 -50
- package/dist/migration/index.js +0 -2
- package/dist/modules/artifacts/store.js +0 -61
- package/dist/modules/browser/actions.js +0 -76
- package/dist/modules/browser/bridge-client.js +0 -199
- package/dist/modules/browser/bridge-path.js +0 -10
- package/dist/modules/browser/cookie-store.js +0 -24
- package/dist/modules/browser/driver.js +0 -136
- package/dist/modules/browser/index.js +0 -7
- package/dist/modules/browser/module.js +0 -29
- package/dist/modules/browser/session.js +0 -342
- package/dist/modules/browser/snapshot.js +0 -148
- package/dist/modules/browser/types.js +0 -12
- package/dist/modules/certification/cli.js +0 -213
- package/dist/modules/certification/fact-checker.js +0 -82
- package/dist/modules/certification/loader.js +0 -106
- package/dist/modules/certification/manifest.js +0 -58
- package/dist/modules/certification/runner.js +0 -245
- package/dist/modules/certification/scenarios.js +0 -407
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/context/chunk-query.js +0 -100
- package/dist/modules/context/fact-extractor.js +0 -168
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -440
- package/dist/modules/execution/audit-runners.js +0 -206
- package/dist/modules/execution/auditor.js +0 -218
- package/dist/modules/execution/execution-plugin.js +0 -431
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/module.js +0 -625
- package/dist/modules/execution/moe-executor.js +0 -304
- package/dist/modules/execution/plan-coverage.js +0 -68
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-store.js +0 -196
- package/dist/modules/execution/plan-tool.js +0 -677
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -94
- package/dist/modules/execution/stuck-detector.js +0 -746
- package/dist/modules/execution/tracker.js +0 -69
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -235
- package/dist/modules/execution/windows-commands.js +0 -41
- package/dist/modules/hallucination/confidence.js +0 -66
- package/dist/modules/hallucination/consistency.js +0 -26
- package/dist/modules/hallucination/detector.js +0 -47
- package/dist/modules/hallucination/factual.js +0 -169
- package/dist/modules/hallucination/index.js +0 -5
- package/dist/modules/hallucination/js-identifiers.js +0 -262
- package/dist/modules/hallucination/llm-judge.js +0 -101
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -40
- package/dist/modules/indexer/index.js +0 -3
- package/dist/modules/indexer/module.js +0 -246
- package/dist/modules/indexer/project-profile.js +0 -183
- package/dist/modules/indexer/walker.js +0 -101
- package/dist/modules/lsp/check-tool.js +0 -58
- package/dist/modules/lsp/client.js +0 -389
- package/dist/modules/lsp/command.js +0 -60
- package/dist/modules/lsp/config.js +0 -135
- package/dist/modules/lsp/index.js +0 -3
- package/dist/modules/lsp/module.js +0 -260
- package/dist/modules/lsp/probe.js +0 -86
- package/dist/modules/lsp/project-root.js +0 -32
- package/dist/modules/lsp/startup-check.js +0 -144
- package/dist/modules/lsp/types.js +0 -1
- package/dist/modules/mcp/client.js +0 -399
- package/dist/modules/mcp/index.js +0 -3
- package/dist/modules/mcp/module.js +0 -142
- package/dist/modules/mcp/registry.js +0 -15
- package/dist/modules/memory/index.js +0 -1
- package/dist/modules/memory/module.js +0 -96
- package/dist/modules/memory/search.js +0 -42
- package/dist/modules/memory/store.js +0 -69
- package/dist/modules/pipelines/engine.js +0 -60
- package/dist/modules/pipelines/index.js +0 -3
- package/dist/modules/pipelines/parser.js +0 -56
- package/dist/modules/pipelines/template.js +0 -14
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -334
- package/dist/modules/plugins/builtin/notify.js +0 -9
- package/dist/modules/plugins/index.js +0 -1
- package/dist/modules/plugins/loader.js +0 -70
- package/dist/modules/plugins/manager.js +0 -261
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/pricing/index.js +0 -61
- package/dist/modules/pricing/prices.js +0 -129
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/processes/index.js +0 -2
- package/dist/modules/processes/registry.js +0 -327
- package/dist/modules/processes/runner.js +0 -23
- package/dist/modules/providers/create.js +0 -22
- package/dist/modules/providers/fallback.js +0 -79
- package/dist/modules/providers/health.js +0 -46
- package/dist/modules/providers/index.js +0 -5
- package/dist/modules/providers/manager.js +0 -161
- package/dist/modules/providers/presets.js +0 -128
- package/dist/modules/providers/registry.js +0 -22
- package/dist/modules/providers/types.js +0 -1
- package/dist/modules/registry.js +0 -48
- package/dist/modules/security/audit-log.js +0 -136
- package/dist/modules/security/audit-notifier.js +0 -292
- package/dist/modules/security/command-validator.js +0 -219
- package/dist/modules/security/content-scanner.js +0 -53
- package/dist/modules/security/data-sanitizer.js +0 -89
- package/dist/modules/security/encryption.js +0 -242
- package/dist/modules/security/index.js +0 -14
- package/dist/modules/security/network-validator.js +0 -88
- package/dist/modules/security/path-validator.js +0 -203
- package/dist/modules/security/rate-limiter.js +0 -119
- package/dist/modules/security/security-policies.js +0 -531
- package/dist/modules/security/session-encryption.js +0 -210
- package/dist/modules/security/session-isolation.js +0 -95
- package/dist/modules/session/index.js +0 -3
- package/dist/modules/session/manager.js +0 -172
- package/dist/modules/session/module.js +0 -24
- package/dist/modules/session/store.js +0 -222
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/loader.js +0 -72
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/skills/module.js +0 -129
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/checker.js +0 -96
- package/dist/modules/updater/index.js +0 -2
- package/dist/modules/updater/module.js +0 -116
- package/dist/modules/user-profile/compressor.js +0 -16
- package/dist/modules/user-profile/index.js +0 -1
- package/dist/modules/user-profile/profile.js +0 -68
- package/dist/tools/approve.js +0 -33
- package/dist/tools/attach-image.js +0 -101
- package/dist/tools/bash.js +0 -519
- package/dist/tools/browser.js +0 -115
- package/dist/tools/chunk-query.js +0 -100
- package/dist/tools/create-dir.js +0 -56
- package/dist/tools/delete-file.js +0 -63
- package/dist/tools/download-file.js +0 -117
- package/dist/tools/edit-file.js +0 -80
- package/dist/tools/enable-tools.js +0 -59
- package/dist/tools/executor.js +0 -154
- package/dist/tools/file-info.js +0 -47
- package/dist/tools/filter-tools.js +0 -17
- package/dist/tools/glob-tool.js +0 -27
- package/dist/tools/grep-tool.js +0 -125
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/tools/index.js +0 -78
- package/dist/tools/list-dir.js +0 -49
- package/dist/tools/load-skill.js +0 -43
- package/dist/tools/mcp-call.js +0 -69
- package/dist/tools/move-file.js +0 -86
- package/dist/tools/path-utils.js +0 -101
- package/dist/tools/pipeline-run.js +0 -145
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-kill.js +0 -40
- package/dist/tools/process-list.js +0 -37
- package/dist/tools/process-log.js +0 -54
- package/dist/tools/question.js +0 -141
- package/dist/tools/read-file.js +0 -179
- package/dist/tools/recall.js +0 -118
- package/dist/tools/registry.js +0 -47
- package/dist/tools/remember.js +0 -68
- package/dist/tools/scope-check.js +0 -32
- package/dist/tools/search-history.js +0 -85
- package/dist/tools/subagent.js +0 -196
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -123
- package/dist/tools/web-browse.js +0 -87
- package/dist/tools/web-fetch.js +0 -119
- package/dist/tools/web-search.js +0 -105
- package/dist/tools/write-file.js +0 -82
- package/dist/ui/box.js +0 -77
- package/dist/ui/colors.js +0 -4
- package/dist/ui/diff.js +0 -178
- package/dist/ui/index.js +0 -6
- package/dist/ui/line-editor.js +0 -822
- package/dist/ui/line-math.js +0 -73
- package/dist/ui/md-formatter.js +0 -212
- package/dist/ui/output.js +0 -13
- package/dist/ui/plan-view.js +0 -103
- package/dist/ui/renderer.js +0 -259
- package/dist/ui/spinner.js +0 -70
- package/dist/ui/table.js +0 -144
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from "fs";
|
|
2
|
-
import { resolve, join, basename } from "path";
|
|
3
|
-
import { t } from "../../i18n/index";
|
|
4
|
-
import { extractFileLikeTokens, stripUrls } from "../hallucination/js-identifiers";
|
|
5
|
-
import { SKIP_DIRS, findTestFile, findTypecheckRoot, hasTestStep, runTests, runTypecheck, } from "./audit-runners";
|
|
6
|
-
export { parseTypecheckErrors } from "./audit-runners";
|
|
7
|
-
const MASS_EDIT_THRESHOLD = 10;
|
|
8
|
-
/** Subtree walk depth cap for file resolution. */
|
|
9
|
-
const RESOLVE_MAX_DEPTH = 8;
|
|
10
|
-
/**
|
|
11
|
-
* Resolve a plan-step file token to an existing file.
|
|
12
|
-
*
|
|
13
|
-
* A step description frequently names a file by its bare basename
|
|
14
|
-
* ("App.tsx") or a partially-prefixed path ("src/App.tsx") while the real
|
|
15
|
-
* file lives in a nested project directory relative to the agent baseDir
|
|
16
|
-
* (e.g. `bicycle-shop/src/App.tsx`). Naively resolving against baseDir
|
|
17
|
-
* reports the file as missing — observed in session ses_mssl5izw where the
|
|
18
|
-
* final audit gate rejected a fully-completed task with "1 files missing"
|
|
19
|
-
* forever, sending the model into a read/retry loop.
|
|
20
|
-
*
|
|
21
|
-
* Strategy: exact resolve first, then a bounded subtree walk matching either
|
|
22
|
-
* the exact basename or a path suffix (so both "App.tsx" and
|
|
23
|
-
* "src/App.tsx" find "bicycle-shop/src/App.tsx").
|
|
24
|
-
*/
|
|
25
|
-
export function findExistingFile(baseDir, filePath) {
|
|
26
|
-
const direct = resolve(baseDir, filePath);
|
|
27
|
-
if (existsSync(direct))
|
|
28
|
-
return direct;
|
|
29
|
-
const name = basename(filePath).toLowerCase();
|
|
30
|
-
const suffix = filePath.replace(/\\/g, "/").toLowerCase();
|
|
31
|
-
let found = null;
|
|
32
|
-
const walk = (dir, depth) => {
|
|
33
|
-
if (found || depth > RESOLVE_MAX_DEPTH)
|
|
34
|
-
return;
|
|
35
|
-
let entries;
|
|
36
|
-
try {
|
|
37
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
for (const e of entries) {
|
|
43
|
-
if (found)
|
|
44
|
-
return;
|
|
45
|
-
const full = join(dir, e.name);
|
|
46
|
-
if (e.isDirectory()) {
|
|
47
|
-
if (SKIP_DIRS.has(e.name))
|
|
48
|
-
continue;
|
|
49
|
-
walk(full, depth + 1);
|
|
50
|
-
}
|
|
51
|
-
else if (e.name.toLowerCase() === name ||
|
|
52
|
-
full
|
|
53
|
-
.replace(/\\/g, "/")
|
|
54
|
-
.toLowerCase()
|
|
55
|
-
.endsWith("/" + suffix)) {
|
|
56
|
-
found = full;
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
walk(baseDir, 0);
|
|
62
|
-
return found;
|
|
63
|
-
}
|
|
64
|
-
export class Auditor {
|
|
65
|
-
baseDir;
|
|
66
|
-
constructor(baseDir) {
|
|
67
|
-
this.baseDir = baseDir;
|
|
68
|
-
}
|
|
69
|
-
async audit(plan) {
|
|
70
|
-
// Only steps the model claims completed may require their named files to
|
|
71
|
-
// exist. Skipped/pending steps must never contribute "missing files"
|
|
72
|
-
// (observed: a skipped step's prose mention of "type.ts" kept the audit
|
|
73
|
-
// gate rejecting a finished task with "1 files missing" forever).
|
|
74
|
-
const doneCreate = plan.steps.filter((s) => s.status === "done" && s.kind !== "delete");
|
|
75
|
-
const doneDelete = plan.steps.filter((s) => s.status === "done" && s.kind === "delete");
|
|
76
|
-
const createText = doneCreate.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
|
|
77
|
-
const deleteText = doneDelete.map((s) => s.description.replace(/\([^)]*\)/g, " ")).join(" ");
|
|
78
|
-
// File tokens are extracted with the SAME shared filter as the
|
|
79
|
-
// hallucination factual check (js-identifiers.ts). URLs are stripped
|
|
80
|
-
// first so a hostname / https://host/repo.git in a step description
|
|
81
|
-
// can never be reported as a missing file (observed: a git-remote
|
|
82
|
-
// plan flagged `whom-pointing-jewel-maine.trycloudflare.com`).
|
|
83
|
-
const createFiles = extractFileLikeTokens(stripUrls(createText));
|
|
84
|
-
const deleteFiles = extractFileLikeTokens(stripUrls(deleteText));
|
|
85
|
-
const missingFiles = [];
|
|
86
|
-
const existingFiles = [];
|
|
87
|
-
const leftoverFiles = [];
|
|
88
|
-
for (const filePath of createFiles) {
|
|
89
|
-
const resolved = findExistingFile(this.baseDir, filePath);
|
|
90
|
-
if (resolved) {
|
|
91
|
-
// Keep the resolved absolute path: findTypecheckRoot walks up
|
|
92
|
-
// from it to the nearest tsconfig, and a relative token would
|
|
93
|
-
// resolve against the process cwd instead of baseDir.
|
|
94
|
-
existingFiles.push(resolved);
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
missingFiles.push(filePath);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// A done delete-kind step requires its named files to be GONE.
|
|
101
|
-
for (const filePath of deleteFiles) {
|
|
102
|
-
if (findExistingFile(this.baseDir, filePath)) {
|
|
103
|
-
leftoverFiles.push(filePath);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Real test run: when the plan includes a verification step, do not
|
|
107
|
-
// trust the model's word (or exit codes) — actually execute the tests
|
|
108
|
-
// and check the runner summary. This catches the "all 7 passing ✅"
|
|
109
|
-
// final answers that hide 3 failing tests.
|
|
110
|
-
let testRun = null;
|
|
111
|
-
if (hasTestStep(plan) && findTestFile(this.baseDir)) {
|
|
112
|
-
try {
|
|
113
|
-
testRun = await runTests(this.baseDir);
|
|
114
|
-
}
|
|
115
|
-
catch {
|
|
116
|
-
testRun = null; // infra failure — don't block on it
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
// Real typecheck: the audit must catch "the build is broken" even when
|
|
120
|
-
// every file exists (observed: deleting .js files left the .tsx sources
|
|
121
|
-
// with 11 tsc errors and the agent still declared success). Runs only
|
|
122
|
-
// when a tsconfig.json is reachable from baseDir or the resolved step
|
|
123
|
-
// files AND the file checks already passed — an audit that is doomed by
|
|
124
|
-
// missing/leftover files must not pay a 90s tsc spawn. Inconclusive
|
|
125
|
-
// results never block.
|
|
126
|
-
let typecheckError = null;
|
|
127
|
-
if (missingFiles.length === 0 && leftoverFiles.length === 0) {
|
|
128
|
-
const root = findTypecheckRoot(this.baseDir, existingFiles);
|
|
129
|
-
if (root) {
|
|
130
|
-
try {
|
|
131
|
-
typecheckError = await runTypecheck(root);
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
typecheckError = null;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
const doneSteps = plan.steps.filter((s) => s.status === "done").length;
|
|
139
|
-
// done + skipped are both deliberate terminal states: `done` claims the
|
|
140
|
-
// work, `skipped` documents a decision that the step is not needed (the
|
|
141
|
-
// deliverable gate's documented escape hatch). Neither is a lie the
|
|
142
|
-
// audit must catch — a plan with a legitimately skipped step must be
|
|
143
|
-
// able to pass (observed: skipped step 1 kept the task rejected forever).
|
|
144
|
-
const terminalSteps = plan.steps.filter((s) => s.status === "done" || s.status === "skipped").length;
|
|
145
|
-
const totalSteps = plan.steps.length;
|
|
146
|
-
const auditedFiles = [...new Set([...createFiles, ...deleteFiles])];
|
|
147
|
-
let massEditWarning = null;
|
|
148
|
-
if (auditedFiles.length > MASS_EDIT_THRESHOLD) {
|
|
149
|
-
massEditWarning = t("exec.mass_edit_warning", {
|
|
150
|
-
count: String(auditedFiles.length),
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
const testsFailing = testRun !== null && !testRun.passed;
|
|
154
|
-
const typecheckFailing = typecheckError !== null;
|
|
155
|
-
const passed = missingFiles.length === 0 && leftoverFiles.length === 0 && !testsFailing && !typecheckFailing;
|
|
156
|
-
// A "Task complete ✓" summary for an unfinished plan is a lie the model
|
|
157
|
-
// reads as a green light (observed: audit gate injected "[✓] Task
|
|
158
|
-
// complete: 1/3 steps done" while the plan had 2 pending steps). The
|
|
159
|
-
// summary must only claim completion when every step is actually done.
|
|
160
|
-
const stepsPending = terminalSteps < totalSteps;
|
|
161
|
-
let summary;
|
|
162
|
-
if (missingFiles.length > 0) {
|
|
163
|
-
summary = t("exec.audit_fail", {
|
|
164
|
-
done: doneSteps,
|
|
165
|
-
total: totalSteps,
|
|
166
|
-
files: missingFiles.length,
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
else if (leftoverFiles.length > 0) {
|
|
170
|
-
summary = t("exec.audit_leftovers", {
|
|
171
|
-
done: doneSteps,
|
|
172
|
-
total: totalSteps,
|
|
173
|
-
files: leftoverFiles.length,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
else if (testsFailing) {
|
|
177
|
-
summary = t("exec.audit_fail_tests", {
|
|
178
|
-
done: doneSteps,
|
|
179
|
-
total: totalSteps,
|
|
180
|
-
failed: String(testRun.failed),
|
|
181
|
-
passed: String(testRun.passedCount),
|
|
182
|
-
detail: testRun.detail,
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
else if (typecheckFailing) {
|
|
186
|
-
summary = t("exec.audit_fail_typecheck", {
|
|
187
|
-
done: doneSteps,
|
|
188
|
-
total: totalSteps,
|
|
189
|
-
missing: String(missingFiles.length),
|
|
190
|
-
typeError: typecheckError,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
else if (stepsPending) {
|
|
194
|
-
summary = t("exec.audit_pending", {
|
|
195
|
-
done: doneSteps,
|
|
196
|
-
total: totalSteps,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
summary = t("exec.audit_pass", {
|
|
201
|
-
done: doneSteps,
|
|
202
|
-
total: totalSteps,
|
|
203
|
-
files: existingFiles.length,
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
return {
|
|
207
|
-
passed,
|
|
208
|
-
missingFiles,
|
|
209
|
-
leftoverFiles,
|
|
210
|
-
createdFiles: existingFiles,
|
|
211
|
-
modifiedFiles: [],
|
|
212
|
-
summary,
|
|
213
|
-
massEditWarning,
|
|
214
|
-
testRun,
|
|
215
|
-
typecheckError,
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
@@ -1,431 +0,0 @@
|
|
|
1
|
-
import { t } from "../../i18n/index";
|
|
2
|
-
import { detectTestResults } from "../../tools/bash";
|
|
3
|
-
import { forbiddenWindowsCommand } from "./windows-commands";
|
|
4
|
-
import { extractFileLikeTokens, stripUrls } from "../hallucination/js-identifiers";
|
|
5
|
-
import { platform } from "os";
|
|
6
|
-
/**
|
|
7
|
-
* Cooldown (in iterations) between stuck-recovery injections. Exported so the
|
|
8
|
-
* ExecutionModule can seed its state's lastRecoveryIteration with the same
|
|
9
|
-
* offset the plugin compares against.
|
|
10
|
-
*/
|
|
11
|
-
export const STUCK_RECOVERY_COOLDOWN = 5;
|
|
12
|
-
const MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3;
|
|
13
|
-
const FORCE_SKIP_THRESHOLD = 10;
|
|
14
|
-
/** Re-log the SAME stuck-warning key at most every N iterations (a new key
|
|
15
|
-
* always logs immediately). Prevents the identical warning from flooding
|
|
16
|
-
* every iteration (ses_mt4fn58c: "Tool bash failed N times" after ~30
|
|
17
|
-
* successful writes, "No write/exec" on every read). */
|
|
18
|
-
export const STUCK_WARN_REPEAT_EVERY = 5;
|
|
19
|
-
/** How many file-mutating tool calls (write/edit/bash/download) without an
|
|
20
|
-
* active plan trigger the plan-creation nudge. Evidence-based (rule #10):
|
|
21
|
-
* the agent is clearly working on files, so a plan is warranted. */
|
|
22
|
-
const PLAN_NUDGE_THRESHOLD = 2;
|
|
23
|
-
/** Tools whose success can have changed the filesystem — the plan auto-advance
|
|
24
|
-
* re-checks the current step's deliverables after them. Read-only tools are
|
|
25
|
-
* deliberately excluded (see the onAfterTool advance call below). */
|
|
26
|
-
const FS_MUTATING_TOOLS = new Set([
|
|
27
|
-
"write_file",
|
|
28
|
-
"edit_file",
|
|
29
|
-
"delete_file",
|
|
30
|
-
"move_file",
|
|
31
|
-
"create_dir",
|
|
32
|
-
"bash",
|
|
33
|
-
"download_file",
|
|
34
|
-
"subagent",
|
|
35
|
-
"mcp_call",
|
|
36
|
-
"pipeline_run",
|
|
37
|
-
"browser",
|
|
38
|
-
]);
|
|
39
|
-
function normalizeBrokenPath(p) {
|
|
40
|
-
return p.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
41
|
-
}
|
|
42
|
-
/** Parse a tsc / bun build / node --check error line into its file path.
|
|
43
|
-
* Returns null when the line carries no file anchor (e.g. `error: Could not
|
|
44
|
-
* resolve "..."`). */
|
|
45
|
-
export function parseBrokenFile(line) {
|
|
46
|
-
const trimmed = line.trim();
|
|
47
|
-
// tsc: src/index.tsx(17,15): error TS2322: ...
|
|
48
|
-
const tsc = /^(.+?)\s*\(\d+,\d+\)\s*:\s*error TS\d+/.exec(trimmed);
|
|
49
|
-
if (tsc)
|
|
50
|
-
return normalizeBrokenPath(tsc[1]);
|
|
51
|
-
// bun build / node --check: /abs/file.tsx:1:10: error: ...
|
|
52
|
-
const bn = /^(.+?):\d+:\d+\s*:\s*(?:error|SyntaxError)/.exec(trimmed);
|
|
53
|
-
if (bn)
|
|
54
|
-
return normalizeBrokenPath(bn[1]);
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
/** Extract per-file compile failures from a write/edit result output. Only
|
|
58
|
-
* the marker lines are inspected (`[Project typecheck failed]: <line>` /
|
|
59
|
-
* `[Syntax check failed]: <line>`) so a diff body can never false-positive. */
|
|
60
|
-
export function extractBrokenFiles(output) {
|
|
61
|
-
const out = [];
|
|
62
|
-
for (const line of output.split("\n")) {
|
|
63
|
-
const marker = /\[(?:Project typecheck failed|Syntax check failed)\]:\s*(.+)$/.exec(line);
|
|
64
|
-
if (!marker)
|
|
65
|
-
continue;
|
|
66
|
-
const errLine = marker[1].trim();
|
|
67
|
-
out.push({ file: parseBrokenFile(errLine) ?? "", error: errLine.slice(0, 300) });
|
|
68
|
-
}
|
|
69
|
-
return out;
|
|
70
|
-
}
|
|
71
|
-
/**
|
|
72
|
-
* Gate for `plan update status=done`: refuse while the last write/edit still
|
|
73
|
-
* reports a compile error. A failure with no file anchor blocks the whole
|
|
74
|
-
* project; otherwise a step's named file tokens are matched against the
|
|
75
|
-
* broken files (path-suffix match, case-insensitive). A step naming no files
|
|
76
|
-
* cannot be blamed — its done stays vacuous. Returns the first error to show
|
|
77
|
-
* the model, or null to allow the done.
|
|
78
|
-
*/
|
|
79
|
-
export function stepTypecheckGate(failures, stepDescription) {
|
|
80
|
-
if (failures.size === 0)
|
|
81
|
-
return null;
|
|
82
|
-
const projectError = failures.get("");
|
|
83
|
-
if (projectError)
|
|
84
|
-
return projectError;
|
|
85
|
-
const stepTokens = extractFileLikeTokens(stripUrls(stepDescription)).map((p) => normalizeBrokenPath(p).toLowerCase());
|
|
86
|
-
if (stepTokens.length === 0)
|
|
87
|
-
return null;
|
|
88
|
-
for (const [file, err] of failures) {
|
|
89
|
-
const f = normalizeBrokenPath(file).toLowerCase();
|
|
90
|
-
if (stepTokens.some((tok) => f.endsWith(tok) || tok.endsWith(f)))
|
|
91
|
-
return err;
|
|
92
|
-
}
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* The execution plugin hooks, extracted verbatim from ExecutionModule.getPlugin().
|
|
97
|
-
* All shared mutable state (pendingMessages, forbiddenBashFailures, state,
|
|
98
|
-
* tracker) is owned by the module and mutated by reference through `deps`.
|
|
99
|
-
*/
|
|
100
|
-
export function createExecutionPlugin(deps) {
|
|
101
|
-
return {
|
|
102
|
-
name: "execution",
|
|
103
|
-
onBeforeThink: (ctx) => {
|
|
104
|
-
// Flush deferred <system-summary> messages first so they are visible
|
|
105
|
-
// to the next model call but never interleave tool messages.
|
|
106
|
-
if (ctx.contextManager && deps.pendingMessages.length > 0) {
|
|
107
|
-
for (const m of deps.pendingMessages.splice(0)) {
|
|
108
|
-
ctx.contextManager.addMessage(m);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (deps.trackerRef.current?.isComplete()) {
|
|
112
|
-
// All steps done/skipped — stop counting "no progress" against
|
|
113
|
-
// the last step and silence step-stuck warnings for a finished
|
|
114
|
-
// plan, BUT keep the tool-call history so a read-without-write
|
|
115
|
-
// loop is still caught (observed: plan complete + false-positive
|
|
116
|
-
// audit → the model re-read files for 50+ iterations with zero
|
|
117
|
-
// writes and no recovery hint).
|
|
118
|
-
deps.stuckDetector.resetStepProgress();
|
|
119
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
120
|
-
deps.state.lastStepId = -1;
|
|
121
|
-
deps.state.stuckNotified = false;
|
|
122
|
-
deps.state.mutationsWithoutPlan = 0;
|
|
123
|
-
deps.state.planNudgeSent = false;
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
const step = deps.trackerRef.current?.getCurrentStep();
|
|
127
|
-
if (deps.trackerRef.current && step) {
|
|
128
|
-
if (step.id !== deps.state.lastStepId) {
|
|
129
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
130
|
-
deps.state.lastStepId = step.id;
|
|
131
|
-
deps.state.stuckNotified = false;
|
|
132
|
-
}
|
|
133
|
-
deps.stuckDetector.setCurrentStep(step.id, step.description);
|
|
134
|
-
deps.stuckDetector.recordIteration(step.id);
|
|
135
|
-
// A plan is active — any mutation counter no longer applies.
|
|
136
|
-
deps.state.mutationsWithoutPlan = 0;
|
|
137
|
-
deps.state.planNudgeSent = false;
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
deps.stuckDetector.reset();
|
|
141
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
142
|
-
deps.state.lastStepId = -1;
|
|
143
|
-
deps.state.stuckNotified = false;
|
|
144
|
-
// Evidence-based plan nudge (rule #10 — no bare iteration counter,
|
|
145
|
-
// no keyword task classification): only after the agent actually
|
|
146
|
-
// made file-mutating tool calls (write/edit/bash/download) without
|
|
147
|
-
// a plan does the agent need a plan.
|
|
148
|
-
const mutations = deps.state.mutationsWithoutPlan;
|
|
149
|
-
if (mutations >= PLAN_NUDGE_THRESHOLD &&
|
|
150
|
-
!deps.state.planNudgeSent &&
|
|
151
|
-
ctx.contextManager) {
|
|
152
|
-
deps.state.planNudgeSent = true;
|
|
153
|
-
ctx.contextManager.addMessage({
|
|
154
|
-
role: "user",
|
|
155
|
-
content: `<system-summary>${t("exec.plan_nudge", {
|
|
156
|
-
count: String(mutations),
|
|
157
|
-
})}</system-summary>`,
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
const stuckReason = deps.stuckDetector.getStuckReason();
|
|
163
|
-
if (stuckReason) {
|
|
164
|
-
// Log once per warning episode instead of spamming every iteration:
|
|
165
|
-
// a new warning key logs immediately; the SAME key re-logs only every
|
|
166
|
-
// STUCK_WARN_REPEAT_EVERY iterations. The step-stuck reason keeps its
|
|
167
|
-
// dedicated once-per-episode flag.
|
|
168
|
-
const isStuck = deps.stuckDetector.isStuck();
|
|
169
|
-
const warnKey = deps.stuckDetector.getWarningKey();
|
|
170
|
-
const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
171
|
-
const logIt = isStuck
|
|
172
|
-
? !deps.state.stuckNotified
|
|
173
|
-
: warnKey !== deps.state.lastStuckWarnKey ||
|
|
174
|
-
iter - deps.state.lastStuckWarnIter >= STUCK_WARN_REPEAT_EVERY;
|
|
175
|
-
if (logIt) {
|
|
176
|
-
ctx.logger?.warn(stuckReason);
|
|
177
|
-
ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
|
|
178
|
-
deps.state.lastStuckWarnKey = warnKey;
|
|
179
|
-
deps.state.lastStuckWarnIter = iter;
|
|
180
|
-
if (isStuck)
|
|
181
|
-
deps.state.stuckNotified = true;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
deps.state.lastStuckWarnKey = "";
|
|
186
|
-
}
|
|
187
|
-
if (deps.stuckDetector.isStuck() ||
|
|
188
|
-
deps.stuckDetector.hasRepetitiveToolCalls() ||
|
|
189
|
-
deps.stuckDetector.hasReadOnlyLoop()) {
|
|
190
|
-
const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
191
|
-
if (currentIter - deps.state.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
|
|
192
|
-
const recovery = deps.stuckDetector.getRecoveryMessage();
|
|
193
|
-
if (recovery && ctx.contextManager) {
|
|
194
|
-
// Include tsc error context so the model knows WHY it is stuck.
|
|
195
|
-
let tscContext = "";
|
|
196
|
-
if (deps.state.typecheckFailures.size > 0) {
|
|
197
|
-
const currentStep = deps.trackerRef.current?.getCurrentStep();
|
|
198
|
-
if (currentStep) {
|
|
199
|
-
const stepFiles = extractFileLikeTokens(stripUrls(currentStep.description));
|
|
200
|
-
for (const [file, error] of deps.state.typecheckFailures) {
|
|
201
|
-
if (!file || stepFiles.some((f) => file.endsWith(f) || f.endsWith(file))) {
|
|
202
|
-
tscContext = `\nLast compile error: ${error}`;
|
|
203
|
-
break;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (!tscContext) {
|
|
207
|
-
const first = deps.state.typecheckFailures.entries().next().value;
|
|
208
|
-
if (first)
|
|
209
|
-
tscContext = `\nLast compile error: ${first[1]}`;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
ctx.contextManager.addMessage({
|
|
214
|
-
role: "user",
|
|
215
|
-
content: `<system-summary>${recovery}${tscContext}</system-summary>`,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
deps.stuckDetector.recordEscalation();
|
|
219
|
-
deps.state.lastRecoveryIteration = currentIter;
|
|
220
|
-
if (deps.stuckDetector.shouldEscalate() && ctx.onMeta) {
|
|
221
|
-
const escalation = t("exec.escalation", {
|
|
222
|
-
stepId: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
223
|
-
description: deps.trackerRef.current?.getCurrentStep()?.description ?? "",
|
|
224
|
-
});
|
|
225
|
-
ctx.onMeta(escalation);
|
|
226
|
-
}
|
|
227
|
-
// Force skip after too many iterations on the same step
|
|
228
|
-
if (deps.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD &&
|
|
229
|
-
ctx.contextManager) {
|
|
230
|
-
const step = deps.trackerRef.current?.getCurrentStep();
|
|
231
|
-
ctx.contextManager.addMessage({
|
|
232
|
-
role: "user",
|
|
233
|
-
content: `<system-summary>${t("exec.stop_directive", {
|
|
234
|
-
stepId: String(step?.id ?? "?"),
|
|
235
|
-
description: step?.description ?? "",
|
|
236
|
-
iterations: String(deps.stuckDetector.getIterationsOnCurrentStep()),
|
|
237
|
-
})}</system-summary>`,
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
},
|
|
243
|
-
onBeforeTool: (_ctx, call) => {
|
|
244
|
-
const warning = deps.checkPlanAlignment(call);
|
|
245
|
-
if (warning) {
|
|
246
|
-
deps.pendingMessages.push({
|
|
247
|
-
role: "user",
|
|
248
|
-
content: `<system-summary>${warning}</system-summary>`,
|
|
249
|
-
});
|
|
250
|
-
deps.state.consecutivePlanWarnings++;
|
|
251
|
-
if (deps.state.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
|
|
252
|
-
deps.pendingMessages.push({
|
|
253
|
-
role: "user",
|
|
254
|
-
content: `<system-summary>${t("exec.plan_blocked", { step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`,
|
|
255
|
-
});
|
|
256
|
-
// Block with an explicit reason so the model sees WHY the call
|
|
257
|
-
// was blocked (executor shows it via tool.blocked_reason).
|
|
258
|
-
return t("exec.plan_blocked", {
|
|
259
|
-
step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
260
|
-
max: MAX_PLAN_WARNINGS_BEFORE_BLOCK,
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
266
|
-
}
|
|
267
|
-
return true;
|
|
268
|
-
},
|
|
269
|
-
onToolCall: (ctx) => {
|
|
270
|
-
const toolName = ctx?.toolName;
|
|
271
|
-
const args = ctx?.args;
|
|
272
|
-
if (toolName && args) {
|
|
273
|
-
deps.stuckDetector.recordToolCall(toolName, args);
|
|
274
|
-
// Count file-mutating calls made with NO active plan — feeds the
|
|
275
|
-
// evidence-based plan-creation nudge in onBeforeThink.
|
|
276
|
-
if (!deps.trackerRef.current &&
|
|
277
|
-
(toolName === "write_file" ||
|
|
278
|
-
toolName === "edit_file" ||
|
|
279
|
-
toolName === "bash" ||
|
|
280
|
-
toolName === "download_file")) {
|
|
281
|
-
deps.state.mutationsWithoutPlan++;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
},
|
|
285
|
-
onAfterTool: (ctx, call, result) => {
|
|
286
|
-
// Remember every bash invocation (successful or not) so stuck
|
|
287
|
-
// detection can reason about e.g. repeated runs with empty output.
|
|
288
|
-
if (call.name === "bash") {
|
|
289
|
-
deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
|
|
290
|
-
}
|
|
291
|
-
// Auto-detect delete intent from model actions: when delete_file is
|
|
292
|
-
// called, find the matching plan step and set kind="delete". This is
|
|
293
|
-
// language-agnostic — based on what the model DID, not what it said.
|
|
294
|
-
if (call.name === "delete_file" && result.success && deps.trackerRef.current) {
|
|
295
|
-
const deletedPath = String(call.arguments?.path ?? "");
|
|
296
|
-
if (deletedPath) {
|
|
297
|
-
const plan = deps.trackerRef.current.getPlan();
|
|
298
|
-
const tokens = extractFileLikeTokens(stripUrls(deletedPath));
|
|
299
|
-
for (const step of plan.steps) {
|
|
300
|
-
if (step.status !== "pending" && step.status !== "in_progress")
|
|
301
|
-
continue;
|
|
302
|
-
const stepTokens = extractFileLikeTokens(stripUrls(step.description));
|
|
303
|
-
const overlap = tokens.some((t) => stepTokens.some((st) => t.endsWith(st) || st.endsWith(t)));
|
|
304
|
-
if (overlap && step.kind !== "delete") {
|
|
305
|
-
step.kind = "delete";
|
|
306
|
-
deps.store.saveActive(plan);
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
// A write/edit/bash that reports success but still carries a
|
|
313
|
-
// type/syntax error in its output is NOT a success — the model must
|
|
314
|
-
// fix the actual error, not keep rewriting the file blindly. Feed it
|
|
315
|
-
// to the stuck detector so the rewrite-loop hint fires.
|
|
316
|
-
const toolText = String(result.output ?? "");
|
|
317
|
-
const hasTypeError = /error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText);
|
|
318
|
-
if (hasTypeError) {
|
|
319
|
-
deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
|
|
320
|
-
}
|
|
321
|
-
// Track compile failures per-file so the plan done-gate can refuse
|
|
322
|
-
// "done" while the last edit still doesn't compile (observed: the agent
|
|
323
|
-
// marked plan steps done while the project typecheck failed on every
|
|
324
|
-
// write — a "fix it later" cascade). A CLEAN result clears the map: the
|
|
325
|
-
// debounced project tsc re-ran and passed (or no tsconfig applies), so
|
|
326
|
-
// old failures must not block completion forever.
|
|
327
|
-
if (call.name === "write_file" || call.name === "edit_file") {
|
|
328
|
-
const broken = extractBrokenFiles(toolText);
|
|
329
|
-
if (broken.length > 0) {
|
|
330
|
-
for (const b of broken)
|
|
331
|
-
deps.state.typecheckFailures.set(b.file, b.error);
|
|
332
|
-
}
|
|
333
|
-
else if (result.success) {
|
|
334
|
-
deps.state.typecheckFailures.clear();
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
if (!result.success) {
|
|
338
|
-
// Feed the bash attempt outcome into the flailing window BEFORE the
|
|
339
|
-
// per-command handling — rotation across different failing commands
|
|
340
|
-
// (wc → cat → Get-Content → …) must still trip the detector.
|
|
341
|
-
if (call.name === "bash") {
|
|
342
|
-
deps.stuckDetector.recordBashAttempt(false);
|
|
343
|
-
}
|
|
344
|
-
// Hard stop for forbidden Windows commands: after the second
|
|
345
|
-
// failure of the same forbidden command (grep/sed/ls/find/…),
|
|
346
|
-
// inject a STOP message so the session stops burning bash calls
|
|
347
|
-
// on commands cmd.exe does not have. Mirrors the bash tool's own
|
|
348
|
-
// win32-gated hard-stop (src/tools/bash.ts) — on macOS/Linux
|
|
349
|
-
// these commands exist and fail for legit reasons.
|
|
350
|
-
if (call.name === "bash" && platform() === "win32") {
|
|
351
|
-
const cmd = String(call.arguments?.command ?? "");
|
|
352
|
-
const forbidden = forbiddenWindowsCommand(cmd);
|
|
353
|
-
if (forbidden) {
|
|
354
|
-
const n = (deps.forbiddenBashFailures.get(forbidden) || 0) + 1;
|
|
355
|
-
deps.forbiddenBashFailures.set(forbidden, n);
|
|
356
|
-
if (n === 2) {
|
|
357
|
-
deps.pendingMessages.push({
|
|
358
|
-
role: "user",
|
|
359
|
-
content: `<system-summary>${t("exec.forbidden_cmd", { cmd: forbidden })}</system-summary>`,
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
// A type/syntax error in an otherwise "successful" result was already
|
|
365
|
-
// recorded above. A failing call (result.success === false) would
|
|
366
|
-
// otherwise double-count the same error (typecheck branch + failure
|
|
367
|
-
// branch), inflating the error-signature count and triggering the
|
|
368
|
-
// web search at half the intended threshold.
|
|
369
|
-
if (!hasTypeError) {
|
|
370
|
-
deps.stuckDetector.recordToolError(call.name, result.output);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
deps.stuckDetector.recordToolSuccess();
|
|
375
|
-
if (call.name === "bash") {
|
|
376
|
-
deps.stuckDetector.recordBashAttempt(true);
|
|
377
|
-
}
|
|
378
|
-
// When a bash command runs code successfully, suggest marking the
|
|
379
|
-
// step done — unless the output shows failing tests, which must
|
|
380
|
-
// never be reported as a clean success.
|
|
381
|
-
if (call.name === "bash" && result.success) {
|
|
382
|
-
const cmd = String(call.arguments?.command ?? "");
|
|
383
|
-
const testRun = detectTestResults(String(result.output ?? ""));
|
|
384
|
-
if (testRun && testRun.failed > 0) {
|
|
385
|
-
deps.pendingMessages.push({
|
|
386
|
-
role: "user",
|
|
387
|
-
content: `<system-summary>${testRun.framework} reported ${testRun.failed} FAILING test(s) (${testRun.passed} passing). Do NOT mark the current step as done — fix the failing tests (read the failure output, correct the code) and re-run them until all pass.</system-summary>`,
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
|
|
391
|
-
deps.pendingMessages.push({
|
|
392
|
-
role: "user",
|
|
393
|
-
content: `<system-summary>${testRun.framework}: all ${testRun.passed} test(s) passed for "${cmd}". You may mark the current step as done via plan update step=N status=done.</system-summary>`,
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (result.success && (call.name === "write_file" || call.name === "edit_file")) {
|
|
399
|
-
const filePath = call.arguments?.path;
|
|
400
|
-
if (filePath) {
|
|
401
|
-
deps.stuckDetector.recordFileRewrite(filePath);
|
|
402
|
-
if (deps.stuckDetector.hasExcessiveRewrites()) {
|
|
403
|
-
const file = deps.stuckDetector.getExcessiveRewriteFile();
|
|
404
|
-
const count = deps.stuckDetector.getFileRewriteCount(file);
|
|
405
|
-
if (ctx.onMeta) {
|
|
406
|
-
ctx.onMeta(t("exec.file_rewrite_warning", {
|
|
407
|
-
file: file,
|
|
408
|
-
count: String(count),
|
|
409
|
-
}));
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
// Auto-advance fires after every successful FILESYSTEM-MUTATING tool —
|
|
415
|
-
// not just write_file/edit_file. Small models often create files through
|
|
416
|
-
// bash (`echo ... > f`, heredocs) or delete/move via shell instead of the
|
|
417
|
-
// dedicated tools, and the plan then never advances past the step even
|
|
418
|
-
// though its deliverables exist (certification 3.5/3.8: files on disk,
|
|
419
|
-
// plan stuck at 1/4 and 4/5). Read-only tools MUST stay excluded: a
|
|
420
|
-
// successful read/glob would otherwise complete an "Update X" step whose
|
|
421
|
-
// target file already exists from an earlier step — without any edit.
|
|
422
|
-
// advancePlanIfStepComplete is a no-op when tokens don't resolve.
|
|
423
|
-
if (result.success && FS_MUTATING_TOOLS.has(call.name)) {
|
|
424
|
-
deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
|
|
425
|
-
}
|
|
426
|
-
// Automatic web search for a repeatedly failing error (fire-and-forget,
|
|
427
|
-
// results land in pendingMessages on the next onBeforeThink flush).
|
|
428
|
-
deps.maybeSearchError(ctx, call);
|
|
429
|
-
},
|
|
430
|
-
};
|
|
431
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { ExecutionModule } from "./module";
|
|
2
|
-
export { PlanCreator } from "./planner";
|
|
3
|
-
export { PlanTracker } from "./tracker";
|
|
4
|
-
export { StepVerifier } from "./verifier";
|
|
5
|
-
export { StuckDetector } from "./stuck-detector";
|
|
6
|
-
export { Auditor } from "./auditor";
|
|
7
|
-
export { PlanPersister } from "./plan-persister";
|
|
8
|
-
export { PlanStore } from "./plan-store";
|