micro-models-agent 0.43.3 → 0.45.0
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 +312 -312
- package/bin/mma.mjs +41 -41
- package/dist/main.js +333 -66
- 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 -333
- package/dist/cli/completer.js +0 -168
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -140
- package/dist/cli/repl-commands.js +0 -633
- package/dist/cli/repl.js +0 -486
- package/dist/cli/security-commands.js +0 -166
- package/dist/cli/setup.js +0 -249
- package/dist/config/config.js +0 -202
- package/dist/config/defaults.js +0 -100
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -3
- package/dist/config/security.js +0 -200
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -110
- package/dist/core/agent.js +0 -695
- package/dist/core/bootstrap.js +0 -337
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -55
- package/dist/core/session-logger.js +0 -155
- package/dist/core/types.js +0 -1
- package/dist/core/workspace.js +0 -76
- package/dist/i18n/en.json +0 -525
- package/dist/i18n/index.js +0 -46
- package/dist/i18n/ru.json +0 -525
- package/dist/index.js +0 -22
- package/dist/llm/image-utils.js +0 -144
- package/dist/llm/index.js +0 -4
- package/dist/llm/model-loader.js +0 -78
- package/dist/llm/openai-compat.js +0 -353
- package/dist/llm/orchestrator.js +0 -194
- 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/browser/actions.js +0 -46
- package/dist/modules/browser/cookie-store.js +0 -24
- package/dist/modules/browser/index.js +0 -5
- package/dist/modules/browser/module.js +0 -28
- package/dist/modules/browser/session.js +0 -335
- package/dist/modules/browser/snapshot.js +0 -114
- package/dist/modules/browser/types.js +0 -9
- package/dist/modules/certification/cli.js +0 -176
- package/dist/modules/certification/fact-checker.js +0 -84
- package/dist/modules/certification/loader.js +0 -111
- package/dist/modules/certification/manifest.js +0 -50
- package/dist/modules/certification/runner.js +0 -162
- package/dist/modules/certification/scenarios.js +0 -124
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -349
- package/dist/modules/execution/auditor.js +0 -66
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/module.js +0 -779
- package/dist/modules/execution/moe-executor.js +0 -266
- 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 -159
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -85
- package/dist/modules/execution/stuck-detector.js +0 -347
- package/dist/modules/execution/tracker.js +0 -67
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -178
- package/dist/modules/hallucination/confidence.js +0 -59
- package/dist/modules/hallucination/consistency.js +0 -26
- package/dist/modules/hallucination/detector.js +0 -46
- package/dist/modules/hallucination/factual.js +0 -190
- package/dist/modules/hallucination/index.js +0 -5
- package/dist/modules/hallucination/js-identifiers.js +0 -72
- package/dist/modules/hallucination/llm-judge.js +0 -103
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -38
- package/dist/modules/indexer/index.js +0 -3
- package/dist/modules/indexer/module.js +0 -192
- package/dist/modules/indexer/walker.js +0 -101
- package/dist/modules/lsp/client.js +0 -235
- package/dist/modules/lsp/config.js +0 -81
- package/dist/modules/lsp/index.js +0 -3
- package/dist/modules/lsp/module.js +0 -68
- 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 -146
- package/dist/modules/mcp/registry.js +0 -15
- package/dist/modules/memory/index.js +0 -1
- package/dist/modules/memory/module.js +0 -48
- package/dist/modules/memory/search.js +0 -40
- 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 -53
- package/dist/modules/pipelines/template.js +0 -14
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -226
- package/dist/modules/plugins/builtin/notify.js +0 -8
- package/dist/modules/plugins/index.js +0 -1
- package/dist/modules/plugins/loader.js +0 -28
- package/dist/modules/plugins/manager.js +0 -161
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/processes/index.js +0 -2
- package/dist/modules/processes/registry.js +0 -238
- package/dist/modules/processes/runner.js +0 -23
- package/dist/modules/registry.js +0 -45
- 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 -211
- package/dist/modules/security/content-scanner.js +0 -53
- package/dist/modules/security/data-sanitizer.js +0 -97
- package/dist/modules/security/encryption.js +0 -240
- package/dist/modules/security/index.js +0 -14
- package/dist/modules/security/network-validator.js +0 -79
- package/dist/modules/security/path-validator.js +0 -209
- package/dist/modules/security/rate-limiter.js +0 -119
- package/dist/modules/security/security-policies.js +0 -547
- 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 -228
- 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/module.js +0 -130
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/checker.js +0 -32
- package/dist/modules/updater/index.js +0 -1
- 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 -337
- package/dist/tools/browser.js +0 -97
- package/dist/tools/create-dir.js +0 -55
- package/dist/tools/delete-file.js +0 -62
- package/dist/tools/edit-file.js +0 -79
- package/dist/tools/executor.js +0 -145
- package/dist/tools/file-info.js +0 -45
- package/dist/tools/filter-tools.js +0 -10
- package/dist/tools/glob-tool.js +0 -26
- package/dist/tools/grep-tool.js +0 -86
- package/dist/tools/index.js +0 -67
- package/dist/tools/list-dir.js +0 -47
- package/dist/tools/load-skill.js +0 -44
- 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 -38
- package/dist/tools/process-log.js +0 -41
- package/dist/tools/question.js +0 -142
- package/dist/tools/read-file.js +0 -83
- package/dist/tools/recall.js +0 -110
- package/dist/tools/registry.js +0 -36
- 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 -151
- 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 -83
- package/dist/ui/box.js +0 -81
- package/dist/ui/colors.js +0 -4
- package/dist/ui/diff.js +0 -178
- package/dist/ui/index.js +0 -6
- package/dist/ui/md-formatter.js +0 -212
- package/dist/ui/output.js +0 -13
- package/dist/ui/renderer.js +0 -204
- package/dist/ui/spinner.js +0 -70
- package/dist/ui/table.js +0 -144
|
@@ -1,779 +0,0 @@
|
|
|
1
|
-
import { t } from "../../i18n/index";
|
|
2
|
-
import { PlanCreator } from "./planner";
|
|
3
|
-
import { PlanTracker } from "./tracker";
|
|
4
|
-
import { StepVerifier } from "./verifier";
|
|
5
|
-
import { StuckDetector } from "./stuck-detector";
|
|
6
|
-
import { Auditor } from "./auditor";
|
|
7
|
-
import { PlanStore } from "./plan-store";
|
|
8
|
-
import { checkPlanCoverage } from "./plan-coverage";
|
|
9
|
-
import { getMessageText } from "../../llm/provider";
|
|
10
|
-
import { existsSync, readFileSync } from "fs";
|
|
11
|
-
import { resolve } from "path";
|
|
12
|
-
const STUCK_RECOVERY_COOLDOWN = 5;
|
|
13
|
-
const MAX_PLAN_WARNINGS_BEFORE_BLOCK = 3;
|
|
14
|
-
const FORCE_SKIP_THRESHOLD = 10;
|
|
15
|
-
export class ExecutionModule {
|
|
16
|
-
name = "execution";
|
|
17
|
-
tracker = null;
|
|
18
|
-
verifier;
|
|
19
|
-
stuckDetector;
|
|
20
|
-
auditor;
|
|
21
|
-
store;
|
|
22
|
-
baseDir;
|
|
23
|
-
lastRecoveryIteration = -STUCK_RECOVERY_COOLDOWN;
|
|
24
|
-
consecutivePlanWarnings = 0;
|
|
25
|
-
lastStepId = -1;
|
|
26
|
-
// How many times the deps-step gate has warned for each step id — used to
|
|
27
|
-
// escalate from a soft hint to a MUST-update instruction (models otherwise
|
|
28
|
-
// spin on "install dependencies" steps they invented themselves).
|
|
29
|
-
depsGateHints = new Map();
|
|
30
|
-
// Skip the first audit after restoring a plan from disk — gives the user
|
|
31
|
-
// one chance to change the subject before the audit gate locks in.
|
|
32
|
-
_auditSkipsRemaining = 0;
|
|
33
|
-
stuckNotified = false;
|
|
34
|
-
/**
|
|
35
|
-
* Deferred <system-summary> messages. Injected by onBeforeTool/onAfterTool
|
|
36
|
-
* but flushed in onBeforeThink, so they never land between an assistant
|
|
37
|
-
* tool_call message and its tool results (which breaks message pairing and
|
|
38
|
-
* buries the warnings the model should react to).
|
|
39
|
-
*/
|
|
40
|
-
pendingMessages = [];
|
|
41
|
-
constructor(baseDir, stuckThreshold = 6) {
|
|
42
|
-
this.baseDir = baseDir;
|
|
43
|
-
this.verifier = new StepVerifier(baseDir);
|
|
44
|
-
this.stuckDetector = new StuckDetector(stuckThreshold);
|
|
45
|
-
this.auditor = new Auditor(baseDir);
|
|
46
|
-
this.store = new PlanStore(baseDir);
|
|
47
|
-
}
|
|
48
|
-
setPlan(plan) {
|
|
49
|
-
this.tracker = new PlanTracker(plan);
|
|
50
|
-
this.store.saveActive(plan);
|
|
51
|
-
this._auditSkipsRemaining = 0; // plan explicitly created — arm the audit gate
|
|
52
|
-
}
|
|
53
|
-
restorePlan() {
|
|
54
|
-
const plan = this.store.loadActive();
|
|
55
|
-
if (!plan)
|
|
56
|
-
return false;
|
|
57
|
-
this.tracker = new PlanTracker(plan);
|
|
58
|
-
// Advance to the first non-done step
|
|
59
|
-
this.tracker.syncCurrentStep();
|
|
60
|
-
this._auditSkipsRemaining = 1; // restored plan — one free pass
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
getTracker() {
|
|
64
|
-
return this.tracker;
|
|
65
|
-
}
|
|
66
|
-
getStore() {
|
|
67
|
-
return this.store;
|
|
68
|
-
}
|
|
69
|
-
getStuckDetector() {
|
|
70
|
-
return this.stuckDetector;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Final audit before the agent may declare the task done. Returns null when
|
|
74
|
-
* no plan is active; otherwise checks plan completion + artifact existence.
|
|
75
|
-
*/
|
|
76
|
-
async runFinalAudit() {
|
|
77
|
-
if (!this.tracker)
|
|
78
|
-
return null;
|
|
79
|
-
if (this._auditSkipsRemaining > 0) {
|
|
80
|
-
this._auditSkipsRemaining--;
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
const plan = this.tracker.getPlan();
|
|
84
|
-
const audit = await this.auditor.audit(plan);
|
|
85
|
-
const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped"
|
|
86
|
-
? [`${s.id}. ${s.description}`]
|
|
87
|
-
: []);
|
|
88
|
-
const done = plan.steps.filter((s) => s.status === "done").length;
|
|
89
|
-
const passed = audit.passed && pendingSteps.length === 0;
|
|
90
|
-
return {
|
|
91
|
-
passed,
|
|
92
|
-
done,
|
|
93
|
-
total: plan.steps.length,
|
|
94
|
-
pendingSteps,
|
|
95
|
-
missingFiles: audit.missingFiles,
|
|
96
|
-
summary: audit.summary,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
getSystemPromptBlock() {
|
|
100
|
-
if (!this.tracker)
|
|
101
|
-
return null;
|
|
102
|
-
return {
|
|
103
|
-
content: this.tracker.toPromptBlock(),
|
|
104
|
-
priority: "critical",
|
|
105
|
-
essential: true,
|
|
106
|
-
estimatedTokens: 200,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
getToolDefinitions() {
|
|
110
|
-
return [
|
|
111
|
-
{
|
|
112
|
-
name: "plan",
|
|
113
|
-
description: 'Create, update, show, abort, list, switch, or re-plan multi-step plans.\n\nActions:\n- create: Start a new plan. Previous active plan is auto-preserved: incomplete → draft, complete → archive.\n- update: Mark step status (done/failed/skipped), or rebuild plan with new steps.\n- show: Print current plan checklist.\n- abort: Archive current plan and clear active slot.\n- list: Show all plans (active, drafts, archived) with progress.\n- switch: Make a different plan active (by plan id).\n- re-plan: Iterative replanning: keep completed steps, replace remaining with new steps.\n\nWrite CONCRETE steps with exact file paths and commands:\n- Specify WHICH files to create with exact paths (e.g. "create src/components/Header.tsx with navigation and logo")\n- Specify WHICH packages to install (e.g. "run npm install react react-dom")\n- Specify WHICH CLI commands to run with exact arguments\n- Each step should include at least one file extension (.ts, .tsx, .json, etc.) or a command verb (install, create, build, run, add, init)\n- Good: "Создать src/components/Header.tsx с навигацией и логотипом"\n- Bad: "Настройка проекта" (too vague — what exactly needs to be configured?)',
|
|
114
|
-
parameters: {
|
|
115
|
-
type: "object",
|
|
116
|
-
properties: {
|
|
117
|
-
action: {
|
|
118
|
-
type: "string",
|
|
119
|
-
enum: [
|
|
120
|
-
"create",
|
|
121
|
-
"update",
|
|
122
|
-
"show",
|
|
123
|
-
"abort",
|
|
124
|
-
"list",
|
|
125
|
-
"switch",
|
|
126
|
-
"re-plan",
|
|
127
|
-
],
|
|
128
|
-
},
|
|
129
|
-
title: { type: "string" },
|
|
130
|
-
steps: { type: "array", items: { type: "string" } },
|
|
131
|
-
step: { type: "number" },
|
|
132
|
-
status: { type: "string", enum: ["done", "failed", "skipped"] },
|
|
133
|
-
note: { type: "string" },
|
|
134
|
-
id: { type: "string", description: "Plan id (for switch action)" },
|
|
135
|
-
},
|
|
136
|
-
required: ["action"],
|
|
137
|
-
},
|
|
138
|
-
handler: async (_ctx, args) => {
|
|
139
|
-
const action = String(args.action);
|
|
140
|
-
if (action === "list") {
|
|
141
|
-
const metas = this.store.listAll();
|
|
142
|
-
if (metas.length === 0) {
|
|
143
|
-
return { success: true, output: t("plan.list_empty") };
|
|
144
|
-
}
|
|
145
|
-
const lines = metas.map((m) => {
|
|
146
|
-
const icon = m.status === "active"
|
|
147
|
-
? "[*]"
|
|
148
|
-
: m.status === "draft"
|
|
149
|
-
? "[ ]"
|
|
150
|
-
: "[-]";
|
|
151
|
-
const namePart = m.name ? ` (${m.name})` : "";
|
|
152
|
-
return `${icon} ${m.id}${namePart} — ${m.title} ${m.doneCount}/${m.stepCount}`;
|
|
153
|
-
});
|
|
154
|
-
return {
|
|
155
|
-
success: true,
|
|
156
|
-
output: `${t("plan.list_header")}\n${lines.join("\n")}`,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
if (action === "switch") {
|
|
160
|
-
const planId = String(args.id || "");
|
|
161
|
-
if (!planId) {
|
|
162
|
-
return { success: false, output: t("plan.switch_no_id") };
|
|
163
|
-
}
|
|
164
|
-
const found = this.store.find(planId);
|
|
165
|
-
if (!found) {
|
|
166
|
-
return {
|
|
167
|
-
success: false,
|
|
168
|
-
output: t("plan.not_found", { id: planId }),
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
this.preserveActive();
|
|
172
|
-
if (found.status === "draft") {
|
|
173
|
-
this.store.removeDraft(found.plan.id);
|
|
174
|
-
}
|
|
175
|
-
else if (found.status === "archived") {
|
|
176
|
-
this.store.removeArchived(found.plan.id);
|
|
177
|
-
}
|
|
178
|
-
this.setPlan(found.plan);
|
|
179
|
-
const display = PlanCreator.toPromptBlock(found.plan, 0);
|
|
180
|
-
return {
|
|
181
|
-
success: true,
|
|
182
|
-
output: t("plan.switched", {
|
|
183
|
-
id: found.plan.id,
|
|
184
|
-
title: found.plan.title,
|
|
185
|
-
}),
|
|
186
|
-
display,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
if (action === "re-plan") {
|
|
190
|
-
if (!this.tracker) {
|
|
191
|
-
return { success: false, output: t("plan.no_active") };
|
|
192
|
-
}
|
|
193
|
-
const newSteps = Array.isArray(args.steps)
|
|
194
|
-
? args.steps.map(String)
|
|
195
|
-
: [];
|
|
196
|
-
if (newSteps.length === 0) {
|
|
197
|
-
return { success: false, output: t("plan.replan_no_steps") };
|
|
198
|
-
}
|
|
199
|
-
const oldPlan = this.tracker.getPlan();
|
|
200
|
-
const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
|
|
201
|
-
const keptCount = replanned.steps.length - newSteps.length;
|
|
202
|
-
this.setPlan(replanned);
|
|
203
|
-
const display = PlanCreator.toPromptBlock(replanned, keptCount);
|
|
204
|
-
return {
|
|
205
|
-
success: true,
|
|
206
|
-
output: t("plan.replanned", {
|
|
207
|
-
kept: String(keptCount),
|
|
208
|
-
steps: String(newSteps.length),
|
|
209
|
-
}),
|
|
210
|
-
display,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
if (action === "create") {
|
|
214
|
-
const title = String(args.title || "Task Plan");
|
|
215
|
-
const steps = Array.isArray(args.steps)
|
|
216
|
-
? args.steps.map(String)
|
|
217
|
-
: [];
|
|
218
|
-
if (steps.length === 0) {
|
|
219
|
-
return { success: false, output: t("plan.no_steps") };
|
|
220
|
-
}
|
|
221
|
-
this.preserveActive();
|
|
222
|
-
const plan = PlanCreator.createPlan(title, steps, this.baseDir);
|
|
223
|
-
this.setPlan(plan);
|
|
224
|
-
const display = PlanCreator.toPromptBlock(plan, 0);
|
|
225
|
-
let output = t("plan.created", {
|
|
226
|
-
title,
|
|
227
|
-
steps: String(steps.length),
|
|
228
|
-
});
|
|
229
|
-
let displayOut = display;
|
|
230
|
-
const taskText = this.getTaskText(_ctx);
|
|
231
|
-
if (taskText) {
|
|
232
|
-
const { missing } = checkPlanCoverage(taskText, steps);
|
|
233
|
-
if (missing.length > 0) {
|
|
234
|
-
const warn = t("plan.coverage_warning", {
|
|
235
|
-
missing: missing.join(", "),
|
|
236
|
-
});
|
|
237
|
-
output += `\n⚠️ ${warn}`;
|
|
238
|
-
displayOut = `${display}\n⚠️ ${warn}`;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
success: true,
|
|
243
|
-
output,
|
|
244
|
-
display: displayOut,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
if (action === "show") {
|
|
248
|
-
if (!this.tracker) {
|
|
249
|
-
return { success: false, output: t("plan.no_active") };
|
|
250
|
-
}
|
|
251
|
-
const display = this.tracker.toPromptBlock();
|
|
252
|
-
return {
|
|
253
|
-
success: true,
|
|
254
|
-
output: `${t("plan.show_header")}\n${display}`,
|
|
255
|
-
display,
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
if (action === "update" && args.step && this.tracker) {
|
|
259
|
-
this.tracker.updateStepStatus(Number(args.step), args.status || "done");
|
|
260
|
-
if (args.note)
|
|
261
|
-
this.tracker.addNote(Number(args.step), String(args.note));
|
|
262
|
-
this.tracker.syncCurrentStep();
|
|
263
|
-
this.store.saveActive(this.tracker.getPlan());
|
|
264
|
-
const progress = this.tracker.getProgressString();
|
|
265
|
-
const display = this.tracker.toPromptBlock();
|
|
266
|
-
_ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
|
|
267
|
-
return {
|
|
268
|
-
success: true,
|
|
269
|
-
output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${progress}`,
|
|
270
|
-
display,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
if (action === "update" &&
|
|
274
|
-
Array.isArray(args.steps) &&
|
|
275
|
-
args.steps.length > 0 &&
|
|
276
|
-
this.tracker) {
|
|
277
|
-
const title = String(args.title || this.tracker.getPlan().title);
|
|
278
|
-
const steps = args.steps.map(String);
|
|
279
|
-
const plan = PlanCreator.createPlan(title, steps, this.baseDir);
|
|
280
|
-
this.setPlan(plan);
|
|
281
|
-
const display = PlanCreator.toPromptBlock(plan, 0);
|
|
282
|
-
const output = t("plan.updated", {
|
|
283
|
-
title,
|
|
284
|
-
steps: String(steps.length),
|
|
285
|
-
});
|
|
286
|
-
return {
|
|
287
|
-
success: true,
|
|
288
|
-
output,
|
|
289
|
-
display,
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
if (action === "abort") {
|
|
293
|
-
if (this.tracker) {
|
|
294
|
-
this.store.archivePlan(this.tracker.getPlan());
|
|
295
|
-
}
|
|
296
|
-
this.tracker = null;
|
|
297
|
-
this.store.clearActive();
|
|
298
|
-
return { success: true, output: t("plan.aborted") };
|
|
299
|
-
}
|
|
300
|
-
if (!this.tracker) {
|
|
301
|
-
return { success: false, output: t("plan.no_active") };
|
|
302
|
-
}
|
|
303
|
-
return {
|
|
304
|
-
success: false,
|
|
305
|
-
output: t("plan.unknown_action", { action }),
|
|
306
|
-
};
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
{
|
|
310
|
-
name: "todo",
|
|
311
|
-
description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
|
|
312
|
-
parameters: {
|
|
313
|
-
type: "object",
|
|
314
|
-
properties: {
|
|
315
|
-
action: { type: "string", enum: ["add", "done", "list"] },
|
|
316
|
-
items: { type: "array", items: { type: "string" } },
|
|
317
|
-
},
|
|
318
|
-
required: ["action"],
|
|
319
|
-
},
|
|
320
|
-
handler: async (_ctx, args) => {
|
|
321
|
-
if (!this.tracker) {
|
|
322
|
-
return { success: false, output: t("plan.no_active") };
|
|
323
|
-
}
|
|
324
|
-
const action = String(args.action);
|
|
325
|
-
const currentStep = this.tracker.getCurrentStep();
|
|
326
|
-
if (!currentStep) {
|
|
327
|
-
return { success: false, output: t("plan.step_not_found") };
|
|
328
|
-
}
|
|
329
|
-
if (action === "add" && Array.isArray(args.items)) {
|
|
330
|
-
const items = args.items.map(String);
|
|
331
|
-
currentStep.note = currentStep.note
|
|
332
|
-
? `${currentStep.note}; ${items.join(", ")}`
|
|
333
|
-
: items.join(", ");
|
|
334
|
-
return {
|
|
335
|
-
success: true,
|
|
336
|
-
output: t("todo.added", {
|
|
337
|
-
count: String(items.length),
|
|
338
|
-
items: items.join(", "),
|
|
339
|
-
}),
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
if (action === "done") {
|
|
343
|
-
if (!currentStep) {
|
|
344
|
-
return { success: false, output: t("plan.step_not_found") };
|
|
345
|
-
}
|
|
346
|
-
this.tracker.updateStepStatus(currentStep.id, "done");
|
|
347
|
-
this.tracker.syncCurrentStep();
|
|
348
|
-
this.store.saveActive(this.tracker.getPlan());
|
|
349
|
-
const display = this.tracker.toPromptBlock();
|
|
350
|
-
return {
|
|
351
|
-
success: true,
|
|
352
|
-
output: t("plan.step_status", {
|
|
353
|
-
step: String(currentStep.id),
|
|
354
|
-
status: "done",
|
|
355
|
-
}),
|
|
356
|
-
display,
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
if (action === "list") {
|
|
360
|
-
const note = currentStep.note || "(no sub-tasks)";
|
|
361
|
-
return {
|
|
362
|
-
success: true,
|
|
363
|
-
output: `Step ${currentStep.id}: ${currentStep.description}\nSub-tasks: ${note}`,
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
return {
|
|
367
|
-
success: false,
|
|
368
|
-
output: t("todo.unknown_action", { action }),
|
|
369
|
-
};
|
|
370
|
-
},
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
name: "verify",
|
|
374
|
-
description: "Run verification for the current step. Checks files mentioned in the step description.",
|
|
375
|
-
parameters: {
|
|
376
|
-
type: "object",
|
|
377
|
-
properties: {
|
|
378
|
-
step: { type: "number", description: "Step number to verify" },
|
|
379
|
-
},
|
|
380
|
-
},
|
|
381
|
-
handler: async (_ctx, args) => {
|
|
382
|
-
if (!this.tracker)
|
|
383
|
-
return { success: false, output: t("plan.no_active") };
|
|
384
|
-
const step = args.step
|
|
385
|
-
? this.tracker.getStep(Number(args.step))
|
|
386
|
-
: this.tracker.getCurrentStep();
|
|
387
|
-
if (!step)
|
|
388
|
-
return { success: false, output: t("plan.step_not_found") };
|
|
389
|
-
const result = await this.verifier.verifyStep(step.description);
|
|
390
|
-
return {
|
|
391
|
-
success: result.passed,
|
|
392
|
-
output: result.passed
|
|
393
|
-
? t("verify.passed")
|
|
394
|
-
: t("verify.failed", {
|
|
395
|
-
details: result.failed.map((f) => f.message).join("; "),
|
|
396
|
-
}),
|
|
397
|
-
};
|
|
398
|
-
},
|
|
399
|
-
},
|
|
400
|
-
];
|
|
401
|
-
}
|
|
402
|
-
getPlugin() {
|
|
403
|
-
return {
|
|
404
|
-
name: "execution",
|
|
405
|
-
onBeforeThink: (ctx) => {
|
|
406
|
-
// Flush deferred <system-summary> messages first so they are visible
|
|
407
|
-
// to the next model call but never interleave tool messages.
|
|
408
|
-
if (ctx.contextManager && this.pendingMessages.length > 0) {
|
|
409
|
-
for (const m of this.pendingMessages.splice(0)) {
|
|
410
|
-
ctx.contextManager.addMessage(m);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
if (this.tracker?.isComplete()) {
|
|
414
|
-
// All steps done/skipped — stop counting "no progress" against the
|
|
415
|
-
// last step and silence stuck warnings for a finished plan.
|
|
416
|
-
this.stuckDetector.reset();
|
|
417
|
-
this.consecutivePlanWarnings = 0;
|
|
418
|
-
this.lastStepId = -1;
|
|
419
|
-
this.stuckNotified = false;
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
const step = this.tracker?.getCurrentStep();
|
|
423
|
-
if (this.tracker && step) {
|
|
424
|
-
if (step.id !== this.lastStepId) {
|
|
425
|
-
this.consecutivePlanWarnings = 0;
|
|
426
|
-
this.lastStepId = step.id;
|
|
427
|
-
this.stuckNotified = false;
|
|
428
|
-
}
|
|
429
|
-
this.stuckDetector.setCurrentStep(step.id, step.description);
|
|
430
|
-
this.stuckDetector.recordIteration(step.id);
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
this.stuckDetector.reset();
|
|
434
|
-
this.consecutivePlanWarnings = 0;
|
|
435
|
-
this.lastStepId = -1;
|
|
436
|
-
this.stuckNotified = false;
|
|
437
|
-
const iter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
438
|
-
if (iter === 3 && !this.tracker && ctx.contextManager) {
|
|
439
|
-
ctx.contextManager.addMessage({
|
|
440
|
-
role: "user",
|
|
441
|
-
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>`,
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
if (iter >= 6 && !this.tracker && ctx.contextManager) {
|
|
445
|
-
ctx.contextManager.addMessage({
|
|
446
|
-
role: "user",
|
|
447
|
-
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>`,
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
const stuckReason = this.stuckDetector.getStuckReason();
|
|
453
|
-
if (stuckReason) {
|
|
454
|
-
// Log once per stuck episode instead of spamming every iteration.
|
|
455
|
-
const logIt = this.stuckDetector.isStuck()
|
|
456
|
-
? !this.stuckNotified
|
|
457
|
-
: true;
|
|
458
|
-
if (logIt) {
|
|
459
|
-
ctx.logger?.warn(stuckReason);
|
|
460
|
-
ctx.sessionLog?.plan("stuck-warning", stuckReason, typeof ctx.iteration === "number" ? ctx.iteration : undefined);
|
|
461
|
-
if (this.stuckDetector.isStuck())
|
|
462
|
-
this.stuckNotified = true;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
if (this.stuckDetector.isStuck() ||
|
|
466
|
-
this.stuckDetector.hasRepetitiveToolCalls()) {
|
|
467
|
-
const currentIter = typeof ctx.iteration === "number" ? ctx.iteration : 0;
|
|
468
|
-
if (currentIter - this.lastRecoveryIteration >=
|
|
469
|
-
STUCK_RECOVERY_COOLDOWN) {
|
|
470
|
-
const recovery = this.stuckDetector.getRecoveryMessage();
|
|
471
|
-
if (recovery && ctx.contextManager) {
|
|
472
|
-
const lastError = this.stuckDetector.getLastErrorOutput();
|
|
473
|
-
const skillHint = lastError
|
|
474
|
-
? "\nIf you have relevant skills available, consider loading one with load_skill for expert guidance."
|
|
475
|
-
: "";
|
|
476
|
-
// Actionable hints based on actual error output
|
|
477
|
-
const actionableHints = this.stuckDetector.getActionableHints();
|
|
478
|
-
const actionableHintStr = actionableHints.length > 0
|
|
479
|
-
? `\n${t("exec.hints", { hints: actionableHints.map((h) => `- ${h}`).join("\n") })}`
|
|
480
|
-
: "";
|
|
481
|
-
// Tool alternative suggestion
|
|
482
|
-
const alternative = this.stuckDetector.getToolAlternative();
|
|
483
|
-
const altHint = alternative
|
|
484
|
-
? `\nTool "${this.stuckDetector.getLastFailedTool()}" is failing. Try "${alternative}" instead.`
|
|
485
|
-
: "";
|
|
486
|
-
ctx.contextManager.addMessage({
|
|
487
|
-
role: "user",
|
|
488
|
-
content: `<system-summary>${recovery}${skillHint}${actionableHintStr}${altHint}</system-summary>`,
|
|
489
|
-
});
|
|
490
|
-
}
|
|
491
|
-
const hints = this.stuckDetector.getHints();
|
|
492
|
-
if (hints.length > 0 && ctx.contextManager) {
|
|
493
|
-
const hintMsg = t("exec.hints", {
|
|
494
|
-
hints: hints.map((h) => `- ${h}`).join("\n"),
|
|
495
|
-
});
|
|
496
|
-
ctx.contextManager.addMessage({
|
|
497
|
-
role: "user",
|
|
498
|
-
content: `<system-summary>${hintMsg}</system-summary>`,
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
this.stuckDetector.recordEscalation();
|
|
502
|
-
this.lastRecoveryIteration = currentIter;
|
|
503
|
-
if (this.stuckDetector.shouldEscalate() && ctx.onMeta) {
|
|
504
|
-
const escalation = t("exec.escalation", {
|
|
505
|
-
stepId: String(this.tracker?.getCurrentStep()?.id ?? "?"),
|
|
506
|
-
description: this.tracker?.getCurrentStep()?.description ?? "",
|
|
507
|
-
});
|
|
508
|
-
ctx.onMeta(escalation);
|
|
509
|
-
}
|
|
510
|
-
// Force skip after too many iterations on the same step
|
|
511
|
-
if (this.stuckDetector.getIterationsOnCurrentStep() >=
|
|
512
|
-
FORCE_SKIP_THRESHOLD &&
|
|
513
|
-
ctx.contextManager) {
|
|
514
|
-
const step = this.tracker?.getCurrentStep();
|
|
515
|
-
ctx.contextManager.addMessage({
|
|
516
|
-
role: "user",
|
|
517
|
-
content: `<system-summary>STOP. Step ${step?.id ?? "?"} ("${step?.description ?? ""}") took ${this.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>`,
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
},
|
|
523
|
-
onBeforeTool: (_ctx, call) => {
|
|
524
|
-
const warning = this.checkPlanAlignment(call);
|
|
525
|
-
if (warning) {
|
|
526
|
-
this.pendingMessages.push({
|
|
527
|
-
role: "user",
|
|
528
|
-
content: `<system-summary>${warning}</system-summary>`,
|
|
529
|
-
});
|
|
530
|
-
this.consecutivePlanWarnings++;
|
|
531
|
-
if (this.consecutivePlanWarnings >= MAX_PLAN_WARNINGS_BEFORE_BLOCK) {
|
|
532
|
-
this.pendingMessages.push({
|
|
533
|
-
role: "user",
|
|
534
|
-
content: `<system-summary>${t("exec.plan_blocked", { step: String(this.tracker?.getCurrentStep()?.id ?? "?"), max: MAX_PLAN_WARNINGS_BEFORE_BLOCK })}</system-summary>`,
|
|
535
|
-
});
|
|
536
|
-
// Block with an explicit reason so the model sees WHY the call
|
|
537
|
-
// was blocked (executor shows it via tool.blocked_reason).
|
|
538
|
-
return t("exec.plan_blocked", {
|
|
539
|
-
step: String(this.tracker?.getCurrentStep()?.id ?? "?"),
|
|
540
|
-
max: MAX_PLAN_WARNINGS_BEFORE_BLOCK,
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
else {
|
|
545
|
-
this.consecutivePlanWarnings = 0;
|
|
546
|
-
}
|
|
547
|
-
return true;
|
|
548
|
-
},
|
|
549
|
-
onToolCall: (ctx) => {
|
|
550
|
-
const toolName = ctx?.toolName;
|
|
551
|
-
const args = ctx?.args;
|
|
552
|
-
if (toolName && args) {
|
|
553
|
-
this.stuckDetector.recordToolCall(toolName, args);
|
|
554
|
-
}
|
|
555
|
-
},
|
|
556
|
-
onAfterTool: (ctx, call, result) => {
|
|
557
|
-
if (!result.success) {
|
|
558
|
-
this.stuckDetector.recordToolError(call.name, result.output);
|
|
559
|
-
// Immediately queue an actionable hint on failure (don't wait for
|
|
560
|
-
// the stuck threshold). Flushed in onBeforeThink so it does not
|
|
561
|
-
// land between tool messages.
|
|
562
|
-
const actionableHints = this.stuckDetector.getActionableHints();
|
|
563
|
-
const alternative = this.stuckDetector.getToolAlternative();
|
|
564
|
-
if (actionableHints.length > 0 || alternative) {
|
|
565
|
-
const parts = [...actionableHints];
|
|
566
|
-
if (alternative) {
|
|
567
|
-
parts.push(`Tool "${call.name}" crashed. Try "${alternative}" instead.`);
|
|
568
|
-
}
|
|
569
|
-
this.pendingMessages.push({
|
|
570
|
-
role: "user",
|
|
571
|
-
content: `<system-summary>${t("exec.hints", { hints: parts.map((h) => `- ${h}`).join("\n") })}</system-summary>`,
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
else {
|
|
576
|
-
this.stuckDetector.recordToolSuccess();
|
|
577
|
-
// When a bash command runs code successfully, suggest marking step as done
|
|
578
|
-
if (call.name === "bash" && result.success) {
|
|
579
|
-
const cmd = String(call.arguments?.command ?? "");
|
|
580
|
-
if (/node|tsx|ts-node|python|npm\s+(start|test|run)/.test(cmd)) {
|
|
581
|
-
this.pendingMessages.push({
|
|
582
|
-
role: "user",
|
|
583
|
-
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>`,
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
if (result.success &&
|
|
589
|
-
(call.name === "write_file" || call.name === "edit_file")) {
|
|
590
|
-
const filePath = call.arguments?.path;
|
|
591
|
-
if (filePath) {
|
|
592
|
-
this.stuckDetector.recordFileRewrite(filePath);
|
|
593
|
-
if (this.stuckDetector.hasExcessiveRewrites()) {
|
|
594
|
-
const file = this.stuckDetector.getExcessiveRewriteFile();
|
|
595
|
-
const count = this.stuckDetector.getFileRewriteCount(file);
|
|
596
|
-
if (ctx.onMeta) {
|
|
597
|
-
ctx.onMeta(t("exec.file_rewrite_warning", {
|
|
598
|
-
file: file,
|
|
599
|
-
count: String(count),
|
|
600
|
-
}));
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
this.advancePlanIfStepComplete(ctx.contextManager, ctx.sessionLog);
|
|
605
|
-
}
|
|
606
|
-
},
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
preserveActive() {
|
|
610
|
-
if (!this.tracker)
|
|
611
|
-
return;
|
|
612
|
-
const plan = this.tracker.getPlan();
|
|
613
|
-
if (plan.steps.every((s) => s.status === "done" || s.status === "skipped")) {
|
|
614
|
-
this.store.archivePlan(plan);
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
this.store.saveDraft(plan);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
checkPlanAlignment(call) {
|
|
621
|
-
if (!this.tracker)
|
|
622
|
-
return null;
|
|
623
|
-
const step = this.tracker.getCurrentStep();
|
|
624
|
-
if (!step)
|
|
625
|
-
return null;
|
|
626
|
-
const allowedAlways = [
|
|
627
|
-
"plan",
|
|
628
|
-
"todo",
|
|
629
|
-
"verify",
|
|
630
|
-
"list_dir",
|
|
631
|
-
"read_file",
|
|
632
|
-
"glob",
|
|
633
|
-
"grep",
|
|
634
|
-
"file_info",
|
|
635
|
-
"load_skill",
|
|
636
|
-
];
|
|
637
|
-
if (allowedAlways.includes(call.name))
|
|
638
|
-
return null;
|
|
639
|
-
const stepPaths = (step.description.match(/\b[\w./\\-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
|
|
640
|
-
if (stepPaths.length === 0)
|
|
641
|
-
return null;
|
|
642
|
-
const argStr = JSON.stringify(call.arguments).toLowerCase();
|
|
643
|
-
const callPaths = (argStr.match(/\b[\w./\\-]+\.[a-z]+/gi) || []).map((p) => p.toLowerCase());
|
|
644
|
-
if (callPaths.length === 0)
|
|
645
|
-
return null;
|
|
646
|
-
// Files that already belong to COMPLETED (done/skipped) steps are
|
|
647
|
-
// legitimate rewrite targets — the agent iterates on them while fixing
|
|
648
|
-
// LSP/type errors. Blocking those would stop normal bug-fixing.
|
|
649
|
-
const plan = this.tracker.getPlan();
|
|
650
|
-
const finishedPaths = new Set();
|
|
651
|
-
for (const s of plan.steps) {
|
|
652
|
-
if (s.status === "done" || s.status === "skipped") {
|
|
653
|
-
const ps = s.description
|
|
654
|
-
.match(/\b[\w./\\-]+\.[a-z]+/gi)
|
|
655
|
-
?.map((p) => p.toLowerCase()) ?? [];
|
|
656
|
-
ps.forEach((p) => finishedPaths.add(p));
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
const offPath = callPaths.some((p) => {
|
|
660
|
-
if (finishedPaths.has(p) ||
|
|
661
|
-
[...finishedPaths].some((s) => s.includes(p))) {
|
|
662
|
-
return false;
|
|
663
|
-
}
|
|
664
|
-
return !stepPaths.some((s) => p.includes(s) || s.includes(p));
|
|
665
|
-
});
|
|
666
|
-
if (!offPath)
|
|
667
|
-
return null;
|
|
668
|
-
return t("exec.plan_warning", {
|
|
669
|
-
step: String(step.id),
|
|
670
|
-
description: step.description,
|
|
671
|
-
tool: call.name,
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
advancePlanIfStepComplete(contextManager, sessionLog) {
|
|
675
|
-
const step = this.tracker?.getCurrentStep();
|
|
676
|
-
if (!step)
|
|
677
|
-
return;
|
|
678
|
-
const stepText = step.description.toLowerCase();
|
|
679
|
-
const stepPaths = step.description.match(/\b[\w./\\-]+\.[a-z]+/gi) || [];
|
|
680
|
-
// Step gate: verify dependencies when mentioned — check BEFORE file paths
|
|
681
|
-
const isDepsStep = stepText.includes("install") ||
|
|
682
|
-
stepText.includes("зависим") ||
|
|
683
|
-
stepText.includes("init") ||
|
|
684
|
-
stepText.includes("инициализац");
|
|
685
|
-
if (isDepsStep) {
|
|
686
|
-
const lockFiles = [
|
|
687
|
-
"package-lock.json",
|
|
688
|
-
"bun.lockb",
|
|
689
|
-
"yarn.lock",
|
|
690
|
-
"pnpm-lock.yaml",
|
|
691
|
-
"node_modules",
|
|
692
|
-
"go.sum",
|
|
693
|
-
"Cargo.lock",
|
|
694
|
-
"Pipfile.lock",
|
|
695
|
-
"poetry.lock",
|
|
696
|
-
"requirements.txt",
|
|
697
|
-
];
|
|
698
|
-
const hasLockFile = lockFiles.some((f) => existsSync(resolve(this.baseDir, f)));
|
|
699
|
-
if (!hasLockFile) {
|
|
700
|
-
if (contextManager) {
|
|
701
|
-
const hints = this.depsGateHints.get(step.id) || 0;
|
|
702
|
-
this.depsGateHints.set(step.id, hints + 1);
|
|
703
|
-
const force = hints >= 1;
|
|
704
|
-
const content = force
|
|
705
|
-
? t("exec.step_gate_deps_force", { step: String(step.id) })
|
|
706
|
-
: t("exec.step_gate_deps", {
|
|
707
|
-
step: String(step.id),
|
|
708
|
-
description: step.description,
|
|
709
|
-
});
|
|
710
|
-
contextManager.addMessage({
|
|
711
|
-
role: "user",
|
|
712
|
-
content: `<system-summary>${content}</system-summary>`,
|
|
713
|
-
});
|
|
714
|
-
}
|
|
715
|
-
return;
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
if (stepPaths.length === 0)
|
|
719
|
-
return;
|
|
720
|
-
const allExist = stepPaths.every((p) => existsSync(resolve(this.baseDir, p)));
|
|
721
|
-
if (!allExist)
|
|
722
|
-
return;
|
|
723
|
-
// Step gate: verify files have content (not empty)
|
|
724
|
-
const emptyFiles = [];
|
|
725
|
-
for (const p of stepPaths) {
|
|
726
|
-
try {
|
|
727
|
-
const content = readFileSync(resolve(this.baseDir, p), "utf-8");
|
|
728
|
-
if (content.trim().length < 10) {
|
|
729
|
-
emptyFiles.push(p);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
catch {
|
|
733
|
-
// If we can't read it, rely on existsSync result
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
if (emptyFiles.length > 0 && contextManager) {
|
|
737
|
-
contextManager.addMessage({
|
|
738
|
-
role: "user",
|
|
739
|
-
content: `<system-summary>${t("exec.step_gate_empty", { step: String(step.id), files: emptyFiles.join(", ") })}</system-summary>`,
|
|
740
|
-
});
|
|
741
|
-
return;
|
|
742
|
-
}
|
|
743
|
-
// All checks passed — advance
|
|
744
|
-
this.tracker?.updateStepStatus(step.id, "done");
|
|
745
|
-
const hadNext = this.tracker?.advance() ?? false;
|
|
746
|
-
if (this.tracker) {
|
|
747
|
-
this.store.saveActive(this.tracker.getPlan());
|
|
748
|
-
sessionLog?.plan("auto-advance", `Step ${step.id} auto-completed | ${this.tracker.getProgressString()} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
|
|
749
|
-
}
|
|
750
|
-
if (contextManager) {
|
|
751
|
-
const nextStep = hadNext ? this.tracker?.getCurrentStep() : null;
|
|
752
|
-
const nextMsg = nextStep
|
|
753
|
-
? t("exec.step_gate_ok", {
|
|
754
|
-
step: String(step.id),
|
|
755
|
-
nextStep: String(nextStep.id),
|
|
756
|
-
nextDesc: nextStep.description,
|
|
757
|
-
})
|
|
758
|
-
: t("exec.step_gate_last", { step: String(step.id) });
|
|
759
|
-
contextManager.addMessage({
|
|
760
|
-
role: "user",
|
|
761
|
-
content: `<system-summary>${nextMsg}</system-summary>`,
|
|
762
|
-
});
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Extract the original task statement from the first user message in the
|
|
767
|
-
* conversation history. Used to validate plan coverage against requirements.
|
|
768
|
-
*/
|
|
769
|
-
getTaskText(ctx) {
|
|
770
|
-
const manager = ctx?.contextManager;
|
|
771
|
-
if (!manager)
|
|
772
|
-
return "";
|
|
773
|
-
const history = manager.getActiveHistory();
|
|
774
|
-
const firstUser = history.find((m) => m.role === "user");
|
|
775
|
-
if (!firstUser)
|
|
776
|
-
return "";
|
|
777
|
-
return getMessageText(firstUser.content).trim();
|
|
778
|
-
}
|
|
779
|
-
}
|