micro-models-agent 0.44.0 → 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.
Files changed (188) hide show
  1. package/README.md +312 -312
  2. package/bin/mma.mjs +41 -41
  3. package/dist/main.js +241 -56
  4. package/dist/skills/builtin/git.md +36 -36
  5. package/dist/skills/builtin/typescript.md +35 -35
  6. package/package.json +1 -1
  7. package/dist/cli/commands.js +0 -333
  8. package/dist/cli/completer.js +0 -168
  9. package/dist/cli/index.js +0 -2
  10. package/dist/cli/main.js +0 -140
  11. package/dist/cli/repl-commands.js +0 -633
  12. package/dist/cli/repl.js +0 -486
  13. package/dist/cli/security-commands.js +0 -166
  14. package/dist/cli/setup.js +0 -249
  15. package/dist/config/config.js +0 -202
  16. package/dist/config/defaults.js +0 -100
  17. package/dist/config/experts.js +0 -15
  18. package/dist/config/index.js +0 -3
  19. package/dist/config/security.js +0 -200
  20. package/dist/config/types.js +0 -1
  21. package/dist/core/agent-moe.js +0 -110
  22. package/dist/core/agent.js +0 -695
  23. package/dist/core/bootstrap.js +0 -337
  24. package/dist/core/index.js +0 -2
  25. package/dist/core/prompt-builder.js +0 -55
  26. package/dist/core/session-logger.js +0 -155
  27. package/dist/core/types.js +0 -1
  28. package/dist/core/workspace.js +0 -76
  29. package/dist/i18n/en.json +0 -525
  30. package/dist/i18n/index.js +0 -46
  31. package/dist/i18n/ru.json +0 -525
  32. package/dist/index.js +0 -22
  33. package/dist/llm/image-utils.js +0 -144
  34. package/dist/llm/index.js +0 -4
  35. package/dist/llm/model-loader.js +0 -78
  36. package/dist/llm/openai-compat.js +0 -353
  37. package/dist/llm/orchestrator.js +0 -194
  38. package/dist/llm/provider.js +0 -10
  39. package/dist/llm/response.js +0 -39
  40. package/dist/llm/token-counter.js +0 -39
  41. package/dist/llm/types.js +0 -1
  42. package/dist/logger/app-logger.js +0 -143
  43. package/dist/logger/file-log.js +0 -151
  44. package/dist/logger/index.js +0 -1
  45. package/dist/migration/backup.js +0 -45
  46. package/dist/migration/detect.js +0 -50
  47. package/dist/migration/index.js +0 -2
  48. package/dist/modules/browser/actions.js +0 -46
  49. package/dist/modules/browser/cookie-store.js +0 -24
  50. package/dist/modules/browser/index.js +0 -5
  51. package/dist/modules/browser/module.js +0 -28
  52. package/dist/modules/browser/session.js +0 -335
  53. package/dist/modules/browser/snapshot.js +0 -114
  54. package/dist/modules/browser/types.js +0 -9
  55. package/dist/modules/certification/cli.js +0 -176
  56. package/dist/modules/certification/fact-checker.js +0 -84
  57. package/dist/modules/certification/loader.js +0 -111
  58. package/dist/modules/certification/manifest.js +0 -50
  59. package/dist/modules/certification/runner.js +0 -162
  60. package/dist/modules/certification/scenarios.js +0 -124
  61. package/dist/modules/certification/types.js +0 -1
  62. package/dist/modules/context/index.js +0 -1
  63. package/dist/modules/context/manager.js +0 -349
  64. package/dist/modules/execution/auditor.js +0 -66
  65. package/dist/modules/execution/index.js +0 -8
  66. package/dist/modules/execution/module.js +0 -779
  67. package/dist/modules/execution/moe-executor.js +0 -266
  68. package/dist/modules/execution/plan-coverage.js +0 -68
  69. package/dist/modules/execution/plan-persister.js +0 -46
  70. package/dist/modules/execution/plan-store.js +0 -159
  71. package/dist/modules/execution/plan-validator.js +0 -153
  72. package/dist/modules/execution/planner.js +0 -85
  73. package/dist/modules/execution/stuck-detector.js +0 -347
  74. package/dist/modules/execution/tracker.js +0 -67
  75. package/dist/modules/execution/types.js +0 -1
  76. package/dist/modules/execution/verifier.js +0 -178
  77. package/dist/modules/hallucination/confidence.js +0 -59
  78. package/dist/modules/hallucination/consistency.js +0 -26
  79. package/dist/modules/hallucination/detector.js +0 -46
  80. package/dist/modules/hallucination/factual.js +0 -190
  81. package/dist/modules/hallucination/index.js +0 -5
  82. package/dist/modules/hallucination/js-identifiers.js +0 -72
  83. package/dist/modules/hallucination/llm-judge.js +0 -103
  84. package/dist/modules/index.js +0 -5
  85. package/dist/modules/indexer/cache.js +0 -38
  86. package/dist/modules/indexer/index.js +0 -3
  87. package/dist/modules/indexer/module.js +0 -192
  88. package/dist/modules/indexer/walker.js +0 -101
  89. package/dist/modules/lsp/client.js +0 -235
  90. package/dist/modules/lsp/config.js +0 -81
  91. package/dist/modules/lsp/index.js +0 -3
  92. package/dist/modules/lsp/module.js +0 -68
  93. package/dist/modules/lsp/types.js +0 -1
  94. package/dist/modules/mcp/client.js +0 -399
  95. package/dist/modules/mcp/index.js +0 -3
  96. package/dist/modules/mcp/module.js +0 -146
  97. package/dist/modules/mcp/registry.js +0 -15
  98. package/dist/modules/memory/index.js +0 -1
  99. package/dist/modules/memory/module.js +0 -48
  100. package/dist/modules/memory/search.js +0 -40
  101. package/dist/modules/memory/store.js +0 -69
  102. package/dist/modules/pipelines/engine.js +0 -60
  103. package/dist/modules/pipelines/index.js +0 -3
  104. package/dist/modules/pipelines/parser.js +0 -53
  105. package/dist/modules/pipelines/template.js +0 -14
  106. package/dist/modules/plugins/builtin/lint-on-write.js +0 -226
  107. package/dist/modules/plugins/builtin/notify.js +0 -8
  108. package/dist/modules/plugins/index.js +0 -1
  109. package/dist/modules/plugins/loader.js +0 -28
  110. package/dist/modules/plugins/manager.js +0 -161
  111. package/dist/modules/plugins/types.js +0 -1
  112. package/dist/modules/processes/index.js +0 -2
  113. package/dist/modules/processes/registry.js +0 -238
  114. package/dist/modules/processes/runner.js +0 -23
  115. package/dist/modules/registry.js +0 -45
  116. package/dist/modules/security/audit-log.js +0 -136
  117. package/dist/modules/security/audit-notifier.js +0 -292
  118. package/dist/modules/security/command-validator.js +0 -211
  119. package/dist/modules/security/content-scanner.js +0 -53
  120. package/dist/modules/security/data-sanitizer.js +0 -97
  121. package/dist/modules/security/encryption.js +0 -240
  122. package/dist/modules/security/index.js +0 -14
  123. package/dist/modules/security/network-validator.js +0 -79
  124. package/dist/modules/security/path-validator.js +0 -209
  125. package/dist/modules/security/rate-limiter.js +0 -119
  126. package/dist/modules/security/security-policies.js +0 -547
  127. package/dist/modules/security/session-encryption.js +0 -210
  128. package/dist/modules/security/session-isolation.js +0 -95
  129. package/dist/modules/session/index.js +0 -3
  130. package/dist/modules/session/manager.js +0 -172
  131. package/dist/modules/session/module.js +0 -24
  132. package/dist/modules/session/store.js +0 -228
  133. package/dist/modules/session/types.js +0 -1
  134. package/dist/modules/skills/index.js +0 -2
  135. package/dist/modules/skills/loader.js +0 -72
  136. package/dist/modules/skills/module.js +0 -130
  137. package/dist/modules/types.js +0 -1
  138. package/dist/modules/updater/checker.js +0 -32
  139. package/dist/modules/updater/index.js +0 -1
  140. package/dist/modules/user-profile/compressor.js +0 -16
  141. package/dist/modules/user-profile/index.js +0 -1
  142. package/dist/modules/user-profile/profile.js +0 -68
  143. package/dist/tools/approve.js +0 -32
  144. package/dist/tools/attach-image.js +0 -89
  145. package/dist/tools/bash.js +0 -337
  146. package/dist/tools/browser.js +0 -97
  147. package/dist/tools/create-dir.js +0 -55
  148. package/dist/tools/delete-file.js +0 -62
  149. package/dist/tools/edit-file.js +0 -79
  150. package/dist/tools/executor.js +0 -145
  151. package/dist/tools/file-info.js +0 -45
  152. package/dist/tools/filter-tools.js +0 -10
  153. package/dist/tools/glob-tool.js +0 -26
  154. package/dist/tools/grep-tool.js +0 -86
  155. package/dist/tools/index.js +0 -67
  156. package/dist/tools/list-dir.js +0 -47
  157. package/dist/tools/load-skill.js +0 -44
  158. package/dist/tools/mcp-call.js +0 -68
  159. package/dist/tools/move-file.js +0 -85
  160. package/dist/tools/path-utils.js +0 -51
  161. package/dist/tools/pipeline-run.js +0 -144
  162. package/dist/tools/preview.js +0 -2
  163. package/dist/tools/process-kill.js +0 -29
  164. package/dist/tools/process-list.js +0 -38
  165. package/dist/tools/process-log.js +0 -41
  166. package/dist/tools/question.js +0 -142
  167. package/dist/tools/read-file.js +0 -83
  168. package/dist/tools/recall.js +0 -110
  169. package/dist/tools/registry.js +0 -36
  170. package/dist/tools/remember.js +0 -67
  171. package/dist/tools/scope-check.js +0 -30
  172. package/dist/tools/search-history.js +0 -84
  173. package/dist/tools/subagent.js +0 -151
  174. package/dist/tools/types.js +0 -1
  175. package/dist/tools/user-input.js +0 -123
  176. package/dist/tools/web-browse.js +0 -86
  177. package/dist/tools/web-fetch.js +0 -98
  178. package/dist/tools/web-search.js +0 -78
  179. package/dist/tools/write-file.js +0 -83
  180. package/dist/ui/box.js +0 -81
  181. package/dist/ui/colors.js +0 -4
  182. package/dist/ui/diff.js +0 -178
  183. package/dist/ui/index.js +0 -6
  184. package/dist/ui/md-formatter.js +0 -212
  185. package/dist/ui/output.js +0 -13
  186. package/dist/ui/renderer.js +0 -204
  187. package/dist/ui/spinner.js +0 -70
  188. 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
- }