micro-models-agent 2.16.0 → 2.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/dist/assets/app.js +1 -314
- package/dist/assets/hljs.js +5 -1244
- package/dist/assets/index.html +11 -0
- package/dist/assets/js/api.js +1 -28
- package/dist/assets/js/core/menus.js +1 -0
- package/dist/assets/js/core/store/feed.js +1 -0
- package/dist/assets/js/core/store/index.js +1 -0
- package/dist/assets/js/core/store/session.js +1 -0
- package/dist/assets/js/core/store/settings.js +1 -0
- package/dist/assets/js/core/store/tabs.js +1 -0
- package/dist/assets/js/dialog.js +1 -105
- package/dist/assets/js/dom.js +1 -178
- package/dist/assets/js/feed/core.js +1 -0
- package/dist/assets/js/feed/history.js +1 -0
- package/dist/assets/js/feed/plan.js +1 -0
- package/dist/assets/js/feed/question.js +3 -0
- package/dist/assets/js/feed/sidebar.js +1 -0
- package/dist/assets/js/feed/stream.js +1 -0
- package/dist/assets/js/feed/turn.js +5 -0
- package/dist/assets/js/feed.js +1 -1211
- package/dist/assets/js/format.js +2 -122
- package/dist/assets/js/i18n.js +1 -46
- package/dist/assets/js/rail.js +1 -114
- package/dist/assets/js/session/cmdk.js +1 -0
- package/dist/assets/js/session/composer.js +1 -0
- package/dist/assets/js/session/sessions-menu.js +1 -0
- package/dist/assets/js/session/tabs.js +1 -0
- package/dist/assets/js/settings/controls.js +1 -0
- package/dist/assets/js/settings/editors.js +1 -0
- package/dist/assets/js/settings/form.js +1 -0
- package/dist/assets/js/settings/modal.js +1 -0
- package/dist/assets/js/settings/panels.js +1 -0
- package/dist/assets/js/settings/providers.js +1 -0
- package/dist/assets/js/settings/search.js +1 -0
- package/dist/assets/js/settings/subdialog.js +1 -0
- package/dist/assets/js/settings/write.js +1 -0
- package/dist/assets/js/settings.js +1 -1914
- package/dist/assets/js/sidebar/core.js +1 -0
- package/dist/assets/js/sidebar/lsp.js +2 -0
- package/dist/assets/js/sidebar/map.js +1 -0
- package/dist/assets/js/sidebar/state.js +1 -0
- package/dist/assets/js/sidebar/trace.js +5 -0
- package/dist/assets/js/sidebar.js +1 -705
- package/dist/assets/js/store.js +1 -82
- package/dist/assets/js/subdialog.js +1 -0
- package/dist/assets/md.js +1 -277
- package/dist/assets/setup.js +3 -816
- package/dist/assets/smd.js +29 -1665
- package/dist/assets/styles.css +1 -1592
- package/dist/certification/certifications.json +1 -494
- package/dist/i18n/en.json +1 -1851
- package/dist/i18n/ru.json +1 -1854
- package/dist/main.js +815 -55797
- package/dist/modules/browser/bridge-server.mjs +2 -252
- package/package.json +10 -5
- package/dist/assets/js/settings-controls.js +0 -452
- package/dist/assets/js/tabs.js +0 -867
- package/dist/cli/cache-line.js +0 -31
- package/dist/cli/command-suggest.js +0 -39
- package/dist/cli/commands.js +0 -781
- package/dist/cli/completer.js +0 -268
- package/dist/cli/index.js +0 -3
- package/dist/cli/json-payload.js +0 -32
- package/dist/cli/main.js +0 -346
- package/dist/cli/onboarding-commands.js +0 -126
- package/dist/cli/plugin-commands.js +0 -38
- package/dist/cli/relaunch.js +0 -40
- package/dist/cli/repl-commands-lsp.js +0 -75
- package/dist/cli/repl-commands-skills.js +0 -105
- package/dist/cli/repl-commands.js +0 -1218
- package/dist/cli/repl.js +0 -1238
- package/dist/cli/run-result.js +0 -38
- package/dist/cli/security-commands.js +0 -177
- package/dist/cli/setup-order.js +0 -6
- package/dist/cli/setup-prompt.js +0 -50
- package/dist/cli/setup.js +0 -161
- package/dist/cli/web-command.js +0 -98
- package/dist/config/budget.js +0 -48
- package/dist/config/config.js +0 -345
- package/dist/config/defaults.js +0 -197
- package/dist/config/domains.js +0 -135
- package/dist/config/env.js +0 -46
- package/dist/config/experts.js +0 -17
- package/dist/config/index.js +0 -5
- package/dist/config/paths.js +0 -56
- package/dist/config/redact.js +0 -44
- package/dist/config/registry-extra.js +0 -334
- package/dist/config/registry.js +0 -584
- package/dist/config/security.js +0 -212
- package/dist/config/timeouts.js +0 -74
- package/dist/config/types.js +0 -1
- package/dist/config/utils.js +0 -57
- package/dist/core/agent/artifact-progress.js +0 -59
- package/dist/core/agent/artifact-resolve.js +0 -79
- package/dist/core/agent/audit-gate.js +0 -53
- package/dist/core/agent/compaction.js +0 -89
- package/dist/core/agent/constants.js +0 -90
- package/dist/core/agent/context-log.js +0 -454
- package/dist/core/agent/context-renderer.js +0 -45
- package/dist/core/agent/critic-gate.js +0 -132
- package/dist/core/agent/hallucination-gate.js +0 -197
- package/dist/core/agent/loop-state.js +0 -68
- package/dist/core/agent/prefix-monitor.js +0 -101
- package/dist/core/agent/reasoning-resolver.js +0 -65
- package/dist/core/agent/token-tracker.js +0 -99
- package/dist/core/agent/tool-batch.js +0 -312
- package/dist/core/agent/tool-images.js +0 -18
- package/dist/core/agent/tool-output.js +0 -116
- package/dist/core/agent/tool-pairing.js +0 -60
- package/dist/core/agent-moe.js +0 -248
- package/dist/core/agent.js +0 -1299
- package/dist/core/bootstrap/agent-phase.js +0 -66
- package/dist/core/bootstrap/config-phase.js +0 -86
- package/dist/core/bootstrap/modules-phase.js +0 -269
- package/dist/core/bootstrap/provider-phase.js +0 -183
- package/dist/core/bootstrap/session-phase.js +0 -204
- package/dist/core/bootstrap/state.js +0 -94
- package/dist/core/bootstrap/workspace-phase.js +0 -115
- package/dist/core/bootstrap.js +0 -112
- package/dist/core/crash-handler.js +0 -62
- package/dist/core/environment.js +0 -224
- package/dist/core/host-plan.js +0 -43
- package/dist/core/host.js +0 -23
- package/dist/core/index.js +0 -3
- package/dist/core/prompt-builder.js +0 -95
- package/dist/core/prompt-overflow.js +0 -306
- package/dist/core/session-logger.js +0 -375
- package/dist/core/system-prompt.js +0 -117
- package/dist/core/types.js +0 -1
- package/dist/core/version.js +0 -28
- package/dist/core/web-host.js +0 -34
- package/dist/core/workspace.js +0 -80
- package/dist/i18n/index.js +0 -80
- package/dist/index.js +0 -30
- package/dist/llm/cache-usage.js +0 -76
- package/dist/llm/image-utils.js +0 -138
- package/dist/llm/index.js +0 -4
- package/dist/llm/llm-errors.js +0 -50
- package/dist/llm/model-loader.js +0 -86
- package/dist/llm/models-dev.js +0 -121
- package/dist/llm/openai-compat.js +0 -756
- package/dist/llm/orchestrator.js +0 -294
- package/dist/llm/provider-budget.js +0 -68
- package/dist/llm/provider-context.js +0 -233
- package/dist/llm/provider.js +0 -9
- package/dist/llm/response.js +0 -44
- package/dist/llm/stream-state.js +0 -26
- package/dist/llm/token-counter.js +0 -83
- package/dist/llm/types.js +0 -1
- package/dist/logger/app-logger.js +0 -212
- package/dist/logger/file-log.js +0 -160
- package/dist/logger/index.js +0 -2
- package/dist/migration/backup.js +0 -56
- package/dist/migration/detect.js +0 -61
- package/dist/migration/index.js +0 -3
- package/dist/modules/artifacts/store.js +0 -62
- package/dist/modules/browser/actions.js +0 -79
- package/dist/modules/browser/bridge-client.js +0 -203
- package/dist/modules/browser/bridge-path.js +0 -10
- package/dist/modules/browser/console-buffer.js +0 -37
- package/dist/modules/browser/cookie-store.js +0 -29
- package/dist/modules/browser/driver-types.js +0 -1
- package/dist/modules/browser/driver.js +0 -186
- package/dist/modules/browser/index.js +0 -9
- package/dist/modules/browser/module.js +0 -30
- package/dist/modules/browser/session.js +0 -322
- package/dist/modules/browser/snapshot.js +0 -154
- package/dist/modules/browser/types.js +0 -13
- package/dist/modules/certification/cli.js +0 -264
- package/dist/modules/certification/fact-checker.js +0 -90
- package/dist/modules/certification/loader.js +0 -127
- package/dist/modules/certification/manifest.js +0 -231
- package/dist/modules/certification/runner.js +0 -233
- package/dist/modules/certification/scenarios.js +0 -586
- package/dist/modules/certification/syntax-scenarios.js +0 -50
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/context/chunk-query.js +0 -122
- package/dist/modules/context/fact-extractor.js +0 -170
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/context/index.js +0 -2
- package/dist/modules/context/manager.js +0 -482
- package/dist/modules/critic/client.js +0 -176
- package/dist/modules/critic/evidence.js +0 -81
- package/dist/modules/critic/provider.js +0 -45
- package/dist/modules/critic/tools.js +0 -78
- package/dist/modules/critic/types.js +0 -1
- package/dist/modules/execution/audit-runners.js +0 -206
- package/dist/modules/execution/auditor.js +0 -222
- package/dist/modules/execution/execution-plugin.js +0 -478
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/input-from.js +0 -70
- package/dist/modules/execution/module.js +0 -891
- package/dist/modules/execution/moe-executor.js +0 -443
- package/dist/modules/execution/plan-actions.js +0 -642
- package/dist/modules/execution/plan-checks.js +0 -35
- package/dist/modules/execution/plan-coverage.js +0 -71
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-steps.js +0 -31
- package/dist/modules/execution/plan-store.js +0 -274
- package/dist/modules/execution/plan-tool.js +0 -249
- package/dist/modules/execution/plan-validator.js +0 -197
- package/dist/modules/execution/planner.js +0 -103
- package/dist/modules/execution/stuck-detector.js +0 -834
- package/dist/modules/execution/tracker.js +0 -92
- package/dist/modules/execution/transient-error.js +0 -30
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -343
- package/dist/modules/execution/windows-commands.js +0 -52
- package/dist/modules/hallucination/artifact-claim.js +0 -93
- package/dist/modules/hallucination/confidence.js +0 -81
- package/dist/modules/hallucination/consistency.js +0 -37
- package/dist/modules/hallucination/detector.js +0 -57
- package/dist/modules/hallucination/factual.js +0 -205
- package/dist/modules/hallucination/index.js +0 -6
- package/dist/modules/hallucination/js-identifiers.js +0 -261
- package/dist/modules/hallucination/llm-judge.js +0 -116
- package/dist/modules/hallucination/success-claim.js +0 -59
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -88
- package/dist/modules/indexer/index.js +0 -4
- package/dist/modules/indexer/map-command.js +0 -33
- package/dist/modules/indexer/map-select.js +0 -87
- package/dist/modules/indexer/module.js +0 -293
- package/dist/modules/indexer/project-profile.js +0 -184
- package/dist/modules/indexer/symbols.js +0 -189
- package/dist/modules/indexer/walker.js +0 -285
- package/dist/modules/lsp/check-tool.js +0 -61
- package/dist/modules/lsp/client.js +0 -406
- package/dist/modules/lsp/command.js +0 -53
- package/dist/modules/lsp/config.js +0 -189
- package/dist/modules/lsp/index.js +0 -4
- package/dist/modules/lsp/module.js +0 -295
- package/dist/modules/lsp/probe.js +0 -86
- package/dist/modules/lsp/project-root.js +0 -69
- package/dist/modules/lsp/startup-check.js +0 -154
- package/dist/modules/lsp/types.js +0 -1
- package/dist/modules/mcp/client.js +0 -445
- package/dist/modules/mcp/index.js +0 -4
- package/dist/modules/mcp/module.js +0 -295
- package/dist/modules/mcp/registry.js +0 -20
- package/dist/modules/memory/index.js +0 -2
- package/dist/modules/memory/module.js +0 -101
- package/dist/modules/memory/search.js +0 -45
- package/dist/modules/memory/store.js +0 -107
- package/dist/modules/onboarding/init.js +0 -47
- package/dist/modules/onboarding/meet.js +0 -30
- package/dist/modules/onboarding/mode.js +0 -30
- package/dist/modules/onboarding/prompts.js +0 -43
- package/dist/modules/pipelines/engine.js +0 -70
- package/dist/modules/pipelines/index.js +0 -4
- package/dist/modules/pipelines/parser.js +0 -59
- package/dist/modules/pipelines/template.js +0 -18
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -377
- package/dist/modules/plugins/builtin/notify.js +0 -11
- package/dist/modules/plugins/index.js +0 -2
- package/dist/modules/plugins/loader.js +0 -85
- package/dist/modules/plugins/manager.js +0 -244
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/pricing/index.js +0 -70
- package/dist/modules/pricing/prices.js +0 -147
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/processes/index.js +0 -4
- package/dist/modules/processes/kill-tree.js +0 -52
- package/dist/modules/processes/registry.js +0 -294
- package/dist/modules/processes/runner.js +0 -23
- package/dist/modules/providers/cache.js +0 -23
- package/dist/modules/providers/capabilities.js +0 -28
- package/dist/modules/providers/create.js +0 -21
- package/dist/modules/providers/factory.js +0 -28
- package/dist/modules/providers/fallback.js +0 -81
- package/dist/modules/providers/health.js +0 -47
- package/dist/modules/providers/index.js +0 -6
- package/dist/modules/providers/manager.js +0 -207
- package/dist/modules/providers/naming.js +0 -15
- package/dist/modules/providers/preflight.js +0 -71
- package/dist/modules/providers/presets.js +0 -232
- package/dist/modules/providers/registry.js +0 -27
- package/dist/modules/providers/types.js +0 -1
- package/dist/modules/reasoning/policy.js +0 -42
- package/dist/modules/reasoning/probe.js +0 -116
- package/dist/modules/registry.js +0 -48
- package/dist/modules/security/audit-log.js +0 -119
- package/dist/modules/security/audit-notifier.js +0 -267
- package/dist/modules/security/command-validator.js +0 -272
- package/dist/modules/security/content-scanner.js +0 -53
- package/dist/modules/security/data-sanitizer.js +0 -82
- package/dist/modules/security/encryption.js +0 -216
- package/dist/modules/security/index.js +0 -15
- package/dist/modules/security/network-validator.js +0 -236
- package/dist/modules/security/path-validator.js +0 -261
- package/dist/modules/security/protected-paths.js +0 -73
- package/dist/modules/security/rate-limiter.js +0 -98
- package/dist/modules/security/security-policies.js +0 -499
- package/dist/modules/security/session-encryption.js +0 -182
- package/dist/modules/security/session-isolation.js +0 -75
- package/dist/modules/session/auto-name.js +0 -63
- package/dist/modules/session/export.js +0 -98
- package/dist/modules/session/index.js +0 -4
- package/dist/modules/session/manager.js +0 -234
- package/dist/modules/session/module.js +0 -33
- package/dist/modules/session/store.js +0 -295
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/setup/answers.js +0 -35
- package/dist/modules/setup/apply.js +0 -79
- package/dist/modules/setup/discovery.js +0 -72
- package/dist/modules/skills/index.js +0 -3
- package/dist/modules/skills/loader.js +0 -86
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/skills/module.js +0 -141
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/changelog-reader.js +0 -93
- package/dist/modules/updater/checker.js +0 -101
- package/dist/modules/updater/dev-detect.js +0 -18
- package/dist/modules/updater/index.js +0 -4
- package/dist/modules/updater/module.js +0 -129
- package/dist/modules/user-profile/compressor.js +0 -18
- package/dist/modules/user-profile/index.js +0 -2
- package/dist/modules/user-profile/profile.js +0 -77
- package/dist/modules/webui/answer-api.js +0 -29
- package/dist/modules/webui/assets.js +0 -50
- package/dist/modules/webui/body.js +0 -21
- package/dist/modules/webui/commands-settings.js +0 -209
- package/dist/modules/webui/commands-shared.js +0 -60
- package/dist/modules/webui/commands.js +0 -174
- package/dist/modules/webui/config-api.js +0 -134
- package/dist/modules/webui/file-api.js +0 -202
- package/dist/modules/webui/forward.js +0 -159
- package/dist/modules/webui/history-api.js +0 -112
- package/dist/modules/webui/host-state.js +0 -60
- package/dist/modules/webui/i18n-api.js +0 -9
- package/dist/modules/webui/limits.js +0 -6
- package/dist/modules/webui/log-forward.js +0 -20
- package/dist/modules/webui/memory-commands.js +0 -64
- package/dist/modules/webui/model-cache.js +0 -18
- package/dist/modules/webui/module.js +0 -148
- package/dist/modules/webui/persist.js +0 -24
- package/dist/modules/webui/prompt-io.js +0 -97
- package/dist/modules/webui/providers-api.js +0 -337
- package/dist/modules/webui/providers-view.js +0 -56
- package/dist/modules/webui/reasoning-view.js +0 -15
- package/dist/modules/webui/run-agent.js +0 -81
- package/dist/modules/webui/server.js +0 -284
- package/dist/modules/webui/sessions-api.js +0 -178
- package/dist/modules/webui/settings-api.js +0 -561
- package/dist/modules/webui/setup-api.js +0 -201
- package/dist/modules/webui/state.js +0 -74
- package/dist/modules/webui/util.js +0 -261
- package/dist/output/bus.js +0 -37
- package/dist/output/channel.js +0 -285
- package/dist/output/format.js +0 -15
- package/dist/output/index.js +0 -8
- package/dist/output/json-sink.js +0 -24
- package/dist/output/machine.js +0 -9
- package/dist/output/session-sink.js +0 -27
- package/dist/output/types.js +0 -1
- package/dist/tools/approve.js +0 -41
- package/dist/tools/attach-image.js +0 -102
- package/dist/tools/auto-fixer.js +0 -180
- package/dist/tools/auto-tags.js +0 -83
- package/dist/tools/bash.js +0 -649
- package/dist/tools/browser.js +0 -160
- package/dist/tools/chunk-query.js +0 -112
- package/dist/tools/create-dir.js +0 -60
- package/dist/tools/delete-file.js +0 -67
- package/dist/tools/download-file.js +0 -142
- package/dist/tools/edit-file.js +0 -136
- package/dist/tools/enable-tools.js +0 -59
- package/dist/tools/executor.js +0 -304
- package/dist/tools/file-info.js +0 -49
- package/dist/tools/filter-tools.js +0 -17
- package/dist/tools/glob-tool.js +0 -37
- package/dist/tools/grep-tool.js +0 -142
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/tools/index.js +0 -88
- package/dist/tools/list-dir.js +0 -53
- package/dist/tools/load-skill.js +0 -51
- package/dist/tools/mcp-call.js +0 -80
- package/dist/tools/meet-done.js +0 -14
- package/dist/tools/move-file.js +0 -94
- package/dist/tools/path-utils.js +0 -108
- package/dist/tools/pipeline-run.js +0 -167
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-ids.js +0 -31
- package/dist/tools/process-kill.js +0 -33
- package/dist/tools/process-list.js +0 -38
- package/dist/tools/process-log.js +0 -49
- package/dist/tools/process-wait.js +0 -63
- package/dist/tools/prompt-io.js +0 -29
- package/dist/tools/question.js +0 -288
- package/dist/tools/read-file.js +0 -200
- package/dist/tools/recall.js +0 -124
- package/dist/tools/registry.js +0 -48
- package/dist/tools/remember.js +0 -72
- package/dist/tools/scope-check.js +0 -32
- package/dist/tools/scope-request.js +0 -96
- package/dist/tools/search-history.js +0 -88
- package/dist/tools/session-info.js +0 -46
- package/dist/tools/set-thinking.js +0 -75
- package/dist/tools/subagent.js +0 -257
- package/dist/tools/syntax-validator.js +0 -379
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -134
- package/dist/tools/web-browse.js +0 -93
- package/dist/tools/web-fetch.js +0 -108
- package/dist/tools/web-search.js +0 -107
- package/dist/tools/write-file.js +0 -128
- package/dist/ui/action-log.js +0 -13
- package/dist/ui/box.js +0 -78
- package/dist/ui/colors.js +0 -6
- package/dist/ui/diff.js +0 -189
- package/dist/ui/index.js +0 -7
- package/dist/ui/line-editor.js +0 -1083
- package/dist/ui/line-math.js +0 -98
- package/dist/ui/md-formatter.js +0 -322
- package/dist/ui/output.js +0 -19
- package/dist/ui/plan-view.js +0 -109
- package/dist/ui/renderer.js +0 -589
- package/dist/ui/spinner.js +0 -91
- package/dist/ui/table.js +0 -142
- package/dist/ui/theme.js +0 -143
- package/dist/utils/child-run.js +0 -20
- package/dist/utils/error.js +0 -4
- package/dist/utils/index.js +0 -6
- package/dist/utils/retry.js +0 -17
- package/dist/utils/sleep.js +0 -23
- package/dist/utils/truncate.js +0 -9
|
@@ -1,891 +0,0 @@
|
|
|
1
|
-
import { t } from "../../i18n/index";
|
|
2
|
-
import { PlanTracker } from "./tracker";
|
|
3
|
-
import { StepVerifier } from "./verifier";
|
|
4
|
-
import { StuckDetector, isSearchableError, buildSearchQuery } from "./stuck-detector";
|
|
5
|
-
import { Auditor, findExistingFile } from "./auditor";
|
|
6
|
-
import { PlanStore } from "./plan-store";
|
|
7
|
-
import { getMessageText } from "../../llm/provider";
|
|
8
|
-
import { extractFileLikeTokens, stripUrls } from "../hallucination/js-identifiers";
|
|
9
|
-
import { toForwardSlash } from "../../tools/path-utils";
|
|
10
|
-
import { existsSync, readFileSync } from "fs";
|
|
11
|
-
import { resolve } from "path";
|
|
12
|
-
import { performWebSearch } from "../../tools/web-search";
|
|
13
|
-
import { getSessionSecurityConfig } from "../security/session-isolation";
|
|
14
|
-
import { createPlanToolDefinitions } from "./plan-tool";
|
|
15
|
-
import { isValidCheck } from "./plan-checks";
|
|
16
|
-
import { createExecutionPlugin, STUCK_RECOVERY_COOLDOWN, STUCK_WARN_REPEAT_EVERY, } from "./execution-plugin";
|
|
17
|
-
import { EXECUTION_ERROR_SEARCH_MIN_INTERVAL_MS as ERROR_SEARCH_MIN_INTERVAL_MS } from "../../config/timeouts";
|
|
18
|
-
/**
|
|
19
|
-
* Минимальный интервал между автопоисками ошибок. Спек просил кап
|
|
20
|
-
* STUCK_RECOVERY_COOLDOWN-итераций, но в onAfterTool нет счётчика итераций
|
|
21
|
-
* (контекст экзекутора) — та же анти-спам защита, но по времени. Инжектится
|
|
22
|
-
* через конструктор для тестов (0 отключает).
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* Тулы, которым можно при активном шаге плана БЕЗ проверки step-alignment.
|
|
26
|
-
* Либо не трогают ФС, либо структурно/read-only инспекции, нужные агенту в
|
|
27
|
-
* любой точке (диагностика, индекс/поиск, память, интроспекция процессов,
|
|
28
|
-
* интерактивные промпты). К текущему шагу пинятся только файл-мутирующие.
|
|
29
|
-
*
|
|
30
|
-
* `lsp_check` и `project_map` здесь явно: оба alwaysOn, а системный промпт
|
|
31
|
-
* ТРЕБУЕТ `lsp_check` после каждой записи (включая переписи файлов уже
|
|
32
|
-
* закрытых шагов). Без них каждый диагностический вызов жёг plan-warning и мог
|
|
33
|
-
* hard-block'нуть легитимную запись на 3-м вызове (было: "3 вызовов подряд вне
|
|
34
|
-
* текущего шага", пока агент писал deliverable текущего же шага).
|
|
35
|
-
*/
|
|
36
|
-
const PLAN_ALIGNMENT_EXEMPT_TOOLS = new Set([
|
|
37
|
-
// structural / execution control
|
|
38
|
-
"plan",
|
|
39
|
-
"plan_step",
|
|
40
|
-
"todo",
|
|
41
|
-
"verify",
|
|
42
|
-
"enable_tools",
|
|
43
|
-
// filesystem reads
|
|
44
|
-
"list_dir",
|
|
45
|
-
"read_file",
|
|
46
|
-
"glob",
|
|
47
|
-
"grep",
|
|
48
|
-
"file_info",
|
|
49
|
-
// read-only diagnostics + code index
|
|
50
|
-
"lsp_check",
|
|
51
|
-
"project_map",
|
|
52
|
-
"chunk_query",
|
|
53
|
-
// web / memory / session reads
|
|
54
|
-
"web_search",
|
|
55
|
-
"web_fetch",
|
|
56
|
-
"web_browse",
|
|
57
|
-
"recall",
|
|
58
|
-
"search_history",
|
|
59
|
-
"session_info",
|
|
60
|
-
// process introspection
|
|
61
|
-
"process_list",
|
|
62
|
-
"process_log",
|
|
63
|
-
// interactive prompts (no filesystem mutation)
|
|
64
|
-
"question",
|
|
65
|
-
"approve",
|
|
66
|
-
// skills
|
|
67
|
-
"load_skill",
|
|
68
|
-
]);
|
|
69
|
-
/**
|
|
70
|
-
* Каноническая форма файлового токена для alignment: прямые слэши + lowercase.
|
|
71
|
-
* Вызовы тулов несут абсолютные Windows-пути (`C:\…\test\src\main.ts`), а шаги
|
|
72
|
-
* плана — относительные (`src/main.ts`); сравнение сырых строк делало каждый
|
|
73
|
-
* on-track write off-path (бэкслэш vs прямой слэш не пересекаются).
|
|
74
|
-
* Нормализация обеих сторон чинит overlap через substring/`endsWith` ниже.
|
|
75
|
-
*/
|
|
76
|
-
function planPathToken(p) {
|
|
77
|
-
return toForwardSlash(p).toLowerCase();
|
|
78
|
-
}
|
|
79
|
-
/** Execution-модуль: план, stuck-детектор, аудитор, гейты выравнивания и автопоиск ошибок. */
|
|
80
|
-
export class ExecutionModule {
|
|
81
|
-
name = "execution";
|
|
82
|
-
tracker = null;
|
|
83
|
-
/** Сессия-владелец текущего трекера. План следует за сессией: смена
|
|
84
|
-
* сессии через switchSession() персистит трекер в слот старой сессии
|
|
85
|
-
* и загружает план новой (см. docs/specs/2026-09-29-session-plan-binding-design.md). */
|
|
86
|
-
sessionId = null;
|
|
87
|
-
verifier;
|
|
88
|
-
stuckDetector;
|
|
89
|
-
auditor;
|
|
90
|
-
store;
|
|
91
|
-
baseDir;
|
|
92
|
-
// Пропуск первого аудита после восстановления плана с диска — даём юзеру
|
|
93
|
-
// шанс сменить тему, прежде чем audit-gate зафиксируется.
|
|
94
|
-
_auditSkipsRemaining = 0;
|
|
95
|
-
/**
|
|
96
|
-
* Последний план, ЗАВЕРШЁННЫЙ и авто-архивированный. `plan update` на
|
|
97
|
-
* финальном шаге чистит трекер — раньше runFinalAudit() возвращал null и
|
|
98
|
-
* молча скипал финальный аудит (тесты + tsc + файлы) готовой задачи.
|
|
99
|
-
* Храним завершённый план, чтобы audit-gate отработал.
|
|
100
|
-
*/
|
|
101
|
-
completedPlan = null;
|
|
102
|
-
/**
|
|
103
|
-
* Отложенные <system-summary>. Инжектятся onBeforeTool/onAfterTool, но
|
|
104
|
-
* флашатся в onBeforeThink — никогда не встают между assistant tool_call и
|
|
105
|
-
* его tool-результатами (рвёт спаривание и хоронит варнинги, на которые
|
|
106
|
-
* модель должна реагировать).
|
|
107
|
-
*/
|
|
108
|
-
pendingMessages = [];
|
|
109
|
-
// Per-командные счётчики фейлов запрещённых Windows-команд (grep/sed/ls/
|
|
110
|
-
// find/… через тул bash). Hard stop после второго фейла evidence-based
|
|
111
|
-
// (реальные фейлы команд), не классификация задач (правило #10).
|
|
112
|
-
forbiddenBashFailures = new Map();
|
|
113
|
-
searchRunner;
|
|
114
|
-
searchesThisSession = 0;
|
|
115
|
-
retryWebSearchSignatures = new Set();
|
|
116
|
-
lastErrorSearchAt = 0;
|
|
117
|
-
errorSearchCooldownMs;
|
|
118
|
-
hallucinationDetector = null;
|
|
119
|
-
/**
|
|
120
|
-
* Мутабельное состояние execution-плагина. Владеем ЗДЕСЬ, передаём ПО ССЫЛКЕ
|
|
121
|
-
* фабрике плагина — плагин пишет deps.state.* напрямую, записи видны модулю
|
|
122
|
-
* без write-back.
|
|
123
|
-
*/
|
|
124
|
-
state = {
|
|
125
|
-
lastRecoveryIteration: -STUCK_RECOVERY_COOLDOWN,
|
|
126
|
-
consecutivePlanWarnings: 0,
|
|
127
|
-
lastStepId: -1,
|
|
128
|
-
stuckNotified: false,
|
|
129
|
-
lastStuckWarnKey: "",
|
|
130
|
-
lastStuckWarnIter: -STUCK_WARN_REPEAT_EVERY,
|
|
131
|
-
escalationDirectiveSent: false,
|
|
132
|
-
depsGateHints: new Map(),
|
|
133
|
-
mutationsWithoutPlan: 0,
|
|
134
|
-
planNudgeSent: false,
|
|
135
|
-
typecheckFailures: new Map(),
|
|
136
|
-
};
|
|
137
|
-
/**
|
|
138
|
-
* Read/write доступ к трекеру, общий для фабрик plan-tool и execution-plugin.
|
|
139
|
-
* Abort тула `plan` чистит его (current = null) — сеттер держит this.tracker
|
|
140
|
-
* в синхроне. Геттеры литерала биндят `this` на литерал, поэтому инстанс
|
|
141
|
-
* захвачен замыканием.
|
|
142
|
-
*
|
|
143
|
-
* Сеттер зеркалит per-session слот (план следует за сессией): план-тулы
|
|
144
|
-
* (`abort/switch/delete/purge/...`) пишут через trackerRef напрямую, минуя
|
|
145
|
-
* setPlan — без хука слот протух бы и воскрешал отменённый план возвратом
|
|
146
|
-
* в сессию. IO fail-open: слот не должен ронять тулы.
|
|
147
|
-
*/
|
|
148
|
-
trackerRef = (() => {
|
|
149
|
-
const self = this;
|
|
150
|
-
return {
|
|
151
|
-
get current() {
|
|
152
|
-
return self.tracker;
|
|
153
|
-
},
|
|
154
|
-
set current(v) {
|
|
155
|
-
self.tracker = v;
|
|
156
|
-
self.flushSessionPlan();
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
})();
|
|
160
|
-
/** Зеркалит текущий трекер в слот активной сессии (null → clear).
|
|
161
|
-
* Вызывать после прямых присвоений this.tracker вне setPlan/switchSession. */
|
|
162
|
-
flushSessionPlan() {
|
|
163
|
-
if (!this.sessionId)
|
|
164
|
-
return;
|
|
165
|
-
try {
|
|
166
|
-
if (this.tracker)
|
|
167
|
-
this.store.saveSessionActive(this.sessionId, this.tracker.getPlan());
|
|
168
|
-
else
|
|
169
|
-
this.store.clearSessionActive(this.sessionId);
|
|
170
|
-
}
|
|
171
|
-
catch {
|
|
172
|
-
// fail-open: per-session слот не должен ронять тулы/гейты
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
constructor(baseDir, stuckThreshold = 6, webSearchThreshold = 5, searchRunner = performWebSearch, errorSearchCooldownMs = ERROR_SEARCH_MIN_INTERVAL_MS) {
|
|
176
|
-
this.baseDir = baseDir;
|
|
177
|
-
this.verifier = new StepVerifier(baseDir);
|
|
178
|
-
this.stuckDetector = new StuckDetector(stuckThreshold, 3, webSearchThreshold);
|
|
179
|
-
this.auditor = new Auditor(baseDir);
|
|
180
|
-
this.store = new PlanStore(baseDir);
|
|
181
|
-
this.searchRunner = searchRunner;
|
|
182
|
-
this.errorSearchCooldownMs = errorSearchCooldownMs;
|
|
183
|
-
}
|
|
184
|
-
/** Ставит план активным (синхрон указателя, персист, сброс завершённого/скипов). */
|
|
185
|
-
setPlan(plan) {
|
|
186
|
-
this.tracker = new PlanTracker(plan);
|
|
187
|
-
// Указатель мимо done/skipped шагов восстановленного/перепланированного
|
|
188
|
-
// плана: display ("current: step N") и off-track alignment матчатся с
|
|
189
|
-
// реальностью, а не вечно указывают на шаг 1.
|
|
190
|
-
this.tracker.syncCurrentStep();
|
|
191
|
-
this.store.saveActive(plan);
|
|
192
|
-
if (this.sessionId) {
|
|
193
|
-
try {
|
|
194
|
-
this.store.saveSessionActive(this.sessionId, plan);
|
|
195
|
-
}
|
|
196
|
-
catch {
|
|
197
|
-
// fail-open: per-session слот не должен ронять создание плана
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
this.completedPlan = null; // новый план вытесняет завершённый
|
|
201
|
-
this._auditSkipsRemaining = 0; // план явно создан — взводим audit-gate
|
|
202
|
-
}
|
|
203
|
-
/** Переключает трекер на план другой сессии. Персистит текущий трекер
|
|
204
|
-
* в слот старой сессии (пустота тоже запоминается через clear, чтобы
|
|
205
|
-
* возврат не воскрешал чужой план), загружает план новой сессии.
|
|
206
|
-
* Завершённый на диске план — в архив, как в restorePlan. */
|
|
207
|
-
switchSession(id) {
|
|
208
|
-
const prev = this.sessionId;
|
|
209
|
-
if (prev === id)
|
|
210
|
-
return;
|
|
211
|
-
// Персист уходящей стороны. Стартовый трекер без sessionId (restorePlan
|
|
212
|
-
// до известной сессии) усыновляется новой сессией, а не теряется.
|
|
213
|
-
if (this.tracker) {
|
|
214
|
-
try {
|
|
215
|
-
this.store.saveSessionActive(prev ?? id ?? "", this.tracker.getPlan());
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
// fail-open: битый id слота / ФС — трекер в памяти остаётся
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
else if (prev) {
|
|
222
|
-
try {
|
|
223
|
-
this.store.clearSessionActive(prev);
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
226
|
-
/* best-effort */
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
this.sessionId = id;
|
|
230
|
-
this.completedPlan = null;
|
|
231
|
-
if (!id) {
|
|
232
|
-
this.tracker = null;
|
|
233
|
-
this._auditSkipsRemaining = 0;
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
let plan = null;
|
|
237
|
-
try {
|
|
238
|
-
plan = this.store.loadSessionActive(id);
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
plan = null;
|
|
242
|
-
}
|
|
243
|
-
if (!plan) {
|
|
244
|
-
this.tracker = null;
|
|
245
|
-
this._auditSkipsRemaining = 0;
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
const restored = new PlanTracker(plan);
|
|
249
|
-
if (restored.isComplete()) {
|
|
250
|
-
this.store.archivePlan(plan);
|
|
251
|
-
try {
|
|
252
|
-
this.store.clearSessionActive(id);
|
|
253
|
-
}
|
|
254
|
-
catch {
|
|
255
|
-
/* best-effort */
|
|
256
|
-
}
|
|
257
|
-
this.tracker = null;
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
this.tracker = restored;
|
|
261
|
-
this.tracker.syncCurrentStep();
|
|
262
|
-
this._auditSkipsRemaining = 1; // как восстановленный план
|
|
263
|
-
}
|
|
264
|
-
/** Забывает сессию: чистит её слот плана. Удаляемая сессия не может быть
|
|
265
|
-
* текущей (web гардит 409), но REPL /delete — может: тогда сбрасываем
|
|
266
|
-
* и трекер, чтобы не висел план-призрак. */
|
|
267
|
-
forgetSession(id) {
|
|
268
|
-
try {
|
|
269
|
-
this.store.clearSessionActive(id);
|
|
270
|
-
}
|
|
271
|
-
catch {
|
|
272
|
-
/* best-effort */
|
|
273
|
-
}
|
|
274
|
-
if (this.sessionId === id) {
|
|
275
|
-
this.sessionId = null;
|
|
276
|
-
this.tracker = null;
|
|
277
|
-
this.completedPlan = null;
|
|
278
|
-
this._auditSkipsRemaining = 0;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
/** Вяжет детектор галлюцинаций: тул plan проверяет удалённость файлов (auto-switch kind gate). */
|
|
282
|
-
setHallucinationDetector(detector) {
|
|
283
|
-
this.hallucinationDetector = detector;
|
|
284
|
-
}
|
|
285
|
-
/** Запоминает последний завершённый план: финальный аудит проверит его и
|
|
286
|
-
* после очистки трекера авто-архивом. */
|
|
287
|
-
recordCompleted(plan) {
|
|
288
|
-
this.completedPlan = plan;
|
|
289
|
-
}
|
|
290
|
-
/** Забывает завершённый план (abort/delete/purge — аудитить нечего). */
|
|
291
|
-
clearCompleted() {
|
|
292
|
-
this.completedPlan = null;
|
|
293
|
-
}
|
|
294
|
-
/** Восстанавливает активный план с диска. False, если нет/уже завершён (тогда в архив). */
|
|
295
|
-
restorePlan() {
|
|
296
|
-
const plan = this.store.loadActive();
|
|
297
|
-
if (!plan)
|
|
298
|
-
return false;
|
|
299
|
-
const restored = new PlanTracker(plan);
|
|
300
|
-
// План, уже завершённый на момент сохранения, активным не возвращается —
|
|
301
|
-
// архивируем, чтобы не якорил агента (восстановленные готовые планы давали
|
|
302
|
-
// петлю "continue step 4" на рестарте).
|
|
303
|
-
if (restored.isComplete()) {
|
|
304
|
-
this.store.archivePlan(plan);
|
|
305
|
-
this.tracker = null;
|
|
306
|
-
return false;
|
|
307
|
-
}
|
|
308
|
-
this.tracker = restored;
|
|
309
|
-
// Двигаем к первому недонесённому шагу
|
|
310
|
-
this.tracker.syncCurrentStep();
|
|
311
|
-
this._auditSkipsRemaining = 1; // восстановленный план — один свободный проход
|
|
312
|
-
return true;
|
|
313
|
-
}
|
|
314
|
-
/** Трекер (null без активного плана). */
|
|
315
|
-
getTracker() {
|
|
316
|
-
return this.tracker;
|
|
317
|
-
}
|
|
318
|
-
/** Текущий трекаемый план (единый источник правды для UI).
|
|
319
|
-
* Null в свежей сессии — план с диска восстанавливается только при resume
|
|
320
|
-
* сессии с историей, REPL протухших планов не видит. */
|
|
321
|
-
getActivePlan() {
|
|
322
|
-
return this.tracker?.getPlan() ?? null;
|
|
323
|
-
}
|
|
324
|
-
/** Стор планов. */
|
|
325
|
-
getStore() {
|
|
326
|
-
return this.store;
|
|
327
|
-
}
|
|
328
|
-
/** Stuck-детектор. */
|
|
329
|
-
getStuckDetector() {
|
|
330
|
-
return this.stuckDetector;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* Автопоиск в вебе повторяющейся ошибки (≥ порога errorWebSearch одинаковых
|
|
334
|
-
* сигнатур). Поиск fire-and-forget (результат тула не блокирует), результаты
|
|
335
|
-
* очередятся <system-summary> в pendingMessages.
|
|
336
|
-
*
|
|
337
|
-
* Гейты: мастер-свитч webSearch + свитч errorWebSearch → повторная сигнатура
|
|
338
|
-
* → не-исканная (или ретрайная) → сессионный кап → содержательный текст ошибки →
|
|
339
|
-
* кулдаун. Один поиск на сигнатуру; пустые результаты — один ретрай
|
|
340
|
-
* (размаркировка раз), затем сигнатура считается исканной.
|
|
341
|
-
*/
|
|
342
|
-
maybeSearchError(ctx, call) {
|
|
343
|
-
if (ctx.config?.webSearch?.enabled === false)
|
|
344
|
-
return;
|
|
345
|
-
const cfg = ctx.config?.errorWebSearch;
|
|
346
|
-
if (!cfg?.enabled)
|
|
347
|
-
return;
|
|
348
|
-
const sig = this.stuckDetector.getRepeatedErrorSignature();
|
|
349
|
-
if (!sig)
|
|
350
|
-
return;
|
|
351
|
-
if (this.stuckDetector.hasErrorSearched(sig) && !this.retryWebSearchSignatures.has(sig))
|
|
352
|
-
return;
|
|
353
|
-
if (this.searchesThisSession >= (cfg.maxSearchesPerSession ?? 3))
|
|
354
|
-
return;
|
|
355
|
-
const lastError = this.stuckDetector.getLastErrorOutput();
|
|
356
|
-
if (!isSearchableError(lastError)) {
|
|
357
|
-
ctx.logger?.debug(t("exec.error_search_no_query"));
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const now = Date.now();
|
|
361
|
-
if (now - this.lastErrorSearchAt < this.errorSearchCooldownMs)
|
|
362
|
-
return;
|
|
363
|
-
const stepDesc = this.tracker?.getCurrentStep()?.description ?? "";
|
|
364
|
-
const query = buildSearchQuery(stepDesc, this.stuckDetector.getLastBashCommand(), lastError, call.name, cfg.maxQueryChars ?? 200);
|
|
365
|
-
const count = this.stuckDetector.getErrorSignatureCount(sig);
|
|
366
|
-
this.stuckDetector.markErrorSearched(sig);
|
|
367
|
-
this.searchesThisSession++;
|
|
368
|
-
this.lastErrorSearchAt = now;
|
|
369
|
-
const networkConfig = getSessionSecurityConfig(ctx.config, ctx.sessionContext).network;
|
|
370
|
-
this.searchRunner(query, cfg.maxResults ?? 5, networkConfig, cfg.requestTimeoutMs ?? 10000)
|
|
371
|
-
.then((res) => {
|
|
372
|
-
if (!res.success || res.results.length === 0) {
|
|
373
|
-
if (!this.retryWebSearchSignatures.has(sig)) {
|
|
374
|
-
this.retryWebSearchSignatures.add(sig);
|
|
375
|
-
this.stuckDetector.unmarkErrorSearched(sig);
|
|
376
|
-
}
|
|
377
|
-
ctx.logger?.warn(t("exec.error_search_failed", { query }));
|
|
378
|
-
ctx.sessionLog?.plan("web-search", `empty/failed: ${query}`);
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
const resultsText = res.results
|
|
382
|
-
.map((r, i) => `${i + 1}. ${r.title} \u2014 ${r.url}\n ${r.snippet}`)
|
|
383
|
-
.join("\n");
|
|
384
|
-
this.pendingMessages.push({
|
|
385
|
-
role: "user",
|
|
386
|
-
content: `<system-summary>${t("exec.error_search_results", {
|
|
387
|
-
sig,
|
|
388
|
-
count: String(count),
|
|
389
|
-
query,
|
|
390
|
-
results: resultsText,
|
|
391
|
-
})}</system-summary>`,
|
|
392
|
-
});
|
|
393
|
-
})
|
|
394
|
-
.catch((e) => ctx.logger?.warn(`error web search: ${e.message}`));
|
|
395
|
-
}
|
|
396
|
-
/**
|
|
397
|
-
* Финальный аудит перед правом агента заявить "готово". Null без активного
|
|
398
|
-
* плана; иначе — готовность плана + существование артефактов.
|
|
399
|
-
*/
|
|
400
|
-
/**
|
|
401
|
-
* Evidence-based автозакрытие пендинг шагов с deliverables на диске. Мелкие
|
|
402
|
-
* модели рутинно доделывают работу, но забывают бухгалтерию `plan update`
|
|
403
|
-
* (было в ses_mthn3c2a: 2/5 шагов done при всех артефактах на месте — audit-gate
|
|
404
|
-
* реджектил корректный финальный ответ и жёг retry-бюджет на бухгалтерию, не
|
|
405
|
-
* на работу).
|
|
406
|
-
*
|
|
407
|
-
* Стандарт ТОТ ЖЕ, что аудитор применяет к done-шагам: каждый файлоподобный
|
|
408
|
-
* токен описания резолвится в существующий файл. Шаги без файловых токенов
|
|
409
|
-
* остаются пендинг (верифицировать нечего).
|
|
410
|
-
*/
|
|
411
|
-
autoCloseEvidenceSteps(plan) {
|
|
412
|
-
let closed = 0;
|
|
413
|
-
for (const step of plan.steps) {
|
|
414
|
-
if (step.status === "done" || step.status === "skipped")
|
|
415
|
-
continue;
|
|
416
|
-
const tokens = extractFileLikeTokens(stripUrls(step.description));
|
|
417
|
-
if (tokens.length === 0)
|
|
418
|
-
continue;
|
|
419
|
-
const resolved = tokens.map((f) => findExistingFile(this.baseDir, f));
|
|
420
|
-
if (step.kind === "delete") {
|
|
421
|
-
// Delete-шаг закрыт, только если каждая именованная цель ОТСУТСТВУЕТ.
|
|
422
|
-
// (Старая existence-проверка маркила done при файлах на месте —
|
|
423
|
-
// deliverable-gate инвертировался.)
|
|
424
|
-
if (resolved.some((r) => r !== null))
|
|
425
|
-
continue;
|
|
426
|
-
}
|
|
427
|
-
else {
|
|
428
|
-
// create: каждая цель существует И непуста. Пустой плейсхолдер — не
|
|
429
|
-
// deliverable, а аудитор смотрит только существование — автозакрытие
|
|
430
|
-
// дало бы ложный "passed".
|
|
431
|
-
if (resolved.some((r) => r === null))
|
|
432
|
-
continue;
|
|
433
|
-
const empty = resolved.some((r) => {
|
|
434
|
-
try {
|
|
435
|
-
return readFileSync(r, "utf-8").trim().length < 10;
|
|
436
|
-
}
|
|
437
|
-
catch {
|
|
438
|
-
return false; // unreadable — fall back to existence
|
|
439
|
-
}
|
|
440
|
-
});
|
|
441
|
-
if (empty)
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
step.status = "done";
|
|
445
|
-
step.note = `auto-closed by final audit: ${step.kind === "delete" ? "all named files removed" : "all named files exist with content"} (${tokens.join(", ")})`;
|
|
446
|
-
closed++;
|
|
447
|
-
}
|
|
448
|
-
return closed;
|
|
449
|
-
}
|
|
450
|
-
/** Финальный аудит активного/завершённого плана: файлы, тесты, tsc, пендинги. Null — аудитить нечего. */
|
|
451
|
-
async runFinalAudit() {
|
|
452
|
-
// Нет активного трекера: аудитим последний завершённый план вместо bail.
|
|
453
|
-
// План, закрытый через `plan update`, авто-архивируется и чистит трекер;
|
|
454
|
-
// без этого финальный аудит (тесты + tsc + файлы) молча скипался и модель
|
|
455
|
-
// заявляла успех на битых артефактах.
|
|
456
|
-
if (!this.tracker) {
|
|
457
|
-
if (!this.completedPlan)
|
|
458
|
-
return null;
|
|
459
|
-
const plan = this.completedPlan;
|
|
460
|
-
const autoClosed = this.autoCloseEvidenceSteps(plan);
|
|
461
|
-
const audit = await this.auditor.audit(plan);
|
|
462
|
-
const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
|
|
463
|
-
const done = plan.steps.filter((s) => s.status === "done").length;
|
|
464
|
-
if (autoClosed > 0) {
|
|
465
|
-
this.store.saveActive(plan);
|
|
466
|
-
this.flushSessionPlan();
|
|
467
|
-
}
|
|
468
|
-
return {
|
|
469
|
-
passed: audit.passed && pendingSteps.length === 0,
|
|
470
|
-
done,
|
|
471
|
-
total: plan.steps.length,
|
|
472
|
-
pendingSteps,
|
|
473
|
-
missingFiles: audit.missingFiles,
|
|
474
|
-
summary: audit.summary,
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
if (this._auditSkipsRemaining > 0) {
|
|
478
|
-
this._auditSkipsRemaining--;
|
|
479
|
-
return null;
|
|
480
|
-
}
|
|
481
|
-
const plan = this.tracker.getPlan();
|
|
482
|
-
const autoClosed = this.autoCloseEvidenceSteps(plan);
|
|
483
|
-
if (autoClosed > 0) {
|
|
484
|
-
this.store.saveActive(plan);
|
|
485
|
-
this.flushSessionPlan();
|
|
486
|
-
}
|
|
487
|
-
const audit = await this.auditor.audit(plan);
|
|
488
|
-
const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
|
|
489
|
-
const done = plan.steps.filter((s) => s.status === "done").length;
|
|
490
|
-
const passed = audit.passed && pendingSteps.length === 0;
|
|
491
|
-
return {
|
|
492
|
-
passed,
|
|
493
|
-
done,
|
|
494
|
-
total: plan.steps.length,
|
|
495
|
-
pendingSteps,
|
|
496
|
-
missingFiles: audit.missingFiles,
|
|
497
|
-
summary: audit.summary,
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
/** Блок системного промпта с чеклистом плана (null без активного/незавершённого). */
|
|
501
|
-
getSystemPromptBlock() {
|
|
502
|
-
if (!this.tracker || this.tracker.isComplete())
|
|
503
|
-
return null;
|
|
504
|
-
return {
|
|
505
|
-
content: this.tracker.toPromptBlock(),
|
|
506
|
-
priority: "critical",
|
|
507
|
-
essential: true,
|
|
508
|
-
estimatedTokens: 200,
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* Короткая строка "текущий план" для сводки компакции. Null без активного
|
|
513
|
-
* плана. Для 9B-моделей важна явная инструкция "continue it, do NOT create
|
|
514
|
-
* a new plan" — после компакции блок плана виден, но модель всё равно
|
|
515
|
-
* пересоздаёт план (было: ses_msvuao0h).
|
|
516
|
-
*
|
|
517
|
-
* Готовый план в сводке — "план done, можно новый", а НЕ "continue step 4,
|
|
518
|
-
* do NOT create a new plan", якоривший петлю ses_msxjbnm4.
|
|
519
|
-
*/
|
|
520
|
-
getPlanSummary() {
|
|
521
|
-
if (!this.tracker)
|
|
522
|
-
return null;
|
|
523
|
-
const plan = this.tracker.getPlan();
|
|
524
|
-
const done = plan.steps.filter((s) => s.status === "done").length;
|
|
525
|
-
const total = plan.steps.length;
|
|
526
|
-
if (this.tracker.isComplete()) {
|
|
527
|
-
return t("exec.plan_summary_complete", {
|
|
528
|
-
id: plan.id,
|
|
529
|
-
title: plan.title,
|
|
530
|
-
done: String(done),
|
|
531
|
-
total: String(total),
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
const current = this.tracker.getCurrentStepIndex() + 1;
|
|
535
|
-
return t("exec.plan_summary_continue", {
|
|
536
|
-
id: plan.id,
|
|
537
|
-
title: plan.title,
|
|
538
|
-
done: String(done),
|
|
539
|
-
total: String(total),
|
|
540
|
-
current: String(current),
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
/** Определения тулов модуля (plan/todo/verify через фабрику plan-tool). */
|
|
544
|
-
getToolDefinitions() {
|
|
545
|
-
return createPlanToolDefinitions({
|
|
546
|
-
store: this.store,
|
|
547
|
-
verifier: this.verifier,
|
|
548
|
-
trackerRef: this.trackerRef,
|
|
549
|
-
preserveActive: () => this.preserveActive(),
|
|
550
|
-
setPlan: (p) => this.setPlan(p),
|
|
551
|
-
recordCompleted: (p) => this.recordCompleted(p),
|
|
552
|
-
clearCompleted: () => this.clearCompleted(),
|
|
553
|
-
hasStepDeliverables: (s) => this.hasStepDeliverables(s),
|
|
554
|
-
missingStepDeliverables: (s) => this.missingStepDeliverables(s),
|
|
555
|
-
stillExistingDeliverables: (s) => this.stillExistingDeliverables(s),
|
|
556
|
-
parseKinds: (a, st) => this.parseKinds(a, st),
|
|
557
|
-
parseChecks: (a, st) => this.parseChecks(a, st),
|
|
558
|
-
getTaskText: (c) => this.getTaskText(c),
|
|
559
|
-
typecheckFailures: this.state.typecheckFailures,
|
|
560
|
-
baseDir: this.baseDir,
|
|
561
|
-
wasFileDeleted: this.hallucinationDetector
|
|
562
|
-
? (path) => this.hallucinationDetector.getConsistencyCheck().getDeletedFiles().includes(path)
|
|
563
|
-
: undefined,
|
|
564
|
-
});
|
|
565
|
-
}
|
|
566
|
-
/** Плагин модуля (хуки цикла из фабрики execution-plugin). */
|
|
567
|
-
getPlugin() {
|
|
568
|
-
return createExecutionPlugin({
|
|
569
|
-
stuckDetector: this.stuckDetector,
|
|
570
|
-
trackerRef: this.trackerRef,
|
|
571
|
-
pendingMessages: this.pendingMessages,
|
|
572
|
-
forbiddenBashFailures: this.forbiddenBashFailures,
|
|
573
|
-
state: this.state,
|
|
574
|
-
store: this.store,
|
|
575
|
-
checkPlanAlignment: (call) => this.checkPlanAlignment(call),
|
|
576
|
-
advancePlanIfStepComplete: (sl) => this.advancePlanIfStepComplete(sl),
|
|
577
|
-
maybeSearchError: (ctx, call) => this.maybeSearchError(ctx, call),
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
|
-
preserveActive() {
|
|
581
|
-
if (!this.tracker)
|
|
582
|
-
return;
|
|
583
|
-
const plan = this.tracker.getPlan();
|
|
584
|
-
if (plan.steps.every((s) => s.status === "done" || s.status === "skipped")) {
|
|
585
|
-
this.store.archivePlan(plan);
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
this.store.saveDraft(plan);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
checkPlanAlignment(call) {
|
|
592
|
-
if (!this.tracker)
|
|
593
|
-
return null;
|
|
594
|
-
const step = this.tracker.getCurrentStep();
|
|
595
|
-
if (!step)
|
|
596
|
-
return null;
|
|
597
|
-
if (PLAN_ALIGNMENT_EXEMPT_TOOLS.has(call.name))
|
|
598
|
-
return null;
|
|
599
|
-
const stepPaths = extractFileLikeTokens(stripUrls(step.description)).map(planPathToken);
|
|
600
|
-
if (stepPaths.length === 0)
|
|
601
|
-
return null;
|
|
602
|
-
// Инспектируем только path-like аргументы. JSON-стринг всего blob'а args
|
|
603
|
-
// скармливал кодовые токены `content` write_file/edit_file в off-path чек
|
|
604
|
-
// (напр. `import "./styles.css"` в теле файла) и давал фантомные alignment-
|
|
605
|
-
// варнинги на on-track записях.
|
|
606
|
-
const argStr = this.pathArgStrings(call.arguments).join(" ");
|
|
607
|
-
const callPaths = extractFileLikeTokens(stripUrls(argStr)).map(planPathToken);
|
|
608
|
-
if (callPaths.length === 0)
|
|
609
|
-
return null;
|
|
610
|
-
// Файлы уже ЗАКРЫТЫХ (done/skipped) шагов — легитимные цели переписи:
|
|
611
|
-
// агент итерируется по ним, чиня LSP/type-ошибки. Блок здесь остановил бы
|
|
612
|
-
// нормальный багфикс.
|
|
613
|
-
const plan = this.tracker.getPlan();
|
|
614
|
-
const finishedPaths = new Set();
|
|
615
|
-
for (const s of plan.steps) {
|
|
616
|
-
if (s.status === "done" || s.status === "skipped") {
|
|
617
|
-
extractFileLikeTokens(stripUrls(s.description))
|
|
618
|
-
.map(planPathToken)
|
|
619
|
-
.forEach((p) => finishedPaths.add(p));
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
// Двунаправленный overlap path-токенов, общий для ОБЕИХ проверок ниже. Шаг
|
|
623
|
-
// может звать "main.ts", а тул — "src/main.ts" (или наоборот): односторонний
|
|
624
|
-
// `s.includes(p)` частый nested/частичный кейс пропускал (было: перепись
|
|
625
|
-
// src/main.ts закрытого шага флаговалась off-path текущего и помогла
|
|
626
|
-
// hard-block'нуть третий вызов).
|
|
627
|
-
const matchesAny = (p, tokens) => tokens.some((t) => t === p || t.includes(p) || p.includes(t));
|
|
628
|
-
const finished = [...finishedPaths];
|
|
629
|
-
const offPath = callPaths.some((p) => {
|
|
630
|
-
if (matchesAny(p, finished))
|
|
631
|
-
return false;
|
|
632
|
-
return !matchesAny(p, stepPaths);
|
|
633
|
-
});
|
|
634
|
-
if (!offPath)
|
|
635
|
-
return null;
|
|
636
|
-
return t("exec.plan_warning", {
|
|
637
|
-
step: String(step.id),
|
|
638
|
-
description: step.description,
|
|
639
|
-
tool: call.name,
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
/** Строковые значения path-like аргументов вызова. Ограничивает off-path
|
|
643
|
-
* чек аргументами, реально именующими файлы (path, file, dir, src, dst,
|
|
644
|
-
* source, target, input, output, destination, glob, workdir, cwd, command,
|
|
645
|
-
* pattern), полезную нагрузку не сканирует. */
|
|
646
|
-
pathArgStrings(args) {
|
|
647
|
-
if (!args)
|
|
648
|
-
return [];
|
|
649
|
-
const out = [];
|
|
650
|
-
for (const [key, value] of Object.entries(args)) {
|
|
651
|
-
if (!/path|file|dir|src|dst|source|target|input|output|destination|glob|workdir|cwd|command|pattern|query/i.test(key)) {
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
if (typeof value === "string")
|
|
655
|
-
out.push(value);
|
|
656
|
-
else if (Array.isArray(value)) {
|
|
657
|
-
for (const v of value)
|
|
658
|
-
if (typeof v === "string")
|
|
659
|
-
out.push(v);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
return out;
|
|
663
|
-
}
|
|
664
|
-
advancePlanIfStepComplete(sessionLog) {
|
|
665
|
-
const step = this.tracker?.getCurrentStep();
|
|
666
|
-
if (!step)
|
|
667
|
-
return;
|
|
668
|
-
const stepText = step.description.toLowerCase();
|
|
669
|
-
// Общий файловый фильтр (allow-list реальных расширений, сегменты только
|
|
670
|
-
// из расширения отброшены). Шаг "Удалить .js, оставить .tsx/.ts" НЕ даёт
|
|
671
|
-
// фантомный токен "tsx/.ts" — его не существует, allGone тривиально true и
|
|
672
|
-
// автопродвинуло бы delete-шаг при живых файлах на удаление (сырой regex
|
|
673
|
-
// делал ровно так).
|
|
674
|
-
const stepPaths = extractFileLikeTokens(stripUrls(step.description)) || [];
|
|
675
|
-
// Гейт шагов: зависимости проверяем при упоминании — ДО путей файлов.
|
|
676
|
-
// Нужен реальный глагол пакетного менеджера (правило #10 — без голого
|
|
677
|
-
// keyword-матчинга: один "init" или "setup project" локфайловый хинт НЕ
|
|
678
|
-
// триггерят, только конкретные install-команды).
|
|
679
|
-
const isDepsStep = /(?:npm|bun|yarn|pnpm|pip|pip3|pipenv|poetry|composer|deno|go|gem|cargo)\s+(?:install|add|init|i|get)\b|bun\s+(?:add|install)|npm\s+(?:i|install|add)|(?:install|установ\w*)\s+(?:dependencies|зависимост\w*|пакет\w*|packages?)/i.test(stepText);
|
|
680
|
-
if (isDepsStep) {
|
|
681
|
-
const lockFiles = [
|
|
682
|
-
"package-lock.json",
|
|
683
|
-
"bun.lockb",
|
|
684
|
-
"yarn.lock",
|
|
685
|
-
"pnpm-lock.yaml",
|
|
686
|
-
"node_modules",
|
|
687
|
-
"go.sum",
|
|
688
|
-
"Cargo.lock",
|
|
689
|
-
"Pipfile.lock",
|
|
690
|
-
"poetry.lock",
|
|
691
|
-
"requirements.txt",
|
|
692
|
-
];
|
|
693
|
-
const hasLockFile = lockFiles.some((f) => existsSync(resolve(this.baseDir, f)));
|
|
694
|
-
if (!hasLockFile) {
|
|
695
|
-
const hints = this.state.depsGateHints.get(step.id) || 0;
|
|
696
|
-
this.state.depsGateHints.set(step.id, hints + 1);
|
|
697
|
-
const force = hints >= 1;
|
|
698
|
-
const content = force
|
|
699
|
-
? t("exec.step_gate_deps_force", { step: String(step.id) })
|
|
700
|
-
: t("exec.step_gate_deps", {
|
|
701
|
-
step: String(step.id),
|
|
702
|
-
description: step.description,
|
|
703
|
-
});
|
|
704
|
-
this.pendingMessages.push({
|
|
705
|
-
role: "user",
|
|
706
|
-
content: `<system-summary>${content}</system-summary>`,
|
|
707
|
-
});
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
if (stepPaths.length === 0)
|
|
712
|
-
return;
|
|
713
|
-
// Токены резолвим ТЕМ ЖЕ обходом поддерева, что deliverable-gate и аудитор
|
|
714
|
-
// (findExistingFile): вложенный файл ("bicycle-shop/src/bicycles.ts" для
|
|
715
|
-
// токена "src/bicycles.ts") засчитывается. Старый точный
|
|
716
|
-
// existsSync(resolve()) вложенные проекты не матчил — автопродвижение
|
|
717
|
-
// молчало и после записи реального файла.
|
|
718
|
-
const resolved = stepPaths.map((p) => findExistingFile(this.baseDir, p));
|
|
719
|
-
const allExist = resolved.every((r) => r !== null);
|
|
720
|
-
const allGone = resolved.every((r) => r === null);
|
|
721
|
-
const satisfied = step.kind === "delete" ? allGone && !allExist : allExist;
|
|
722
|
-
if (!satisfied)
|
|
723
|
-
return;
|
|
724
|
-
// Гейт шагов: файлы проверяем на контент (не пустые)
|
|
725
|
-
if (step.kind !== "delete") {
|
|
726
|
-
const emptyFiles = [];
|
|
727
|
-
for (const r of resolved) {
|
|
728
|
-
if (!r)
|
|
729
|
-
continue;
|
|
730
|
-
try {
|
|
731
|
-
const content = readFileSync(r, "utf-8");
|
|
732
|
-
if (content.trim().length < 10) {
|
|
733
|
-
emptyFiles.push(r);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
catch {
|
|
737
|
-
// Нечитаем — откат на existence
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
if (emptyFiles.length > 0) {
|
|
741
|
-
this.pendingMessages.push({
|
|
742
|
-
role: "user",
|
|
743
|
-
content: `<system-summary>${t("exec.step_gate_empty", { step: String(step.id), files: emptyFiles.join(", ") })}</system-summary>`,
|
|
744
|
-
});
|
|
745
|
-
return;
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
// Все проверки прошли — двигаем
|
|
749
|
-
this.tracker?.updateStepStatus(step.id, "done");
|
|
750
|
-
const hadNext = this.tracker?.advance() ?? false;
|
|
751
|
-
if (this.tracker) {
|
|
752
|
-
this.store.saveActive(this.tracker.getPlan());
|
|
753
|
-
this.flushSessionPlan();
|
|
754
|
-
sessionLog?.plan("auto-advance", `Step ${step.id} auto-completed | ${this.tracker.getProgressString()} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
|
|
755
|
-
}
|
|
756
|
-
const nextStep = hadNext ? this.tracker?.getCurrentStep() : null;
|
|
757
|
-
const nextMsg = nextStep
|
|
758
|
-
? t("exec.step_gate_ok", {
|
|
759
|
-
step: String(step.id),
|
|
760
|
-
nextStep: String(nextStep.id),
|
|
761
|
-
nextDesc: nextStep.description,
|
|
762
|
-
})
|
|
763
|
-
: t("exec.step_gate_last", { step: String(step.id) });
|
|
764
|
-
this.pendingMessages.push({
|
|
765
|
-
role: "user",
|
|
766
|
-
content: `<system-summary>${nextMsg}</system-summary>`,
|
|
767
|
-
});
|
|
768
|
-
// Авто-архив только что завершённого плана (все шаги done/skipped).
|
|
769
|
-
// Готовый план не висит активным вечно, якоря агента на готовую задачу
|
|
770
|
-
// ("continue step 4, do NOT create a new plan" — петля ses_msxjbnm4).
|
|
771
|
-
// Per-step deliverable-gate файлы уже проверил.
|
|
772
|
-
if (this.tracker?.isComplete()) {
|
|
773
|
-
const completed = this.tracker.getPlan();
|
|
774
|
-
this.store.archivePlan(completed);
|
|
775
|
-
this.recordCompleted(completed); // final audit still verifies it
|
|
776
|
-
this.tracker = null;
|
|
777
|
-
this.flushSessionPlan(); // завершённый план не висит в слоте сессии
|
|
778
|
-
sessionLog?.plan("auto-archive", `Plan ${completed.id} complete — archived`);
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
/**
|
|
782
|
-
* Файловые токены из описания шага, не резолвящиеся в реальный файл.
|
|
783
|
-
* Тот же общий экстрактор токенов (allow-list реальных расширений, URL
|
|
784
|
-
* вырезаны) и subtree-резолвер аудитора: "bicycles.ts" находит
|
|
785
|
-
* "bicycle-shop/src/data/bicycles.ts", а hostname в описании — никогда
|
|
786
|
-
* missing deliverable. Шаги без файловых токенов (чистые команды, прогоны
|
|
787
|
-
* проверок) неверифицируемы.
|
|
788
|
-
*/
|
|
789
|
-
missingStepDeliverables(step) {
|
|
790
|
-
const tokens = extractFileLikeTokens(stripUrls(step.description));
|
|
791
|
-
if (tokens.length === 0)
|
|
792
|
-
return [];
|
|
793
|
-
return tokens.filter((p) => !findExistingFile(this.baseDir, p));
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* Файловые токены из описания шага, всё ещё резолвящиеся в реальный файл.
|
|
797
|
-
* Инверсия missingStepDeliverables — для гейта delete-шагов ("done" значит
|
|
798
|
-
* файлы пропали).
|
|
799
|
-
*/
|
|
800
|
-
stillExistingDeliverables(step) {
|
|
801
|
-
const tokens = extractFileLikeTokens(stripUrls(step.description));
|
|
802
|
-
if (tokens.length === 0)
|
|
803
|
-
return [];
|
|
804
|
-
return tokens.filter((p) => findExistingFile(this.baseDir, p));
|
|
805
|
-
}
|
|
806
|
-
/** Есть ли в описании шага хоть один реальный файловый токен. Шаги без
|
|
807
|
-
* токенов (чистые команды, прогоны проверок) не автоверифицируются —
|
|
808
|
-
* done-gate для них vacuous. */
|
|
809
|
-
hasStepDeliverables(step) {
|
|
810
|
-
return extractFileLikeTokens(stripUrls(step.description)).length > 0;
|
|
811
|
-
}
|
|
812
|
-
/**
|
|
813
|
-
* Валидирует опциональный пер-шаговый аргумент `kinds` для create плана и
|
|
814
|
-
* update-с-шагами. Возвращает выровненные по длине kinds или i18n-строку
|
|
815
|
-
* ошибки. Нет kinds → null (вызывающий фолбэчится на all-create). Общий для
|
|
816
|
-
* ОБЕИХ веток: перестройка через update не дропает kinds молча.
|
|
817
|
-
*/
|
|
818
|
-
parseKinds(args, steps) {
|
|
819
|
-
if (args.kinds === undefined)
|
|
820
|
-
return { kinds: null, error: null };
|
|
821
|
-
if (!Array.isArray(args.kinds)) {
|
|
822
|
-
return { kinds: null, error: t("plan.kinds_not_array") };
|
|
823
|
-
}
|
|
824
|
-
const kindsRaw = args.kinds.map(String);
|
|
825
|
-
if (kindsRaw.length !== steps.length) {
|
|
826
|
-
return { kinds: null, error: t("plan.kinds_mismatch") };
|
|
827
|
-
}
|
|
828
|
-
const invalid = kindsRaw.filter((k) => k !== "create" && k !== "delete");
|
|
829
|
-
if (invalid.length > 0) {
|
|
830
|
-
return {
|
|
831
|
-
kinds: null,
|
|
832
|
-
error: t("plan.kinds_invalid", {
|
|
833
|
-
kinds: invalid.join(", "),
|
|
834
|
-
}),
|
|
835
|
-
};
|
|
836
|
-
}
|
|
837
|
-
return { kinds: kindsRaw, error: null };
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
|
-
* Валидирует пер-шаговый аргумент `checks` для create / re-plan плана.
|
|
841
|
-
* Checks ОБЯЗАТЕЛЬНЫ (шаг без контракта верификации свой done не докажет):
|
|
842
|
-
* отсутствующие, несовпавшие по длине или с битым префиксом возвращают
|
|
843
|
-
* i18n-строку ошибки. Валидные префиксы: file-exists: / command: /
|
|
844
|
-
* manual: (см. plan-checks.ts).
|
|
845
|
-
*/
|
|
846
|
-
parseChecks(args, steps) {
|
|
847
|
-
if (args.checks === undefined) {
|
|
848
|
-
return { checks: null, error: t("plan.checks_required") };
|
|
849
|
-
}
|
|
850
|
-
if (!Array.isArray(args.checks)) {
|
|
851
|
-
return { checks: null, error: t("plan.checks_not_array") };
|
|
852
|
-
}
|
|
853
|
-
const raw = args.checks.map(String);
|
|
854
|
-
if (raw.length !== steps.length) {
|
|
855
|
-
return { checks: null, error: t("plan.checks_mismatch") };
|
|
856
|
-
}
|
|
857
|
-
const bad = raw.filter((c) => !isValidCheck(c));
|
|
858
|
-
if (bad.length > 0) {
|
|
859
|
-
return {
|
|
860
|
-
checks: null,
|
|
861
|
-
error: t("plan.checks_invalid", {
|
|
862
|
-
kinds: bad.slice(0, 3).join("; "),
|
|
863
|
-
}),
|
|
864
|
-
};
|
|
865
|
-
}
|
|
866
|
-
return { checks: raw.map((c) => c.trim()), error: null };
|
|
867
|
-
}
|
|
868
|
-
/**
|
|
869
|
-
* Достаёт исходную формулировку задачи из первого user-сообщения истории.
|
|
870
|
-
* Нужно для валидации покрытия плана против требований.
|
|
871
|
-
*/
|
|
872
|
-
getTaskText(ctx) {
|
|
873
|
-
const manager = ctx?.contextManager;
|
|
874
|
-
if (!manager)
|
|
875
|
-
return "";
|
|
876
|
-
const history = manager.getActiveHistory();
|
|
877
|
-
// Первое user-сообщение может быть инжектированным <system-summary>
|
|
878
|
-
// (блок компакции) — это НЕ задача. Покрытие сравнивает план с реальной
|
|
879
|
-
// инструкцией модели (было: протухший файловый список сводки компакции дал
|
|
880
|
-
// bogus-варнинг "в плане не хватает файлов").
|
|
881
|
-
const firstUser = history.find((m) => {
|
|
882
|
-
if (m.role !== "user")
|
|
883
|
-
return false;
|
|
884
|
-
const text = getMessageText(m.content);
|
|
885
|
-
return !text.startsWith("<system-summary>") && !text.includes("[Compressed:");
|
|
886
|
-
});
|
|
887
|
-
if (!firstUser)
|
|
888
|
-
return "";
|
|
889
|
-
return getMessageText(firstUser.content).trim();
|
|
890
|
-
}
|
|
891
|
-
}
|