micro-models-agent 0.46.2 → 0.46.3

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 (218) hide show
  1. package/README.md +312 -312
  2. package/dist/main.js +711 -784
  3. package/dist/modules/browser/bridge-server.mjs +202 -202
  4. package/dist/skills/builtin/git.md +36 -36
  5. package/dist/skills/builtin/typescript.md +35 -35
  6. package/package.json +48 -48
  7. package/dist/cli/commands.js +0 -323
  8. package/dist/cli/completer.js +0 -167
  9. package/dist/cli/index.js +0 -2
  10. package/dist/cli/main.js +0 -165
  11. package/dist/cli/plugin-commands.js +0 -36
  12. package/dist/cli/repl-commands.js +0 -661
  13. package/dist/cli/repl.js +0 -616
  14. package/dist/cli/run-result.js +0 -22
  15. package/dist/cli/security-commands.js +0 -164
  16. package/dist/cli/setup.js +0 -231
  17. package/dist/config/config.js +0 -249
  18. package/dist/config/defaults.js +0 -124
  19. package/dist/config/experts.js +0 -15
  20. package/dist/config/index.js +0 -3
  21. package/dist/config/security.js +0 -193
  22. package/dist/config/types.js +0 -1
  23. package/dist/core/agent-moe.js +0 -102
  24. package/dist/core/agent.js +0 -886
  25. package/dist/core/bootstrap.js +0 -404
  26. package/dist/core/index.js +0 -2
  27. package/dist/core/prompt-builder.js +0 -76
  28. package/dist/core/session-logger.js +0 -197
  29. package/dist/core/types.js +0 -1
  30. package/dist/core/version.js +0 -24
  31. package/dist/core/workspace.js +0 -76
  32. package/dist/i18n/en.json +0 -598
  33. package/dist/i18n/index.js +0 -46
  34. package/dist/i18n/ru.json +0 -598
  35. package/dist/index.js +0 -22
  36. package/dist/llm/image-utils.js +0 -143
  37. package/dist/llm/index.js +0 -4
  38. package/dist/llm/model-loader.js +0 -78
  39. package/dist/llm/openai-compat.js +0 -359
  40. package/dist/llm/orchestrator.js +0 -198
  41. package/dist/llm/provider.js +0 -10
  42. package/dist/llm/response.js +0 -39
  43. package/dist/llm/token-counter.js +0 -39
  44. package/dist/llm/types.js +0 -1
  45. package/dist/logger/app-logger.js +0 -143
  46. package/dist/logger/file-log.js +0 -151
  47. package/dist/logger/index.js +0 -1
  48. package/dist/migration/backup.js +0 -45
  49. package/dist/migration/detect.js +0 -50
  50. package/dist/migration/index.js +0 -2
  51. package/dist/modules/artifacts/store.js +0 -61
  52. package/dist/modules/browser/actions.js +0 -76
  53. package/dist/modules/browser/bridge-client.js +0 -199
  54. package/dist/modules/browser/bridge-path.js +0 -10
  55. package/dist/modules/browser/cookie-store.js +0 -24
  56. package/dist/modules/browser/driver.js +0 -136
  57. package/dist/modules/browser/index.js +0 -7
  58. package/dist/modules/browser/module.js +0 -29
  59. package/dist/modules/browser/session.js +0 -338
  60. package/dist/modules/browser/snapshot.js +0 -148
  61. package/dist/modules/browser/types.js +0 -12
  62. package/dist/modules/certification/cli.js +0 -174
  63. package/dist/modules/certification/fact-checker.js +0 -82
  64. package/dist/modules/certification/loader.js +0 -105
  65. package/dist/modules/certification/manifest.js +0 -50
  66. package/dist/modules/certification/runner.js +0 -159
  67. package/dist/modules/certification/scenarios.js +0 -124
  68. package/dist/modules/certification/types.js +0 -1
  69. package/dist/modules/context/chunk-query.js +0 -100
  70. package/dist/modules/context/fact-extractor.js +0 -162
  71. package/dist/modules/context/history.js +0 -15
  72. package/dist/modules/context/index.js +0 -1
  73. package/dist/modules/context/manager.js +0 -423
  74. package/dist/modules/execution/audit-runners.js +0 -152
  75. package/dist/modules/execution/auditor.js +0 -218
  76. package/dist/modules/execution/execution-plugin.js +0 -272
  77. package/dist/modules/execution/index.js +0 -8
  78. package/dist/modules/execution/module.js +0 -436
  79. package/dist/modules/execution/moe-executor.js +0 -291
  80. package/dist/modules/execution/plan-coverage.js +0 -68
  81. package/dist/modules/execution/plan-persister.js +0 -46
  82. package/dist/modules/execution/plan-store.js +0 -157
  83. package/dist/modules/execution/plan-tool.js +0 -508
  84. package/dist/modules/execution/plan-validator.js +0 -153
  85. package/dist/modules/execution/planner.js +0 -90
  86. package/dist/modules/execution/stuck-detector.js +0 -510
  87. package/dist/modules/execution/tracker.js +0 -67
  88. package/dist/modules/execution/types.js +0 -1
  89. package/dist/modules/execution/verifier.js +0 -222
  90. package/dist/modules/execution/windows-commands.js +0 -41
  91. package/dist/modules/hallucination/confidence.js +0 -66
  92. package/dist/modules/hallucination/consistency.js +0 -26
  93. package/dist/modules/hallucination/detector.js +0 -43
  94. package/dist/modules/hallucination/factual.js +0 -129
  95. package/dist/modules/hallucination/index.js +0 -5
  96. package/dist/modules/hallucination/js-identifiers.js +0 -262
  97. package/dist/modules/hallucination/llm-judge.js +0 -101
  98. package/dist/modules/index.js +0 -5
  99. package/dist/modules/indexer/cache.js +0 -40
  100. package/dist/modules/indexer/index.js +0 -3
  101. package/dist/modules/indexer/module.js +0 -245
  102. package/dist/modules/indexer/project-profile.js +0 -183
  103. package/dist/modules/indexer/walker.js +0 -101
  104. package/dist/modules/lsp/check-tool.js +0 -58
  105. package/dist/modules/lsp/client.js +0 -278
  106. package/dist/modules/lsp/command.js +0 -60
  107. package/dist/modules/lsp/config.js +0 -135
  108. package/dist/modules/lsp/index.js +0 -3
  109. package/dist/modules/lsp/module.js +0 -232
  110. package/dist/modules/lsp/probe.js +0 -76
  111. package/dist/modules/lsp/project-root.js +0 -32
  112. package/dist/modules/lsp/startup-check.js +0 -141
  113. package/dist/modules/lsp/types.js +0 -1
  114. package/dist/modules/mcp/client.js +0 -399
  115. package/dist/modules/mcp/index.js +0 -3
  116. package/dist/modules/mcp/module.js +0 -142
  117. package/dist/modules/mcp/registry.js +0 -15
  118. package/dist/modules/memory/index.js +0 -1
  119. package/dist/modules/memory/module.js +0 -96
  120. package/dist/modules/memory/search.js +0 -42
  121. package/dist/modules/memory/store.js +0 -69
  122. package/dist/modules/pipelines/engine.js +0 -60
  123. package/dist/modules/pipelines/index.js +0 -3
  124. package/dist/modules/pipelines/parser.js +0 -56
  125. package/dist/modules/pipelines/template.js +0 -14
  126. package/dist/modules/plugins/builtin/lint-on-write.js +0 -231
  127. package/dist/modules/plugins/builtin/notify.js +0 -9
  128. package/dist/modules/plugins/index.js +0 -1
  129. package/dist/modules/plugins/loader.js +0 -70
  130. package/dist/modules/plugins/manager.js +0 -217
  131. package/dist/modules/plugins/types.js +0 -1
  132. package/dist/modules/processes/detect.js +0 -34
  133. package/dist/modules/processes/index.js +0 -2
  134. package/dist/modules/processes/registry.js +0 -327
  135. package/dist/modules/processes/runner.js +0 -23
  136. package/dist/modules/registry.js +0 -47
  137. package/dist/modules/security/audit-log.js +0 -136
  138. package/dist/modules/security/audit-notifier.js +0 -292
  139. package/dist/modules/security/command-validator.js +0 -205
  140. package/dist/modules/security/content-scanner.js +0 -53
  141. package/dist/modules/security/data-sanitizer.js +0 -89
  142. package/dist/modules/security/encryption.js +0 -242
  143. package/dist/modules/security/index.js +0 -14
  144. package/dist/modules/security/network-validator.js +0 -71
  145. package/dist/modules/security/path-validator.js +0 -207
  146. package/dist/modules/security/rate-limiter.js +0 -119
  147. package/dist/modules/security/security-policies.js +0 -531
  148. package/dist/modules/security/session-encryption.js +0 -210
  149. package/dist/modules/security/session-isolation.js +0 -95
  150. package/dist/modules/session/index.js +0 -3
  151. package/dist/modules/session/manager.js +0 -172
  152. package/dist/modules/session/module.js +0 -24
  153. package/dist/modules/session/store.js +0 -222
  154. package/dist/modules/session/types.js +0 -1
  155. package/dist/modules/skills/index.js +0 -2
  156. package/dist/modules/skills/loader.js +0 -72
  157. package/dist/modules/skills/matcher.js +0 -27
  158. package/dist/modules/skills/module.js +0 -129
  159. package/dist/modules/types.js +0 -1
  160. package/dist/modules/updater/checker.js +0 -96
  161. package/dist/modules/updater/index.js +0 -2
  162. package/dist/modules/updater/module.js +0 -116
  163. package/dist/modules/user-profile/compressor.js +0 -16
  164. package/dist/modules/user-profile/index.js +0 -1
  165. package/dist/modules/user-profile/profile.js +0 -68
  166. package/dist/tools/approve.js +0 -32
  167. package/dist/tools/attach-image.js +0 -89
  168. package/dist/tools/bash.js +0 -496
  169. package/dist/tools/browser.js +0 -114
  170. package/dist/tools/chunk-query.js +0 -99
  171. package/dist/tools/create-dir.js +0 -55
  172. package/dist/tools/delete-file.js +0 -62
  173. package/dist/tools/download-file.js +0 -116
  174. package/dist/tools/edit-file.js +0 -79
  175. package/dist/tools/enable-tools.js +0 -58
  176. package/dist/tools/executor.js +0 -144
  177. package/dist/tools/file-info.js +0 -46
  178. package/dist/tools/filter-tools.js +0 -17
  179. package/dist/tools/glob-tool.js +0 -26
  180. package/dist/tools/grep-tool.js +0 -84
  181. package/dist/tools/hidden-tools-block.js +0 -37
  182. package/dist/tools/index.js +0 -78
  183. package/dist/tools/list-dir.js +0 -48
  184. package/dist/tools/load-skill.js +0 -42
  185. package/dist/tools/mcp-call.js +0 -68
  186. package/dist/tools/move-file.js +0 -85
  187. package/dist/tools/path-utils.js +0 -51
  188. package/dist/tools/pipeline-run.js +0 -144
  189. package/dist/tools/preview.js +0 -2
  190. package/dist/tools/process-kill.js +0 -29
  191. package/dist/tools/process-list.js +0 -36
  192. package/dist/tools/process-log.js +0 -45
  193. package/dist/tools/question.js +0 -140
  194. package/dist/tools/read-file.js +0 -91
  195. package/dist/tools/recall.js +0 -117
  196. package/dist/tools/registry.js +0 -47
  197. package/dist/tools/remember.js +0 -67
  198. package/dist/tools/scope-check.js +0 -30
  199. package/dist/tools/search-history.js +0 -84
  200. package/dist/tools/subagent.js +0 -196
  201. package/dist/tools/types.js +0 -1
  202. package/dist/tools/user-input.js +0 -123
  203. package/dist/tools/web-browse.js +0 -86
  204. package/dist/tools/web-fetch.js +0 -98
  205. package/dist/tools/web-search.js +0 -78
  206. package/dist/tools/write-file.js +0 -81
  207. package/dist/ui/box.js +0 -77
  208. package/dist/ui/colors.js +0 -4
  209. package/dist/ui/diff.js +0 -178
  210. package/dist/ui/index.js +0 -6
  211. package/dist/ui/line-editor.js +0 -703
  212. package/dist/ui/line-math.js +0 -69
  213. package/dist/ui/md-formatter.js +0 -212
  214. package/dist/ui/output.js +0 -13
  215. package/dist/ui/plan-view.js +0 -103
  216. package/dist/ui/renderer.js +0 -209
  217. package/dist/ui/spinner.js +0 -70
  218. package/dist/ui/table.js +0 -144
@@ -1,508 +0,0 @@
1
- import { t } from "../../i18n/index";
2
- import { PlanCreator } from "./planner";
3
- import { checkPlanCoverage } from "./plan-coverage";
4
- /**
5
- * The plan / todo / verify tool definitions for the execution module. The
6
- * handlers were extracted verbatim from ExecutionModule.getToolDefinitions() —
7
- * all state access goes through the `deps` closures (the module owns the
8
- * tracker and the plan store).
9
- */
10
- export function createPlanToolDefinitions(deps) {
11
- return [
12
- {
13
- name: "plan",
14
- alwaysOn: true,
15
- 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?)\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).',
16
- parameters: {
17
- type: "object",
18
- properties: {
19
- action: {
20
- type: "string",
21
- enum: ["create", "update", "show", "abort", "list", "switch", "re-plan"],
22
- },
23
- title: { type: "string" },
24
- steps: { type: "array", items: { type: "string" } },
25
- kinds: {
26
- type: "array",
27
- items: { type: "string", enum: ["create", "delete"] },
28
- 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".',
29
- },
30
- step: { type: "number" },
31
- status: { type: "string", enum: ["done", "failed", "skipped"] },
32
- note: { type: "string" },
33
- id: { type: "string", description: "Plan id (for switch action)" },
34
- },
35
- required: ["action"],
36
- },
37
- handler: async (_ctx, args) => {
38
- const action = String(args.action);
39
- if (action === "list") {
40
- const metas = deps.store.listAll();
41
- if (metas.length === 0) {
42
- return { success: true, output: t("plan.list_empty") };
43
- }
44
- const lines = metas.map((m) => {
45
- const icon = m.status === "active" ? "[*]" : m.status === "draft" ? "[ ]" : "[-]";
46
- const namePart = m.name ? ` (${m.name})` : "";
47
- return `${icon} ${m.id}${namePart} — ${m.title} ${m.doneCount}/${m.stepCount}`;
48
- });
49
- return {
50
- success: true,
51
- output: `${t("plan.list_header")}\n${lines.join("\n")}`,
52
- };
53
- }
54
- if (action === "switch") {
55
- const planId = String(args.id || "");
56
- if (!planId) {
57
- return { success: false, output: t("plan.switch_no_id") };
58
- }
59
- const found = deps.store.find(planId);
60
- if (!found) {
61
- return {
62
- success: false,
63
- output: t("plan.not_found", { id: planId }),
64
- };
65
- }
66
- deps.preserveActive();
67
- if (found.status === "draft") {
68
- deps.store.removeDraft(found.plan.id);
69
- }
70
- else if (found.status === "archived") {
71
- deps.store.removeArchived(found.plan.id);
72
- }
73
- deps.setPlan(found.plan);
74
- const display = PlanCreator.toPromptBlock(found.plan, 0);
75
- return {
76
- success: true,
77
- output: t("plan.switched", {
78
- id: found.plan.id,
79
- title: found.plan.title,
80
- }),
81
- display,
82
- };
83
- }
84
- if (action === "re-plan") {
85
- if (!deps.trackerRef.current) {
86
- return { success: false, output: t("plan.no_active") };
87
- }
88
- const newSteps = Array.isArray(args.steps) ? args.steps.map(String) : [];
89
- if (newSteps.length === 0) {
90
- return { success: false, output: t("plan.replan_no_steps") };
91
- }
92
- const tracker = deps.trackerRef.current;
93
- const oldPlan = tracker.getPlan();
94
- const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
95
- const keptCount = replanned.steps.length - newSteps.length;
96
- deps.setPlan(replanned);
97
- const display = PlanCreator.toPromptBlock(replanned, keptCount);
98
- return {
99
- success: true,
100
- output: t("plan.replanned", {
101
- kept: String(keptCount),
102
- steps: String(newSteps.length),
103
- }),
104
- display,
105
- };
106
- }
107
- if (action === "create") {
108
- const title = String(args.title || "Task Plan");
109
- const steps = Array.isArray(args.steps) ? args.steps.map(String) : [];
110
- if (steps.length === 0) {
111
- return { success: false, output: t("plan.no_steps") };
112
- }
113
- const parsed = deps.parseKinds(args, steps);
114
- if (parsed.error) {
115
- return { success: false, output: parsed.error };
116
- }
117
- // Never silently discard an in-progress plan (observed:
118
- // ses_msvuao0h — right after compaction the model called
119
- // `plan create` again, dumping the 1/6 plan into drafts and
120
- // starting from scratch). An active plan WITH progress blocks
121
- // creation until the model consciously aborts it. A fresh plan
122
- // (all steps pending) is safe to replace — the model may have
123
- // just changed its mind about the approach.
124
- const existing = deps.trackerRef.current;
125
- if (existing) {
126
- const activePlan = existing.getPlan();
127
- const progressed = activePlan.steps.some((s) => s.status === "done" || s.status === "failed" || s.status === "skipped");
128
- if (progressed) {
129
- const done = activePlan.steps.filter((s) => s.status === "done").length;
130
- return {
131
- success: false,
132
- output: t("plan.active_in_progress", {
133
- id: activePlan.id,
134
- done: String(done),
135
- total: String(activePlan.steps.length),
136
- current: String(existing.getCurrentStepIndex() + 1),
137
- }),
138
- };
139
- }
140
- }
141
- deps.preserveActive();
142
- const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
143
- deps.setPlan(plan);
144
- const display = PlanCreator.toPromptBlock(plan, 0);
145
- let output = t("plan.created", {
146
- title,
147
- steps: String(steps.length),
148
- });
149
- // The model sometimes passes an id expecting the plan to be
150
- // created under it (observed: id "plan_error_check_e07pb2" was
151
- // ignored, a new id appeared). Be explicit so it doesn't look
152
- // for a plan by the wrong id afterwards.
153
- if (args.id) {
154
- output += `\n${t("plan.id_ignored")}`;
155
- }
156
- if (existing) {
157
- output += `\n${t("plan.existing_fresh")}`;
158
- }
159
- let displayOut = display;
160
- const taskText = deps.getTaskText(_ctx);
161
- if (taskText) {
162
- const { missing } = checkPlanCoverage(taskText, steps);
163
- if (missing.length > 0) {
164
- const warn = t("plan.coverage_warning", {
165
- missing: missing.join(", "),
166
- });
167
- output += `\n⚠️ ${warn}`;
168
- displayOut = `${display}\n⚠️ ${warn}`;
169
- }
170
- }
171
- return {
172
- success: true,
173
- output,
174
- display: displayOut,
175
- };
176
- }
177
- if (action === "show") {
178
- const planId = args.id ? String(args.id) : "";
179
- if (planId && planId !== deps.trackerRef.current?.getPlan()?.id) {
180
- // Show a specific plan (active, draft, or archived) instead
181
- // of ignoring the id and always printing the active one
182
- // (observed: the model asked for an archived plan three
183
- // times and got the active plan back every time).
184
- const found = deps.store.find(planId);
185
- if (!found) {
186
- return {
187
- success: false,
188
- output: t("plan.not_found", { id: planId }),
189
- };
190
- }
191
- const display = PlanCreator.toPromptBlock(found.plan, 0);
192
- return {
193
- success: true,
194
- output: `${t("plan.show_header")}\n${display}`,
195
- display,
196
- };
197
- }
198
- if (!deps.trackerRef.current) {
199
- return { success: false, output: t("plan.no_active") };
200
- }
201
- const display = deps.trackerRef.current.toPromptBlock();
202
- return {
203
- success: true,
204
- output: `${t("plan.show_header")}\n${display}`,
205
- display,
206
- };
207
- }
208
- if (action === "update" && args.step && deps.trackerRef.current) {
209
- const tracker = deps.trackerRef.current;
210
- const stepId = Number(args.step);
211
- const target = tracker.getStep(stepId);
212
- if (!target) {
213
- return { success: false, output: t("plan.step_not_found") };
214
- }
215
- const status = String(args.status || "done");
216
- // Completed plan: re-marking a done step of a finished plan is the
217
- // loop anchor (observed: 3× "plan update step=4 done" on a 4/4 plan
218
- // while a NEW task waited). Archive + clear so the model can create a
219
- // fresh plan for the new task instead of spinning.
220
- if (tracker.isComplete()) {
221
- const plan = tracker.getPlan();
222
- deps.store.archivePlan(plan);
223
- deps.trackerRef.current = null;
224
- const done = plan.steps.filter((s) => s.status === "done").length;
225
- return {
226
- success: true,
227
- output: t("plan.completed_archived", {
228
- id: plan.id,
229
- done: String(done),
230
- total: String(plan.steps.length),
231
- }),
232
- };
233
- }
234
- // Idempotency: re-marking a done step as done usually means the
235
- // plan was already auto-advanced (all step files exist) and the
236
- // model is confirming its own work. Return SUCCESS so the loop
237
- // isn't confused — a small model misreads success:false as a
238
- // failed action and retries/spins. Genuine spinning (repeated
239
- // identical calls without new work) is still caught by the
240
- // stuck-detector's consecutive-identical-call check.
241
- if (status === "done" && target.status === "done") {
242
- const progress = tracker.getProgressString();
243
- const display = tracker.toPromptBlock();
244
- return {
245
- success: true,
246
- output: `${t("plan.step_already_done", {
247
- step: String(stepId),
248
- })}\n${progress}`,
249
- display,
250
- };
251
- }
252
- // Order enforcement: only the CURRENT step may be marked done.
253
- // Prevents jumping ahead (step 5 marked done before steps 1-4,
254
- // observed in the post-mortem session) and the follow-on audit
255
- // failures ("5/5 steps done, 5 files missing").
256
- if (status === "done") {
257
- const blockers = tracker
258
- .getPlan()
259
- .steps.filter((s) => s.id < stepId && s.status !== "done" && s.status !== "skipped");
260
- if (blockers.length > 0) {
261
- return {
262
- success: false,
263
- output: t("plan.order_blocked", {
264
- step: String(stepId),
265
- first: String(blockers[0].id),
266
- desc: blockers[0].description,
267
- }),
268
- };
269
- }
270
- }
271
- // Deliverable gate: marking a step done must match its declared
272
- // intent. "create" (default) → named files must exist; "delete"
273
- // → named files must be GONE. Intent is model-declared (kind),
274
- // never keyword-inferred (rule #10).
275
- if (status === "done") {
276
- if (target.kind === "delete") {
277
- const leftovers = deps.stillExistingDeliverables(target);
278
- if (leftovers.length > 0) {
279
- return {
280
- success: false,
281
- output: t("plan.deliverables_remain", {
282
- step: String(stepId),
283
- files: leftovers.join(", "),
284
- }),
285
- };
286
- }
287
- }
288
- else {
289
- const missing = deps.missingStepDeliverables(target);
290
- if (missing.length > 0) {
291
- return {
292
- success: false,
293
- output: t("plan.deliverables_missing", {
294
- step: String(stepId),
295
- files: missing.join(", "),
296
- }),
297
- };
298
- }
299
- }
300
- }
301
- tracker.updateStepStatus(stepId, status);
302
- if (args.note)
303
- tracker.addNote(Number(args.step), String(args.note));
304
- tracker.syncCurrentStep();
305
- deps.store.saveActive(tracker.getPlan());
306
- // If this update completed the last step → auto-archive. This breaks
307
- // the loop where a finished plan stays active and anchors the model
308
- // to "continue step N, do NOT create a new plan".
309
- if (tracker.isComplete()) {
310
- const plan = tracker.getPlan();
311
- deps.store.archivePlan(plan);
312
- deps.trackerRef.current = null;
313
- const done = plan.steps.filter((s) => s.status === "done").length;
314
- return {
315
- success: true,
316
- output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${t("plan.completed_archived", {
317
- id: plan.id,
318
- done: String(done),
319
- total: String(plan.steps.length),
320
- })}`,
321
- };
322
- }
323
- const progress = tracker.getProgressString();
324
- const display = tracker.toPromptBlock();
325
- _ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${tracker.getCurrentStepIndex() + 1}`);
326
- return {
327
- success: true,
328
- output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${progress}`,
329
- display,
330
- };
331
- }
332
- if (action === "update" &&
333
- Array.isArray(args.steps) &&
334
- args.steps.length > 0 &&
335
- deps.trackerRef.current) {
336
- const tracker = deps.trackerRef.current;
337
- const title = String(args.title || tracker.getPlan().title);
338
- const steps = args.steps.map(String);
339
- const parsed = deps.parseKinds(args, steps);
340
- if (parsed.error) {
341
- return { success: false, output: parsed.error };
342
- }
343
- const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
344
- deps.setPlan(plan);
345
- const display = PlanCreator.toPromptBlock(plan, 0);
346
- const output = t("plan.updated", {
347
- title,
348
- steps: String(steps.length),
349
- });
350
- return {
351
- success: true,
352
- output,
353
- display,
354
- };
355
- }
356
- if (action === "abort") {
357
- const tracker = deps.trackerRef.current;
358
- if (tracker) {
359
- deps.store.archivePlan(tracker.getPlan());
360
- }
361
- deps.trackerRef.current = null;
362
- deps.store.clearActive();
363
- return { success: true, output: t("plan.aborted") };
364
- }
365
- if (!deps.trackerRef.current) {
366
- return { success: false, output: t("plan.no_active") };
367
- }
368
- return {
369
- success: false,
370
- output: t("plan.unknown_action", { action }),
371
- };
372
- },
373
- },
374
- {
375
- name: "todo",
376
- alwaysOn: true,
377
- description: "Manage sub-tasks within current plan step. Use to break down complex steps into smaller tasks.",
378
- parameters: {
379
- type: "object",
380
- properties: {
381
- action: { type: "string", enum: ["add", "done", "list"] },
382
- items: { type: "array", items: { type: "string" } },
383
- },
384
- required: ["action"],
385
- },
386
- handler: async (_ctx, args) => {
387
- if (!deps.trackerRef.current) {
388
- return { success: false, output: t("plan.no_active") };
389
- }
390
- const tracker = deps.trackerRef.current;
391
- const action = String(args.action);
392
- const currentStep = tracker.getCurrentStep();
393
- if (!currentStep) {
394
- return { success: false, output: t("plan.step_not_found") };
395
- }
396
- if (action === "add" && Array.isArray(args.items)) {
397
- const items = args.items.map(String);
398
- const existing = currentStep.subtasks ?? [];
399
- let nextId = existing.reduce((m, s) => Math.max(m, s.id), 0) + 1;
400
- for (const item of items) {
401
- existing.push({ id: nextId++, text: item, done: false });
402
- }
403
- currentStep.subtasks = existing;
404
- deps.store.saveActive(tracker.getPlan());
405
- const display = tracker.toPromptBlock();
406
- return {
407
- success: true,
408
- output: t("todo.added", {
409
- count: String(items.length),
410
- items: items.join(", "),
411
- }),
412
- display,
413
- };
414
- }
415
- if (action === "done") {
416
- const items = Array.isArray(args.items) ? args.items.map(String) : [];
417
- if (items.length === 0) {
418
- return {
419
- success: false,
420
- output: t("todo.no_items"),
421
- };
422
- }
423
- const subs = currentStep.subtasks ?? [];
424
- let marked = 0;
425
- for (const item of items) {
426
- const sub = subs.find((s) => !s.done && s.text.toLowerCase() === item.toLowerCase());
427
- if (sub) {
428
- sub.done = true;
429
- marked++;
430
- }
431
- }
432
- if (marked === 0) {
433
- return {
434
- success: false,
435
- output: t("todo.subtask_not_found", {
436
- items: items.join(", "),
437
- }),
438
- };
439
- }
440
- currentStep.subtasks = subs;
441
- deps.store.saveActive(tracker.getPlan());
442
- const doneCount = subs.filter((s) => s.done).length;
443
- const display = tracker.toPromptBlock();
444
- // Marking a sub-task done must NOT auto-complete the plan
445
- // step — the step is only done when the model confirms the
446
- // whole step via plan update (observed: 3 × "todo done"
447
- // returned "Step 3: done" and marked the step done early).
448
- let output = t("todo.marked_done", {
449
- count: String(marked),
450
- });
451
- output += ` (${doneCount}/${subs.length})`;
452
- if (doneCount === subs.length) {
453
- output += `\nAll sub-tasks done — call plan update step=${currentStep.id} status=done to complete this step.`;
454
- }
455
- return { success: true, output, display };
456
- }
457
- if (action === "list") {
458
- const subs = currentStep.subtasks ?? [];
459
- const lines = subs.length
460
- ? subs.map((s) => `${s.done ? "[x]" : "[ ]"} ${s.text}`)
461
- : ["(no sub-tasks)"];
462
- return {
463
- success: true,
464
- output: `Step ${currentStep.id}: ${currentStep.description}\n${lines.join("\n")}`,
465
- };
466
- }
467
- return {
468
- success: false,
469
- output: t("todo.unknown_action", { action }),
470
- };
471
- },
472
- },
473
- {
474
- name: "verify",
475
- alwaysOn: true,
476
- description: "Run verification for the current step. Checks files mentioned in the step description.",
477
- parameters: {
478
- type: "object",
479
- properties: {
480
- step: { type: "number", description: "Step number to verify" },
481
- },
482
- },
483
- handler: async (_ctx, args) => {
484
- if (!deps.trackerRef.current)
485
- return { success: false, output: t("plan.no_active") };
486
- const tracker = deps.trackerRef.current;
487
- const step = args.step ? tracker.getStep(Number(args.step)) : tracker.getCurrentStep();
488
- if (!step)
489
- return { success: false, output: t("plan.step_not_found") };
490
- const result = await deps.verifier.verifyStep(step.description);
491
- if (result.noFiles) {
492
- return {
493
- success: true,
494
- output: t("verify.no_files", { step: String(step.id) }),
495
- };
496
- }
497
- return {
498
- success: result.passed,
499
- output: result.passed
500
- ? t("verify.passed")
501
- : t("verify.failed", {
502
- details: result.failed.map((f) => f.message).join("; "),
503
- }),
504
- };
505
- },
506
- },
507
- ];
508
- }
@@ -1,153 +0,0 @@
1
- export function validatePlan(plan, config) {
2
- const errors = [];
3
- const warnings = [];
4
- const autoFixes = [];
5
- if (!plan.subtasks || plan.subtasks.length === 0) {
6
- return { valid: false, errors: ["Plan has no subtasks"], warnings: [], autoFixes: [] };
7
- }
8
- errors.push(...expertTagsExist(plan.subtasks, config));
9
- errors.push(...hasCycle(plan.subtasks));
10
- const overlapErrors = hasAllowedFilesOverlap(plan.subtasks);
11
- errors.push(...overlapErrors);
12
- const readOverlaps = deleteReadOverlap(plan.subtasks);
13
- for (const overlap of readOverlaps) {
14
- if (overlap.canAutoFix) {
15
- autoFixes.push({ subtaskId: overlap.subtaskId, fix: `Add depends_on: ${overlap.dependsOn}` });
16
- }
17
- else if (overlap.error) {
18
- errors.push(overlap.error);
19
- }
20
- }
21
- return {
22
- valid: errors.length === 0,
23
- errors,
24
- warnings,
25
- autoFixes,
26
- };
27
- }
28
- export function expertTagsExist(subtasks, config) {
29
- const errors = [];
30
- const knownExperts = new Set(Object.keys(config.experts || {}));
31
- for (const subtask of subtasks) {
32
- if (!knownExperts.has(subtask.expert_tag)) {
33
- errors.push(`Subtask "${subtask.id}" references unknown expert_tag "${subtask.expert_tag}". Known experts: ${Array.from(knownExperts).join(", ")}`);
34
- }
35
- }
36
- return errors;
37
- }
38
- export function hasCycle(subtasks) {
39
- const edges = new Map();
40
- for (const s of subtasks) {
41
- edges.set(s.id, s.depends_on || []);
42
- }
43
- const visited = new Set();
44
- const inStack = new Set();
45
- function dfs(node) {
46
- if (inStack.has(node))
47
- return true;
48
- if (visited.has(node))
49
- return false;
50
- visited.add(node);
51
- inStack.add(node);
52
- const deps = edges.get(node) || [];
53
- for (const dep of deps) {
54
- if (edges.has(dep) && dfs(dep))
55
- return true;
56
- }
57
- inStack.delete(node);
58
- return false;
59
- }
60
- const errors = [];
61
- for (const s of subtasks) {
62
- visited.clear();
63
- inStack.clear();
64
- if (dfs(s.id)) {
65
- errors.push(`Cycle detected involving subtask "${s.id}"`);
66
- break;
67
- }
68
- }
69
- return errors;
70
- }
71
- export function hasAllowedFilesOverlap(subtasks) {
72
- const errors = [];
73
- const writeFiles = new Map();
74
- for (const s of subtasks) {
75
- for (const f of s.allowed_files || []) {
76
- if (!writeFiles.has(f)) {
77
- writeFiles.set(f, []);
78
- }
79
- writeFiles.get(f).push(s.id);
80
- }
81
- }
82
- for (const [file, writers] of writeFiles) {
83
- if (writers.length > 1) {
84
- const noDep = writers.filter((w) => {
85
- const sub = subtasks.find((s) => s.id === w);
86
- return sub && !sub.depends_on?.some((d) => writers.includes(d));
87
- });
88
- if (noDep.length > 1) {
89
- errors.push(`Write-write overlap on "${file}" between subtasks: ${writers.join(", ")} without dependency chain`);
90
- }
91
- }
92
- }
93
- return errors;
94
- }
95
- export function deleteReadOverlap(subtasks) {
96
- const results = [];
97
- for (let i = 0; i < subtasks.length; i++) {
98
- for (let j = 0; j < subtasks.length; j++) {
99
- if (i === j)
100
- continue;
101
- const writer = subtasks[i];
102
- const reader = subtasks[j];
103
- if (!writer.allowed_files || !reader.read_only_files)
104
- continue;
105
- const readFiles = new Set(reader.read_only_files);
106
- const writeFiles = writer.allowed_files;
107
- const overlapping = writeFiles.filter((f) => {
108
- for (const rf of readFiles) {
109
- if (f.startsWith(rf) || rf.startsWith(f))
110
- return true;
111
- }
112
- return false;
113
- });
114
- if (overlapping.length > 0 && !reader.depends_on?.includes(writer.id)) {
115
- const canAutoFix = !reader.depends_on || reader.depends_on.length === 0;
116
- if (canAutoFix) {
117
- results.push({
118
- subtaskId: reader.id,
119
- dependsOn: writer.id,
120
- canAutoFix: true,
121
- });
122
- }
123
- else {
124
- results.push({
125
- subtaskId: reader.id,
126
- dependsOn: writer.id,
127
- canAutoFix: false,
128
- error: `Read-after-write overlap between "${writer.id}" (writer) and "${reader.id}" (reader) on "${overlapping.join(", ")}" — add depends_on: [${writer.id}]`,
129
- });
130
- }
131
- }
132
- }
133
- }
134
- return results;
135
- }
136
- export function applyAutoFixes(plan, autoFixes) {
137
- const fixed = { ...plan, subtasks: plan.subtasks.map((s) => ({ ...s })) };
138
- for (const fix of autoFixes) {
139
- const sub = fixed.subtasks.find((s) => s.id === fix.subtaskId);
140
- if (!sub)
141
- continue;
142
- const dependsOnMatch = fix.fix.match(/depends_on:\s*(\S+)/);
143
- if (dependsOnMatch) {
144
- const dep = dependsOnMatch[1];
145
- if (!sub.depends_on)
146
- sub.depends_on = [];
147
- if (!sub.depends_on.includes(dep)) {
148
- sub.depends_on.push(dep);
149
- }
150
- }
151
- }
152
- return fixed;
153
- }