micro-models-agent 0.51.1 → 0.51.2
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 +817 -573
- 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 +48 -48
- package/dist/cli/commands.js +0 -323
- package/dist/cli/completer.js +0 -167
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -165
- package/dist/cli/plugin-commands.js +0 -36
- package/dist/cli/repl-commands.js +0 -661
- package/dist/cli/repl.js +0 -616
- package/dist/cli/run-result.js +0 -22
- package/dist/cli/security-commands.js +0 -164
- package/dist/cli/setup.js +0 -231
- package/dist/config/config.js +0 -249
- package/dist/config/defaults.js +0 -124
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -3
- package/dist/config/security.js +0 -193
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -102
- package/dist/core/agent.js +0 -886
- package/dist/core/bootstrap.js +0 -404
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -76
- package/dist/core/session-logger.js +0 -197
- package/dist/core/types.js +0 -1
- package/dist/core/version.js +0 -24
- package/dist/core/workspace.js +0 -76
- package/dist/i18n/en.json +0 -598
- package/dist/i18n/index.js +0 -46
- package/dist/i18n/ru.json +0 -598
- 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 -359
- package/dist/llm/orchestrator.js +0 -198
- 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 -143
- 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 -338
- package/dist/modules/browser/snapshot.js +0 -148
- package/dist/modules/browser/types.js +0 -12
- package/dist/modules/certification/cli.js +0 -174
- package/dist/modules/certification/fact-checker.js +0 -82
- package/dist/modules/certification/loader.js +0 -105
- package/dist/modules/certification/manifest.js +0 -50
- package/dist/modules/certification/runner.js +0 -159
- package/dist/modules/certification/scenarios.js +0 -124
- 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 -162
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -423
- package/dist/modules/execution/audit-runners.js +0 -152
- package/dist/modules/execution/auditor.js +0 -218
- package/dist/modules/execution/execution-plugin.js +0 -272
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/module.js +0 -436
- package/dist/modules/execution/moe-executor.js +0 -291
- 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 -157
- package/dist/modules/execution/plan-tool.js +0 -508
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -90
- package/dist/modules/execution/stuck-detector.js +0 -510
- package/dist/modules/execution/tracker.js +0 -67
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -222
- 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 -43
- package/dist/modules/hallucination/factual.js +0 -129
- 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 -245
- 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 -278
- 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 -232
- package/dist/modules/lsp/probe.js +0 -76
- package/dist/modules/lsp/project-root.js +0 -32
- package/dist/modules/lsp/startup-check.js +0 -141
- 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 -231
- 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 -217
- package/dist/modules/plugins/types.js +0 -1
- 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/registry.js +0 -47
- 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 -205
- 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 -71
- package/dist/modules/security/path-validator.js +0 -207
- 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 -32
- package/dist/tools/attach-image.js +0 -89
- package/dist/tools/bash.js +0 -496
- package/dist/tools/browser.js +0 -114
- package/dist/tools/chunk-query.js +0 -99
- package/dist/tools/create-dir.js +0 -55
- package/dist/tools/delete-file.js +0 -62
- package/dist/tools/download-file.js +0 -116
- package/dist/tools/edit-file.js +0 -79
- package/dist/tools/enable-tools.js +0 -58
- package/dist/tools/executor.js +0 -144
- package/dist/tools/file-info.js +0 -46
- package/dist/tools/filter-tools.js +0 -17
- package/dist/tools/glob-tool.js +0 -26
- package/dist/tools/grep-tool.js +0 -84
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/tools/index.js +0 -78
- package/dist/tools/list-dir.js +0 -48
- package/dist/tools/load-skill.js +0 -42
- package/dist/tools/mcp-call.js +0 -68
- package/dist/tools/move-file.js +0 -85
- package/dist/tools/path-utils.js +0 -51
- package/dist/tools/pipeline-run.js +0 -144
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-kill.js +0 -29
- package/dist/tools/process-list.js +0 -36
- package/dist/tools/process-log.js +0 -45
- package/dist/tools/question.js +0 -140
- package/dist/tools/read-file.js +0 -91
- package/dist/tools/recall.js +0 -117
- package/dist/tools/registry.js +0 -47
- package/dist/tools/remember.js +0 -67
- package/dist/tools/scope-check.js +0 -30
- package/dist/tools/search-history.js +0 -84
- 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 -86
- package/dist/tools/web-fetch.js +0 -98
- package/dist/tools/web-search.js +0 -78
- package/dist/tools/write-file.js +0 -81
- 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 -703
- package/dist/ui/line-math.js +0 -69
- 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 -209
- package/dist/ui/spinner.js +0 -70
- package/dist/ui/table.js +0 -144
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync } from "fs";
|
|
2
|
-
import { dirname, join, resolve } from "path";
|
|
3
|
-
import { detectTestResults } from "../../tools/bash";
|
|
4
|
-
import { processRegistry } from "../processes";
|
|
5
|
-
/** Directories never searched for test files. */
|
|
6
|
-
export const SKIP_DIRS = new Set([
|
|
7
|
-
"node_modules",
|
|
8
|
-
".git",
|
|
9
|
-
".mma",
|
|
10
|
-
"dist",
|
|
11
|
-
"build",
|
|
12
|
-
"coverage",
|
|
13
|
-
".next",
|
|
14
|
-
".nuxt",
|
|
15
|
-
"vendor",
|
|
16
|
-
]);
|
|
17
|
-
const TEST_EXT_RE = /\.(test|spec)\.[jt]sx?$/i;
|
|
18
|
-
const TEST_STEP_RE = /\b(test(ing|s)?|тест(ы|ирование|ировать)?|провер\w*\s+тест|запустить\s+тест)\b|bun test|npm test|vitest|pytest|go test|jest|mocha/i;
|
|
19
|
-
/** Recursive, depth-limited walk that stops early on the first test file. */
|
|
20
|
-
export function findTestFile(dir, depth = 0) {
|
|
21
|
-
if (depth > 5)
|
|
22
|
-
return null;
|
|
23
|
-
let entries;
|
|
24
|
-
try {
|
|
25
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
for (const e of entries) {
|
|
31
|
-
const full = join(dir, e.name);
|
|
32
|
-
if (e.isDirectory()) {
|
|
33
|
-
if (SKIP_DIRS.has(e.name))
|
|
34
|
-
continue;
|
|
35
|
-
const found = findTestFile(full, depth + 1);
|
|
36
|
-
if (found)
|
|
37
|
-
return found;
|
|
38
|
-
}
|
|
39
|
-
else if (TEST_EXT_RE.test(e.name)) {
|
|
40
|
-
return full;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
/** The plan's step descriptions mention running/checking tests. */
|
|
46
|
-
export function hasTestStep(plan) {
|
|
47
|
-
return plan.steps.some((s) => TEST_STEP_RE.test(s.description));
|
|
48
|
-
}
|
|
49
|
-
/** Extract failing test names from a runner's output (bun/vitest style). */
|
|
50
|
-
function extractFailingNames(output, limit = 5) {
|
|
51
|
-
const names = [];
|
|
52
|
-
for (const m of output.matchAll(/\(fail\)\s*([^\n]+)/g)) {
|
|
53
|
-
const name = m[1].trim();
|
|
54
|
-
if (name && !names.includes(name))
|
|
55
|
-
names.push(name);
|
|
56
|
-
if (names.length >= limit)
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
return names;
|
|
60
|
-
}
|
|
61
|
-
export async function runTests(baseDir) {
|
|
62
|
-
const entry = processRegistry.start("bun test", baseDir);
|
|
63
|
-
const exited = await processRegistry.waitForExit(entry.id, 90_000);
|
|
64
|
-
const output = entry.log.join("\n");
|
|
65
|
-
processRegistry.remove(entry.id);
|
|
66
|
-
if (!exited) {
|
|
67
|
-
// Timed out — we cannot claim success or failure from an unfinished run.
|
|
68
|
-
return {
|
|
69
|
-
checked: true,
|
|
70
|
-
passed: true, // don't block completion on an inconclusive run
|
|
71
|
-
failed: 0,
|
|
72
|
-
passedCount: 0,
|
|
73
|
-
detail: "test run timed out after 90s — result unknown",
|
|
74
|
-
command: "bun test",
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
const run = detectTestResults(output);
|
|
78
|
-
if (!run) {
|
|
79
|
-
// No recognizable runner summary. A non-zero exit means the test run
|
|
80
|
-
// (or its runner) failed — the audit must not pass on it; report a
|
|
81
|
-
// single "at least one failed" since we cannot count them. Exit 0
|
|
82
|
-
// with unrecognized output stays checked-but-inconclusive.
|
|
83
|
-
return {
|
|
84
|
-
checked: true,
|
|
85
|
-
passed: entry.exitCode === 0,
|
|
86
|
-
failed: entry.exitCode === 0 ? 0 : 1,
|
|
87
|
-
passedCount: 0,
|
|
88
|
-
detail: output.slice(0, 200).trim(),
|
|
89
|
-
command: "bun test",
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
const names = extractFailingNames(output);
|
|
93
|
-
return {
|
|
94
|
-
checked: true,
|
|
95
|
-
passed: run.failed === 0,
|
|
96
|
-
failed: run.failed,
|
|
97
|
-
passedCount: run.passed,
|
|
98
|
-
detail: names.length
|
|
99
|
-
? names.join("; ")
|
|
100
|
-
: run.summary || `${run.failed} failed / ${run.passed} passed`,
|
|
101
|
-
command: "bun test",
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
/** First `error TS…` line in a tsc output, or null when none/inconclusive. */
|
|
105
|
-
export function parseTypecheckErrors(output) {
|
|
106
|
-
const line = output.split("\n").find((l) => /error TS\d+/.test(l));
|
|
107
|
-
return line ? line.trim().slice(0, 300) : null;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Locate the nearest project root that owns a tsconfig.json.
|
|
111
|
-
*
|
|
112
|
-
* The agent frequently creates the project in a nested subfolder of its
|
|
113
|
-
* baseDir (e.g. `bicycle-shop/`), so the tsconfig lives at
|
|
114
|
-
* `baseDir/bicycle-shop/` and the old `existsSync(baseDir/tsconfig.json)`
|
|
115
|
-
* gate silently skipped the final typecheck — the audit passed even with
|
|
116
|
-
* dozens of tsc errors (observed in both analyzed sessions). This walks each
|
|
117
|
-
* candidate (baseDir plus the resolved step files) upward to the nearest
|
|
118
|
-
* tsconfig.json, preferring the shallowest hit so the baseDir project wins
|
|
119
|
-
* over a stray tsconfig further up. Returns null when no tsconfig exists in
|
|
120
|
-
* any reachable subtree. `existingFiles` must contain resolved absolute paths
|
|
121
|
-
* (relative tokens would resolve against the process cwd, not baseDir).
|
|
122
|
-
*/
|
|
123
|
-
export function findTypecheckRoot(baseDir, existingFiles = []) {
|
|
124
|
-
const candidates = [baseDir, ...existingFiles];
|
|
125
|
-
let best = null;
|
|
126
|
-
for (const start of candidates) {
|
|
127
|
-
let dir = resolve(start);
|
|
128
|
-
for (let depth = 0; depth <= 10; depth++) {
|
|
129
|
-
if (existsSync(join(dir, "tsconfig.json"))) {
|
|
130
|
-
if (!best || depth < best.depth)
|
|
131
|
-
best = { depth, root: dir };
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
const parent = dirname(dir);
|
|
135
|
-
if (parent === dir)
|
|
136
|
-
break;
|
|
137
|
-
dir = parent;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return best?.root ?? null;
|
|
141
|
-
}
|
|
142
|
-
export async function runTypecheck(baseDir) {
|
|
143
|
-
const entry = processRegistry.start("npx --no-install tsc --noEmit --skipLibCheck", baseDir);
|
|
144
|
-
const exited = await processRegistry.waitForExit(entry.id, 90_000);
|
|
145
|
-
const output = entry.log.join("\n");
|
|
146
|
-
processRegistry.remove(entry.id);
|
|
147
|
-
// A timeout, or output with no `error TS` lines (missing typescript / wrong
|
|
148
|
-
// working dir) is inconclusive — never block completion on those.
|
|
149
|
-
if (!exited)
|
|
150
|
-
return null;
|
|
151
|
-
return parseTypecheckErrors(output);
|
|
152
|
-
}
|
|
@@ -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,272 +0,0 @@
|
|
|
1
|
-
import { t } from "../../i18n/index";
|
|
2
|
-
import { detectTestResults } from "../../tools/bash";
|
|
3
|
-
import { forbiddenWindowsCommand } from "./windows-commands";
|
|
4
|
-
import { platform } from "os";
|
|
5
|
-
/**
|
|
6
|
-
* Cooldown (in iterations) between stuck-recovery injections. Exported so the
|
|
7
|
-
* ExecutionModule can seed its state's lastRecoveryIteration with the same
|
|
8
|
-
* offset the plugin compares against.
|
|
9
|
-
*/
|
|
10
|
-
export const STUCK_RECOVERY_COOLDOWN = 5;
|
|
11
|
-
const MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3;
|
|
12
|
-
const FORCE_SKIP_THRESHOLD = 10;
|
|
13
|
-
/**
|
|
14
|
-
* The execution plugin hooks, extracted verbatim from ExecutionModule.getPlugin().
|
|
15
|
-
* All shared mutable state (pendingMessages, forbiddenBashFailures, state,
|
|
16
|
-
* tracker) is owned by the module and mutated by reference through `deps`.
|
|
17
|
-
*/
|
|
18
|
-
export function createExecutionPlugin(deps) {
|
|
19
|
-
return {
|
|
20
|
-
name: "execution",
|
|
21
|
-
onBeforeThink: (ctx) => {
|
|
22
|
-
// Flush deferred <system-summary> messages first so they are visible
|
|
23
|
-
// to the next model call but never interleave tool messages.
|
|
24
|
-
if (ctx.contextManager && deps.pendingMessages.length > 0) {
|
|
25
|
-
for (const m of deps.pendingMessages.splice(0)) {
|
|
26
|
-
ctx.contextManager.addMessage(m);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
if (deps.trackerRef.current?.isComplete()) {
|
|
30
|
-
// All steps done/skipped — stop counting "no progress" against
|
|
31
|
-
// the last step and silence step-stuck warnings for a finished
|
|
32
|
-
// plan, BUT keep the tool-call history so a read-without-write
|
|
33
|
-
// loop is still caught (observed: plan complete + false-positive
|
|
34
|
-
// audit → the model re-read files for 50+ iterations with zero
|
|
35
|
-
// writes and no recovery hint).
|
|
36
|
-
deps.stuckDetector.resetStepProgress();
|
|
37
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
38
|
-
deps.state.lastStepId = -1;
|
|
39
|
-
deps.state.stuckNotified = false;
|
|
40
|
-
}
|
|
41
|
-
else {
|
|
42
|
-
const step = deps.trackerRef.current?.getCurrentStep();
|
|
43
|
-
if (deps.trackerRef.current && step) {
|
|
44
|
-
if (step.id !== deps.state.lastStepId) {
|
|
45
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
46
|
-
deps.state.lastStepId = step.id;
|
|
47
|
-
deps.state.stuckNotified = false;
|
|
48
|
-
}
|
|
49
|
-
deps.stuckDetector.setCurrentStep(step.id, step.description);
|
|
50
|
-
deps.stuckDetector.recordIteration(step.id);
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
deps.stuckDetector.reset();
|
|
54
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
55
|
-
deps.state.lastStepId = -1;
|
|
56
|
-
deps.state.stuckNotified = false;
|
|
57
|
-
const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
58
|
-
if (iter === 3 && !deps.trackerRef.current && ctx.contextManager) {
|
|
59
|
-
ctx.contextManager.addMessage({
|
|
60
|
-
role: "user",
|
|
61
|
-
content: `<system-summary>You have made 3 tool calls without creating a plan. For any task that involves creating files, installing packages, or multiple steps — you MUST use plan create BEFORE continuing. Use the plan tool now with concrete steps (exact filenames, commands, deliverables). Do NOT make any more write/edit/bash calls until you have a plan.</system-summary>`,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
if (iter >= 6 && !deps.trackerRef.current && ctx.contextManager) {
|
|
65
|
-
ctx.contextManager.addMessage({
|
|
66
|
-
role: "user",
|
|
67
|
-
content: `<system-summary>STOP. ${iter} iterations without a plan. You MUST call plan create RIGHT NOW. No more tool calls until you create a plan.</system-summary>`,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
const stuckReason = deps.stuckDetector.getStuckReason();
|
|
73
|
-
if (stuckReason) {
|
|
74
|
-
// Log once per stuck episode instead of spamming every iteration.
|
|
75
|
-
const logIt = deps.stuckDetector.isStuck() ? !deps.state.stuckNotified : true;
|
|
76
|
-
if (logIt) {
|
|
77
|
-
ctx.logger?.warn(stuckReason);
|
|
78
|
-
ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
|
|
79
|
-
if (deps.stuckDetector.isStuck())
|
|
80
|
-
deps.state.stuckNotified = true;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
if (deps.stuckDetector.isStuck() ||
|
|
84
|
-
deps.stuckDetector.hasRepetitiveToolCalls() ||
|
|
85
|
-
deps.stuckDetector.hasReadOnlyLoop()) {
|
|
86
|
-
const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
87
|
-
if (currentIter - deps.state.lastRecoveryIteration >= STUCK_RECOVERY_COOLDOWN) {
|
|
88
|
-
const recovery = deps.stuckDetector.getRecoveryMessage();
|
|
89
|
-
if (recovery && ctx.contextManager) {
|
|
90
|
-
const lastError = deps.stuckDetector.getLastErrorOutput();
|
|
91
|
-
const skillHint = lastError
|
|
92
|
-
? "\nIf you have relevant skills available, consider loading one with load_skill for expert guidance."
|
|
93
|
-
: "";
|
|
94
|
-
// Actionable hints based on actual error output
|
|
95
|
-
const actionableHints = deps.stuckDetector.getActionableHints();
|
|
96
|
-
const actionableHintStr = actionableHints.length > 0
|
|
97
|
-
? `\n${t("exec.hints", { hints: actionableHints.map((h) => `- ${h}`).join("\n") })}`
|
|
98
|
-
: "";
|
|
99
|
-
// Tool alternative suggestion
|
|
100
|
-
const alternative = deps.stuckDetector.getToolAlternative();
|
|
101
|
-
const altHint = alternative
|
|
102
|
-
? `\nTool "${deps.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.`
|
|
103
|
-
: "";
|
|
104
|
-
ctx.contextManager.addMessage({
|
|
105
|
-
role: "user",
|
|
106
|
-
content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
const hints = deps.stuckDetector.getHints();
|
|
110
|
-
if (hints.length > 0 && ctx.contextManager) {
|
|
111
|
-
const hintMsg = t("exec.hints", {
|
|
112
|
-
hints: hints.map((h) => `- ${h}`).join("\n"),
|
|
113
|
-
});
|
|
114
|
-
ctx.contextManager.addMessage({
|
|
115
|
-
role: "user",
|
|
116
|
-
content: `<system-summary>${hintMsg}</system-summary>`,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
deps.stuckDetector.recordEscalation();
|
|
120
|
-
deps.state.lastRecoveryIteration = currentIter;
|
|
121
|
-
if (deps.stuckDetector.shouldEscalate() && ctx.onMeta) {
|
|
122
|
-
const escalation = t("exec.escalation", {
|
|
123
|
-
stepId: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
124
|
-
description: deps.trackerRef.current?.getCurrentStep()?.description ?? "",
|
|
125
|
-
});
|
|
126
|
-
ctx.onMeta(escalation);
|
|
127
|
-
}
|
|
128
|
-
// Force skip after too many iterations on the same step
|
|
129
|
-
if (deps.stuckDetector.getIterationsOnCurrentStep() >= FORCE_SKIP_THRESHOLD &&
|
|
130
|
-
ctx.contextManager) {
|
|
131
|
-
const step = deps.trackerRef.current?.getCurrentStep();
|
|
132
|
-
ctx.contextManager.addMessage({
|
|
133
|
-
role: "user",
|
|
134
|
-
content: `<system-summary>STOP. Step ${step?.id ?? "?"} ("${step?.description ?? ""}") took ${deps.stuckDetector.getIterationsOnCurrentStep()} iterations with no progress. DO NOT continue this step. Immediately call: plan update step=${step?.id ?? "?"} status=done (if code works despite warnings) OR plan update step=${step?.id ?? "?"} status=skipped note="reason". Do NOT make any other tool calls before updating the plan.</system-summary>`,
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
},
|
|
140
|
-
onBeforeTool: (_ctx, call) => {
|
|
141
|
-
const warning = deps.checkPlanAlignment(call);
|
|
142
|
-
if (warning) {
|
|
143
|
-
deps.pendingMessages.push({
|
|
144
|
-
role: "user",
|
|
145
|
-
content: `<system-summary>${warning}</system-summary>`,
|
|
146
|
-
});
|
|
147
|
-
deps.state.consecutivePlanWarnings++;
|
|
148
|
-
if (deps.state.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
|
|
149
|
-
deps.pendingMessages.push({
|
|
150
|
-
role: "user",
|
|
151
|
-
content: `<system-summary>${t("exec.plan_blocked", { step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`,
|
|
152
|
-
});
|
|
153
|
-
// Block with an explicit reason so the model sees WHY the call
|
|
154
|
-
// was blocked (executor shows it via tool.blocked_reason).
|
|
155
|
-
return t("exec.plan_blocked", {
|
|
156
|
-
step: String(deps.trackerRef.current?.getCurrentStep()?.id ?? "?"),
|
|
157
|
-
max: MAX_PLAN_WARNINGS_BEFORE_BLOCK,
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
deps.state.consecutivePlanWarnings = 0;
|
|
163
|
-
}
|
|
164
|
-
return true;
|
|
165
|
-
},
|
|
166
|
-
onToolCall: (ctx) => {
|
|
167
|
-
const toolName = ctx?.toolName;
|
|
168
|
-
const args = ctx?.args;
|
|
169
|
-
if (toolName && args) {
|
|
170
|
-
deps.stuckDetector.recordToolCall(toolName, args);
|
|
171
|
-
}
|
|
172
|
-
},
|
|
173
|
-
onAfterTool: (ctx, call, result) => {
|
|
174
|
-
// Remember every bash invocation (successful or not) so stuck
|
|
175
|
-
// detection can reason about e.g. repeated runs with empty output.
|
|
176
|
-
if (call.name === "bash") {
|
|
177
|
-
deps.stuckDetector.recordBashOutput(String(call.arguments?.command ?? ""), String(result.output ?? ""));
|
|
178
|
-
}
|
|
179
|
-
// A write/edit/bash that reports success but still carries a
|
|
180
|
-
// type/syntax error in its output is NOT a success — the model must
|
|
181
|
-
// fix the actual error, not keep rewriting the file blindly. Feed it
|
|
182
|
-
// to the stuck detector so the rewrite-loop hint fires.
|
|
183
|
-
const toolText = String(result.output ?? "");
|
|
184
|
-
if (/error TS\d+|\[Project typecheck failed\]|\[Syntax check failed\]/.test(toolText)) {
|
|
185
|
-
deps.stuckDetector.recordToolError(call.name, toolText.slice(0, 300));
|
|
186
|
-
}
|
|
187
|
-
if (!result.success) {
|
|
188
|
-
// Hard stop for forbidden Windows commands: after the second
|
|
189
|
-
// failure of the same forbidden command (grep/sed/ls/find/…),
|
|
190
|
-
// inject a STOP message so the session stops burning bash calls
|
|
191
|
-
// on commands cmd.exe does not have. Mirrors the bash tool's own
|
|
192
|
-
// win32-gated hard-stop (src/tools/bash.ts) — on macOS/Linux
|
|
193
|
-
// these commands exist and fail for legit reasons.
|
|
194
|
-
if (call.name === "bash" && platform() === "win32") {
|
|
195
|
-
const cmd = String(call.arguments?.command ?? "");
|
|
196
|
-
const forbidden = forbiddenWindowsCommand(cmd);
|
|
197
|
-
if (forbidden) {
|
|
198
|
-
const n = (deps.forbiddenBashFailures.get(forbidden) || 0) + 1;
|
|
199
|
-
deps.forbiddenBashFailures.set(forbidden, n);
|
|
200
|
-
if (n === 2) {
|
|
201
|
-
deps.pendingMessages.push({
|
|
202
|
-
role: "user",
|
|
203
|
-
content: `<system-summary>${t("exec.forbidden_cmd", { cmd: forbidden })}</system-summary>`,
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
deps.stuckDetector.recordToolError(call.name, result.output);
|
|
209
|
-
// Immediately queue an actionable hint on failure (don't wait for
|
|
210
|
-
// the stuck threshold). Flushed in onBeforeThink so it does not
|
|
211
|
-
// land between tool messages.
|
|
212
|
-
const actionableHints = deps.stuckDetector.getActionableHints();
|
|
213
|
-
const alternative = deps.stuckDetector.getToolAlternative();
|
|
214
|
-
if (actionableHints.length > 0 || alternative) {
|
|
215
|
-
const parts = [...actionableHints];
|
|
216
|
-
if (alternative) {
|
|
217
|
-
parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
|
|
218
|
-
}
|
|
219
|
-
deps.pendingMessages.push({
|
|
220
|
-
role: "user",
|
|
221
|
-
content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join("\n") })}</system-summary>`,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
else {
|
|
226
|
-
deps.stuckDetector.recordToolSuccess();
|
|
227
|
-
// When a bash command runs code successfully, suggest marking the
|
|
228
|
-
// step done — unless the output shows failing tests, which must
|
|
229
|
-
// never be reported as a clean success.
|
|
230
|
-
if (call.name === "bash" && result.success) {
|
|
231
|
-
const cmd = String(call.arguments?.command ?? "");
|
|
232
|
-
const testRun = detectTestResults(String(result.output ?? ""));
|
|
233
|
-
if (testRun && testRun.failed > 0) {
|
|
234
|
-
deps.pendingMessages.push({
|
|
235
|
-
role: "user",
|
|
236
|
-
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>`,
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
else if (testRun && testRun.failed === 0 && testRun.passed > 0) {
|
|
240
|
-
deps.pendingMessages.push({
|
|
241
|
-
role: "user",
|
|
242
|
-
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>`,
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
else if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
|
|
246
|
-
deps.pendingMessages.push({
|
|
247
|
-
role: "user",
|
|
248
|
-
content: `<system-summary>The command "${cmd}" completed successfully. If this was testing your code, mark the current step as done via plan update step=N status=done.</system-summary>`,
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
if (result.success && (call.name === "write_file" || call.name === "edit_file")) {
|
|
254
|
-
const filePath = call.arguments?.path;
|
|
255
|
-
if (filePath) {
|
|
256
|
-
deps.stuckDetector.recordFileRewrite(filePath);
|
|
257
|
-
if (deps.stuckDetector.hasExcessiveRewrites()) {
|
|
258
|
-
const file = deps.stuckDetector.getExcessiveRewriteFile();
|
|
259
|
-
const count = deps.stuckDetector.getFileRewriteCount(file);
|
|
260
|
-
if (ctx.onMeta) {
|
|
261
|
-
ctx.onMeta(t("exec.file_rewrite_warning", {
|
|
262
|
-
file: file,
|
|
263
|
-
count: String(count),
|
|
264
|
-
}));
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
deps.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
|
|
269
|
-
}
|
|
270
|
-
},
|
|
271
|
-
};
|
|
272
|
-
}
|
|
@@ -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";
|