micro-models-agent 0.59.0 → 0.60.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 (233) hide show
  1. package/CHANGELOG.md +578 -564
  2. package/README.md +358 -358
  3. package/dist/certification/certifications.json +493 -493
  4. package/dist/i18n/en.json +732 -718
  5. package/dist/i18n/ru.json +732 -718
  6. package/dist/main.js +504 -62
  7. package/dist/modules/browser/bridge-server.mjs +219 -219
  8. package/dist/skills/builtin/git.md +36 -36
  9. package/dist/skills/builtin/typescript.md +35 -35
  10. package/package.json +51 -51
  11. package/dist/cli/commands.js +0 -447
  12. package/dist/cli/completer.js +0 -167
  13. package/dist/cli/index.js +0 -2
  14. package/dist/cli/main.js +0 -153
  15. package/dist/cli/plugin-commands.js +0 -36
  16. package/dist/cli/repl-commands.js +0 -761
  17. package/dist/cli/repl.js +0 -702
  18. package/dist/cli/run-result.js +0 -33
  19. package/dist/cli/security-commands.js +0 -164
  20. package/dist/cli/setup.js +0 -237
  21. package/dist/config/config.js +0 -276
  22. package/dist/config/defaults.js +0 -141
  23. package/dist/config/domains.js +0 -179
  24. package/dist/config/experts.js +0 -15
  25. package/dist/config/index.js +0 -4
  26. package/dist/config/security.js +0 -213
  27. package/dist/config/types.js +0 -1
  28. package/dist/core/agent-moe.js +0 -102
  29. package/dist/core/agent.js +0 -1018
  30. package/dist/core/bootstrap.js +0 -481
  31. package/dist/core/crash-handler.js +0 -51
  32. package/dist/core/environment.js +0 -199
  33. package/dist/core/index.js +0 -2
  34. package/dist/core/prompt-builder.js +0 -76
  35. package/dist/core/session-logger.js +0 -251
  36. package/dist/core/types.js +0 -1
  37. package/dist/core/version.js +0 -26
  38. package/dist/core/workspace.js +0 -76
  39. package/dist/i18n/index.js +0 -46
  40. package/dist/index.js +0 -22
  41. package/dist/llm/image-utils.js +0 -143
  42. package/dist/llm/index.js +0 -4
  43. package/dist/llm/model-loader.js +0 -78
  44. package/dist/llm/openai-compat.js +0 -497
  45. package/dist/llm/orchestrator.js +0 -200
  46. package/dist/llm/provider.js +0 -10
  47. package/dist/llm/response.js +0 -39
  48. package/dist/llm/token-counter.js +0 -39
  49. package/dist/llm/types.js +0 -1
  50. package/dist/logger/app-logger.js +0 -189
  51. package/dist/logger/file-log.js +0 -151
  52. package/dist/logger/index.js +0 -1
  53. package/dist/migration/backup.js +0 -45
  54. package/dist/migration/detect.js +0 -50
  55. package/dist/migration/index.js +0 -2
  56. package/dist/modules/artifacts/store.js +0 -61
  57. package/dist/modules/browser/actions.js +0 -76
  58. package/dist/modules/browser/bridge-client.js +0 -199
  59. package/dist/modules/browser/bridge-path.js +0 -10
  60. package/dist/modules/browser/cookie-store.js +0 -24
  61. package/dist/modules/browser/driver.js +0 -136
  62. package/dist/modules/browser/index.js +0 -7
  63. package/dist/modules/browser/module.js +0 -29
  64. package/dist/modules/browser/session.js +0 -342
  65. package/dist/modules/browser/snapshot.js +0 -148
  66. package/dist/modules/browser/types.js +0 -12
  67. package/dist/modules/certification/cli.js +0 -213
  68. package/dist/modules/certification/fact-checker.js +0 -82
  69. package/dist/modules/certification/loader.js +0 -106
  70. package/dist/modules/certification/manifest.js +0 -58
  71. package/dist/modules/certification/runner.js +0 -245
  72. package/dist/modules/certification/scenarios.js +0 -407
  73. package/dist/modules/certification/types.js +0 -1
  74. package/dist/modules/context/chunk-query.js +0 -100
  75. package/dist/modules/context/fact-extractor.js +0 -168
  76. package/dist/modules/context/history.js +0 -15
  77. package/dist/modules/context/index.js +0 -1
  78. package/dist/modules/context/manager.js +0 -440
  79. package/dist/modules/execution/audit-runners.js +0 -206
  80. package/dist/modules/execution/auditor.js +0 -218
  81. package/dist/modules/execution/execution-plugin.js +0 -431
  82. package/dist/modules/execution/index.js +0 -8
  83. package/dist/modules/execution/module.js +0 -625
  84. package/dist/modules/execution/moe-executor.js +0 -304
  85. package/dist/modules/execution/plan-coverage.js +0 -68
  86. package/dist/modules/execution/plan-persister.js +0 -46
  87. package/dist/modules/execution/plan-store.js +0 -196
  88. package/dist/modules/execution/plan-tool.js +0 -677
  89. package/dist/modules/execution/plan-validator.js +0 -153
  90. package/dist/modules/execution/planner.js +0 -94
  91. package/dist/modules/execution/stuck-detector.js +0 -746
  92. package/dist/modules/execution/tracker.js +0 -69
  93. package/dist/modules/execution/types.js +0 -1
  94. package/dist/modules/execution/verifier.js +0 -235
  95. package/dist/modules/execution/windows-commands.js +0 -41
  96. package/dist/modules/hallucination/confidence.js +0 -66
  97. package/dist/modules/hallucination/consistency.js +0 -26
  98. package/dist/modules/hallucination/detector.js +0 -47
  99. package/dist/modules/hallucination/factual.js +0 -169
  100. package/dist/modules/hallucination/index.js +0 -5
  101. package/dist/modules/hallucination/js-identifiers.js +0 -262
  102. package/dist/modules/hallucination/llm-judge.js +0 -101
  103. package/dist/modules/index.js +0 -5
  104. package/dist/modules/indexer/cache.js +0 -40
  105. package/dist/modules/indexer/index.js +0 -3
  106. package/dist/modules/indexer/module.js +0 -246
  107. package/dist/modules/indexer/project-profile.js +0 -183
  108. package/dist/modules/indexer/walker.js +0 -101
  109. package/dist/modules/lsp/check-tool.js +0 -58
  110. package/dist/modules/lsp/client.js +0 -389
  111. package/dist/modules/lsp/command.js +0 -60
  112. package/dist/modules/lsp/config.js +0 -135
  113. package/dist/modules/lsp/index.js +0 -3
  114. package/dist/modules/lsp/module.js +0 -260
  115. package/dist/modules/lsp/probe.js +0 -86
  116. package/dist/modules/lsp/project-root.js +0 -32
  117. package/dist/modules/lsp/startup-check.js +0 -144
  118. package/dist/modules/lsp/types.js +0 -1
  119. package/dist/modules/mcp/client.js +0 -399
  120. package/dist/modules/mcp/index.js +0 -3
  121. package/dist/modules/mcp/module.js +0 -142
  122. package/dist/modules/mcp/registry.js +0 -15
  123. package/dist/modules/memory/index.js +0 -1
  124. package/dist/modules/memory/module.js +0 -96
  125. package/dist/modules/memory/search.js +0 -42
  126. package/dist/modules/memory/store.js +0 -69
  127. package/dist/modules/pipelines/engine.js +0 -60
  128. package/dist/modules/pipelines/index.js +0 -3
  129. package/dist/modules/pipelines/parser.js +0 -56
  130. package/dist/modules/pipelines/template.js +0 -14
  131. package/dist/modules/plugins/builtin/lint-on-write.js +0 -334
  132. package/dist/modules/plugins/builtin/notify.js +0 -9
  133. package/dist/modules/plugins/index.js +0 -1
  134. package/dist/modules/plugins/loader.js +0 -70
  135. package/dist/modules/plugins/manager.js +0 -261
  136. package/dist/modules/plugins/types.js +0 -1
  137. package/dist/modules/pricing/index.js +0 -61
  138. package/dist/modules/pricing/prices.js +0 -129
  139. package/dist/modules/processes/detect.js +0 -34
  140. package/dist/modules/processes/index.js +0 -2
  141. package/dist/modules/processes/registry.js +0 -327
  142. package/dist/modules/processes/runner.js +0 -23
  143. package/dist/modules/providers/create.js +0 -22
  144. package/dist/modules/providers/fallback.js +0 -79
  145. package/dist/modules/providers/health.js +0 -46
  146. package/dist/modules/providers/index.js +0 -5
  147. package/dist/modules/providers/manager.js +0 -161
  148. package/dist/modules/providers/presets.js +0 -128
  149. package/dist/modules/providers/registry.js +0 -22
  150. package/dist/modules/providers/types.js +0 -1
  151. package/dist/modules/registry.js +0 -48
  152. package/dist/modules/security/audit-log.js +0 -136
  153. package/dist/modules/security/audit-notifier.js +0 -292
  154. package/dist/modules/security/command-validator.js +0 -219
  155. package/dist/modules/security/content-scanner.js +0 -53
  156. package/dist/modules/security/data-sanitizer.js +0 -89
  157. package/dist/modules/security/encryption.js +0 -242
  158. package/dist/modules/security/index.js +0 -14
  159. package/dist/modules/security/network-validator.js +0 -88
  160. package/dist/modules/security/path-validator.js +0 -203
  161. package/dist/modules/security/rate-limiter.js +0 -119
  162. package/dist/modules/security/security-policies.js +0 -531
  163. package/dist/modules/security/session-encryption.js +0 -210
  164. package/dist/modules/security/session-isolation.js +0 -95
  165. package/dist/modules/session/index.js +0 -3
  166. package/dist/modules/session/manager.js +0 -172
  167. package/dist/modules/session/module.js +0 -24
  168. package/dist/modules/session/store.js +0 -222
  169. package/dist/modules/session/types.js +0 -1
  170. package/dist/modules/skills/index.js +0 -2
  171. package/dist/modules/skills/loader.js +0 -72
  172. package/dist/modules/skills/matcher.js +0 -27
  173. package/dist/modules/skills/module.js +0 -129
  174. package/dist/modules/types.js +0 -1
  175. package/dist/modules/updater/checker.js +0 -96
  176. package/dist/modules/updater/index.js +0 -2
  177. package/dist/modules/updater/module.js +0 -116
  178. package/dist/modules/user-profile/compressor.js +0 -16
  179. package/dist/modules/user-profile/index.js +0 -1
  180. package/dist/modules/user-profile/profile.js +0 -68
  181. package/dist/tools/approve.js +0 -33
  182. package/dist/tools/attach-image.js +0 -101
  183. package/dist/tools/bash.js +0 -519
  184. package/dist/tools/browser.js +0 -115
  185. package/dist/tools/chunk-query.js +0 -100
  186. package/dist/tools/create-dir.js +0 -56
  187. package/dist/tools/delete-file.js +0 -63
  188. package/dist/tools/download-file.js +0 -117
  189. package/dist/tools/edit-file.js +0 -80
  190. package/dist/tools/enable-tools.js +0 -59
  191. package/dist/tools/executor.js +0 -154
  192. package/dist/tools/file-info.js +0 -47
  193. package/dist/tools/filter-tools.js +0 -17
  194. package/dist/tools/glob-tool.js +0 -27
  195. package/dist/tools/grep-tool.js +0 -125
  196. package/dist/tools/hidden-tools-block.js +0 -37
  197. package/dist/tools/index.js +0 -78
  198. package/dist/tools/list-dir.js +0 -49
  199. package/dist/tools/load-skill.js +0 -43
  200. package/dist/tools/mcp-call.js +0 -69
  201. package/dist/tools/move-file.js +0 -86
  202. package/dist/tools/path-utils.js +0 -101
  203. package/dist/tools/pipeline-run.js +0 -145
  204. package/dist/tools/preview.js +0 -2
  205. package/dist/tools/process-kill.js +0 -40
  206. package/dist/tools/process-list.js +0 -37
  207. package/dist/tools/process-log.js +0 -54
  208. package/dist/tools/question.js +0 -141
  209. package/dist/tools/read-file.js +0 -179
  210. package/dist/tools/recall.js +0 -118
  211. package/dist/tools/registry.js +0 -47
  212. package/dist/tools/remember.js +0 -68
  213. package/dist/tools/scope-check.js +0 -32
  214. package/dist/tools/search-history.js +0 -85
  215. package/dist/tools/subagent.js +0 -196
  216. package/dist/tools/types.js +0 -1
  217. package/dist/tools/user-input.js +0 -123
  218. package/dist/tools/web-browse.js +0 -87
  219. package/dist/tools/web-fetch.js +0 -119
  220. package/dist/tools/web-search.js +0 -105
  221. package/dist/tools/write-file.js +0 -82
  222. package/dist/ui/box.js +0 -77
  223. package/dist/ui/colors.js +0 -4
  224. package/dist/ui/diff.js +0 -178
  225. package/dist/ui/index.js +0 -6
  226. package/dist/ui/line-editor.js +0 -822
  227. package/dist/ui/line-math.js +0 -73
  228. package/dist/ui/md-formatter.js +0 -212
  229. package/dist/ui/output.js +0 -13
  230. package/dist/ui/plan-view.js +0 -103
  231. package/dist/ui/renderer.js +0 -259
  232. package/dist/ui/spinner.js +0 -70
  233. package/dist/ui/table.js +0 -144
@@ -1,677 +0,0 @@
1
- import { t } from "../../i18n/index";
2
- import { PlanCreator, getPlanProgress } from "./planner";
3
- import { checkPlanCoverage } from "./plan-coverage";
4
- import { stepTypecheckGate } from "./execution-plugin";
5
- /**
6
- * The plan / todo / verify tool definitions for the execution module. The
7
- * handlers were extracted verbatim from ExecutionModule.getToolDefinitions() —
8
- * all state access goes through the `deps` closures (the module owns the
9
- * tracker and the plan store).
10
- */
11
- export function createPlanToolDefinitions(deps) {
12
- return [
13
- {
14
- name: "plan",
15
- icon: "📋",
16
- alwaysOn: true,
17
- description: 'Create, update, show, abort, list, switch, delete, purge, 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 the current plan (or the plan given by id) and clear the 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- delete: Permanently delete a plan by id (plan delete id=plan_xxx).\n- purge: Delete ALL plans (active, drafts, archived).\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?)\n- For steps that REMOVE files, pass kinds: ["delete"] aligned with steps (the done-gate verifies those files are gone instead of demanding they exist).',
18
- parameters: {
19
- type: "object",
20
- properties: {
21
- action: {
22
- type: "string",
23
- enum: [
24
- "create",
25
- "update",
26
- "show",
27
- "abort",
28
- "list",
29
- "switch",
30
- "re-plan",
31
- "delete",
32
- "purge",
33
- ],
34
- },
35
- title: { type: "string" },
36
- steps: { type: "array", items: { type: "string" } },
37
- kinds: {
38
- type: "array",
39
- items: { type: "string", enum: ["create", "delete"] },
40
- description: 'Optional per-step intent, aligned 1:1 with steps. Pass "delete" for steps whose purpose is REMOVING files — the done-gate then requires those files to be GONE (not present). Defaults to "create".',
41
- },
42
- step: { type: "number" },
43
- status: { type: "string", enum: ["done", "failed", "skipped"] },
44
- note: { type: "string" },
45
- id: { type: "string", description: "Plan id (for switch action)" },
46
- },
47
- required: ["action"],
48
- },
49
- handler: async (_ctx, args) => {
50
- const action = String(args.action);
51
- if (action === "list") {
52
- const metas = deps.store.listAll();
53
- if (metas.length === 0) {
54
- return { success: true, output: t("plan.list_empty") };
55
- }
56
- const lines = metas.map((m) => {
57
- const icon = m.status === "active" ? "[*]" : m.status === "draft" ? "[ ]" : "[-]";
58
- const namePart = m.name ? ` (${m.name})` : "";
59
- return `${icon} ${m.id}${namePart} — ${m.title} ${m.doneCount}/${m.stepCount}`;
60
- });
61
- lines.push("");
62
- lines.push(t("plan.list_legend"));
63
- return {
64
- success: true,
65
- output: `${t("plan.list_header")}\n${lines.join("\n")}`,
66
- };
67
- }
68
- if (action === "switch") {
69
- const planId = String(args.id || "");
70
- if (!planId) {
71
- return { success: false, output: t("plan.switch_no_id") };
72
- }
73
- const found = deps.store.find(planId);
74
- if (!found) {
75
- return {
76
- success: false,
77
- output: t("plan.not_found", { id: planId }),
78
- };
79
- }
80
- // Switching to the plan that is already active must be a no-op, not
81
- // a duplicate (observed: switch on the active plan preserved the
82
- // active.json and re-set the same plan, leaving two copies).
83
- if (found.status === "active") {
84
- return {
85
- success: true,
86
- output: t("plan.already_active", {
87
- id: found.plan.id,
88
- title: found.plan.title,
89
- }),
90
- display: deps.trackerRef.current?.toPromptBlock(),
91
- };
92
- }
93
- deps.preserveActive();
94
- if (found.status === "draft") {
95
- deps.store.removeDraft(found.plan.id);
96
- }
97
- else if (found.status === "archived") {
98
- deps.store.removeArchived(found.plan.id);
99
- }
100
- deps.setPlan(found.plan);
101
- // Display from the tracker (setPlan syncs the pointer past any
102
- // done/skipped steps) so "current: step N" points at the first
103
- // pending step of the switched-to plan, not always step 1.
104
- const display = deps.trackerRef.current?.toPromptBlock();
105
- return {
106
- success: true,
107
- output: t("plan.switched", {
108
- id: found.plan.id,
109
- title: found.plan.title,
110
- }),
111
- display,
112
- };
113
- }
114
- if (action === "re-plan") {
115
- if (!deps.trackerRef.current) {
116
- return { success: false, output: t("plan.no_active") };
117
- }
118
- const newSteps = Array.isArray(args.steps) ? args.steps.map(String) : [];
119
- if (newSteps.length === 0) {
120
- return { success: false, output: t("plan.replan_no_steps") };
121
- }
122
- const tracker = deps.trackerRef.current;
123
- const oldPlan = tracker.getPlan();
124
- const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
125
- const keptCount = replanned.steps.length - newSteps.length;
126
- deps.setPlan(replanned);
127
- const display = deps.trackerRef.current?.toPromptBlock();
128
- return {
129
- success: true,
130
- output: t("plan.replanned", {
131
- kept: String(keptCount),
132
- steps: String(newSteps.length),
133
- }),
134
- display,
135
- };
136
- }
137
- if (action === "create") {
138
- const title = String(args.title || "Task Plan");
139
- const steps = Array.isArray(args.steps) ? args.steps.map(String) : [];
140
- if (steps.length === 0) {
141
- return { success: false, output: t("plan.no_steps") };
142
- }
143
- const parsed = deps.parseKinds(args, steps);
144
- if (parsed.error) {
145
- return { success: false, output: parsed.error };
146
- }
147
- // Never silently discard an in-progress plan (observed:
148
- // ses_msvuao0h — right after compaction the model called
149
- // `plan create` again, dumping the 1/6 plan into drafts and
150
- // starting from scratch). An active plan WITH progress blocks
151
- // creation until the model consciously aborts it. A fresh plan
152
- // (all steps pending) is safe to replace — the model may have
153
- // just changed its mind about the approach.
154
- const existing = deps.trackerRef.current;
155
- if (existing) {
156
- const activePlan = existing.getPlan();
157
- const progressed = activePlan.steps.some((s) => s.status === "done" || s.status === "failed" || s.status === "skipped");
158
- if (progressed) {
159
- const done = activePlan.steps.filter((s) => s.status === "done").length;
160
- return {
161
- success: false,
162
- output: t("plan.active_in_progress", {
163
- id: activePlan.id,
164
- done: String(done),
165
- total: String(activePlan.steps.length),
166
- current: String(existing.getCurrentStepIndex() + 1),
167
- }),
168
- };
169
- }
170
- }
171
- deps.preserveActive();
172
- const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
173
- deps.setPlan(plan);
174
- const display = PlanCreator.toPromptBlock(plan, 0);
175
- let output = t("plan.created", {
176
- title,
177
- steps: String(steps.length),
178
- });
179
- // The model sometimes passes an id expecting the plan to be
180
- // created under it (observed: id "plan_error_check_e07pb2" was
181
- // ignored, a new id appeared). Be explicit so it doesn't look
182
- // for a plan by the wrong id afterwards.
183
- if (args.id) {
184
- output += `\n${t("plan.id_ignored")}`;
185
- }
186
- if (existing) {
187
- output += `\n${t("plan.existing_fresh")}`;
188
- }
189
- let displayOut = display;
190
- const taskText = deps.getTaskText(_ctx);
191
- if (taskText) {
192
- const { missing } = checkPlanCoverage(taskText, steps);
193
- if (missing.length > 0) {
194
- const warn = t("plan.coverage_warning", {
195
- missing: missing.join(", "),
196
- });
197
- output += `\n⚠️ ${warn}`;
198
- displayOut = `${display}\n⚠️ ${warn}`;
199
- }
200
- }
201
- return {
202
- success: true,
203
- output,
204
- display: displayOut,
205
- };
206
- }
207
- if (action === "show") {
208
- const planId = args.id ? String(args.id) : "";
209
- if (planId && planId !== deps.trackerRef.current?.getPlan()?.id) {
210
- // Show a specific plan (active, draft, or archived) instead
211
- // of ignoring the id and always printing the active one
212
- // (observed: the model asked for an archived plan three
213
- // times and got the active plan back every time).
214
- const found = deps.store.find(planId);
215
- if (!found) {
216
- return {
217
- success: false,
218
- output: t("plan.not_found", { id: planId }),
219
- };
220
- }
221
- const display = PlanCreator.toPromptBlock(found.plan, 0);
222
- return {
223
- success: true,
224
- output: `${t("plan.show_header")}\n${display}`,
225
- display,
226
- };
227
- }
228
- if (!deps.trackerRef.current) {
229
- return { success: false, output: t("plan.no_active") };
230
- }
231
- const display = deps.trackerRef.current.toPromptBlock();
232
- return {
233
- success: true,
234
- output: `${t("plan.show_header")}\n${display}`,
235
- display,
236
- };
237
- }
238
- if (action === "update" && args.step && deps.trackerRef.current) {
239
- const tracker = deps.trackerRef.current;
240
- const stepId = Number(args.step);
241
- const target = tracker.getStep(stepId);
242
- if (!target) {
243
- return { success: false, output: t("plan.step_not_found") };
244
- }
245
- const status = String(args.status || "done");
246
- // Completed plan: re-marking a done step of a finished plan is the
247
- // loop anchor (observed: 3× "plan update step=4 done" on a 4/4 plan
248
- // while a NEW task waited). Archive + clear so the model can create a
249
- // fresh plan for the new task instead of spinning.
250
- if (tracker.isComplete()) {
251
- const plan = tracker.getPlan();
252
- deps.store.archivePlan(plan);
253
- deps.recordCompleted(plan); // final audit still verifies it
254
- deps.trackerRef.current = null;
255
- const p = getPlanProgress(plan);
256
- return {
257
- success: true,
258
- output: t("plan.completed_archived", {
259
- id: plan.id,
260
- done: String(p.settled),
261
- total: String(plan.steps.length),
262
- }),
263
- };
264
- }
265
- // Idempotency: re-marking a done step as done usually means the
266
- // plan was already auto-advanced (all step files exist) and the
267
- // model is confirming its own work. Return SUCCESS so the loop
268
- // isn't confused — a small model misreads success:false as a
269
- // failed action and retries/spins. Genuine spinning (repeated
270
- // identical calls without new work) is still caught by the
271
- // stuck-detector's consecutive-identical-call check.
272
- if (status === "done" && target.status === "done") {
273
- const progress = tracker.getProgressString();
274
- const display = tracker.toPromptBlock();
275
- return {
276
- success: true,
277
- output: `${t("plan.step_already_done", {
278
- step: String(stepId),
279
- })}\n${progress}`,
280
- display,
281
- };
282
- }
283
- // Order enforcement: only the CURRENT step may be marked done.
284
- // Prevents jumping ahead (step 5 marked done before steps 1-4,
285
- // observed in the post-mortem session) and the follow-on audit
286
- // failures ("5/5 steps done, 5 files missing").
287
- if (status === "done") {
288
- const blockers = tracker
289
- .getPlan()
290
- .steps.filter((s) => s.id < stepId && s.status !== "done" && s.status !== "skipped");
291
- if (blockers.length > 0) {
292
- return {
293
- success: false,
294
- output: t("plan.order_blocked", {
295
- step: String(stepId),
296
- first: String(blockers[0].id),
297
- desc: blockers[0].description,
298
- }),
299
- };
300
- }
301
- }
302
- // Deliverable gate: marking a step done must match its declared
303
- // intent. "create" (default) → named files must exist; "delete"
304
- // → named files must be GONE. Intent is model-declared (kind),
305
- // never keyword-inferred (rule #10).
306
- //
307
- // Auto-switch: when kind is "create" but ALL named files are gone,
308
- // check if the agent actually deleted them (via delete_file/bash rm).
309
- // Only then switch to "delete" — files that were never created
310
- // (create-kind steps) remain blocked so the agent must create them.
311
- if (status === "done" && target.kind !== "delete") {
312
- const missing = deps.missingStepDeliverables(target);
313
- if (missing.length > 0) {
314
- const existing = deps.stillExistingDeliverables(target);
315
- const allGone = deps.hasStepDeliverables(target) && existing.length === 0;
316
- if (allGone) {
317
- // Confirm the agent actually deleted these files (not just
318
- // never created them) before auto-switching.
319
- const wasDeleted = deps.wasFileDeleted
320
- ? missing.every((f) => deps.wasFileDeleted(f))
321
- : false;
322
- if (wasDeleted) {
323
- target.kind = "delete";
324
- deps.store.saveActive(tracker.getPlan());
325
- }
326
- }
327
- }
328
- }
329
- if (status === "done") {
330
- if (target.kind === "delete") {
331
- const leftovers = deps.stillExistingDeliverables(target);
332
- if (leftovers.length > 0) {
333
- return {
334
- success: false,
335
- output: t("plan.deliverables_remain", {
336
- step: String(stepId),
337
- files: leftovers.join(", "),
338
- }),
339
- };
340
- }
341
- }
342
- else {
343
- const missing = deps.missingStepDeliverables(target);
344
- if (missing.length > 0) {
345
- return {
346
- success: false,
347
- output: t("plan.deliverables_missing", {
348
- step: String(stepId),
349
- files: missing.join(", "),
350
- }),
351
- };
352
- }
353
- }
354
- }
355
- // Typecheck gate: "done" while the last edit still fails to compile
356
- // is a self-certification the harness must refuse (observed: steps
357
- // marked done with the project typecheck failing on every write —
358
- // a "fix it later" cascade that never got fixed). The model must
359
- // either fix the errors (a clean re-edit clears the map) or mark
360
- // the step skipped.
361
- if (status === "done") {
362
- const blocked = stepTypecheckGate(deps.typecheckFailures, target.description);
363
- if (blocked) {
364
- return {
365
- success: false,
366
- output: t("plan.typecheck_failed", {
367
- step: String(stepId),
368
- error: blocked,
369
- }),
370
- };
371
- }
372
- }
373
- tracker.updateStepStatus(stepId, status);
374
- if (args.note)
375
- tracker.addNote(Number(args.step), String(args.note));
376
- tracker.syncCurrentStep();
377
- deps.store.saveActive(tracker.getPlan());
378
- // A "done" step that names no file tokens has a vacuous gate — its
379
- // completion cannot be auto-verified. Inform the model so it can
380
- // run an explicit check or add file paths to the step description.
381
- const vacuousGate = status === "done" && !deps.hasStepDeliverables(target);
382
- const vacuousNote = vacuousGate
383
- ? `\n${t("plan.no_deliverables", { step: String(stepId) })}`
384
- : "";
385
- // If this update completed the last step → auto-archive. This breaks
386
- // the loop where a finished plan stays active and anchors the model
387
- // to "continue step N, do NOT create a new plan".
388
- if (tracker.isComplete()) {
389
- const plan = tracker.getPlan();
390
- deps.store.archivePlan(plan);
391
- deps.recordCompleted(plan); // final audit still verifies it
392
- deps.trackerRef.current = null;
393
- const p = getPlanProgress(plan);
394
- return {
395
- success: true,
396
- output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${t("plan.completed_archived", {
397
- id: plan.id,
398
- done: String(p.settled),
399
- total: String(plan.steps.length),
400
- })}${vacuousNote}`,
401
- };
402
- }
403
- const progress = tracker.getProgressString();
404
- const display = tracker.toPromptBlock();
405
- _ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${tracker.getCurrentStepIndex() + 1}`);
406
- return {
407
- success: true,
408
- output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${progress}${vacuousNote}`,
409
- display,
410
- };
411
- }
412
- if (action === "update" &&
413
- Array.isArray(args.steps) &&
414
- args.steps.length > 0 &&
415
- !args.step) {
416
- const tracker = deps.trackerRef.current;
417
- if (!tracker) {
418
- return { success: false, output: t("plan.no_active") };
419
- }
420
- // Rebuilding a plan that already has progress silently discards
421
- // every status (ses_mt4fn58c: the model sent its whole step list
422
- // with ✅-emoji statuses expecting progress marks and got a fresh
423
- // plan back). A not-yet-started plan is safe to rebuild.
424
- const progressed = tracker.getPlan().steps.some((s) => s.status !== "pending");
425
- if (progressed) {
426
- return { success: false, output: t("plan.bulk_update_rejected") };
427
- }
428
- const title = String(args.title || tracker.getPlan().title);
429
- const steps = args.steps.map(String);
430
- const parsed = deps.parseKinds(args, steps);
431
- if (parsed.error) {
432
- return { success: false, output: parsed.error };
433
- }
434
- const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
435
- deps.setPlan(plan);
436
- const display = deps.trackerRef.current?.toPromptBlock();
437
- const output = t("plan.updated", {
438
- title,
439
- steps: String(steps.length),
440
- });
441
- return {
442
- success: true,
443
- output,
444
- display,
445
- };
446
- }
447
- if (action === "abort") {
448
- const planId = args.id ? String(args.id) : "";
449
- if (planId) {
450
- // Abort a SPECIFIC plan (not the active one). Draft → archive,
451
- // active → archive + clear tracker. Archived plans are already
452
- // terminal — there is nothing to abort.
453
- const found = deps.store.find(planId);
454
- if (!found) {
455
- return {
456
- success: false,
457
- output: t("plan.not_found", { id: planId }),
458
- };
459
- }
460
- if (found.status === "archived") {
461
- return {
462
- success: false,
463
- output: t("plan.already_archived", { id: planId }),
464
- };
465
- }
466
- deps.store.archivePlan(found.plan); // clears active if it was active
467
- if (found.status === "active") {
468
- deps.trackerRef.current = null;
469
- deps.clearCompleted();
470
- }
471
- return {
472
- success: true,
473
- output: t("plan.aborted_id", { id: found.plan.id }),
474
- };
475
- }
476
- const tracker = deps.trackerRef.current;
477
- if (!tracker) {
478
- // Abort with no active plan and no id — previously returned
479
- // success:true, which made the model believe it aborted a plan
480
- // that did not exist (observed: 3× abort on an empty list).
481
- return { success: false, output: t("plan.no_active") };
482
- }
483
- deps.store.archivePlan(tracker.getPlan());
484
- deps.trackerRef.current = null;
485
- deps.store.clearActive();
486
- deps.clearCompleted();
487
- return { success: true, output: t("plan.aborted") };
488
- }
489
- if (action === "delete") {
490
- const planId = String(args.id || "");
491
- if (!planId) {
492
- return { success: false, output: t("plan.delete_no_id") };
493
- }
494
- const deleted = deps.store.deletePlan(planId);
495
- if (!deleted) {
496
- return {
497
- success: false,
498
- output: t("plan.not_found", { id: planId }),
499
- };
500
- }
501
- if (deleted === "active") {
502
- deps.trackerRef.current = null;
503
- deps.clearCompleted();
504
- }
505
- return {
506
- success: true,
507
- output: t("plan.deleted", { id: planId }),
508
- };
509
- }
510
- if (action === "purge") {
511
- const count = deps.store.purgeAll();
512
- deps.trackerRef.current = null;
513
- deps.clearCompleted();
514
- return {
515
- success: true,
516
- output: t("plan.purged", { count: String(count) }),
517
- };
518
- }
519
- if (!deps.trackerRef.current) {
520
- // Update with an id that points at a draft/archived plan: say the
521
- // plan is read-only instead of a bare "No active plan" (observed:
522
- // 10+ iterations re-updating an archived plan and getting the same
523
- // unhelpful answer back).
524
- if (args.id) {
525
- const found = deps.store.find(String(args.id));
526
- if (found && found.status !== "active") {
527
- return {
528
- success: false,
529
- output: t("plan.readonly", { id: found.plan.id, status: found.status }),
530
- };
531
- }
532
- }
533
- return { success: false, output: t("plan.no_active") };
534
- }
535
- return {
536
- success: false,
537
- output: t("plan.unknown_action", { action }),
538
- };
539
- },
540
- },
541
- {
542
- name: "todo",
543
- icon: "☑️",
544
- alwaysOn: true,
545
- description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
546
- parameters: {
547
- type: "object",
548
- properties: {
549
- action: { type: "string", enum: ["add", "done", "list"] },
550
- items: { type: "array", items: { type: "string" } },
551
- },
552
- required: ["action"],
553
- },
554
- handler: async (_ctx, args) => {
555
- if (!deps.trackerRef.current) {
556
- return { success: false, output: t("plan.no_active") };
557
- }
558
- const tracker = deps.trackerRef.current;
559
- const action = String(args.action);
560
- const currentStep = tracker.getCurrentStep();
561
- if (!currentStep) {
562
- return { success: false, output: t("plan.step_not_found") };
563
- }
564
- if (action === "add" && Array.isArray(args.items)) {
565
- const items = args.items.map(String);
566
- const existing = currentStep.subtasks ?? [];
567
- let nextId = existing.reduce((m, s) => Math.max(m, s.id), 0) + 1;
568
- for (const item of items) {
569
- existing.push({ id: nextId++, text: item, done: false });
570
- }
571
- currentStep.subtasks = existing;
572
- deps.store.saveActive(tracker.getPlan());
573
- const display = tracker.toPromptBlock();
574
- return {
575
- success: true,
576
- output: t("todo.added", {
577
- count: String(items.length),
578
- items: items.join(", "),
579
- }),
580
- display,
581
- };
582
- }
583
- if (action === "done") {
584
- const items = Array.isArray(args.items) ? args.items.map(String) : [];
585
- if (items.length === 0) {
586
- return {
587
- success: false,
588
- output: t("todo.no_items"),
589
- };
590
- }
591
- const subs = currentStep.subtasks ?? [];
592
- let marked = 0;
593
- for (const item of items) {
594
- const sub = subs.find((s) => !s.done && s.text.toLowerCase() === item.toLowerCase());
595
- if (sub) {
596
- sub.done = true;
597
- marked++;
598
- }
599
- }
600
- if (marked === 0) {
601
- return {
602
- success: false,
603
- output: t("todo.subtask_not_found", {
604
- items: items.join(", "),
605
- }),
606
- };
607
- }
608
- currentStep.subtasks = subs;
609
- deps.store.saveActive(tracker.getPlan());
610
- const doneCount = subs.filter((s) => s.done).length;
611
- const display = tracker.toPromptBlock();
612
- // Marking a sub-task done must NOT auto-complete the plan
613
- // step — the step is only done when the model confirms the
614
- // whole step via plan update (observed: 3 × "todo done"
615
- // returned "Step 3: done" and marked the step done early).
616
- let output = t("todo.marked_done", {
617
- count: String(marked),
618
- });
619
- output += ` (${doneCount}/${subs.length})`;
620
- if (doneCount === subs.length) {
621
- output += `\n${t("plan.subtasks_done", { stepId: String(currentStep.id) })}`;
622
- }
623
- return { success: true, output, display };
624
- }
625
- if (action === "list") {
626
- const subs = currentStep.subtasks ?? [];
627
- const lines = subs.length
628
- ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`)
629
- : [t("plan.no_subtasks")];
630
- return {
631
- success: true,
632
- output: `${t("plan.step_label", { stepId: String(currentStep.id), description: currentStep.description })}\n${lines.join("\n")}`,
633
- };
634
- }
635
- return {
636
- success: false,
637
- output: t("todo.unknown_action", { action }),
638
- };
639
- },
640
- },
641
- {
642
- name: "verify",
643
- icon: "✅",
644
- alwaysOn: true,
645
- description: "Run verification for the current step. Checks files mentioned in the step description.",
646
- parameters: {
647
- type: "object",
648
- properties: {
649
- step: { type: "number", description: "Step number to verify" },
650
- },
651
- },
652
- handler: async (_ctx, args) => {
653
- if (!deps.trackerRef.current)
654
- return { success: false, output: t("plan.no_active") };
655
- const tracker = deps.trackerRef.current;
656
- const step = args.step ? tracker.getStep(Number(args.step)) : tracker.getCurrentStep();
657
- if (!step)
658
- return { success: false, output: t("plan.step_not_found") };
659
- const result = await deps.verifier.verifyStep(step.description, step.kind);
660
- if (result.noFiles) {
661
- return {
662
- success: true,
663
- output: t("verify.no_files", { step: String(step.id) }),
664
- };
665
- }
666
- return {
667
- success: result.passed,
668
- output: result.passed
669
- ? t("verify.passed")
670
- : t("verify.failed", {
671
- details: result.failed.map((f) => f.message).join("; "),
672
- }),
673
- };
674
- },
675
- },
676
- ];
677
- }