micro-models-agent 2.10.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.
- package/CHANGELOG.md +33 -0
- package/dist/assets/app.js +22 -4
- package/dist/assets/index.html +29 -0
- package/dist/assets/js/dom.js +2 -1
- package/dist/assets/js/feed.js +38 -26
- package/dist/assets/js/format.js +1 -1
- package/dist/assets/js/rail.js +114 -0
- package/dist/assets/js/settings-controls.js +2 -2
- package/dist/assets/js/settings.js +246 -51
- package/dist/assets/js/store.js +3 -0
- package/dist/assets/js/tabs.js +104 -2
- package/dist/assets/styles.css +215 -66
- package/dist/cli/cache-line.js +1 -0
- package/dist/cli/command-suggest.js +5 -4
- package/dist/cli/commands.js +231 -122
- package/dist/cli/completer.js +132 -31
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-payload.js +4 -4
- package/dist/cli/main.js +166 -61
- package/dist/cli/onboarding-commands.js +126 -0
- package/dist/cli/plugin-commands.js +2 -1
- package/dist/cli/relaunch.js +16 -13
- package/dist/cli/repl-commands-lsp.js +75 -0
- package/dist/cli/repl-commands-skills.js +105 -0
- package/dist/cli/repl-commands.js +589 -266
- package/dist/cli/repl.js +436 -177
- package/dist/cli/run-result.js +5 -6
- package/dist/cli/security-commands.js +37 -28
- package/dist/cli/setup-order.js +4 -55
- package/dist/cli/setup-prompt.js +3 -2
- package/dist/cli/setup.js +27 -107
- package/dist/cli/web-command.js +98 -0
- package/dist/config/budget.js +5 -5
- package/dist/config/config.js +103 -96
- package/dist/config/defaults.js +38 -8
- package/dist/config/domains.js +24 -27
- package/dist/config/env.js +46 -0
- package/dist/config/experts.js +2 -0
- package/dist/config/index.js +1 -0
- package/dist/config/paths.js +56 -0
- package/dist/config/redact.js +44 -0
- package/dist/config/registry-extra.js +334 -0
- package/dist/config/registry.js +584 -0
- package/dist/config/security.js +35 -36
- package/dist/config/timeouts.js +74 -0
- package/dist/config/utils.js +16 -15
- package/dist/core/agent/artifact-progress.js +59 -0
- package/dist/core/agent/artifact-resolve.js +79 -0
- package/dist/core/agent/audit-gate.js +7 -3
- package/dist/core/agent/compaction.js +2 -2
- package/dist/core/agent/constants.js +33 -4
- package/dist/core/agent/context-log.js +454 -0
- package/dist/core/agent/context-renderer.js +5 -0
- package/dist/core/agent/critic-gate.js +132 -0
- package/dist/core/agent/hallucination-gate.js +148 -38
- package/dist/core/agent/loop-state.js +15 -0
- package/dist/core/agent/prefix-monitor.js +1 -1
- package/dist/core/agent/reasoning-resolver.js +10 -1
- package/dist/core/agent/token-tracker.js +7 -4
- package/dist/core/agent/tool-batch.js +93 -18
- package/dist/core/agent/tool-images.js +18 -0
- package/dist/core/agent/tool-output.js +65 -11
- package/dist/core/agent/tool-pairing.js +60 -0
- package/dist/core/agent-moe.js +25 -24
- package/dist/core/agent.js +429 -108
- package/dist/core/bootstrap/agent-phase.js +66 -0
- package/dist/core/bootstrap/config-phase.js +86 -0
- package/dist/core/bootstrap/modules-phase.js +269 -0
- package/dist/core/bootstrap/provider-phase.js +183 -0
- package/dist/core/bootstrap/session-phase.js +204 -0
- package/dist/core/bootstrap/state.js +94 -0
- package/dist/core/bootstrap/workspace-phase.js +115 -0
- package/dist/core/bootstrap.js +92 -660
- package/dist/core/crash-handler.js +17 -7
- package/dist/core/environment.js +37 -12
- package/dist/core/host-plan.js +43 -0
- package/dist/core/host.js +23 -0
- package/dist/core/index.js +1 -0
- package/dist/core/prompt-builder.js +17 -1
- package/dist/core/prompt-overflow.js +42 -43
- package/dist/core/session-logger.js +122 -30
- package/dist/core/system-prompt.js +117 -0
- package/dist/core/version.js +6 -4
- package/dist/core/web-host.js +34 -0
- package/dist/core/workspace.js +19 -15
- package/dist/i18n/en.json +106 -21
- package/dist/i18n/index.js +37 -3
- package/dist/i18n/ru.json +109 -21
- package/dist/index.js +7 -3
- package/dist/llm/image-utils.js +55 -64
- package/dist/llm/llm-errors.js +9 -0
- package/dist/llm/model-loader.js +10 -32
- package/dist/llm/models-dev.js +121 -0
- package/dist/llm/openai-compat.js +146 -73
- package/dist/llm/orchestrator.js +33 -11
- package/dist/llm/provider-budget.js +2 -2
- package/dist/llm/provider-context.js +233 -0
- package/dist/llm/provider.js +1 -1
- package/dist/llm/response.js +5 -0
- package/dist/llm/token-counter.js +27 -11
- package/dist/logger/app-logger.js +48 -22
- package/dist/logger/file-log.js +28 -19
- package/dist/logger/index.js +1 -0
- package/dist/main.js +24285 -22447
- package/dist/migration/backup.js +14 -3
- package/dist/migration/detect.js +12 -3
- package/dist/migration/index.js +1 -0
- package/dist/modules/artifacts/store.js +9 -8
- package/dist/modules/browser/actions.js +6 -0
- package/dist/modules/browser/bridge-client.js +14 -12
- package/dist/modules/browser/bridge-path.js +4 -4
- package/dist/modules/browser/console-buffer.js +37 -0
- package/dist/modules/browser/cookie-store.js +5 -0
- package/dist/modules/browser/driver-types.js +1 -0
- package/dist/modules/browser/driver.js +19 -11
- package/dist/modules/browser/index.js +3 -1
- package/dist/modules/browser/module.js +2 -1
- package/dist/modules/browser/session.js +10 -30
- package/dist/modules/browser/snapshot.js +19 -13
- package/dist/modules/browser/types.js +1 -0
- package/dist/modules/certification/cli.js +28 -20
- package/dist/modules/certification/fact-checker.js +9 -1
- package/dist/modules/certification/loader.js +9 -2
- package/dist/modules/certification/manifest.js +83 -21
- package/dist/modules/certification/runner.js +23 -13
- package/dist/modules/certification/scenarios.js +20 -20
- package/dist/modules/certification/syntax-scenarios.js +1 -2
- package/dist/modules/context/chunk-query.js +11 -9
- package/dist/modules/context/fact-extractor.js +37 -39
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +106 -80
- package/dist/modules/critic/client.js +176 -0
- package/dist/modules/critic/evidence.js +81 -0
- package/dist/modules/critic/provider.js +45 -0
- package/dist/modules/critic/tools.js +78 -0
- package/dist/modules/critic/types.js +1 -0
- package/dist/modules/execution/audit-runners.js +28 -34
- package/dist/modules/execution/auditor.js +53 -49
- package/dist/modules/execution/execution-plugin.js +142 -102
- package/dist/modules/execution/index.js +1 -1
- package/dist/modules/execution/input-from.js +37 -13
- package/dist/modules/execution/module.js +368 -191
- package/dist/modules/execution/moe-executor.js +53 -26
- package/dist/modules/execution/plan-actions.js +320 -202
- package/dist/modules/execution/plan-checks.js +35 -0
- package/dist/modules/execution/plan-coverage.js +36 -33
- package/dist/modules/execution/plan-steps.js +11 -3
- package/dist/modules/execution/plan-store.js +83 -17
- package/dist/modules/execution/plan-tool.js +89 -35
- package/dist/modules/execution/plan-validator.js +30 -10
- package/dist/modules/execution/planner.js +22 -13
- package/dist/modules/execution/stuck-detector.js +196 -93
- package/dist/modules/execution/tracker.js +22 -8
- package/dist/modules/execution/verifier.js +69 -48
- package/dist/modules/execution/windows-commands.js +12 -12
- package/dist/modules/hallucination/artifact-claim.js +93 -0
- package/dist/modules/hallucination/confidence.js +13 -11
- package/dist/modules/hallucination/consistency.js +10 -2
- package/dist/modules/hallucination/detector.js +10 -5
- package/dist/modules/hallucination/factual.js +43 -26
- package/dist/modules/hallucination/index.js +1 -0
- package/dist/modules/hallucination/js-identifiers.js +44 -45
- package/dist/modules/hallucination/llm-judge.js +17 -12
- package/dist/modules/hallucination/success-claim.js +59 -0
- package/dist/modules/indexer/cache.js +52 -4
- package/dist/modules/indexer/index.js +1 -0
- package/dist/modules/indexer/map-command.js +2 -4
- package/dist/modules/indexer/map-select.js +16 -16
- package/dist/modules/indexer/module.js +47 -12
- package/dist/modules/indexer/project-profile.js +8 -7
- package/dist/modules/indexer/symbols.js +15 -15
- package/dist/modules/indexer/walker.js +159 -29
- package/dist/modules/lsp/check-tool.js +7 -5
- package/dist/modules/lsp/client.js +39 -39
- package/dist/modules/lsp/command.js +17 -24
- package/dist/modules/lsp/config.js +12 -11
- package/dist/modules/lsp/index.js +1 -0
- package/dist/modules/lsp/module.js +48 -46
- package/dist/modules/lsp/probe.js +13 -14
- package/dist/modules/lsp/project-root.js +14 -17
- package/dist/modules/lsp/startup-check.js +18 -16
- package/dist/modules/mcp/client.js +59 -62
- package/dist/modules/mcp/index.js +1 -0
- package/dist/modules/mcp/module.js +66 -37
- package/dist/modules/mcp/registry.js +5 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/module.js +12 -8
- package/dist/modules/memory/search.js +4 -1
- package/dist/modules/memory/store.js +40 -2
- package/dist/modules/onboarding/init.js +47 -0
- package/dist/modules/onboarding/meet.js +30 -0
- package/dist/modules/onboarding/mode.js +30 -0
- package/dist/modules/onboarding/prompts.js +43 -0
- package/dist/modules/pipelines/engine.js +10 -0
- package/dist/modules/pipelines/index.js +1 -0
- package/dist/modules/pipelines/parser.js +4 -1
- package/dist/modules/pipelines/template.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
- package/dist/modules/plugins/builtin/notify.js +2 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +18 -3
- package/dist/modules/plugins/manager.js +37 -17
- package/dist/modules/pricing/index.js +10 -11
- package/dist/modules/pricing/prices.js +12 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +27 -31
- package/dist/modules/processes/registry.js +44 -25
- package/dist/modules/processes/runner.js +4 -4
- package/dist/modules/providers/capabilities.js +28 -0
- package/dist/modules/providers/create.js +3 -4
- package/dist/modules/providers/factory.js +7 -7
- package/dist/modules/providers/fallback.js +12 -12
- package/dist/modules/providers/health.js +4 -4
- package/dist/modules/providers/manager.js +50 -19
- package/dist/modules/providers/naming.js +15 -0
- package/dist/modules/providers/preflight.js +71 -0
- package/dist/modules/providers/presets.js +39 -8
- package/dist/modules/providers/registry.js +7 -2
- package/dist/modules/reasoning/policy.js +8 -6
- package/dist/modules/reasoning/probe.js +30 -25
- package/dist/modules/security/audit-log.js +19 -36
- package/dist/modules/security/audit-notifier.js +35 -75
- package/dist/modules/security/command-validator.js +99 -51
- package/dist/modules/security/content-scanner.js +8 -8
- package/dist/modules/security/data-sanitizer.js +9 -16
- package/dist/modules/security/encryption.js +32 -52
- package/dist/modules/security/index.js +2 -1
- package/dist/modules/security/network-validator.js +120 -43
- package/dist/modules/security/path-validator.js +61 -46
- package/dist/modules/security/protected-paths.js +73 -0
- package/dist/modules/security/rate-limiter.js +17 -49
- package/dist/modules/security/security-policies.js +25 -57
- package/dist/modules/security/session-encryption.js +32 -71
- package/dist/modules/security/session-isolation.js +15 -28
- package/dist/modules/session/auto-name.js +63 -0
- package/dist/modules/session/export.js +98 -0
- package/dist/modules/session/index.js +1 -0
- package/dist/modules/session/manager.js +79 -28
- package/dist/modules/session/module.js +4 -3
- package/dist/modules/session/store.js +57 -20
- package/dist/modules/setup/answers.js +35 -0
- package/dist/modules/setup/apply.js +79 -0
- package/dist/modules/setup/discovery.js +72 -0
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/loader.js +9 -1
- package/dist/modules/skills/module.js +11 -0
- package/dist/modules/updater/changelog-reader.js +15 -16
- package/dist/modules/updater/checker.js +9 -4
- package/dist/modules/updater/dev-detect.js +7 -6
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +17 -15
- package/dist/modules/user-profile/compressor.js +2 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +10 -1
- package/dist/modules/webui/answer-api.js +29 -0
- package/dist/modules/webui/assets.js +50 -0
- package/dist/modules/webui/body.js +21 -0
- package/dist/modules/webui/commands-settings.js +209 -0
- package/dist/modules/webui/commands-shared.js +60 -0
- package/dist/modules/webui/commands.js +174 -0
- package/dist/modules/webui/config-api.js +134 -0
- package/dist/modules/webui/file-api.js +202 -0
- package/dist/modules/webui/forward.js +159 -0
- package/dist/modules/webui/history-api.js +112 -0
- package/dist/modules/webui/host-state.js +60 -0
- package/dist/modules/webui/i18n-api.js +9 -0
- package/dist/modules/webui/limits.js +6 -0
- package/dist/modules/webui/log-forward.js +20 -0
- package/dist/modules/webui/memory-commands.js +64 -0
- package/dist/modules/webui/model-cache.js +18 -0
- package/dist/modules/webui/module.js +148 -0
- package/dist/modules/webui/persist.js +24 -0
- package/dist/modules/webui/prompt-io.js +97 -0
- package/dist/modules/webui/providers-api.js +337 -0
- package/dist/modules/webui/providers-view.js +56 -0
- package/dist/modules/webui/reasoning-view.js +15 -0
- package/dist/modules/webui/run-agent.js +81 -0
- package/dist/modules/webui/server.js +284 -0
- package/dist/modules/webui/sessions-api.js +178 -0
- package/dist/modules/webui/settings-api.js +561 -0
- package/dist/modules/webui/setup-api.js +201 -0
- package/dist/modules/webui/state.js +74 -0
- package/dist/modules/webui/util.js +261 -0
- package/dist/output/bus.js +7 -2
- package/dist/output/channel.js +80 -28
- package/dist/output/format.js +2 -1
- package/dist/output/index.js +1 -0
- package/dist/output/json-sink.js +5 -3
- package/dist/output/machine.js +4 -3
- package/dist/output/session-sink.js +4 -4
- package/dist/tools/approve.js +4 -0
- package/dist/tools/attach-image.js +5 -4
- package/dist/tools/auto-fixer.js +26 -44
- package/dist/tools/auto-tags.js +83 -0
- package/dist/tools/bash.js +193 -116
- package/dist/tools/browser.js +76 -31
- package/dist/tools/chunk-query.js +14 -6
- package/dist/tools/create-dir.js +9 -5
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/download-file.js +32 -7
- package/dist/tools/edit-file.js +52 -14
- package/dist/tools/enable-tools.js +6 -6
- package/dist/tools/executor.js +132 -27
- package/dist/tools/file-info.js +3 -1
- package/dist/tools/filter-tools.js +3 -3
- package/dist/tools/glob-tool.js +4 -1
- package/dist/tools/grep-tool.js +13 -10
- package/dist/tools/hidden-tools-block.js +6 -6
- package/dist/tools/index.js +16 -8
- package/dist/tools/list-dir.js +4 -2
- package/dist/tools/load-skill.js +7 -0
- package/dist/tools/mcp-call.js +15 -4
- package/dist/tools/meet-done.js +14 -0
- package/dist/tools/move-file.js +14 -7
- package/dist/tools/path-utils.js +29 -29
- package/dist/tools/pipeline-run.js +29 -7
- package/dist/tools/preview.js +1 -1
- package/dist/tools/process-ids.js +31 -0
- package/dist/tools/process-kill.js +9 -16
- package/dist/tools/process-list.js +1 -0
- package/dist/tools/process-log.js +11 -16
- package/dist/tools/process-wait.js +63 -0
- package/dist/tools/prompt-io.js +4 -3
- package/dist/tools/question.js +172 -25
- package/dist/tools/read-file.js +55 -34
- package/dist/tools/recall.js +14 -8
- package/dist/tools/registry.js +6 -5
- package/dist/tools/remember.js +8 -4
- package/dist/tools/scope-request.js +11 -6
- package/dist/tools/search-history.js +5 -2
- package/dist/tools/session-info.js +2 -0
- package/dist/tools/set-thinking.js +8 -4
- package/dist/tools/subagent.js +59 -39
- package/dist/tools/syntax-validator.js +292 -90
- package/dist/tools/user-input.js +22 -18
- package/dist/tools/web-browse.js +9 -3
- package/dist/tools/web-fetch.js +12 -23
- package/dist/tools/web-search.js +7 -5
- package/dist/tools/write-file.js +45 -15
- package/dist/ui/action-log.js +13 -0
- package/dist/ui/box.js +4 -3
- package/dist/ui/colors.js +2 -0
- package/dist/ui/diff.js +6 -5
- package/dist/ui/index.js +1 -0
- package/dist/ui/line-editor.js +230 -122
- package/dist/ui/line-math.js +10 -2
- package/dist/ui/md-formatter.js +57 -37
- package/dist/ui/output.js +8 -3
- package/dist/ui/plan-view.js +21 -10
- package/dist/ui/renderer.js +393 -67
- package/dist/ui/spinner.js +16 -5
- package/dist/ui/table.js +15 -17
- package/dist/ui/theme.js +143 -0
- package/dist/utils/child-run.js +20 -0
- package/dist/utils/error.js +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/retry.js +2 -2
- package/dist/utils/truncate.js +1 -1
- package/package.json +1 -1
|
@@ -12,31 +12,31 @@ import { resolve } from "path";
|
|
|
12
12
|
import { performWebSearch } from "../../tools/web-search";
|
|
13
13
|
import { getSessionSecurityConfig } from "../security/session-isolation";
|
|
14
14
|
import { createPlanToolDefinitions } from "./plan-tool";
|
|
15
|
+
import { isValidCheck } from "./plan-checks";
|
|
15
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";
|
|
16
18
|
/**
|
|
17
|
-
*
|
|
18
|
-
* STUCK_RECOVERY_COOLDOWN
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
const ERROR_SEARCH_MIN_INTERVAL_MS = 30000;
|
|
19
|
+
* Минимальный интервал между автопоисками ошибок. Спек просил кап
|
|
20
|
+
* STUCK_RECOVERY_COOLDOWN-итераций, но в onAfterTool нет счётчика итераций
|
|
21
|
+
* (контекст экзекутора) — та же анти-спам защита, но по времени. Инжектится
|
|
22
|
+
* через конструктор для тестов (0 отключает).
|
|
23
|
+
*/
|
|
23
24
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* file-mutating tools are pinned to the current step's file tokens.
|
|
25
|
+
* Тулы, которым можно при активном шаге плана БЕЗ проверки step-alignment.
|
|
26
|
+
* Либо не трогают ФС, либо структурно/read-only инспекции, нужные агенту в
|
|
27
|
+
* любой точке (диагностика, индекс/поиск, память, интроспекция процессов,
|
|
28
|
+
* интерактивные промпты). К текущему шагу пинятся только файл-мутирующие.
|
|
29
29
|
*
|
|
30
|
-
* `lsp_check`
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* the agent was writing the current step's own deliverable).
|
|
30
|
+
* `lsp_check` и `project_map` здесь явно: оба alwaysOn, а системный промпт
|
|
31
|
+
* ТРЕБУЕТ `lsp_check` после каждой записи (включая переписи файлов уже
|
|
32
|
+
* закрытых шагов). Без них каждый диагностический вызов жёг plan-warning и мог
|
|
33
|
+
* hard-block'нуть легитимную запись на 3-м вызове (было: "3 вызовов подряд вне
|
|
34
|
+
* текущего шага", пока агент писал deliverable текущего же шага).
|
|
36
35
|
*/
|
|
37
36
|
const PLAN_ALIGNMENT_EXEMPT_TOOLS = new Set([
|
|
38
37
|
// structural / execution control
|
|
39
38
|
"plan",
|
|
39
|
+
"plan_step",
|
|
40
40
|
"todo",
|
|
41
41
|
"verify",
|
|
42
42
|
"enable_tools",
|
|
@@ -67,57 +67,59 @@ const PLAN_ALIGNMENT_EXEMPT_TOOLS = new Set([
|
|
|
67
67
|
"load_skill",
|
|
68
68
|
]);
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* (`
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* sides lets the substring/`endsWith` overlap below match.
|
|
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` ниже.
|
|
76
75
|
*/
|
|
77
76
|
function planPathToken(p) {
|
|
78
77
|
return toForwardSlash(p).toLowerCase();
|
|
79
78
|
}
|
|
79
|
+
/** Execution-модуль: план, stuck-детектор, аудитор, гейты выравнивания и автопоиск ошибок. */
|
|
80
80
|
export class ExecutionModule {
|
|
81
81
|
name = "execution";
|
|
82
82
|
tracker = null;
|
|
83
|
+
/** Сессия-владелец текущего трекера. План следует за сессией: смена
|
|
84
|
+
* сессии через switchSession() персистит трекер в слот старой сессии
|
|
85
|
+
* и загружает план новой (см. docs/specs/2026-09-29-session-plan-binding-design.md). */
|
|
86
|
+
sessionId = null;
|
|
83
87
|
verifier;
|
|
84
88
|
stuckDetector;
|
|
85
89
|
auditor;
|
|
86
90
|
store;
|
|
87
91
|
baseDir;
|
|
88
|
-
//
|
|
89
|
-
//
|
|
92
|
+
// Пропуск первого аудита после восстановления плана с диска — даём юзеру
|
|
93
|
+
// шанс сменить тему, прежде чем audit-gate зафиксируется.
|
|
90
94
|
_auditSkipsRemaining = 0;
|
|
91
95
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
+
* Последний план, ЗАВЕРШЁННЫЙ и авто-архивированный. `plan update` на
|
|
97
|
+
* финальном шаге чистит трекер — раньше runFinalAudit() возвращал null и
|
|
98
|
+
* молча скипал финальный аудит (тесты + tsc + файлы) готовой задачи.
|
|
99
|
+
* Храним завершённый план, чтобы audit-gate отработал.
|
|
96
100
|
*/
|
|
97
101
|
completedPlan = null;
|
|
98
102
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
+
* Отложенные <system-summary>. Инжектятся onBeforeTool/onAfterTool, но
|
|
104
|
+
* флашатся в onBeforeThink — никогда не встают между assistant tool_call и
|
|
105
|
+
* его tool-результатами (рвёт спаривание и хоронит варнинги, на которые
|
|
106
|
+
* модель должна реагировать).
|
|
103
107
|
*/
|
|
104
108
|
pendingMessages = [];
|
|
105
|
-
// Per
|
|
106
|
-
// find/…
|
|
107
|
-
//
|
|
108
|
-
// classification (rule #10).
|
|
109
|
+
// Per-командные счётчики фейлов запрещённых Windows-команд (grep/sed/ls/
|
|
110
|
+
// find/… через тул bash). Hard stop после второго фейла evidence-based
|
|
111
|
+
// (реальные фейлы команд), не классификация задач (правило #10).
|
|
109
112
|
forbiddenBashFailures = new Map();
|
|
110
113
|
searchRunner;
|
|
111
|
-
webSearchThreshold;
|
|
112
114
|
searchesThisSession = 0;
|
|
113
115
|
retryWebSearchSignatures = new Set();
|
|
114
116
|
lastErrorSearchAt = 0;
|
|
115
117
|
errorSearchCooldownMs;
|
|
116
118
|
hallucinationDetector = null;
|
|
117
119
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
120
|
+
* Мутабельное состояние execution-плагина. Владеем ЗДЕСЬ, передаём ПО ССЫЛКЕ
|
|
121
|
+
* фабрике плагина — плагин пишет deps.state.* напрямую, записи видны модулю
|
|
122
|
+
* без write-back.
|
|
121
123
|
*/
|
|
122
124
|
state = {
|
|
123
125
|
lastRecoveryIteration: -STUCK_RECOVERY_COOLDOWN,
|
|
@@ -126,17 +128,22 @@ export class ExecutionModule {
|
|
|
126
128
|
stuckNotified: false,
|
|
127
129
|
lastStuckWarnKey: "",
|
|
128
130
|
lastStuckWarnIter: -STUCK_WARN_REPEAT_EVERY,
|
|
131
|
+
escalationDirectiveSent: false,
|
|
129
132
|
depsGateHints: new Map(),
|
|
130
133
|
mutationsWithoutPlan: 0,
|
|
131
134
|
planNudgeSent: false,
|
|
132
135
|
typecheckFailures: new Map(),
|
|
133
136
|
};
|
|
134
137
|
/**
|
|
135
|
-
* Read/write
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
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: слот не должен ронять тулы.
|
|
140
147
|
*/
|
|
141
148
|
trackerRef = (() => {
|
|
142
149
|
const self = this;
|
|
@@ -146,86 +153,191 @@ export class ExecutionModule {
|
|
|
146
153
|
},
|
|
147
154
|
set current(v) {
|
|
148
155
|
self.tracker = v;
|
|
156
|
+
self.flushSessionPlan();
|
|
149
157
|
},
|
|
150
158
|
};
|
|
151
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
|
+
}
|
|
152
175
|
constructor(baseDir, stuckThreshold = 6, webSearchThreshold = 5, searchRunner = performWebSearch, errorSearchCooldownMs = ERROR_SEARCH_MIN_INTERVAL_MS) {
|
|
153
176
|
this.baseDir = baseDir;
|
|
154
177
|
this.verifier = new StepVerifier(baseDir);
|
|
155
178
|
this.stuckDetector = new StuckDetector(stuckThreshold, 3, webSearchThreshold);
|
|
156
179
|
this.auditor = new Auditor(baseDir);
|
|
157
180
|
this.store = new PlanStore(baseDir);
|
|
158
|
-
this.webSearchThreshold = webSearchThreshold;
|
|
159
181
|
this.searchRunner = searchRunner;
|
|
160
182
|
this.errorSearchCooldownMs = errorSearchCooldownMs;
|
|
161
183
|
}
|
|
184
|
+
/** Ставит план активным (синхрон указателя, персист, сброс завершённого/скипов). */
|
|
162
185
|
setPlan(plan) {
|
|
163
186
|
this.tracker = new PlanTracker(plan);
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
187
|
+
// Указатель мимо done/skipped шагов восстановленного/перепланированного
|
|
188
|
+
// плана: display ("current: step N") и off-track alignment матчатся с
|
|
189
|
+
// реальностью, а не вечно указывают на шаг 1.
|
|
167
190
|
this.tracker.syncCurrentStep();
|
|
168
191
|
this.store.saveActive(plan);
|
|
169
|
-
this.
|
|
170
|
-
|
|
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; // как восстановленный план
|
|
171
263
|
}
|
|
172
|
-
/**
|
|
173
|
-
*
|
|
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). */
|
|
174
282
|
setHallucinationDetector(detector) {
|
|
175
283
|
this.hallucinationDetector = detector;
|
|
176
284
|
}
|
|
177
|
-
/**
|
|
178
|
-
*
|
|
285
|
+
/** Запоминает последний завершённый план: финальный аудит проверит его и
|
|
286
|
+
* после очистки трекера авто-архивом. */
|
|
179
287
|
recordCompleted(plan) {
|
|
180
288
|
this.completedPlan = plan;
|
|
181
289
|
}
|
|
182
|
-
/**
|
|
290
|
+
/** Забывает завершённый план (abort/delete/purge — аудитить нечего). */
|
|
183
291
|
clearCompleted() {
|
|
184
292
|
this.completedPlan = null;
|
|
185
293
|
}
|
|
294
|
+
/** Восстанавливает активный план с диска. False, если нет/уже завершён (тогда в архив). */
|
|
186
295
|
restorePlan() {
|
|
187
296
|
const plan = this.store.loadActive();
|
|
188
297
|
if (!plan)
|
|
189
298
|
return false;
|
|
190
299
|
const restored = new PlanTracker(plan);
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
300
|
+
// План, уже завершённый на момент сохранения, активным не возвращается —
|
|
301
|
+
// архивируем, чтобы не якорил агента (восстановленные готовые планы давали
|
|
302
|
+
// петлю "continue step 4" на рестарте).
|
|
194
303
|
if (restored.isComplete()) {
|
|
195
304
|
this.store.archivePlan(plan);
|
|
196
305
|
this.tracker = null;
|
|
197
306
|
return false;
|
|
198
307
|
}
|
|
199
308
|
this.tracker = restored;
|
|
200
|
-
//
|
|
309
|
+
// Двигаем к первому недонесённому шагу
|
|
201
310
|
this.tracker.syncCurrentStep();
|
|
202
|
-
this._auditSkipsRemaining = 1; //
|
|
311
|
+
this._auditSkipsRemaining = 1; // восстановленный план — один свободный проход
|
|
203
312
|
return true;
|
|
204
313
|
}
|
|
314
|
+
/** Трекер (null без активного плана). */
|
|
205
315
|
getTracker() {
|
|
206
316
|
return this.tracker;
|
|
207
317
|
}
|
|
208
|
-
/**
|
|
209
|
-
* Null
|
|
210
|
-
*
|
|
318
|
+
/** Текущий трекаемый план (единый источник правды для UI).
|
|
319
|
+
* Null в свежей сессии — план с диска восстанавливается только при resume
|
|
320
|
+
* сессии с историей, REPL протухших планов не видит. */
|
|
211
321
|
getActivePlan() {
|
|
212
322
|
return this.tracker?.getPlan() ?? null;
|
|
213
323
|
}
|
|
324
|
+
/** Стор планов. */
|
|
214
325
|
getStore() {
|
|
215
326
|
return this.store;
|
|
216
327
|
}
|
|
328
|
+
/** Stuck-детектор. */
|
|
217
329
|
getStuckDetector() {
|
|
218
330
|
return this.stuckDetector;
|
|
219
331
|
}
|
|
220
332
|
/**
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
333
|
+
* Автопоиск в вебе повторяющейся ошибки (≥ порога errorWebSearch одинаковых
|
|
334
|
+
* сигнатур). Поиск fire-and-forget (результат тула не блокирует), результаты
|
|
335
|
+
* очередятся <system-summary> в pendingMessages.
|
|
224
336
|
*
|
|
225
|
-
*
|
|
226
|
-
* →
|
|
227
|
-
*
|
|
228
|
-
* (
|
|
337
|
+
* Гейты: мастер-свитч webSearch + свитч errorWebSearch → повторная сигнатура
|
|
338
|
+
* → не-исканная (или ретрайная) → сессионный кап → содержательный текст ошибки →
|
|
339
|
+
* кулдаун. Один поиск на сигнатуру; пустые результаты — один ретрай
|
|
340
|
+
* (размаркировка раз), затем сигнатура считается исканной.
|
|
229
341
|
*/
|
|
230
342
|
maybeSearchError(ctx, call) {
|
|
231
343
|
if (ctx.config?.webSearch?.enabled === false)
|
|
@@ -282,19 +394,19 @@ export class ExecutionModule {
|
|
|
282
394
|
.catch((e) => ctx.logger?.warn(`error web search: ${e.message}`));
|
|
283
395
|
}
|
|
284
396
|
/**
|
|
285
|
-
*
|
|
286
|
-
*
|
|
397
|
+
* Финальный аудит перед правом агента заявить "готово". Null без активного
|
|
398
|
+
* плана; иначе — готовность плана + существование артефактов.
|
|
287
399
|
*/
|
|
288
400
|
/**
|
|
289
|
-
* Evidence-based
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
401
|
+
* Evidence-based автозакрытие пендинг шагов с deliverables на диске. Мелкие
|
|
402
|
+
* модели рутинно доделывают работу, но забывают бухгалтерию `plan update`
|
|
403
|
+
* (было в ses_mthn3c2a: 2/5 шагов done при всех артефактах на месте — audit-gate
|
|
404
|
+
* реджектил корректный финальный ответ и жёг retry-бюджет на бухгалтерию, не
|
|
405
|
+
* на работу).
|
|
294
406
|
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
407
|
+
* Стандарт ТОТ ЖЕ, что аудитор применяет к done-шагам: каждый файлоподобный
|
|
408
|
+
* токен описания резолвится в существующий файл. Шаги без файловых токенов
|
|
409
|
+
* остаются пендинг (верифицировать нечего).
|
|
298
410
|
*/
|
|
299
411
|
autoCloseEvidenceSteps(plan) {
|
|
300
412
|
let closed = 0;
|
|
@@ -304,20 +416,43 @@ export class ExecutionModule {
|
|
|
304
416
|
const tokens = extractFileLikeTokens(stripUrls(step.description));
|
|
305
417
|
if (tokens.length === 0)
|
|
306
418
|
continue;
|
|
307
|
-
const
|
|
308
|
-
if (
|
|
309
|
-
|
|
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
|
+
}
|
|
310
444
|
step.status = "done";
|
|
311
|
-
step.note = `auto-closed by final audit: all named files exist (${tokens.join(", ")})`;
|
|
445
|
+
step.note = `auto-closed by final audit: ${step.kind === "delete" ? "all named files removed" : "all named files exist with content"} (${tokens.join(", ")})`;
|
|
312
446
|
closed++;
|
|
313
447
|
}
|
|
314
448
|
return closed;
|
|
315
449
|
}
|
|
450
|
+
/** Финальный аудит активного/завершённого плана: файлы, тесты, tsc, пендинги. Null — аудитить нечего. */
|
|
316
451
|
async runFinalAudit() {
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
452
|
+
// Нет активного трекера: аудитим последний завершённый план вместо bail.
|
|
453
|
+
// План, закрытый через `plan update`, авто-архивируется и чистит трекер;
|
|
454
|
+
// без этого финальный аудит (тесты + tsc + файлы) молча скипался и модель
|
|
455
|
+
// заявляла успех на битых артефактах.
|
|
321
456
|
if (!this.tracker) {
|
|
322
457
|
if (!this.completedPlan)
|
|
323
458
|
return null;
|
|
@@ -328,6 +463,7 @@ export class ExecutionModule {
|
|
|
328
463
|
const done = plan.steps.filter((s) => s.status === "done").length;
|
|
329
464
|
if (autoClosed > 0) {
|
|
330
465
|
this.store.saveActive(plan);
|
|
466
|
+
this.flushSessionPlan();
|
|
331
467
|
}
|
|
332
468
|
return {
|
|
333
469
|
passed: audit.passed && pendingSteps.length === 0,
|
|
@@ -346,6 +482,7 @@ export class ExecutionModule {
|
|
|
346
482
|
const autoClosed = this.autoCloseEvidenceSteps(plan);
|
|
347
483
|
if (autoClosed > 0) {
|
|
348
484
|
this.store.saveActive(plan);
|
|
485
|
+
this.flushSessionPlan();
|
|
349
486
|
}
|
|
350
487
|
const audit = await this.auditor.audit(plan);
|
|
351
488
|
const pendingSteps = plan.steps.flatMap((s) => s.status !== "done" && s.status !== "skipped" ? [`${s.id}. ${s.description}`] : []);
|
|
@@ -360,6 +497,7 @@ export class ExecutionModule {
|
|
|
360
497
|
summary: audit.summary,
|
|
361
498
|
};
|
|
362
499
|
}
|
|
500
|
+
/** Блок системного промпта с чеклистом плана (null без активного/незавершённого). */
|
|
363
501
|
getSystemPromptBlock() {
|
|
364
502
|
if (!this.tracker || this.tracker.isComplete())
|
|
365
503
|
return null;
|
|
@@ -371,14 +509,13 @@ export class ExecutionModule {
|
|
|
371
509
|
};
|
|
372
510
|
}
|
|
373
511
|
/**
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
*
|
|
512
|
+
* Короткая строка "текущий план" для сводки компакции. Null без активного
|
|
513
|
+
* плана. Для 9B-моделей важна явная инструкция "continue it, do NOT create
|
|
514
|
+
* a new plan" — после компакции блок плана виден, но модель всё равно
|
|
515
|
+
* пересоздаёт план (было: ses_msvuao0h).
|
|
378
516
|
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
* new plan" which anchored the ses_msxjbnm4 loop.
|
|
517
|
+
* Готовый план в сводке — "план done, можно новый", а НЕ "continue step 4,
|
|
518
|
+
* do NOT create a new plan", якоривший петлю ses_msxjbnm4.
|
|
382
519
|
*/
|
|
383
520
|
getPlanSummary() {
|
|
384
521
|
if (!this.tracker)
|
|
@@ -387,11 +524,23 @@ export class ExecutionModule {
|
|
|
387
524
|
const done = plan.steps.filter((s) => s.status === "done").length;
|
|
388
525
|
const total = plan.steps.length;
|
|
389
526
|
if (this.tracker.isComplete()) {
|
|
390
|
-
return
|
|
527
|
+
return t("exec.plan_summary_complete", {
|
|
528
|
+
id: plan.id,
|
|
529
|
+
title: plan.title,
|
|
530
|
+
done: String(done),
|
|
531
|
+
total: String(total),
|
|
532
|
+
});
|
|
391
533
|
}
|
|
392
534
|
const current = this.tracker.getCurrentStepIndex() + 1;
|
|
393
|
-
return
|
|
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
|
+
});
|
|
394
542
|
}
|
|
543
|
+
/** Определения тулов модуля (plan/todo/verify через фабрику plan-tool). */
|
|
395
544
|
getToolDefinitions() {
|
|
396
545
|
return createPlanToolDefinitions({
|
|
397
546
|
store: this.store,
|
|
@@ -405,6 +554,7 @@ export class ExecutionModule {
|
|
|
405
554
|
missingStepDeliverables: (s) => this.missingStepDeliverables(s),
|
|
406
555
|
stillExistingDeliverables: (s) => this.stillExistingDeliverables(s),
|
|
407
556
|
parseKinds: (a, st) => this.parseKinds(a, st),
|
|
557
|
+
parseChecks: (a, st) => this.parseChecks(a, st),
|
|
408
558
|
getTaskText: (c) => this.getTaskText(c),
|
|
409
559
|
typecheckFailures: this.state.typecheckFailures,
|
|
410
560
|
baseDir: this.baseDir,
|
|
@@ -413,6 +563,7 @@ export class ExecutionModule {
|
|
|
413
563
|
: undefined,
|
|
414
564
|
});
|
|
415
565
|
}
|
|
566
|
+
/** Плагин модуля (хуки цикла из фабрики execution-plugin). */
|
|
416
567
|
getPlugin() {
|
|
417
568
|
return createExecutionPlugin({
|
|
418
569
|
stuckDetector: this.stuckDetector,
|
|
@@ -422,7 +573,7 @@ export class ExecutionModule {
|
|
|
422
573
|
state: this.state,
|
|
423
574
|
store: this.store,
|
|
424
575
|
checkPlanAlignment: (call) => this.checkPlanAlignment(call),
|
|
425
|
-
advancePlanIfStepComplete: (
|
|
576
|
+
advancePlanIfStepComplete: (sl) => this.advancePlanIfStepComplete(sl),
|
|
426
577
|
maybeSearchError: (ctx, call) => this.maybeSearchError(ctx, call),
|
|
427
578
|
});
|
|
428
579
|
}
|
|
@@ -448,17 +599,17 @@ export class ExecutionModule {
|
|
|
448
599
|
const stepPaths = extractFileLikeTokens(stripUrls(step.description)).map(planPathToken);
|
|
449
600
|
if (stepPaths.length === 0)
|
|
450
601
|
return null;
|
|
451
|
-
//
|
|
452
|
-
//
|
|
453
|
-
//
|
|
454
|
-
//
|
|
602
|
+
// Инспектируем только path-like аргументы. JSON-стринг всего blob'а args
|
|
603
|
+
// скармливал кодовые токены `content` write_file/edit_file в off-path чек
|
|
604
|
+
// (напр. `import "./styles.css"` в теле файла) и давал фантомные alignment-
|
|
605
|
+
// варнинги на on-track записях.
|
|
455
606
|
const argStr = this.pathArgStrings(call.arguments).join(" ");
|
|
456
607
|
const callPaths = extractFileLikeTokens(stripUrls(argStr)).map(planPathToken);
|
|
457
608
|
if (callPaths.length === 0)
|
|
458
609
|
return null;
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
//
|
|
610
|
+
// Файлы уже ЗАКРЫТЫХ (done/skipped) шагов — легитимные цели переписи:
|
|
611
|
+
// агент итерируется по ним, чиня LSP/type-ошибки. Блок здесь остановил бы
|
|
612
|
+
// нормальный багфикс.
|
|
462
613
|
const plan = this.tracker.getPlan();
|
|
463
614
|
const finishedPaths = new Set();
|
|
464
615
|
for (const s of plan.steps) {
|
|
@@ -468,11 +619,11 @@ export class ExecutionModule {
|
|
|
468
619
|
.forEach((p) => finishedPaths.add(p));
|
|
469
620
|
}
|
|
470
621
|
}
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
//
|
|
475
|
-
//
|
|
622
|
+
// Двунаправленный overlap path-токенов, общий для ОБЕИХ проверок ниже. Шаг
|
|
623
|
+
// может звать "main.ts", а тул — "src/main.ts" (или наоборот): односторонний
|
|
624
|
+
// `s.includes(p)` частый nested/частичный кейс пропускал (было: перепись
|
|
625
|
+
// src/main.ts закрытого шага флаговалась off-path текущего и помогла
|
|
626
|
+
// hard-block'нуть третий вызов).
|
|
476
627
|
const matchesAny = (p, tokens) => tokens.some((t) => t === p || t.includes(p) || p.includes(t));
|
|
477
628
|
const finished = [...finishedPaths];
|
|
478
629
|
const offPath = callPaths.some((p) => {
|
|
@@ -488,10 +639,10 @@ export class ExecutionModule {
|
|
|
488
639
|
tool: call.name,
|
|
489
640
|
});
|
|
490
641
|
}
|
|
491
|
-
/**
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
642
|
+
/** Строковые значения path-like аргументов вызова. Ограничивает off-path
|
|
643
|
+
* чек аргументами, реально именующими файлы (path, file, dir, src, dst,
|
|
644
|
+
* source, target, input, output, destination, glob, workdir, cwd, command,
|
|
645
|
+
* pattern), полезную нагрузку не сканирует. */
|
|
495
646
|
pathArgStrings(args) {
|
|
496
647
|
if (!args)
|
|
497
648
|
return [];
|
|
@@ -510,21 +661,21 @@ export class ExecutionModule {
|
|
|
510
661
|
}
|
|
511
662
|
return out;
|
|
512
663
|
}
|
|
513
|
-
advancePlanIfStepComplete(
|
|
664
|
+
advancePlanIfStepComplete(sessionLog) {
|
|
514
665
|
const step = this.tracker?.getCurrentStep();
|
|
515
666
|
if (!step)
|
|
516
667
|
return;
|
|
517
668
|
const stepText = step.description.toLowerCase();
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
//
|
|
669
|
+
// Общий файловый фильтр (allow-list реальных расширений, сегменты только
|
|
670
|
+
// из расширения отброшены). Шаг "Удалить .js, оставить .tsx/.ts" НЕ даёт
|
|
671
|
+
// фантомный токен "tsx/.ts" — его не существует, allGone тривиально true и
|
|
672
|
+
// автопродвинуло бы delete-шаг при живых файлах на удаление (сырой regex
|
|
673
|
+
// делал ровно так).
|
|
523
674
|
const stepPaths = extractFileLikeTokens(stripUrls(step.description)) || [];
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
//
|
|
675
|
+
// Гейт шагов: зависимости проверяем при упоминании — ДО путей файлов.
|
|
676
|
+
// Нужен реальный глагол пакетного менеджера (правило #10 — без голого
|
|
677
|
+
// keyword-матчинга: один "init" или "setup project" локфайловый хинт НЕ
|
|
678
|
+
// триггерят, только конкретные install-команды).
|
|
528
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);
|
|
529
680
|
if (isDepsStep) {
|
|
530
681
|
const lockFiles = [
|
|
@@ -541,39 +692,36 @@ export class ExecutionModule {
|
|
|
541
692
|
];
|
|
542
693
|
const hasLockFile = lockFiles.some((f) => existsSync(resolve(this.baseDir, f)));
|
|
543
694
|
if (!hasLockFile) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
:
|
|
551
|
-
|
|
552
|
-
description: step.description,
|
|
553
|
-
});
|
|
554
|
-
contextManager.addMessage({
|
|
555
|
-
role: "user",
|
|
556
|
-
content: `<system-summary>${content}</system-summary>`,
|
|
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,
|
|
557
703
|
});
|
|
558
|
-
|
|
704
|
+
this.pendingMessages.push({
|
|
705
|
+
role: "user",
|
|
706
|
+
content: `<system-summary>${content}</system-summary>`,
|
|
707
|
+
});
|
|
559
708
|
return;
|
|
560
709
|
}
|
|
561
710
|
}
|
|
562
711
|
if (stepPaths.length === 0)
|
|
563
712
|
return;
|
|
564
|
-
//
|
|
565
|
-
//
|
|
566
|
-
//
|
|
567
|
-
//
|
|
568
|
-
//
|
|
569
|
-
// real file.
|
|
713
|
+
// Токены резолвим ТЕМ ЖЕ обходом поддерева, что deliverable-gate и аудитор
|
|
714
|
+
// (findExistingFile): вложенный файл ("bicycle-shop/src/bicycles.ts" для
|
|
715
|
+
// токена "src/bicycles.ts") засчитывается. Старый точный
|
|
716
|
+
// existsSync(resolve()) вложенные проекты не матчил — автопродвижение
|
|
717
|
+
// молчало и после записи реального файла.
|
|
570
718
|
const resolved = stepPaths.map((p) => findExistingFile(this.baseDir, p));
|
|
571
719
|
const allExist = resolved.every((r) => r !== null);
|
|
572
720
|
const allGone = resolved.every((r) => r === null);
|
|
573
721
|
const satisfied = step.kind === "delete" ? allGone && !allExist : allExist;
|
|
574
722
|
if (!satisfied)
|
|
575
723
|
return;
|
|
576
|
-
//
|
|
724
|
+
// Гейт шагов: файлы проверяем на контент (не пустые)
|
|
577
725
|
if (step.kind !== "delete") {
|
|
578
726
|
const emptyFiles = [];
|
|
579
727
|
for (const r of resolved) {
|
|
@@ -586,57 +734,57 @@ export class ExecutionModule {
|
|
|
586
734
|
}
|
|
587
735
|
}
|
|
588
736
|
catch {
|
|
589
|
-
//
|
|
737
|
+
// Нечитаем — откат на existence
|
|
590
738
|
}
|
|
591
739
|
}
|
|
592
|
-
if (emptyFiles.length > 0
|
|
593
|
-
|
|
740
|
+
if (emptyFiles.length > 0) {
|
|
741
|
+
this.pendingMessages.push({
|
|
594
742
|
role: "user",
|
|
595
743
|
content: `<system-summary>${t("exec.step_gate_empty", { step: String(step.id), files: emptyFiles.join(", ") })}</system-summary>`,
|
|
596
744
|
});
|
|
597
745
|
return;
|
|
598
746
|
}
|
|
599
747
|
}
|
|
600
|
-
//
|
|
748
|
+
// Все проверки прошли — двигаем
|
|
601
749
|
this.tracker?.updateStepStatus(step.id, "done");
|
|
602
750
|
const hadNext = this.tracker?.advance() ?? false;
|
|
603
751
|
if (this.tracker) {
|
|
604
752
|
this.store.saveActive(this.tracker.getPlan());
|
|
753
|
+
this.flushSessionPlan();
|
|
605
754
|
sessionLog?.plan("auto-advance", `Step ${step.id} auto-completed | ${this.tracker.getProgressString()} | current: step ${this.tracker.getCurrentStepIndex() + 1}`);
|
|
606
755
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
//
|
|
622
|
-
//
|
|
623
|
-
// to a completed task ("continue step 4, do NOT create a new plan" — the
|
|
624
|
-
// ses_msxjbnm4 loop). The per-step deliverable gate already verified files.
|
|
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 файлы уже проверил.
|
|
625
772
|
if (this.tracker?.isComplete()) {
|
|
626
773
|
const completed = this.tracker.getPlan();
|
|
627
774
|
this.store.archivePlan(completed);
|
|
628
775
|
this.recordCompleted(completed); // final audit still verifies it
|
|
629
776
|
this.tracker = null;
|
|
777
|
+
this.flushSessionPlan(); // завершённый план не висит в слоте сессии
|
|
630
778
|
sessionLog?.plan("auto-archive", `Plan ${completed.id} complete — archived`);
|
|
631
779
|
}
|
|
632
780
|
}
|
|
633
781
|
/**
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
* "
|
|
638
|
-
*
|
|
639
|
-
*
|
|
782
|
+
* Файловые токены из описания шага, не резолвящиеся в реальный файл.
|
|
783
|
+
* Тот же общий экстрактор токенов (allow-list реальных расширений, URL
|
|
784
|
+
* вырезаны) и subtree-резолвер аудитора: "bicycles.ts" находит
|
|
785
|
+
* "bicycle-shop/src/data/bicycles.ts", а hostname в описании — никогда
|
|
786
|
+
* missing deliverable. Шаги без файловых токенов (чистые команды, прогоны
|
|
787
|
+
* проверок) неверифицируемы.
|
|
640
788
|
*/
|
|
641
789
|
missingStepDeliverables(step) {
|
|
642
790
|
const tokens = extractFileLikeTokens(stripUrls(step.description));
|
|
@@ -645,9 +793,9 @@ export class ExecutionModule {
|
|
|
645
793
|
return tokens.filter((p) => !findExistingFile(this.baseDir, p));
|
|
646
794
|
}
|
|
647
795
|
/**
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
796
|
+
* Файловые токены из описания шага, всё ещё резолвящиеся в реальный файл.
|
|
797
|
+
* Инверсия missingStepDeliverables — для гейта delete-шагов ("done" значит
|
|
798
|
+
* файлы пропали).
|
|
651
799
|
*/
|
|
652
800
|
stillExistingDeliverables(step) {
|
|
653
801
|
const tokens = extractFileLikeTokens(stripUrls(step.description));
|
|
@@ -655,17 +803,17 @@ export class ExecutionModule {
|
|
|
655
803
|
return [];
|
|
656
804
|
return tokens.filter((p) => findExistingFile(this.baseDir, p));
|
|
657
805
|
}
|
|
658
|
-
/**
|
|
659
|
-
*
|
|
660
|
-
*
|
|
806
|
+
/** Есть ли в описании шага хоть один реальный файловый токен. Шаги без
|
|
807
|
+
* токенов (чистые команды, прогоны проверок) не автоверифицируются —
|
|
808
|
+
* done-gate для них vacuous. */
|
|
661
809
|
hasStepDeliverables(step) {
|
|
662
810
|
return extractFileLikeTokens(stripUrls(step.description)).length > 0;
|
|
663
811
|
}
|
|
664
812
|
/**
|
|
665
|
-
*
|
|
666
|
-
* update
|
|
667
|
-
*
|
|
668
|
-
*
|
|
813
|
+
* Валидирует опциональный пер-шаговый аргумент `kinds` для create плана и
|
|
814
|
+
* update-с-шагами. Возвращает выровненные по длине kinds или i18n-строку
|
|
815
|
+
* ошибки. Нет kinds → null (вызывающий фолбэчится на all-create). Общий для
|
|
816
|
+
* ОБЕИХ веток: перестройка через update не дропает kinds молча.
|
|
669
817
|
*/
|
|
670
818
|
parseKinds(args, steps) {
|
|
671
819
|
if (args.kinds === undefined)
|
|
@@ -689,18 +837,47 @@ export class ExecutionModule {
|
|
|
689
837
|
return { kinds: kindsRaw, error: null };
|
|
690
838
|
}
|
|
691
839
|
/**
|
|
692
|
-
*
|
|
693
|
-
*
|
|
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
|
+
* Нужно для валидации покрытия плана против требований.
|
|
694
871
|
*/
|
|
695
872
|
getTaskText(ctx) {
|
|
696
873
|
const manager = ctx?.contextManager;
|
|
697
874
|
if (!manager)
|
|
698
875
|
return "";
|
|
699
876
|
const history = manager.getActiveHistory();
|
|
700
|
-
//
|
|
701
|
-
//
|
|
702
|
-
//
|
|
703
|
-
//
|
|
877
|
+
// Первое user-сообщение может быть инжектированным <system-summary>
|
|
878
|
+
// (блок компакции) — это НЕ задача. Покрытие сравнивает план с реальной
|
|
879
|
+
// инструкцией модели (было: протухший файловый список сводки компакции дал
|
|
880
|
+
// bogus-варнинг "в плане не хватает файлов").
|
|
704
881
|
const firstUser = history.find((m) => {
|
|
705
882
|
if (m.role !== "user")
|
|
706
883
|
return false;
|