micro-models-agent 2.11.0 → 2.12.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 (347) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/assets/js/tabs.js +22 -3
  3. package/dist/cli/cache-line.js +1 -0
  4. package/dist/cli/command-suggest.js +5 -4
  5. package/dist/cli/commands.js +231 -122
  6. package/dist/cli/completer.js +132 -31
  7. package/dist/cli/index.js +1 -0
  8. package/dist/cli/json-payload.js +4 -4
  9. package/dist/cli/main.js +166 -61
  10. package/dist/cli/onboarding-commands.js +126 -0
  11. package/dist/cli/plugin-commands.js +2 -1
  12. package/dist/cli/relaunch.js +16 -13
  13. package/dist/cli/repl-commands-lsp.js +75 -0
  14. package/dist/cli/repl-commands-skills.js +105 -0
  15. package/dist/cli/repl-commands.js +589 -266
  16. package/dist/cli/repl.js +436 -177
  17. package/dist/cli/run-result.js +5 -6
  18. package/dist/cli/security-commands.js +37 -28
  19. package/dist/cli/setup-order.js +4 -55
  20. package/dist/cli/setup-prompt.js +3 -2
  21. package/dist/cli/setup.js +27 -107
  22. package/dist/cli/web-command.js +98 -0
  23. package/dist/config/budget.js +5 -5
  24. package/dist/config/config.js +103 -96
  25. package/dist/config/defaults.js +38 -8
  26. package/dist/config/domains.js +24 -27
  27. package/dist/config/env.js +46 -0
  28. package/dist/config/experts.js +2 -0
  29. package/dist/config/index.js +1 -0
  30. package/dist/config/paths.js +56 -0
  31. package/dist/config/redact.js +44 -0
  32. package/dist/config/registry-extra.js +334 -0
  33. package/dist/config/registry.js +584 -0
  34. package/dist/config/security.js +35 -36
  35. package/dist/config/timeouts.js +74 -0
  36. package/dist/config/utils.js +16 -15
  37. package/dist/core/agent/artifact-progress.js +59 -0
  38. package/dist/core/agent/artifact-resolve.js +79 -0
  39. package/dist/core/agent/audit-gate.js +7 -3
  40. package/dist/core/agent/compaction.js +2 -2
  41. package/dist/core/agent/constants.js +33 -4
  42. package/dist/core/agent/context-log.js +454 -0
  43. package/dist/core/agent/context-renderer.js +5 -0
  44. package/dist/core/agent/critic-gate.js +132 -0
  45. package/dist/core/agent/hallucination-gate.js +148 -38
  46. package/dist/core/agent/loop-state.js +15 -0
  47. package/dist/core/agent/prefix-monitor.js +1 -1
  48. package/dist/core/agent/reasoning-resolver.js +10 -1
  49. package/dist/core/agent/token-tracker.js +7 -4
  50. package/dist/core/agent/tool-batch.js +93 -18
  51. package/dist/core/agent/tool-images.js +18 -0
  52. package/dist/core/agent/tool-output.js +65 -11
  53. package/dist/core/agent/tool-pairing.js +60 -0
  54. package/dist/core/agent-moe.js +25 -24
  55. package/dist/core/agent.js +429 -108
  56. package/dist/core/bootstrap/agent-phase.js +66 -0
  57. package/dist/core/bootstrap/config-phase.js +86 -0
  58. package/dist/core/bootstrap/modules-phase.js +269 -0
  59. package/dist/core/bootstrap/provider-phase.js +183 -0
  60. package/dist/core/bootstrap/session-phase.js +204 -0
  61. package/dist/core/bootstrap/state.js +94 -0
  62. package/dist/core/bootstrap/workspace-phase.js +115 -0
  63. package/dist/core/bootstrap.js +92 -660
  64. package/dist/core/crash-handler.js +17 -7
  65. package/dist/core/environment.js +37 -12
  66. package/dist/core/host-plan.js +43 -0
  67. package/dist/core/host.js +23 -0
  68. package/dist/core/index.js +1 -0
  69. package/dist/core/prompt-builder.js +17 -1
  70. package/dist/core/prompt-overflow.js +42 -43
  71. package/dist/core/session-logger.js +122 -30
  72. package/dist/core/system-prompt.js +117 -0
  73. package/dist/core/version.js +6 -4
  74. package/dist/core/web-host.js +34 -0
  75. package/dist/core/workspace.js +19 -15
  76. package/dist/i18n/index.js +37 -3
  77. package/dist/index.js +7 -3
  78. package/dist/llm/image-utils.js +55 -64
  79. package/dist/llm/llm-errors.js +9 -0
  80. package/dist/llm/model-loader.js +10 -32
  81. package/dist/llm/models-dev.js +121 -0
  82. package/dist/llm/openai-compat.js +146 -73
  83. package/dist/llm/orchestrator.js +33 -11
  84. package/dist/llm/provider-budget.js +2 -2
  85. package/dist/llm/provider-context.js +233 -0
  86. package/dist/llm/provider.js +1 -1
  87. package/dist/llm/response.js +5 -0
  88. package/dist/llm/token-counter.js +27 -11
  89. package/dist/logger/app-logger.js +48 -22
  90. package/dist/logger/file-log.js +28 -19
  91. package/dist/logger/index.js +1 -0
  92. package/dist/main.js +273 -47
  93. package/dist/migration/backup.js +14 -3
  94. package/dist/migration/detect.js +12 -3
  95. package/dist/migration/index.js +1 -0
  96. package/dist/modules/artifacts/store.js +9 -8
  97. package/dist/modules/browser/actions.js +6 -0
  98. package/dist/modules/browser/bridge-client.js +14 -12
  99. package/dist/modules/browser/bridge-path.js +4 -4
  100. package/dist/modules/browser/console-buffer.js +37 -0
  101. package/dist/modules/browser/cookie-store.js +5 -0
  102. package/dist/modules/browser/driver-types.js +1 -0
  103. package/dist/modules/browser/driver.js +19 -11
  104. package/dist/modules/browser/index.js +3 -1
  105. package/dist/modules/browser/module.js +2 -1
  106. package/dist/modules/browser/session.js +10 -30
  107. package/dist/modules/browser/snapshot.js +19 -13
  108. package/dist/modules/browser/types.js +1 -0
  109. package/dist/modules/certification/cli.js +28 -20
  110. package/dist/modules/certification/fact-checker.js +9 -1
  111. package/dist/modules/certification/loader.js +9 -2
  112. package/dist/modules/certification/manifest.js +83 -21
  113. package/dist/modules/certification/runner.js +23 -13
  114. package/dist/modules/certification/scenarios.js +20 -20
  115. package/dist/modules/certification/syntax-scenarios.js +1 -2
  116. package/dist/modules/context/chunk-query.js +11 -9
  117. package/dist/modules/context/fact-extractor.js +37 -39
  118. package/dist/modules/context/index.js +1 -0
  119. package/dist/modules/context/manager.js +106 -80
  120. package/dist/modules/critic/client.js +176 -0
  121. package/dist/modules/critic/evidence.js +81 -0
  122. package/dist/modules/critic/provider.js +45 -0
  123. package/dist/modules/critic/tools.js +78 -0
  124. package/dist/modules/critic/types.js +1 -0
  125. package/dist/modules/execution/audit-runners.js +28 -34
  126. package/dist/modules/execution/auditor.js +53 -49
  127. package/dist/modules/execution/execution-plugin.js +142 -102
  128. package/dist/modules/execution/index.js +1 -1
  129. package/dist/modules/execution/input-from.js +37 -13
  130. package/dist/modules/execution/module.js +368 -191
  131. package/dist/modules/execution/moe-executor.js +53 -26
  132. package/dist/modules/execution/plan-actions.js +320 -202
  133. package/dist/modules/execution/plan-checks.js +35 -0
  134. package/dist/modules/execution/plan-coverage.js +36 -33
  135. package/dist/modules/execution/plan-steps.js +11 -3
  136. package/dist/modules/execution/plan-store.js +83 -17
  137. package/dist/modules/execution/plan-tool.js +89 -35
  138. package/dist/modules/execution/plan-validator.js +30 -10
  139. package/dist/modules/execution/planner.js +22 -13
  140. package/dist/modules/execution/stuck-detector.js +196 -93
  141. package/dist/modules/execution/tracker.js +22 -8
  142. package/dist/modules/execution/verifier.js +69 -48
  143. package/dist/modules/execution/windows-commands.js +12 -12
  144. package/dist/modules/hallucination/artifact-claim.js +93 -0
  145. package/dist/modules/hallucination/confidence.js +13 -11
  146. package/dist/modules/hallucination/consistency.js +10 -2
  147. package/dist/modules/hallucination/detector.js +10 -5
  148. package/dist/modules/hallucination/factual.js +43 -26
  149. package/dist/modules/hallucination/index.js +1 -0
  150. package/dist/modules/hallucination/js-identifiers.js +44 -45
  151. package/dist/modules/hallucination/llm-judge.js +17 -12
  152. package/dist/modules/hallucination/success-claim.js +59 -0
  153. package/dist/modules/indexer/cache.js +52 -4
  154. package/dist/modules/indexer/index.js +1 -0
  155. package/dist/modules/indexer/map-command.js +2 -4
  156. package/dist/modules/indexer/map-select.js +16 -16
  157. package/dist/modules/indexer/module.js +47 -12
  158. package/dist/modules/indexer/project-profile.js +8 -7
  159. package/dist/modules/indexer/symbols.js +15 -15
  160. package/dist/modules/indexer/walker.js +159 -29
  161. package/dist/modules/lsp/check-tool.js +7 -5
  162. package/dist/modules/lsp/client.js +39 -39
  163. package/dist/modules/lsp/command.js +17 -24
  164. package/dist/modules/lsp/config.js +12 -11
  165. package/dist/modules/lsp/index.js +1 -0
  166. package/dist/modules/lsp/module.js +48 -46
  167. package/dist/modules/lsp/probe.js +13 -14
  168. package/dist/modules/lsp/project-root.js +14 -17
  169. package/dist/modules/lsp/startup-check.js +18 -16
  170. package/dist/modules/mcp/client.js +59 -62
  171. package/dist/modules/mcp/index.js +1 -0
  172. package/dist/modules/mcp/module.js +66 -37
  173. package/dist/modules/mcp/registry.js +5 -0
  174. package/dist/modules/memory/index.js +1 -0
  175. package/dist/modules/memory/module.js +12 -8
  176. package/dist/modules/memory/search.js +4 -1
  177. package/dist/modules/memory/store.js +40 -2
  178. package/dist/modules/onboarding/init.js +47 -0
  179. package/dist/modules/onboarding/meet.js +30 -0
  180. package/dist/modules/onboarding/mode.js +30 -0
  181. package/dist/modules/onboarding/prompts.js +43 -0
  182. package/dist/modules/pipelines/engine.js +10 -0
  183. package/dist/modules/pipelines/index.js +1 -0
  184. package/dist/modules/pipelines/parser.js +4 -1
  185. package/dist/modules/pipelines/template.js +4 -0
  186. package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
  187. package/dist/modules/plugins/builtin/notify.js +2 -0
  188. package/dist/modules/plugins/index.js +1 -0
  189. package/dist/modules/plugins/loader.js +18 -3
  190. package/dist/modules/plugins/manager.js +37 -17
  191. package/dist/modules/pricing/index.js +10 -11
  192. package/dist/modules/pricing/prices.js +12 -12
  193. package/dist/modules/processes/index.js +1 -0
  194. package/dist/modules/processes/kill-tree.js +27 -31
  195. package/dist/modules/processes/registry.js +44 -25
  196. package/dist/modules/processes/runner.js +4 -4
  197. package/dist/modules/providers/capabilities.js +28 -0
  198. package/dist/modules/providers/create.js +3 -4
  199. package/dist/modules/providers/factory.js +7 -7
  200. package/dist/modules/providers/fallback.js +12 -12
  201. package/dist/modules/providers/health.js +4 -4
  202. package/dist/modules/providers/manager.js +50 -19
  203. package/dist/modules/providers/naming.js +15 -0
  204. package/dist/modules/providers/preflight.js +71 -0
  205. package/dist/modules/providers/presets.js +39 -8
  206. package/dist/modules/providers/registry.js +7 -2
  207. package/dist/modules/reasoning/policy.js +8 -6
  208. package/dist/modules/reasoning/probe.js +30 -25
  209. package/dist/modules/security/audit-log.js +19 -36
  210. package/dist/modules/security/audit-notifier.js +35 -75
  211. package/dist/modules/security/command-validator.js +99 -51
  212. package/dist/modules/security/content-scanner.js +8 -8
  213. package/dist/modules/security/data-sanitizer.js +9 -16
  214. package/dist/modules/security/encryption.js +32 -52
  215. package/dist/modules/security/index.js +2 -1
  216. package/dist/modules/security/network-validator.js +120 -43
  217. package/dist/modules/security/path-validator.js +61 -46
  218. package/dist/modules/security/protected-paths.js +73 -0
  219. package/dist/modules/security/rate-limiter.js +17 -49
  220. package/dist/modules/security/security-policies.js +25 -57
  221. package/dist/modules/security/session-encryption.js +32 -71
  222. package/dist/modules/security/session-isolation.js +15 -28
  223. package/dist/modules/session/auto-name.js +63 -0
  224. package/dist/modules/session/export.js +98 -0
  225. package/dist/modules/session/index.js +1 -0
  226. package/dist/modules/session/manager.js +79 -28
  227. package/dist/modules/session/module.js +4 -3
  228. package/dist/modules/session/store.js +57 -20
  229. package/dist/modules/setup/answers.js +35 -0
  230. package/dist/modules/setup/apply.js +79 -0
  231. package/dist/modules/setup/discovery.js +72 -0
  232. package/dist/modules/skills/index.js +1 -0
  233. package/dist/modules/skills/loader.js +9 -1
  234. package/dist/modules/skills/module.js +11 -0
  235. package/dist/modules/updater/changelog-reader.js +15 -16
  236. package/dist/modules/updater/checker.js +9 -4
  237. package/dist/modules/updater/dev-detect.js +7 -6
  238. package/dist/modules/updater/index.js +1 -0
  239. package/dist/modules/updater/module.js +17 -15
  240. package/dist/modules/user-profile/compressor.js +2 -0
  241. package/dist/modules/user-profile/index.js +1 -0
  242. package/dist/modules/user-profile/profile.js +10 -1
  243. package/dist/modules/webui/answer-api.js +29 -0
  244. package/dist/modules/webui/assets.js +50 -0
  245. package/dist/modules/webui/body.js +21 -0
  246. package/dist/modules/webui/commands-settings.js +209 -0
  247. package/dist/modules/webui/commands-shared.js +60 -0
  248. package/dist/modules/webui/commands.js +174 -0
  249. package/dist/modules/webui/config-api.js +134 -0
  250. package/dist/modules/webui/file-api.js +202 -0
  251. package/dist/modules/webui/forward.js +159 -0
  252. package/dist/modules/webui/history-api.js +112 -0
  253. package/dist/modules/webui/host-state.js +60 -0
  254. package/dist/modules/webui/i18n-api.js +9 -0
  255. package/dist/modules/webui/limits.js +6 -0
  256. package/dist/modules/webui/log-forward.js +20 -0
  257. package/dist/modules/webui/memory-commands.js +64 -0
  258. package/dist/modules/webui/model-cache.js +18 -0
  259. package/dist/modules/webui/module.js +148 -0
  260. package/dist/modules/webui/persist.js +24 -0
  261. package/dist/modules/webui/prompt-io.js +97 -0
  262. package/dist/modules/webui/providers-api.js +337 -0
  263. package/dist/modules/webui/providers-view.js +56 -0
  264. package/dist/modules/webui/reasoning-view.js +15 -0
  265. package/dist/modules/webui/run-agent.js +81 -0
  266. package/dist/modules/webui/server.js +284 -0
  267. package/dist/modules/webui/sessions-api.js +178 -0
  268. package/dist/modules/webui/settings-api.js +561 -0
  269. package/dist/modules/webui/setup-api.js +201 -0
  270. package/dist/modules/webui/state.js +74 -0
  271. package/dist/modules/webui/util.js +261 -0
  272. package/dist/output/bus.js +7 -2
  273. package/dist/output/channel.js +80 -28
  274. package/dist/output/format.js +2 -1
  275. package/dist/output/index.js +1 -0
  276. package/dist/output/json-sink.js +5 -3
  277. package/dist/output/machine.js +4 -3
  278. package/dist/output/session-sink.js +4 -4
  279. package/dist/tools/approve.js +4 -0
  280. package/dist/tools/attach-image.js +5 -4
  281. package/dist/tools/auto-fixer.js +26 -44
  282. package/dist/tools/auto-tags.js +83 -0
  283. package/dist/tools/bash.js +193 -116
  284. package/dist/tools/browser.js +76 -31
  285. package/dist/tools/chunk-query.js +14 -6
  286. package/dist/tools/create-dir.js +9 -5
  287. package/dist/tools/delete-file.js +9 -5
  288. package/dist/tools/download-file.js +32 -7
  289. package/dist/tools/edit-file.js +52 -14
  290. package/dist/tools/enable-tools.js +6 -6
  291. package/dist/tools/executor.js +132 -27
  292. package/dist/tools/file-info.js +3 -1
  293. package/dist/tools/filter-tools.js +3 -3
  294. package/dist/tools/glob-tool.js +4 -1
  295. package/dist/tools/grep-tool.js +13 -10
  296. package/dist/tools/hidden-tools-block.js +6 -6
  297. package/dist/tools/index.js +16 -8
  298. package/dist/tools/list-dir.js +4 -2
  299. package/dist/tools/load-skill.js +7 -0
  300. package/dist/tools/mcp-call.js +15 -4
  301. package/dist/tools/meet-done.js +14 -0
  302. package/dist/tools/move-file.js +14 -7
  303. package/dist/tools/path-utils.js +29 -29
  304. package/dist/tools/pipeline-run.js +29 -7
  305. package/dist/tools/preview.js +1 -1
  306. package/dist/tools/process-ids.js +31 -0
  307. package/dist/tools/process-kill.js +9 -16
  308. package/dist/tools/process-list.js +1 -0
  309. package/dist/tools/process-log.js +11 -16
  310. package/dist/tools/process-wait.js +63 -0
  311. package/dist/tools/prompt-io.js +4 -3
  312. package/dist/tools/question.js +172 -25
  313. package/dist/tools/read-file.js +55 -34
  314. package/dist/tools/recall.js +14 -8
  315. package/dist/tools/registry.js +6 -5
  316. package/dist/tools/remember.js +8 -4
  317. package/dist/tools/scope-request.js +11 -6
  318. package/dist/tools/search-history.js +5 -2
  319. package/dist/tools/session-info.js +2 -0
  320. package/dist/tools/set-thinking.js +8 -4
  321. package/dist/tools/subagent.js +59 -39
  322. package/dist/tools/syntax-validator.js +292 -90
  323. package/dist/tools/user-input.js +22 -18
  324. package/dist/tools/web-browse.js +9 -3
  325. package/dist/tools/web-fetch.js +12 -23
  326. package/dist/tools/web-search.js +7 -5
  327. package/dist/tools/write-file.js +45 -15
  328. package/dist/ui/action-log.js +13 -0
  329. package/dist/ui/box.js +4 -3
  330. package/dist/ui/colors.js +2 -0
  331. package/dist/ui/diff.js +6 -5
  332. package/dist/ui/index.js +1 -0
  333. package/dist/ui/line-editor.js +230 -122
  334. package/dist/ui/line-math.js +10 -2
  335. package/dist/ui/md-formatter.js +57 -37
  336. package/dist/ui/output.js +8 -3
  337. package/dist/ui/plan-view.js +21 -10
  338. package/dist/ui/renderer.js +393 -67
  339. package/dist/ui/spinner.js +16 -5
  340. package/dist/ui/table.js +15 -17
  341. package/dist/ui/theme.js +143 -0
  342. package/dist/utils/child-run.js +20 -0
  343. package/dist/utils/error.js +1 -1
  344. package/dist/utils/index.js +2 -0
  345. package/dist/utils/retry.js +2 -2
  346. package/dist/utils/truncate.js +1 -1
  347. package/package.json +1 -1
@@ -1,8 +1,16 @@
1
1
  import { t } from "../../i18n/index";
2
2
  import { PlanCreator, getPlanProgress } from "./planner";
3
+ import { parseCheck } from "./plan-checks";
3
4
  import { stepTypecheckGate } from "./execution-plugin";
4
- import { switchStepToDelete } from "./plan-steps";
5
+ import { switchStepToCreate, switchStepToDelete } from "./plan-steps";
5
6
  import { checkPlanCoverage } from "./plan-coverage";
7
+ /**
8
+ * Статусы, которые `plan update` вправе ставить. Остальное (произвольный текст
9
+ * модели, `pending`/`in_progress`) реджектится: неизвестный статус не закроет
10
+ * `isComplete()`, план крутился бы вечно, а мусор персистился бы в
11
+ * `.mma/plans/active.json`.
12
+ */
13
+ const STEP_UPDATE_STATUSES = ["done", "failed", "skipped"];
6
14
  function actionList(deps) {
7
15
  const metas = deps.store.listAll();
8
16
  if (metas.length === 0) {
@@ -32,9 +40,8 @@ function actionSwitch(deps, _ctx, args) {
32
40
  output: t("plan.not_found", { id: planId }),
33
41
  };
34
42
  }
35
- // Switching to the plan that is already active must be a no-op, not
36
- // a duplicate (observed: switch on the active plan preserved the
37
- // active.json and re-set the same plan, leaving two copies).
43
+ // Переключение на уже активный план — no-op, не дубль (было: switch на
44
+ // активном сохранял active.json и переставлял тот же план, плодя копии).
38
45
  if (found.status === "active") {
39
46
  return {
40
47
  success: true,
@@ -53,9 +60,9 @@ function actionSwitch(deps, _ctx, args) {
53
60
  deps.store.removeArchived(found.plan.id);
54
61
  }
55
62
  deps.setPlan(found.plan);
56
- // Display from the tracker (setPlan syncs the pointer past any
57
- // done/skipped steps) so "current: step N" points at the first
58
- // pending step of the switched-to plan, not always step 1.
63
+ // Display из трекера (setPlan подтягивает указатель мимо done/skipped),
64
+ // чтобы "current: step N" указывал на первый пендинг шаг переключённого
65
+ // плана, а не вечно на шаг 1.
59
66
  const display = deps.trackerRef.current?.toPromptBlock();
60
67
  return {
61
68
  success: true,
@@ -76,7 +83,11 @@ function actionRePlan(deps, _ctx, args) {
76
83
  }
77
84
  const tracker = deps.trackerRef.current;
78
85
  const oldPlan = tracker.getPlan();
79
- const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined);
86
+ const checked = deps.parseChecks(args, newSteps);
87
+ if (checked.error) {
88
+ return { success: false, output: checked.error };
89
+ }
90
+ const replanned = PlanCreator.replan(oldPlan, newSteps, args.title ? String(args.title) : undefined, checked.checks ?? []);
80
91
  const keptCount = replanned.steps.length - newSteps.length;
81
92
  deps.setPlan(replanned);
82
93
  const display = deps.trackerRef.current?.toPromptBlock();
@@ -99,13 +110,33 @@ function actionCreate(deps, ctx, args) {
99
110
  if (parsed.error) {
100
111
  return { success: false, output: parsed.error };
101
112
  }
102
- // Never silently discard an in-progress plan (observed:
103
- // ses_msvuao0h — right after compaction the model called
104
- // `plan create` again, dumping the 1/6 plan into drafts and
105
- // starting from scratch). An active plan WITH progress blocks
106
- // creation until the model consciously aborts it. A fresh plan
107
- // (all steps pending) is safe to replace — the model may have
108
- // just changed its mind about the approach.
113
+ const checked = deps.parseChecks(args, steps);
114
+ if (checked.error) {
115
+ return { success: false, output: checked.error };
116
+ }
117
+ // Покрытие валидируем ДО любой мутации состояния: заблокированный create не
118
+ // оставит полусозданный активный план (следующий create споткнулся бы об
119
+ // active_in_progress на плане, успех которого модель не видела).
120
+ const taskText = deps.getTaskText(ctx);
121
+ if (taskText) {
122
+ const { missing } = checkPlanCoverage(taskText, steps);
123
+ if (missing.length > 0) {
124
+ // Жёсткий блок (не варнинг): 9B-модель читает "success:true + ⚠️" как
125
+ // успех и 5+ итераций работает по мусору, пока deliverable-gate не
126
+ // остановит. Стопорим на создании со списком файлов.
127
+ return {
128
+ success: false,
129
+ output: t("plan.coverage_blocked", {
130
+ missing: missing.join(", "),
131
+ }),
132
+ };
133
+ }
134
+ }
135
+ // Активный план с прогрессом молча не выбрасываем (было: ses_msvuao0h —
136
+ // сразу после компакции модель снова звала `plan create`, сливая план 1/6 в
137
+ // черновики и стартуя с нуля). Активный план С прогрессом блокирует создание,
138
+ // пока модель осознанно его не абортнит. Свежий план (все шаги pending)
139
+ // заменять безопасно — модель могла передумать насчёт подхода.
109
140
  const existing = deps.trackerRef.current;
110
141
  if (existing) {
111
142
  const activePlan = existing.getPlan();
@@ -124,48 +155,34 @@ function actionCreate(deps, ctx, args) {
124
155
  }
125
156
  }
126
157
  deps.preserveActive();
127
- const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
158
+ const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? [], checked.checks ?? []);
128
159
  deps.setPlan(plan);
129
160
  const display = PlanCreator.toPromptBlock(plan, 0);
130
161
  let output = t("plan.created", {
131
162
  title,
132
163
  steps: String(steps.length),
133
164
  });
134
- // The model sometimes passes an id expecting the plan to be
135
- // created under it (observed: id "plan_error_check_e07pb2" was
136
- // ignored, a new id appeared). Be explicit so it doesn't look
137
- // for a plan by the wrong id afterwards.
165
+ // Модель иногда передаёт id, ожидая план под ним (было: id
166
+ // "plan_error_check_e07pb2" проигнорирован, появился новый id). Объясняем
167
+ // явно, чтобы не искала план по неверному id.
138
168
  if (args.id) {
139
169
  output += `\n${t("plan.id_ignored")}`;
140
170
  }
141
171
  if (existing) {
142
172
  output += `\n${t("plan.existing_fresh")}`;
143
173
  }
144
- let displayOut = display;
145
- const taskText = deps.getTaskText(ctx);
146
- if (taskText) {
147
- const { missing } = checkPlanCoverage(taskText, steps);
148
- if (missing.length > 0) {
149
- const warn = t("plan.coverage_warning", {
150
- missing: missing.join(", "),
151
- });
152
- output += `\n⚠️ ${warn}`;
153
- displayOut = `${display}\n⚠️ ${warn}`;
154
- }
155
- }
156
174
  return {
157
175
  success: true,
158
176
  output,
159
- display: displayOut,
177
+ display,
160
178
  };
161
179
  }
162
180
  function actionShow(deps, _ctx, args) {
163
181
  const planId = args.id ? String(args.id) : "";
164
182
  if (planId && planId !== deps.trackerRef.current?.getPlan()?.id) {
165
- // Show a specific plan (active, draft, or archived) instead
166
- // of ignoring the id and always printing the active one
167
- // (observed: the model asked for an archived plan three
168
- // times and got the active plan back every time).
183
+ // Показываем КОНКРЕТНЫЙ план (активный, черновик, архив), а не игнорируем
184
+ // id вечным выводом активного (было: модель трижды просила архивный план
185
+ // и каждый раз получала активный).
169
186
  const found = deps.store.find(planId);
170
187
  if (!found) {
171
188
  return {
@@ -193,96 +210,34 @@ function actionShow(deps, _ctx, args) {
193
210
  function actionUpdate(deps, ctx, args) {
194
211
  // Обновление конкретного шага.
195
212
  if (args.step && deps.trackerRef.current) {
196
- return updateStep(deps, ctx, args);
213
+ return updatePlanStep(deps, ctx, args);
197
214
  }
198
- // Массовая пересборка плана новыми шагами.
215
+ // Массовая пересборка плана через update запрещена: она молча меняет
216
+ // состав/название плана (churn) и теряет статусы. Модель должна осознанно
217
+ // выбрать `re-plan` (сохраняет завершённые шаги) или `create` (новый план).
199
218
  if (Array.isArray(args.steps) && args.steps.length > 0 && !args.step) {
200
- return bulkUpdate(deps, args);
219
+ return { success: false, output: t("plan.bulk_update_rejected") };
201
220
  }
202
221
  // Ни шага, ни шагов — тот же фолбэк, что был в хвосте монолита.
203
222
  return noActiveOrUnknown(deps, args, "update");
204
223
  }
205
- function updateStep(deps, ctx, args) {
206
- const tracker = deps.trackerRef.current;
207
- const stepId = Number(args.step);
208
- const target = tracker.getStep(stepId);
209
- if (!target) {
210
- return { success: false, output: t("plan.step_not_found") };
211
- }
212
- const status = String(args.status || "done");
213
- // Completed plan: re-marking a done step of a finished plan is the
214
- // loop anchor (observed: 3× "plan update step=4 done" on a 4/4 plan
215
- // while a NEW task waited). Archive + clear so the model can create a
216
- // fresh plan for the new task instead of spinning.
217
- if (tracker.isComplete()) {
218
- const plan = tracker.getPlan();
219
- deps.store.archivePlan(plan);
220
- deps.recordCompleted(plan); // final audit still verifies it
221
- deps.trackerRef.current = null;
222
- const p = getPlanProgress(plan);
223
- return {
224
- success: true,
225
- output: t("plan.completed_archived", {
226
- id: plan.id,
227
- done: String(p.settled),
228
- total: String(plan.steps.length),
229
- }),
230
- };
231
- }
232
- // Idempotency: re-marking a done step as done usually means the
233
- // plan was already auto-advanced (all step files exist) and the
234
- // model is confirming its own work. Return SUCCESS so the loop
235
- // isn't confused — a small model misreads success:false as a
236
- // failed action and retries/spins. Genuine spinning (repeated
237
- // identical calls without new work) is still caught by the
238
- // stuck-detector's consecutive-identical-call check.
239
- if (status === "done" && target.status === "done") {
240
- const progress = tracker.getProgressString();
241
- const display = tracker.toPromptBlock();
242
- return {
243
- success: true,
244
- output: `${t("plan.step_already_done", {
245
- step: String(stepId),
246
- })}\n${progress}`,
247
- display,
248
- };
249
- }
250
- // Order enforcement: only the CURRENT step may be marked done.
251
- // Prevents jumping ahead (step 5 marked done before steps 1-4,
252
- // observed in the post-mortem session) and the follow-on audit
253
- // failures ("5/5 steps done, 5 files missing").
254
- if (status === "done") {
255
- const blockers = tracker
256
- .getPlan()
257
- .steps.filter((s) => s.id < stepId && s.status !== "done" && s.status !== "skipped");
258
- if (blockers.length > 0) {
259
- return {
260
- success: false,
261
- output: t("plan.order_blocked", {
262
- step: String(stepId),
263
- first: String(blockers[0].id),
264
- desc: blockers[0].description,
265
- }),
266
- };
267
- }
268
- }
269
- // Deliverable gate: marking a step done must match its declared
270
- // intent. "create" (default) → named files must exist; "delete"
271
- // → named files must be GONE. Intent is model-declared (kind),
272
- // never keyword-inferred (rule #10).
273
- //
274
- // Auto-switch: when kind is "create" but ALL named files are gone,
275
- // check if the agent actually deleted them (via delete_file/bash rm).
276
- // Only then switch to "delete" — files that were never created
277
- // (create-kind steps) remain blocked so the agent must create them.
278
- if (status === "done" && target.kind !== "delete") {
224
+ /**
225
+ * Выводит kind create/delete шага из СДЕЛАННОГО агентом (не заявленного).
226
+ * Прямо: create-шаг, чьи файлы пропали И трекнуты удалёнными → флип в delete.
227
+ * Обратно: delete-шаг, чьи файлы ни существуют, ни удалялись → флип в create
228
+ * и блок (пустой pass ничего бы не сертифицировал; файлы, снесённые юзером
229
+ * снаружи — известный tradeoff: скипай шаг). Обратный случай возвращает
230
+ * блокирующий ToolResult, иначе null.
231
+ */
232
+ function autoResolveStepKind(deps, tracker, target, stepId) {
233
+ if (target.kind !== "delete") {
279
234
  const missing = deps.missingStepDeliverables(target);
280
235
  if (missing.length > 0) {
281
236
  const existing = deps.stillExistingDeliverables(target);
282
237
  const allGone = deps.hasStepDeliverables(target) && existing.length === 0;
283
238
  if (allGone) {
284
- // Confirm the agent actually deleted these files (not just
285
- // never created them) before auto-switching.
239
+ // Подтверждаем, что агент реально удалил эти файлы (а не просто
240
+ // никогда не создавал) перед авто-переключением.
286
241
  const wasDeleted = deps.wasFileDeleted
287
242
  ? missing.every((f) => deps.wasFileDeleted(f))
288
243
  : false;
@@ -291,70 +246,266 @@ function updateStep(deps, ctx, args) {
291
246
  }
292
247
  }
293
248
  }
249
+ return null;
294
250
  }
295
- if (status === "done") {
296
- if (target.kind === "delete") {
297
- const leftovers = deps.stillExistingDeliverables(target);
298
- if (leftovers.length > 0) {
299
- return {
300
- success: false,
301
- output: t("plan.deliverables_remain", {
302
- step: String(stepId),
303
- files: leftovers.join(", "),
304
- }),
305
- };
306
- }
251
+ const leftovers = deps.stillExistingDeliverables(target);
252
+ if (leftovers.length === 0 && deps.wasFileDeleted) {
253
+ const tokens = deps.missingStepDeliverables(target);
254
+ if (tokens.length > 0 && tokens.every((f) => !deps.wasFileDeleted(f))) {
255
+ switchStepToCreate(tracker.getPlan(), target, (p) => deps.store.saveActive(p));
256
+ return {
257
+ success: false,
258
+ output: t("plan.deliverables_missing", {
259
+ step: String(stepId),
260
+ files: tokens.join(", "),
261
+ }),
262
+ };
307
263
  }
308
- else {
309
- const missing = deps.missingStepDeliverables(target);
310
- if (missing.length > 0) {
311
- return {
264
+ }
265
+ return null;
266
+ }
267
+ /**
268
+ * Проверяет check-контракт шага на done. "file-exists:" верифицируется жёстко
269
+ * здесь (kind=delete инвертирует: файл обязан ОТСУТСТВОВАТЬ); "command:"/"manual:"
270
+ * self-certified с подтверждающей заметкой. Шаги вовсе без check — только из
271
+ * до-чековых планов (back-compat), заметки не дают.
272
+ */
273
+ async function verifyCheckContract(deps, target, stepId) {
274
+ if (!target.check)
275
+ return { note: "", blocked: null };
276
+ const parsed = parseCheck(target.check);
277
+ if (parsed?.kind === "file") {
278
+ const res = await deps.verifier.checkFileExists(parsed.target);
279
+ const ok = target.kind === "delete" ? !res.passed : res.passed;
280
+ if (!ok) {
281
+ return {
282
+ note: "",
283
+ blocked: {
312
284
  success: false,
313
- output: t("plan.deliverables_missing", {
285
+ output: t("plan.check_failed", {
314
286
  step: String(stepId),
315
- files: missing.join(", "),
287
+ check: target.check,
316
288
  }),
317
- };
318
- }
289
+ },
290
+ };
319
291
  }
292
+ return { note: "", blocked: null };
320
293
  }
321
- // Typecheck gate: "done" while the last edit still fails to compile
322
- // is a self-certification the harness must refuse (observed: steps
323
- // marked done with the project typecheck failing on every write —
324
- // a "fix it later" cascade that never got fixed). The model must
325
- // either fix the errors (a clean re-edit clears the map) or mark
326
- // the step skipped.
327
- if (status === "done") {
328
- const blocked = stepTypecheckGate(deps.typecheckFailures, target.description);
329
- if (blocked) {
294
+ if (parsed) {
295
+ return { note: `\n${t("plan.check_self_certified", { check: target.check })}`, blocked: null };
296
+ }
297
+ return { note: "", blocked: null };
298
+ }
299
+ /**
300
+ * Сеттлит повторную метку на уже завершённом плане: архив + очистка, чтобы
301
+ * модель создала свежий план вместо прокрутки готового (было: 3× "plan update
302
+ * step=4 done" на плане 4/4, пока ждала НОВАЯ задача). Возвращает результат
303
+ * завершения, или null если план ещё в работе.
304
+ */
305
+ function settleCompletedPlan(deps, tracker) {
306
+ if (!tracker.isComplete())
307
+ return null;
308
+ const plan = tracker.getPlan();
309
+ deps.store.archivePlan(plan);
310
+ deps.recordCompleted(plan); // финальный аудит его всё равно проверит
311
+ deps.trackerRef.current = null;
312
+ const p = getPlanProgress(plan);
313
+ return {
314
+ success: true,
315
+ output: t("plan.completed_archived", {
316
+ id: plan.id,
317
+ done: String(p.settled),
318
+ total: String(plan.steps.length),
319
+ }),
320
+ };
321
+ }
322
+ /**
323
+ * Идемпотентная переметка: подтверждение уже-done шага — УСПЕХ (мелкая модель
324
+ * читает success:false как провал действия и ретраит/спиннится). Настоящий
325
+ * спиннинг ловит stuck-детектор.
326
+ */
327
+ function checkIdempotentDone(tracker, target, stepId, status) {
328
+ if (status !== "done" || target.status !== "done")
329
+ return null;
330
+ const progress = tracker.getProgressString();
331
+ const display = tracker.toPromptBlock();
332
+ return {
333
+ success: true,
334
+ output: `${t("plan.step_already_done", {
335
+ step: String(stepId),
336
+ })}\n${progress}`,
337
+ display,
338
+ };
339
+ }
340
+ /**
341
+ * Порядок: done маркится только ТЕКУЩИЙ шаг (защита от прыжков вперёд —
342
+ * было: step 5 done раньше шагов 1-4, затем аудит "5/5 steps done, 5 files
343
+ * missing").
344
+ */
345
+ function enforceStepOrder(tracker, stepId) {
346
+ const blockers = tracker
347
+ .getPlan()
348
+ .steps.filter((s) => s.id < stepId && s.status !== "done" && s.status !== "skipped");
349
+ if (blockers.length === 0)
350
+ return null;
351
+ return {
352
+ success: false,
353
+ output: t("plan.order_blocked", {
354
+ step: String(stepId),
355
+ first: String(blockers[0].id),
356
+ desc: blockers[0].description,
357
+ }),
358
+ };
359
+ }
360
+ /**
361
+ * Deliverable-gate: done обязан совпасть с (уже kind-резолвленным) намерением —
362
+ * "create" требует файлы на месте, "delete" — их ОТСУТСТВИЕ.
363
+ */
364
+ function checkDeliverables(deps, target, stepId) {
365
+ if (target.kind === "delete") {
366
+ const leftovers = deps.stillExistingDeliverables(target);
367
+ if (leftovers.length > 0) {
330
368
  return {
331
369
  success: false,
332
- output: t("plan.typecheck_failed", {
370
+ output: t("plan.deliverables_remain", {
333
371
  step: String(stepId),
334
- error: blocked,
372
+ files: leftovers.join(", "),
335
373
  }),
336
374
  };
337
375
  }
376
+ return null;
338
377
  }
378
+ const missing = deps.missingStepDeliverables(target);
379
+ if (missing.length > 0) {
380
+ return {
381
+ success: false,
382
+ output: t("plan.deliverables_missing", {
383
+ step: String(stepId),
384
+ files: missing.join(", "),
385
+ }),
386
+ };
387
+ }
388
+ return null;
389
+ }
390
+ /**
391
+ * Typecheck-gate: "done" при всё ещё не компилирующейся последней записи —
392
+ * самосертификация, которую харнесс обязан реджектить (было: шаги маркились
393
+ * done при падающем на каждой записи typecheck проекта). Чини или скипай.
394
+ */
395
+ function checkTypecheckGate(deps, target, stepId) {
396
+ const blocked = stepTypecheckGate(deps.typecheckFailures, target.description);
397
+ if (!blocked)
398
+ return null;
399
+ return {
400
+ success: false,
401
+ output: t("plan.typecheck_failed", {
402
+ step: String(stepId),
403
+ error: blocked,
404
+ }),
405
+ };
406
+ }
407
+ function guardKnownStatus(c) {
408
+ if (STEP_UPDATE_STATUSES.includes(c.status))
409
+ return null;
410
+ return {
411
+ success: false,
412
+ output: t("plan.bad_status", {
413
+ status: c.status,
414
+ allowed: STEP_UPDATE_STATUSES.join(", "),
415
+ }),
416
+ };
417
+ }
418
+ function guardSettledPlan(c) {
419
+ return settleCompletedPlan(c.deps, c.tracker);
420
+ }
421
+ function guardIdempotent(c) {
422
+ return checkIdempotentDone(c.tracker, c.target, c.stepId, c.status);
423
+ }
424
+ function guardOrder(c) {
425
+ if (c.status !== "done")
426
+ return null;
427
+ return enforceStepOrder(c.tracker, c.stepId);
428
+ }
429
+ async function guardKindAndCheck(c) {
430
+ if (c.status !== "done")
431
+ return null;
432
+ // Kind резолвится из поведения первым (оба направления), затем check-
433
+ // контракт проверяется против резолвленного kind: шаг, флипнутый агентом в
434
+ // delete, оценивает файловый check как delete.
435
+ const resolved = autoResolveStepKind(c.deps, c.tracker, c.target, c.stepId);
436
+ if (resolved)
437
+ return resolved;
438
+ const check = await verifyCheckContract(c.deps, c.target, c.stepId);
439
+ if (check.blocked)
440
+ return check.blocked;
441
+ c.checkNote = check.note;
442
+ return null;
443
+ }
444
+ function guardDeliverables(c) {
445
+ if (c.status !== "done")
446
+ return null;
447
+ return checkDeliverables(c.deps, c.target, c.stepId);
448
+ }
449
+ function guardTypecheck(c) {
450
+ if (c.status !== "done")
451
+ return null;
452
+ return checkTypecheckGate(c.deps, c.target, c.stepId);
453
+ }
454
+ /** Done-гарды в порядке оценки — та же цепочка, что старый инлайн-чейн. */
455
+ const STEP_GUARDS = [
456
+ guardKnownStatus,
457
+ guardSettledPlan,
458
+ guardIdempotent,
459
+ guardOrder,
460
+ guardKindAndCheck,
461
+ guardDeliverables,
462
+ guardTypecheck,
463
+ ];
464
+ /** Обновляет шаг плана через цепочку done-гардов; на последнем шаге — авто-архив. */
465
+ export async function updatePlanStep(deps, ctx, args) {
466
+ const tracker = deps.trackerRef.current;
467
+ const stepId = Number(args.step);
468
+ const target = tracker.getStep(stepId);
469
+ if (!target) {
470
+ return { success: false, output: t("plan.step_not_found") };
471
+ }
472
+ const gctx = {
473
+ deps,
474
+ tracker,
475
+ target,
476
+ stepId,
477
+ status: String(args.status || "done"),
478
+ checkNote: "",
479
+ };
480
+ for (const guard of STEP_GUARDS) {
481
+ const blocked = await guard(gctx);
482
+ if (blocked)
483
+ return blocked;
484
+ }
485
+ const status = gctx.status;
486
+ const checkNote = gctx.checkNote;
339
487
  tracker.updateStepStatus(stepId, status);
340
488
  if (args.note)
341
489
  tracker.addNote(Number(args.step), String(args.note));
342
490
  tracker.syncCurrentStep();
343
491
  deps.store.saveActive(tracker.getPlan());
344
- // A "done" step that names no file tokens has a vacuous gate — its
345
- // completion cannot be auto-verified. Inform the model so it can
346
- // run an explicit check or add file paths to the step description.
347
- const vacuousGate = status === "done" && !deps.hasStepDeliverables(target);
492
+ // Шаги без файловых токенов нормальны в некодовых задачах (research, советы,
493
+ // сравнения) — не гоним модель материализовывать файлы ради deliverables.
494
+ // Заметка срабатывает только в планах с хоть одним файловым шагом и просит
495
+ // лишь task-appropriate проверку.
496
+ const planSteps = tracker.getPlan().steps ?? [];
497
+ const planHasFiles = planSteps.some((s) => deps.hasStepDeliverables(s));
498
+ const vacuousGate = status === "done" && planHasFiles && !deps.hasStepDeliverables(target);
348
499
  const vacuousNote = vacuousGate
349
500
  ? `\n${t("plan.no_deliverables", { step: String(stepId) })}`
350
501
  : "";
351
- // If this update completed the last step → auto-archive. This breaks
352
- // the loop where a finished plan stays active and anchors the model
353
- // to "continue step N, do NOT create a new plan".
502
+ // Это обновление закрыло последний шаг → авто-архив. Рвёт петлю, где готовый
503
+ // план остаётся активным и якорит модель на "продолжай шаг N, НЕ создавай
504
+ // новый план".
354
505
  if (tracker.isComplete()) {
355
506
  const plan = tracker.getPlan();
356
507
  deps.store.archivePlan(plan);
357
- deps.recordCompleted(plan); // final audit still verifies it
508
+ deps.recordCompleted(plan); // финальный аудит его всё равно проверит
358
509
  deps.trackerRef.current = null;
359
510
  const p = getPlanProgress(plan);
360
511
  return {
@@ -363,7 +514,7 @@ function updateStep(deps, ctx, args) {
363
514
  id: plan.id,
364
515
  done: String(p.settled),
365
516
  total: String(plan.steps.length),
366
- })}${vacuousNote}`,
517
+ })}${vacuousNote}${checkNote}`,
367
518
  };
368
519
  }
369
520
  const progress = tracker.getProgressString();
@@ -371,50 +522,15 @@ function updateStep(deps, ctx, args) {
371
522
  ctx.sessionLog?.plan("step-update", `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })} | ${progress} | current: step ${tracker.getCurrentStepIndex() + 1}`);
372
523
  return {
373
524
  success: true,
374
- output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${progress}${vacuousNote}`,
375
- display,
376
- };
377
- }
378
- function bulkUpdate(deps, args) {
379
- const tracker = deps.trackerRef.current;
380
- if (!tracker) {
381
- return { success: false, output: t("plan.no_active") };
382
- }
383
- // Rebuilding a plan that already has progress silently discards
384
- // every status (ses_mt4fn58c: the model sent its whole step list
385
- // with ✅-emoji statuses expecting progress marks and got a fresh
386
- // plan back). A not-yet-started plan is safe to rebuild.
387
- const progressed = tracker.getPlan().steps.some((s) => s.status !== "pending");
388
- if (progressed) {
389
- return { success: false, output: t("plan.bulk_update_rejected") };
390
- }
391
- const title = String(args.title || tracker.getPlan().title);
392
- // Диспетчер вызывает bulkUpdate только когда steps — непустой массив, но
393
- // внутри отдельной функции сужение теряется — восстанавливаем безопасно.
394
- const steps = Array.isArray(args.steps) ? args.steps.map(String) : [];
395
- const parsed = deps.parseKinds(args, steps);
396
- if (parsed.error) {
397
- return { success: false, output: parsed.error };
398
- }
399
- const plan = PlanCreator.createPlan(title, steps, deps.baseDir, parsed.kinds ?? []);
400
- deps.setPlan(plan);
401
- const display = deps.trackerRef.current?.toPromptBlock();
402
- const output = t("plan.updated", {
403
- title,
404
- steps: String(steps.length),
405
- });
406
- return {
407
- success: true,
408
- output,
525
+ output: `${t("plan.step_status", { step: String(args.step), status: String(args.status || "done") })}\n${progress}${vacuousNote}${checkNote}`,
409
526
  display,
410
527
  };
411
528
  }
412
529
  function actionAbort(deps, _ctx, args) {
413
530
  const planId = args.id ? String(args.id) : "";
414
531
  if (planId) {
415
- // Abort a SPECIFIC plan (not the active one). Draft → archive,
416
- // active → archive + clear tracker. Archived plans are already
417
- // terminal — there is nothing to abort.
532
+ // Аборт КОНКРЕТНОГО плана (не активного). Черновик → архив, активный →
533
+ // архив + очистка трекера. Архивные уже терминальны — абортить нечего.
418
534
  const found = deps.store.find(planId);
419
535
  if (!found) {
420
536
  return {
@@ -428,7 +544,7 @@ function actionAbort(deps, _ctx, args) {
428
544
  output: t("plan.already_archived", { id: planId }),
429
545
  };
430
546
  }
431
- deps.store.archivePlan(found.plan); // clears active if it was active
547
+ deps.store.archivePlan(found.plan); // был активным — active тоже чистится
432
548
  if (found.status === "active") {
433
549
  deps.trackerRef.current = null;
434
550
  deps.clearCompleted();
@@ -440,9 +556,9 @@ function actionAbort(deps, _ctx, args) {
440
556
  }
441
557
  const tracker = deps.trackerRef.current;
442
558
  if (!tracker) {
443
- // Abort with no active plan and no id — previously returned
444
- // success:true, which made the model believe it aborted a plan
445
- // that did not exist (observed: 3× abort on an empty list).
559
+ // Аборт без активного плана и без id — раньше возвращал success:true, и
560
+ // модель верила, что абортнула несуществующий план (было: 3× abort на
561
+ // пустом списке).
446
562
  return { success: false, output: t("plan.no_active") };
447
563
  }
448
564
  deps.store.archivePlan(tracker.getPlan());
@@ -451,6 +567,7 @@ function actionAbort(deps, _ctx, args) {
451
567
  deps.clearCompleted();
452
568
  return { success: true, output: t("plan.aborted") };
453
569
  }
570
+ /** Удаляет план по id (активный чистит и трекер). */
454
571
  function actionDelete(deps, _ctx, args) {
455
572
  const planId = String(args.id || "");
456
573
  if (!planId) {
@@ -472,6 +589,7 @@ function actionDelete(deps, _ctx, args) {
472
589
  output: t("plan.deleted", { id: planId }),
473
590
  };
474
591
  }
592
+ /** Удаляет ВСЕ планы (активный, черновики, архив). */
475
593
  function actionPurge(deps) {
476
594
  const count = deps.store.purgeAll();
477
595
  deps.trackerRef.current = null;
@@ -484,10 +602,9 @@ function actionPurge(deps) {
484
602
  /** Фолбэк из хвоста монолита: readonly-план по id или "нет активного плана". */
485
603
  function noActiveOrUnknown(deps, args, action) {
486
604
  if (!deps.trackerRef.current) {
487
- // Update with an id that points at a draft/archived plan: say the
488
- // plan is read-only instead of a bare "No active plan" (observed:
489
- // 10+ iterations re-updating an archived plan and getting the same
490
- // unhelpful answer back).
605
+ // Update с id на черновик/архив: говорим, что план read-only, а не голое
606
+ // "No active plan" (было: 10+ итераций re-update архивного плана с тем же
607
+ // бесполезным ответом).
491
608
  if (args.id) {
492
609
  const found = deps.store.find(String(args.id));
493
610
  if (found && found.status !== "active") {
@@ -504,6 +621,7 @@ function noActiveOrUnknown(deps, args, action) {
504
621
  output: t("plan.unknown_action", { action }),
505
622
  };
506
623
  }
624
+ /** Словарь действий плана: имя → обработчик. */
507
625
  export const PLAN_ACTIONS = {
508
626
  list: actionList,
509
627
  switch: actionSwitch,